利用注解实现动态配置公式并结合POI导出Excel

  • 实现思想
  • 创建导出对象
  • 创建使用注解类
  • 创建封装导出对象属性类
  • 创建测试类
  • 创建ExcelUtil类
  • 实现效果

实现思想

实施顾问提出导出Excel数据时,需要带有公式进行计算。目的是方便客户操作,提高客户的工作效率。
所以在提出支持公式导出时,需要我们有基础导出功能。那么需要分析下导出数据的过程:
一、创建导出对象
二、查询导出对象数据
三、利用POI结合导出的数据封装成Excel格式
通过利用上面的三个基础条件,我们在接到该需求时应该如何处理?
一般情况下,可能大部分人通过查询POI相关文档,通过将列进行写死进行设计公式进行实现。如果客户的需求有变动(添加字段,减少字段等)都需要将代码调整一遍。这样费事费力。那么根据以上条件,我们如何设计一个支持配置导出公式的方案呢?
首先,我们想到的是可以在某个地方配置公式,基于这一个思路。可以采用注解的方式,在导出对象的属性上面设置上表达式。那么问题来了?
表达式应该设置成什么格式呢?通过考虑可以采取字段引用的方式描述表达式

//formula 内容为表达式的描述,为了防止在字段上可能有重复,特此使用了特殊符号进行标识,后续为了替换使用
@ExcelColumn(value = "打卡率",formula="@hgzs@/@ydkzs@*100%" )
private Object dkl;

但是,表达式设置好了,如何进行转换为表达式呢?
所以采取了反射的思想,通过导出对象中注解体现的导出顺序,通过ASCII码的方式将获取到Excel对应的列。
然后在导出的时候,将上面的封装的内容进行判断解析,替换成公式。
简单一句话:导出类上描述上表达式,反射获取字段序号,根据序号获取Excel对应的列名,组装数据时将计算的行号和列名进行组装(例如:A1 +B1),在cell上将表达式进行设置上
废话不多说,直接看代码

创建导出对象


