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

利用注解实现动态配置公式并结合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张图片

你可能感兴趣的:(POI导出Excel,java)