转载原文: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 {
继承之后需要在控制器中重写接口方法,

关于封装数据,主要就是根据自己实际的项目需求,来构造数据了
// 封装数据 @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(); }


前台按钮代码
<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