import lombok.Data;
import java.io.Serializable;
/*** 导出对象*/
@Data
public class ExcelExportVO implements Serializable {@ExcelColumn(value = "区域市场" )private Object marketName;@ExcelColumn(value = "姓名" )private Object pName;@ExcelColumn(value = "应打卡组数" )private Object ydkzs;@ExcelColumn(value = "打卡签到数" )private Object dkqds;@ExcelColumn(value = "打卡签退数" )private Object dkqts;@ExcelColumn(value = "合格组数" )private Object hgzs;//此处自定义一个类型,处理百分比 合格组数/应打卡组数 * 100%@ExcelColumn(value = "打卡率",formula="@hgzs@/@ydkzs@*100%" )private Object dkl;@ExcelColumn(value = "拜访规划数",formula="@lxbfwcs@+@lxbfwwcs@+@khbfs@+@khbfwwcs@"  )private Object bfghs;@ExcelColumn(value = "路线拜访完成数" )private Object lxbfwcs;@ExcelColumn(value = "路线拜访未完成数" )private Object lxbfwwcs;@ExcelColumn(value = "客户拜访完成数" )private Object khbfs;@ExcelColumn(value = "客户拜访未完成数" )private Object khbfwwcs;@ExcelColumn(value = "拜访完成数",formula="@lxbfwcs@+@khbfs@"  )private Object bfwcs;@ExcelColumn(value = "拜访完成率",formula="@bfwcs@/@bfghs@*100%" )private Object bfwcl;@ExcelColumn(value = "路线照片有效次数" )private Object lxzpyxcs;@ExcelColumn(value = "路线及客户照片有效次数" ,formula="@lxzpyxcs@+@khbfs@")private Object lxjkhzpyxcs;@ExcelColumn(value = "照片合格率",formula="@lxjkhzpyxcs@/@bfwcs@*100%"  )private Object zphgl;@ExcelColumn(value = "临时拜访数" )private Object lsbfs;@ExcelColumn(value = "临时照片有效次数")private Object lszpyscs;@ExcelColumn(value = "是否规划线路" )private Object sfghxl;@ExcelColumn(value = "周综合完成率" )private Object zzhwcl;}

创建使用注解类

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 列表需要展示的注解,需要带上此注解,目的是有动态列,并且列表展示哪个就要对哪个加入相关的注解*/
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelColumn {/*** 名称* @return*/public String value() default "";/*** 计算公式:能不能采用jpel表达式搞*/public String formula() default "";}

创建封装导出对象属性类


import lombok.Getter;
import lombok.Setter;import java.io.Serializable;/*** Excel单元格字段样式描述*/
@Getter
@Setter
public class ExcelCellStyleVO implements Serializable {/*** 对应的key值*/private String key;/*** 对应的内容*/private String value;/*** 类型*/private String type;/*** 表达式*/private String formula;/*** 列名*/private String columnName;}

创建测试类

public class ExcelExportNewTest {public static void main(String[] args) throws IOException {int count = 400;JSONArray studentArray = new JSONArray();for (int i = 1; i <= count; i++) {ExcelExportVO s = new ExcelExportVO();s.setMarketName("区域"+i); s.setPName("小米"+i);s.setYdkzs(i);s.setDkqds(i);s.setDkqts(i);s.setHgzs(i);
//          s.setDkl();
//          s.setBfghs();s.setLxbfwcs(i);s.setLxbfwwcs(i);s.setKhbfs(i);s.setKhbfwwcs(i);s.setBfwcs(i);
//          s.setBfwcl();s.setLxzpyxcs(i);s.setLxjkhzpyxcs(i);
//          s.setZphgl(i);s.setLsbfs(i);s.setLszpyscs(i);s.setSfghxl(1);s.setZzhwcl(0);studentArray.add(s);}/** titleList存放了2个元素,分别为titleMap和headMap*/// 1.titleMap存放了该excel的头信息LinkedHashMap<String, String> titleMap = new LinkedHashMap<String, String>();titleMap.put("title1", "sheet1");// 2.headMap存放了该excel的列项//       titleList.add(titleMap);LinkedHashMap<String, ExcelCellStyleVO >  map =            getTitleMap(ExcelExportVO.class);File file = new File("D://ExcelExportDemo/");if (!file.exists()) file.mkdirs();// 创建该文件夹目录OutputStream os = null;try {System.out.println("正在导出xlsx...");long start = System.currentTimeMillis();// .xlsx格式os = new FileOutputStream(file.getAbsolutePath() + File.separator + start + ".xlsx");ExcelNewUtil.exportExcel(titleMap,map, studentArray, ExcelExportVO.class,os);System.out.println("导出完成...共" + count + "条数据,用时" + (System.currentTimeMillis() - start) + "毫秒");System.out.println("文件路径:" + file.getAbsolutePath() + File.separator + start + ".xlsx");} catch (Exception e) {e.printStackTrace();} finally {os.close();}}private static LinkedHashMap<String, ExcelCellStyleVO >  getTitleMap(Class clazz){LinkedHashMap<String, ExcelCellStyleVO> map = new LinkedHashMap<>();Field[] fields = clazz.getDeclaredFields();ExcelCellStyleVO vo = null;Map<String, String>     columnNameMap = new HashMap<>();int i =1;for(Field field : fields) {field.setAccessible(true);String fieldName = field.getName();Annotation annotation = field.getAnnotation(ExcelColumn.class);if (annotation instanceof ExcelColumn) {columnNameMap.put( fieldName,ExcelNewUtil.getColumnName(i)     )  ;}i++;}for(Field field : fields){//属性field.setAccessible(true);String fieldName = field.getName();Annotation annotation = field.getAnnotation(ExcelColumn.class);if (annotation instanceof ExcelColumn) {ExcelColumn excelColumn = (ExcelColumn) annotation;vo =new  ExcelCellStyleVO();vo.setKey(fieldName);vo.setValue(excelColumn.value());String formula = excelColumn.formula();if(StringUtils.isNotBlank(formula)){//将表达式内的公式进行替换,并且带有好对应的下标才行for (Map.Entry<String, String> entry : columnNameMap.entrySet()) {String key = "@"+entry.getKey()+"@";if(formula.contains(key) ){formula =     formula.replace(key,entry.getValue()+"@num@");}}vo.setFormula(formula);   //其实此时需要将表达式进行处理才行,拼装成对应的公式 即 SUM A1,A2}//此时知道当前字段在那一列上vo.setColumnName(columnNameMap.get(fieldName));map.put(fieldName,vo);}}return map;}
}

创建ExcelUtil类

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.yonyou.ocm.common.service.dto.BaseDto;
import oracle.net.aso.i;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.formula.functions.T;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFDataFormat;
import org.apache.poi.xssf.usermodel.XSSFDataValidation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;public class ExcelNewUtil <T> {public static final String DEFAULT_DATE_PATTERN = "yyyy-MM-dd";// 默认日期格式public static final int DEFAULT_COLUMN_WIDTH = 17;// 默认列宽//校验用private static String CONTAIN_LETTER_REGEX = ".*[a-zA-z].*";private static Logger logger = LoggerFactory.getLogger(ExcelNewUtil.class);/*** 导出Excel(.xlsx)格式* @param titleList 表格头信息集合* @param dataArray 数据数组* @param os 文件输出流*/public static void exportExcel(LinkedHashMap<String, String> titleMap, LinkedHashMap<String, ExcelCellStyleVO>  titleList, JSONArray dataArray, Class clazz, OutputStream os) {int minBytes = DEFAULT_COLUMN_WIDTH;/*** 声明一个工作薄*/SXSSFWorkbook workbook = new SXSSFWorkbook(1000);// 大于1000行时会把之前的行写入硬盘workbook.setCompressTempFiles(true);// head样式CellStyle headerStyle = workbook.createCellStyle();headerStyle.setAlignment(HorizontalAlignment.CENTER);headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);headerStyle.setFillForegroundColor(HSSFColor.LIGHT_GREEN.index);// 设置颜色headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);// 前景色纯色填充headerStyle.setBorderTop(BorderStyle.THIN);headerStyle.setBorderRight(BorderStyle.THIN);headerStyle.setBorderBottom(BorderStyle.THIN);headerStyle.setBorderLeft(BorderStyle.THIN);Font headerFont = workbook.createFont();headerFont.setFontHeightInPoints((short) 12);headerFont.setBold(true);//加粗headerStyle.setFont(headerFont);// 单元格样式CellStyle cellStyle = workbook.createCellStyle();cellStyle.setAlignment(HorizontalAlignment.CENTER);cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);cellStyle.setBorderTop(BorderStyle.THIN);cellStyle.setBorderRight(BorderStyle.THIN);cellStyle.setBorderBottom(BorderStyle.THIN);cellStyle.setBorderLeft(BorderStyle.THIN);Font cellFont = workbook.createFont();cellFont.setBold(false);//加粗cellStyle.setFont(cellFont);XSSFDataFormat dataFormat = (XSSFDataFormat) workbook.createDataFormat();CellStyle intStyle = workbook.createCellStyle();intStyle.setAlignment(HorizontalAlignment.CENTER);intStyle.setVerticalAlignment(VerticalAlignment.CENTER);intStyle.setBorderTop(BorderStyle.THIN);intStyle.setBorderRight(BorderStyle.THIN);intStyle.setBorderBottom(BorderStyle.THIN);intStyle.setBorderLeft(BorderStyle.THIN);intStyle.setFont(cellFont);intStyle.setDataFormat(dataFormat.getFormat("#,#0"));CellStyle baifenStyle = workbook.createCellStyle();baifenStyle.setDataFormat(dataFormat.getFormat("0.00%"));String title1 = (String) titleMap.get("title1");LinkedHashMap<String, ExcelCellStyleVO >  headMap = titleList;/*** 生成一个(带名称)表格*/SXSSFSheet sheet = (SXSSFSheet) workbook.createSheet(title1);sheet.createFreezePane(0, 1, 0, 0);// (单独)冻结前三行/*** 生成head相关信息+设置每列宽度*/int[] colWidthArr = new int[headMap.size()];// 列宽数组String[] headKeyArr = new String[headMap.size()];// headKey数组String[] headValArr = new String[headMap.size()];// headVal数组int i = 0;for (Map.Entry<String, ExcelCellStyleVO> entry : headMap.entrySet()) {headKeyArr[i] = entry.getKey();ExcelCellStyleVO styleVO =  entry.getValue();headValArr[i] = styleVO.getValue();int bytes = headKeyArr[i].getBytes().length;colWidthArr[i] = bytes < minBytes ? minBytes : bytes;sheet.setColumnWidth(i, colWidthArr[i] * 256);// 设置列宽i++;}/*** 遍历数据集合,产生Excel行数据*/int rowIndex = 0;for (Object obj : dataArray) {// 生成title+head信息if (rowIndex == 0) {SXSSFRow headerRow = (SXSSFRow) sheet.createRow(0);// head行for (int j = 0; j < headValArr.length; j++) {headerRow.createCell(j).setCellValue(headValArr[j]);headerRow.getCell(j).setCellStyle(headerStyle);}rowIndex = 1;}JSONObject jo = (JSONObject) JSONObject.toJSON(obj);// 生成数据SXSSFRow dataRow = (SXSSFRow) sheet.createRow(rowIndex);// 创建行for (int k = 0; k < headKeyArr.length; k++) {SXSSFCell cell = (SXSSFCell) dataRow.createCell(k);// 创建单元格String curKey = headKeyArr[k];Object o = jo.get(curKey);if(o  != null ){if (o instanceof Integer) {cell.setCellValue(new BigDecimal(o.toString().trim()).doubleValue());} else {cell.setCellValue(o.toString());}}else{cell.setCellValue("");}if(rowIndex != 0 && headMap.containsKey(curKey)){ExcelCellStyleVO vo =   headMap.get(curKey);if(vo != null && StringUtils.isNotBlank( vo.getFormula())){//此时需要进行替换公式 @+++@ 直接就是替换哪一行String formula =  vo.getFormula();formula =  formula.replace("@num@",(rowIndex+1)+"");cell.setCellFormula(formula);if(formula.contains("100%")){cell.setCellStyle(baifenStyle);}}}}rowIndex++;}//我们还需要在写进文件中之前对导入的内容进行设置,让其在打开Excel之前,要求Excel重新计算改工作簿中的所有公式sheet.setForceFormulaRecalculation(true);try {workbook.write(os);os.flush();// 刷新此输出流并强制将所有缓冲的输出字节写出os.close();// 关闭流workbook.dispose();// 释放workbook所占用的所有windows资源} catch (IOException e) {e.printStackTrace();}}/*** 根据索引列获取真实的Excel列【A B C ...】* @param columnNum* @return*/public static  String getColumnName(int columnNum) {int first;int last;String result = "";if (columnNum > 256)columnNum = 256;first = columnNum / 27;last = columnNum - (first * 26);if (first > 0)result = String.valueOf((char) (first + 64));if (last > 0)result = result + String.valueOf((char) (last + 64));return result;}}

实现效果

Java利用注解实现配置动态公式并结合POI导出Excel相关推荐

  1. JAVA导出Excel通用工具类——第一篇:详细介绍POI 导出excel的多种复杂情况,包括动态设置筛选、动态合并横向(纵向)单元格等多种复杂情况——保姆级别,真的不能再详细了,代码拿来即用)

