轉載原文:https://www.jianshu.com/p/5c7b359a159c
如下代碼,本方法主要用於程序定義模板格式,並導出文件。該方法將定義和創建分離,達到了一定解耦合,降低了開發復雜度。但是依然是程序定義模板,對模板的樣式需要程序控制,沒有達到將數據和樣式分離的目的。
改良版,關於添加依賴之類的之前一篇文章里面有。
這篇是把之前的方法抽成通用模板。
一、添加一個實體類
package com.lencity.securitymanagementplatform.data.entity; import java.util.List; public class XlsData { public static final int DATA_TYPE_INTEGER = 0; public static final int DATA_TYPE_STRING = 1; private List<String> titles;//表頭 private List<Integer> types;//數據類型 private List<List<Object>> values;存表數據 public List<Integer> getTypes() { return types; } public void setTypes(List<Integer> types) { this.types = types; } public List<String> getTitles() { return titles; } public void setTitles(List<String> titles) { this.titles = titles; } public List<List<Object>> getValues() { return values; } public void setValues(List<List<Object>> values) { this.values = values; } }
二、創建一個service類
package com.lencity.securitymanagementplatform.service; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFCellStyle; import org.apache.poi.hssf.usermodel.HSSFDataFormat; import org.apache.poi.hssf.usermodel.HSSFFont; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.springframework.stereotype.Service; import com.lencity.securitymanagementplatform.data.entity.XlsData; @Service public class XlsService { //寫一個接口,哪個控制器需要加上導出excel功能就繼承這個接口 public static interface IXlsOutputProcessor { public XlsData processXlsData(Map<String, Object> condition); } //解析數據創建excel public HSSFWorkbook createExcelData(IXlsOutputProcessor processor, Map<String, Object> condition) { XlsData xlsData = processor.processXlsData(condition); HSSFWorkbook workbook = new HSSFWorkbook(); HSSFSheet sheet = workbook.createSheet("統計表");// 創建一個excel表單 HSSFRow titleRow = sheet.createRow(0); // 設置列寬,setColumnWidth的第二個參數要乘以256,這個參數的單位是1/256個字符寬度 sheet.setColumnWidth(1, 15 * 256); sheet.setColumnWidth(3, 20 * 256); HSSFCellStyle style = workbook.createCellStyle(); style.setDataFormat(HSSFDataFormat.getBuiltinFormat("m/d/yy h:mm"));// 設置日期格式 HSSFFont font = workbook.createFont();// 設置為居中加粗 font.setBold(true); style.setAlignment(HorizontalAlignment.CENTER); style.setFont(font); List<String> titles = xlsData.getTitles(); HSSFCell cell; /* 構造表頭 */ for (int i = 0; i < titles.size(); i++) { cell = titleRow.createCell(i); cell.setCellValue(titles.get(i)); cell.setCellStyle(style); } int rowNum = 1; List<Integer> dataTypes = xlsData.getTypes(); List<List<Object>> values = xlsData.getValues(); for (int i = 0; i < values.size(); i++) { List<Object> value = values.get(i); HSSFRow row = sheet.createRow(rowNum); for (int j = 0; j < value.size(); j++) { switch (dataTypes.get(j)) { case XlsData.DATA_TYPE_INTEGER: row.createCell(j).setCellValue((Integer) value.get(j)); break; case XlsData.DATA_TYPE_STRING: row.createCell(j).setCellValue((String) value.get(j)); break; } } rowNum++; } return workbook; } // 瀏覽器導出excel public void buildExcelDocument(String filename, HSSFWorkbook workbook, HttpServletResponse response) throws Exception { response.reset(); response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "utf-8")); OutputStream outputStream = response.getOutputStream(); workbook.write(outputStream); outputStream.flush(); outputStream.close(); } // 下載excel模板功能 public void downloadTemplate(HttpServletResponse response,HttpServletRequest request) throws Exception { String fileName="導出模板.xls"; response.reset(); response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "utf-8")); String filePath=request.getServletContext().getRealPath("/excel/")+fileName; FileInputStream input=new FileInputStream(filePath); OutputStream out=response.getOutputStream(); byte[] b=new byte[2048]; int len; while((len=input.read(b))!=-1) { out.write(b,0,len); } response.setHeader("Content-Length", String.valueOf(input.getChannel().size())); input.close(); } }
三、控制器
假設我們要在用戶頁面加上導出表格的功能,那就在用戶的控制器上繼承接口
public class UserController implements IXlsOutputProcessor {
繼承之后需要在控制器中重寫接口方法,
導出的表格樣式.png
關於封裝數據,主要就是根據自己實際的項目需求,來構造數據了
// 封裝數據 @Override public XlsData processXlsData(Map<String, Object> condition) { List<String> titles = new ArrayList<>();//表頭 List<Integer> dataTypes = new ArrayList<>();//表數據類型 List<List<Object>> values = new ArrayList<>();//表頭對應的數據 titles.add("姓名"); dataTypes.add(XlsData.DATA_TYPE_STRING); titles.add("手機號碼"); dataTypes.add(XlsData.DATA_TYPE_STRING); titles.add("職位"); dataTypes.add(XlsData.DATA_TYPE_STRING); titles.add("部門"); dataTypes.add(XlsData.DATA_TYPE_STRING); List<User> users = userService.getUsersByCondition(condition); XlsData xlsData = new XlsData(); xlsData.setTitles(titles); xlsData.setTypes(dataTypes); for (User user : users) { List<Object> tmpList = new ArrayList<>(); tmpList.add(user.getName()); tmpList.add(user.getMobile()); tmpList.add(user.getPosition()); tmpList.add(departmentService.getDepartmentNameByDepartmentCode(user.getDepartmentCode())); values.add(tmpList); } xlsData.setValues(values); return xlsData; } // 導出excel,前台js,點擊 導出excel 關聯的路徑就是這個 @PostMapping(value = "/downLoadXls") @ResponseBody public String downLoadXls(Map<String, Object> condition, HttpServletResponse response) throws Exception { String fileName = "導出excel.xls"; HSSFWorkbook workbook = xlsService.createExcelData(this, condition); xlsService.buildExcelDocument(fileName, workbook, response); JSONObject jsonObject = new JSONObject(); jsonObject.put("code", 1); return jsonObject.toString(); } // 下載模板,前台js,點擊 下載模板 關聯的路徑就是這個 @PostMapping(value = "/downloadTemplate") @ResponseBody public String downloadTemplate(HttpServletResponse response,HttpServletRequest request) throws Exception { String fileName = "導出excel.xls"; xlsService.downloadTemplate(response, request); JSONObject jsonObject = new JSONObject(); jsonObject.put("code", 1); return jsonObject.toString(); }
前台界面.png
模板的存放位置
前台按鈕代碼
<button type="button" class="btn btn-primary waves-effect" onclick="downloadTemplate()" id="downloadTemplate"> <i class="material-icons">vertical_align_bottom</i> <span>下載模板</span> </button> <button type="button" class="btn btn-primary waves-effect"onclick="exportExcel()"> <i class="material-icons">vertical_align_bottom</i> <span>導出表格</span> </button>
js
form表單里面是頁面的表單篩選條件,如果要導數據庫所有的數據,可把form表單去掉。如果導出的數據是有篩選條件的,需要改一下form表單
function exportExcel() { var name = $("#name").val(); var departmentCode = $("#departmentCode").find("option:selected").val(); var form = $("<form>"); $('body').append(form); form.attr('style','display:none'); form.attr('target',''); form.attr('method','post'); form.attr('action',contextPath+'/user/downLoadXls');//下載文件的請求路徑 //對應查詢條件的開始時間 var input1 = $('<input>'); input1.attr('type','hidden'); input1.attr('name',"name"); input1.attr('value',name); form.append(input1); //對應查詢條件的結束時間 var input2 = $('<input>'); input2.attr('type','hidden'); input2.attr('name','departmentCode'); input2.attr('value',departmentCode); form.append(input2); form.submit(); }
下載模板的js
function downloadTemplate() { var form = $("<form>"); $('body').append(form); form.attr('style', 'display:none'); form.attr('target', ''); form.attr('method', 'post'); form.attr('action', contextPath + '/user/downloadTemplate');// 下載文件的請求路徑 form.submit(); }
二、java通過poi模板導出excel(程序根據模板樣式導出)
此方法與上文不同的是,用戶自定義模板樣式,程序根據參數名稱和sheet名稱來查找填寫相應內容。
附上poi的maven配置:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.16</version>
</dependency>
我使用了最新的版本。
package com.unionpay.cqupay.utils;
import com.unionpay.cqupay.common.CetcBigDataException;
import com.unionpay.cqupay.entity.SheetData;
import com.unionpay.cqupay.pojo.UmUserGatherPojo;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
/**
* @author adam
* @version 1.0
* @date 2019-9-23
*/
public class ExcelUtil {
private final static Logger logger = LoggerFactory.getLogger(ExcelUtil.class);
/**
* Sheet復制
* @param fromSheet
* @param toSheet
* @param copyValueFlag
*/
public static void copySheet(Workbook wb, Sheet fromSheet, Sheet toSheet,
boolean copyValueFlag) {
//合並區域處理
mergerRegion(fromSheet, toSheet);
int index = 0;
for (Iterator<Row> rowIt = fromSheet.rowIterator(); rowIt.hasNext();) {
Row tmpRow = rowIt.next();
Row newRow = toSheet.createRow(tmpRow.getRowNum());
CellStyle style = tmpRow.getRowStyle();
if(style != null)
newRow.setRowStyle(tmpRow.getRowStyle());
newRow.setHeight(tmpRow.getHeight());
//針對第一行設置行寬
if(index == 0) {
int first = tmpRow.getFirstCellNum();
int last = tmpRow.getLastCellNum();
for(int i = first ; i < last ; i++) {
int w = fromSheet.getColumnWidth(i);
toSheet.setColumnWidth(i, w + 1);
}
toSheet.setDefaultColumnWidth(fromSheet.getDefaultColumnWidth());
}
//行復制
copyRow(wb,tmpRow,newRow,copyValueFlag);
index++ ;
}
}
/**
* 行復制功能
* @param fromRow
* @param toRow
*/
static void copyRow(Workbook wb,Row fromRow,Row toRow,boolean copyValueFlag){
for (Iterator<Cell> cellIt = fromRow.cellIterator(); cellIt.hasNext();) {
Cell tmpCell = cellIt.next();
Cell newCell = toRow.createCell(tmpCell.getColumnIndex());
copyCell(wb,tmpCell, newCell, copyValueFlag);
}
}
/**
* 復制原有sheet的合並單元格到新創建的sheet
*
* @param fromSheet 新創建sheet
* @param toSheet 原有的sheet
*/
static void mergerRegion(Sheet fromSheet, Sheet toSheet) {
int sheetMergerCount = fromSheet.getNumMergedRegions();
for (int i = 0; i < sheetMergerCount; i++) {
CellRangeAddress cra = fromSheet.getMergedRegion(i);
toSheet.addMergedRegion(cra);
}
}
/**
* 復制單元格
*
* @param srcCell
* @param distCell
* @param copyValueFlag
* true則連同cell的內容一起復制
*/
public static void copyCell(Workbook wb,Cell srcCell, Cell distCell,
boolean copyValueFlag) {
CellStyle newstyle=wb.createCellStyle();
//copyCellStyle(srcCell.getCellStyle(), newstyle);
//distCell.setEncoding(srcCell.);
newstyle.cloneStyleFrom(srcCell.getCellStyle());
//樣式
distCell.setCellStyle(newstyle);
//評論
if (srcCell.getCellComment() != null) {
distCell.setCellComment(srcCell.getCellComment());
}
// 不同數據類型處理
CellType srcCellType = srcCell.getCellTypeEnum();
distCell.setCellType(srcCellType);
if (copyValueFlag) {
if (srcCellType == CellType.NUMERIC) {
if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(srcCell)) {
distCell.setCellValue(srcCell.getDateCellValue());
} else {
distCell.setCellValue(srcCell.getNumericCellValue());
}
} else if (srcCellType == CellType.STRING ) {
distCell.setCellValue(srcCell.getRichStringCellValue());
} else if (srcCellType == CellType.BLANK ) {
// nothing21
} else if (srcCellType == CellType.BOOLEAN ) {
distCell.setCellValue(srcCell.getBooleanCellValue());
} else if (srcCellType == CellType.ERROR ) {
distCell.setCellErrorValue(srcCell.getErrorCellValue());
} else if (srcCellType == CellType.FORMULA ) {
distCell.setCellFormula(srcCell.getCellFormula());
} else { // nothing29
}
}
}
/**
* 寫入excel數據
* @param model 采用的模板 位置在 src/model/下 模板第一個sheet頁必須是模板sheet
* @param sheetDatas 模板數據
*/
public static void writeData(String model , OutputStream out, SheetData... sheetDatas ) {
Workbook wb = null;
try {
InputStream input = new FileInputStream(model);
// InputStream input = ExcelUtils2.class.getResourceAsStream(model);
if(input == null) {
throw new RuntimeException("模板文件不存在"+model);
}
if(model.endsWith(".xlsx"))
wb = new XSSFWorkbook(input);
else if(model.endsWith(".xls"))
wb = new HSSFWorkbook(input);
else
throw new RuntimeException("模板文件不合法,不是excel類型"+model );
} catch (IOException e) {
//e.printStackTrace();
throw new RuntimeException("加載模板文件失敗"+model);
}
Sheet source = wb.getSheetAt(0);
//就一個的話 直接用模板
int size = sheetDatas.length ;
/*for(int i = 0 ; i < size ; i++) {
if(i == 0) {
if (sheetDatas[0]!=null){
wb.setSheetName(0, sheetDatas[0].getName());
}
} else {
if (sheetDatas[i]!=null) {
Sheet toSheet = wb.createSheet(sheetDatas[i].getName());
//復制格式
copySheet(wb, source, toSheet, true);
}
}
}*/
int numberOfSheets=wb.getNumberOfSheets();
out:for(int i = 0 ; i < size ; i++) {
String sheetDatasName=sheetDatas[i].getName();
if (StringUtils.isNotBlank(sheetDatasName)){
for(int j=0;j<numberOfSheets;j++){
if (sheetDatasName.equals(wb.getSheetAt(j).getSheetName())){
//寫數據
writeData(sheetDatas[i],wb.getSheetAt(j) );
continue out;
}
}
}
}
try {
wb.write(out);
out.flush();
wb.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 向sheet頁中寫入數據
* @param sheetData 數據Map
* @param sheet sheet
*/
public static void writeData(SheetData sheetData , Sheet sheet) {
//從sheet中找到匹配符 #{}表示單個 , ${}表示集合,從該單元格開始向下追加
for(Iterator<Row> rowIt = sheet.rowIterator(); rowIt.hasNext();) {
Row row = rowIt.next();
//取cell
for(int j = row.getFirstCellNum() ; j < row.getLastCellNum() ; j++) {
Cell cell = row.getCell(j);
//判斷cell的內容是否包含 $ 或者#
if(cell != null && cell.getCellTypeEnum() == CellType.STRING && cell.getStringCellValue() != null
&& (cell.getStringCellValue().contains("$") || cell.getStringCellValue().contains("#") )) {
//剝離# $
String[] winds = CommonUtils.getWildcard(cell.getStringCellValue().trim());
for(String wind : winds) {
writeData(sheetData, wind , cell , sheet);
}
}
}
}
}
/**
* 填充數據
* @param sheetData
* @param keyWind #{name}只替換當前 or ${names} 從當前行開始向下替換
*/
static void writeData(SheetData sheetData , String keyWind , Cell cell , Sheet sheet) {
String key = keyWind.substring(2 , keyWind.length() - 1);
if(keyWind.startsWith("#")) {
//簡單替換
Object value = sheetData.get(key);
//為空則替換為空字符串
if(value == null)
value = "" ;
String cellValue = cell.getStringCellValue();
cellValue = cellValue.replace(keyWind, value.toString());
cell.setCellValue(cellValue);
} else if(keyWind.startsWith("$")) {
//從list中每個實體開始解,行數從當前開始
int rowindex = cell.getRowIndex();
int columnindex = cell.getColumnIndex();
List<? extends Object> listdata = sheetData.getDatas();
//不為空的時候開始填充
if(listdata != null && !listdata.isEmpty()){
for(Object o : listdata) {
Object cellValue = CommonUtils.getValue(o, key);
Row row = sheet.getRow(rowindex);
if(row == null) {
row = sheet.createRow(rowindex);
}
//取出cell
Cell c = row.getCell(columnindex);
if(c == null)
c = row.createCell(columnindex);
if(cell.getCellStyle() != null){
c.setCellStyle(cell.getCellStyle());
}
if(cell.getCellTypeEnum() != null) {
c.setCellType(cell.getCellTypeEnum());
}
if(cellValue != null) {
if(cellValue instanceof Number || CommonUtils.isNumber(cellValue) )
c.setCellValue( Double.valueOf(cellValue.toString()));
else if(cellValue instanceof Boolean)
c.setCellValue((Boolean)cellValue);
else if(cellValue instanceof Date)
c.setCellValue(DateUtil.getDayFormatStr((Date)cellValue,sheetData.getTimeFormat()));
else
c.setCellValue(cellValue.toString());
} else {
//數據為空 如果當前單元格已經有數據則重置為空
if(c.getStringCellValue() != null) {
c.setCellValue("");
}
}
rowindex++ ;
}
} else {
//list數據為空則將$全部替換空字符串
String cellValue = "" ;
cell.setCellValue(cellValue);
}
}
}
public static File createFile(String templateName,String fileName, SheetData... sheetData) throws CetcBigDataException {
String model = System.getProperty("user.dir")+File.separator+"res"+File.separator+"model"+File.separator+templateName ;
//創建個臨時文件
File file=new File("temp"+File.separator+"statistics");
if(!file.exists()){//如果文件夾不存在
file.mkdirs();//創建文件夾
}
file=new File("temp"+File.separator+"statistics"+File.separator+fileName);
try {
TemplateExcelUtil.writeData(model, new FileOutputStream(file), sheetData);
}catch (Exception e){
throw new CetcBigDataException("導出表格失敗,失敗原因:"+e.getMessage());
}
return file;
}
public static void main(String[] args) {
//獲取模板
// logger.info("項目路徑{}",System.getProperty("user.dir"));
// String model = System.getProperty("user.dir")+File.separator+"res\\model\\template_user.xlsx" ;
String model = "D:\\cetc\\nas\\cetcData\\excelmodel\\template_user.xlsx" ;
File f = new File("e:/test.xlsx");
SheetData sd = new SheetData("用戶統計");
sd.put("name", "張三");
sd.put("age", 13);
//每個sheet頁加入100條測試數據
//注意這里可以加入pojo也可以直接使用map,理論上map在這里效率更高一些
UmUserGatherPojo vo=new UmUserGatherPojo();
vo.setMonth("201909");
vo.setUserRegisterNum(100);
vo.setUserActiveNum(65);
sd.addData(vo);
UmUserGatherPojo vo2=new UmUserGatherPojo();
vo2.setMonth("201908");
vo2.setUserRegisterNum(90);
vo2.setUserActiveNum(null);
sd.addData(vo2);
try {
ExcelUtils.writeData(model, new FileOutputStream(f) ,sd);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
package com.unionpay.cqupay.entity;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* @author adam
* @version 1.0
* @date 2019-9-23
*/
public class SheetData {
/**
* sheet頁中存儲 #{key} 的數據
*/
private Map<String, Object> map = new HashMap<String, Object>();
/**
* 列表數據存儲 sheet頁中替換${key} 並以列為單位向下賦值
*/
private List<Object> datas = new LinkedList<Object>();
private String name ;
private String timeFormat;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public String getTimeFormat() {
return timeFormat;
}
public void setTimeFormat(String timeFormat) {
this.timeFormat = timeFormat;
}
public SheetData(String name) {
super();
this.name = name;
}
public SheetData(String name,String timeFormat) {
super();
this.name = name;
this.timeFormat=timeFormat;
}
public void put(String key , Object value) {
map.put(key, value);
}
public void remove(String key) {
map.remove(key);
}
public Object get(String key) {
return map.get(key);
}
/**
* 清理map存儲和數據存儲
*/
public void clear() {
map.clear();
datas.clear();
}
public void addData(Object t){
datas.add(t);
}
public void addDatas(List<? extends Object> list) {
datas.addAll(list);
}
public List<Object> getDatas() {
return datas;
}
}
其中用到了CommonUtils公共類中封裝的幾個靜態方法:
下面開始寫測試,編輯一個excel模板:

編寫一個測試數據實體(實際使用Map效率會更好一些):
編寫測試類,注意模型test.xlsx 已經放入src/model/ 目錄下:
輸出文件如下:


模板中單元格的樣式會延續復制,包含顏色,寬度等等。有興趣大家可以一起擴展一下。
作者封裝了ExcelUtil.createFile()方法。在項目中可以靈活調用。如下代碼:
public boolean exportUersStatistics( String email,Integer userId) throws CetcBigDataException {
List<UmUserGatherPojo> list=this.findPojo(userId) ;
SheetData sd = new SheetData("用戶統計表" );
for (UmUserGatherPojo vo : list) {
sd.addData(vo);
}
File file=TemplateExcelUtil.createFile("template_user.xlsx",userId+"user.xlsx",sd);
boolean sendResult = mailUtil.sendMail(email, file.getPath(), "用戶統計表(月報)", "每月統計注冊用戶和活躍用戶");
if (sendResult) {
return true;
} else {
throw new CetcBigDataException("發送郵件失敗");
}
}
參考 https://www.iteye.com/blog/jjxliu306-2383610

