实施顾问提出导出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;
}
}
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;
}
}