    JAVA导出Excel通用工具--第一篇:详细介绍POI 导出excel的多种复杂情况,包括动态设置筛选.动态合并横向(纵向)单元格等多种复杂情况--保姆级别,真的不能再详细了,封装通用工具类,代码拿 ...

  2. java利用poi导出excel功能-附带图片导出

    java利用poi导出excel功能-附带图片导出 写在前面 最近刚离职,闲来无事,于是把上两家公司都有碰到过的需求但都没有去研究实现:即导出带图片的excel报表.于是就折腾了一下这个功能,研究出来 ...

  3. Java web项目利用POI导出EXCEL表格

    SSH2 POI导出EXCEL表格 1.首先导入poi的jar包 HSSFWorkbook :工作簿,代表一个excel的整个文档 HSSFSheet:工作表 HSSFRow :行 HSSFCell: ...

  4. Java POI 导出EXCEL经典实现 Java导出Excel弹出下载框

    原文转载:http://blog.csdn.net/evangel_z/article/details/7332535 目录(?)[+] 在web开发中,有一个经典的功能,就是数据的导入导出.特别是数 ...

  5. Java POI 导出EXCEL经典实现 Java导出Excel

    转自http://blog.csdn.net/evangel_z/article/details/7332535 在web开发中,有一个经典的功能,就是数据的导入导出.特别是数据的导出,在生产管理或者 ...

  6. Java和poi导出excel报表

    一:poi jar下载地址:点击打开链接: 二:工程截图: 三:运行效果截图: 四:源代码: Student.java: package com.poi.bean;import java.util.D ...

  7. cpu java poi 导出_java基于poi导出excel透视表代码实例

    这篇文章主要介绍了java基于poi导出excel透视表代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 从前,我是一个前端程序猿,怀着对打通 ...

  8. @excel注解_java bean 一对多,多对一 poi导出excel表格

    最近造了个poi导出 excel轮子 特点 java bean 一对多.多对一关系合并单元行 支持图片导出 Bean 一对多关系合并行 代码 public class User { @Excel(na ...

  9. Springboot利用poi导出excel下载

    Springboot利用poi导出excel下载 因为项目中之前的做法是用反射获取属性,所以demo中也是用的反射,我看网上很多文章都是存入一个List中,不知道这两种哪种更何合适一点,或者有什么更好 ...

最新文章

  1. 在线作图|在线做UMAP降维分析
  2. Xcode 添加前缀
  3. linux gcc编译c文件头文件,使用GCC编译器编译C语言
  4. ant警告 “warning: 'includeantruntime' was not set”解决方法
  5. nslookup 包含在那个包中_nslookup命令详解
  6. Android 事件处理
  7. Silverlight4.0教程之WebBrowser控件(Silverlight内置HTML浏览器控件)
  8. 小程序和android联调,小程序打开APP指定页面
  9. c语言技术课第一次作业
  10. springboot 网页聊天室
  11. Java继承中的覆盖
  12. 为什么普通人做量化交易会亏钱?
  13. DataMatrix 数据容量
  14. 基于Android的天气预报系统的设计和实现
  15. 求两个数中的最大值最小值算法
  16. html在线聊天界面模板,一款带气泡对话框的HTML5聊天应用界面模板
  17. Pytorch 叶子张量 leaf tensor (叶子节点) (detach)
  18. Intellij IDEA的安装及使用介绍
  19. (转)李开复的美东AI见闻
  20. 在vue中使用flexible响应式布局——默认html字体大小(font-size)是54px的问题

热门文章

  1. Python读入制表位分割的csv文件
  2. Android WebView加载H5音视频自动播放、关闭Activity停止播放
  3. 002.3-b3dm的终极优化
  4. Bootstrap组件学习之导航和导航条
  5. attention机制--加权
  6. 20172321 2017-2018-2 《程序设计与数据结构》第11周学习总结
  7. html网页制作菜鸟,作为一名菜鸟如何做网页制作
  8. MPE环境安装-强化学习的小demo
  9. 12306订票失败,退款指南(银联建行版)
  10. 关于AWS Alb和Route53的使用 小结