自定义Excel注解POI导出

380 阅读3分钟

最近在开发Excel导出,采用了自定义Excel注解的方式,代码如下

第一:自定义注解类ExportExcelAnnota

定义注解类考虑事项

  1. 注解是标注在实体对象的属性上(暂时不考虑列表合并)
  2. 对象属性可能是字典值,需要涉及到字典值转换
  3. 对象属性可能是日期,需要涉及到日期转换显示
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * excel报表导出
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
public @interface ExportExcelAnnota {

    /**
     * 中文值
     * @return
     */
    String value() default "";

    /**
     * 名称
     * @return
     */
    String name() default "";

    /**
     * 是否是字典
     * @return
     */
    boolean dic() default false;

    /**
     * 字典主表名称
     * @return
     */
    String dicName() default "";

    /**
     * 字典主表编码
     * @return
     */
    String dicCode() default "";

    /**
     * 是否是时间
     * @return
     */
    boolean date() default false;

    /**
     * 时间格式化
     * @return
     */
    String pattern() default "yyyy-MM-dd";
}

第二.POI版本3.17

<poi.version>3.17</poi.version>
<poi-ooxml.version>3.17</poi-ooxml.version>
..

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>${poi.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>${poi.version}</version>
</dependency>

第三:实体类

import com.quantaeye.app.rest.common.annotation.ExportExcelAnnota;
import com.quantaeye.service.dto.ImsProjectDeviceDto;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;
import java.util.List;

/**
 * 项目管理:导出对象
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ProjectExportExcelVo {

    private Long id;

    @ExportExcelAnnota(value = "项目编号", name = "projectNo")
    private String projectNo;

    @ExportExcelAnnota(value = "项目名称", name = "projectName")
    private String projectName;

    @ExportExcelAnnota(value = "项目简称", name = "projetAbt")
    private String projetAbt;

    @ExportExcelAnnota(value = "项目类型", name = "projectType",dic = true,dicName = "projectType")
    private Integer projectType;

    @ExportExcelAnnota(value = "项目经理", name = "projectChargePerson")
    private String projectChargePerson;

    @ExportExcelAnnota(value = "市场经理", name = "managerMarket")
    private String managerMarket;

    @ExportExcelAnnota(value = "订单编号", name = "orderNo")
    private String orderNo;

    @ExportExcelAnnota(value = "订单日期", name = "orderDate",date = true,pattern = "yyyy-MM-dd")
    private Date orderDate;

    @ExportExcelAnnota(value = "客户名称", name = "customerName")
    private String customerName;

    @ExportExcelAnnota(value = "客户联系人", name = "customerContacts")
    private String customerContacts;

    @ExportExcelAnnota(value = "合同主体", name = "contractSubject",dic = true,dicName = "合同主体")
    private Integer contractSubject;

    @ExportExcelAnnota(value = "项目开始时间", name = "projectStartTime",date = true,pattern = "yyyy-MM-dd")
    private Date projectStartTime;

    @ExportExcelAnnota(value = "项目结束时间", name = "projectEndTime",date = true,pattern = "yyyy-MM-dd")
    private Date projectEndTime;

    @ExportExcelAnnota(value = "区域", name = "areaName")
    private String areaName;

    @ApiModelProperty(value = "设备信息-设备数量", name = "deviceDtoList")
    private List<ImsProjectDeviceDto> deviceDtoList;

}

第四:工具类:使用的时候,需要预先将用到的字典集合查询出来,exportExcelDemo方法可实现简单的列表导出,exportExcelList方法是实现了对象中有List的excel导出。

import com.quantaeye.app.common.util.DateUtil;
import com.quantaeye.app.common.util.LocalDateUtil;
import com.quantaeye.app.rest.common.annotation.ExportExcelAnnota;
import com.quantaeye.app.rest.common.model.ApiResult;
import com.quantaeye.service.dto.ImsProjectDeviceDto;
import com.quantaeye.service.vo.SysDictResVo;
import com.quantaeye.service.vo.response.ProjectExportExcelVo;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 适用于项目管理-excel列表导出
 */
@Slf4j
public class ExcelExportListUtil {


    /**
     * 项目管理-列表excel导出
     * @param sheetName
     * @param response
     * @param dictMap
     * @param list
     */
    public static void exportExcelList(String sheetName, HttpServletResponse response,ApiResult<Map<String, List<SysDictResVo>>> dictMap,List<ProjectExportExcelVo> list){
        XSSFWorkbook wb = null;
        ServletOutputStream outputStream = null;
        try {
            wb = ExcelExportListUtil.createWorkBook(sheetName,list,dictMap,ProjectExportExcelVo.class, ImsProjectDeviceDto.class);
            list.clear();
            outputStream = response.getOutputStream();
            response.setContentType("application/vnd.ms-excel");
            response.setHeader("Content-disposition", "attachment;filename=project.xlsx");
            wb.write(outputStream);
        } catch (IOException e) {
            log.error("导出项目数据失败. msg={}", e.getMessage());
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.flush();
                    outputStream.close();
                } catch (IOException e) {
                    log.error(e.getMessage());
                }
            }
            if (wb != null) {
                try {
                    wb.close();
                } catch (IOException e) {
                    log.error(e.getMessage());
                }
            }
        }
    }

    /**
     * 项目管理-项目Excel导出
     * 单sheet报表
     * @param sheetName sheet名称
     * @param list
     * @param targetClass
     * @param targetClassTwo
     * @return
     */
    public static XSSFWorkbook createWorkBook(String sheetName,List<ProjectExportExcelVo> list,ApiResult<Map<String, List<SysDictResVo>>> dictMap,Class<?> targetClass,Class<?> targetClassTwo){
        XSSFWorkbook wb = new XSSFWorkbook();
        Sheet sheet = wb.createSheet(sheetName);
        //获取列数据
        List<ExportExcelAnnota> columnNamesOne = getColumnNames(null,targetClass);
        List<ExportExcelAnnota> columnNamesTwo = getColumnNames(null,targetClassTwo);
        List<ExportExcelAnnota> columnNames = new ArrayList<>();
        columnNames.addAll(columnNamesOne);
        columnNames.addAll(columnNamesTwo);
        if(CollectionUtils.isNotEmpty(columnNames)){
            // 手动设置列宽。第一个参数表示要为第几列设;,第二个参数表示列的宽度,n为列高的像素数。
            for (int i = 0; i < columnNames.size(); i++) {
                sheet.setColumnWidth((short) i, (short) (35.7 * 150));
            }
        }
        // 设置表头
        int rowNum = setCellTitle(0,columnNames, wb, sheet);
        //创建内容行
        CellStyle cs2 = getCellStyle(wb);
        if (list != null && list.size() > 0) {
            //设置每行每列的值
            for (int i = 0; i < list.size(); i++) {
                int sindex = rowNum;
                // Row 行,Cell 方格 , Row 和 Cell 都是从0开始计数的
                // 创建一行,在页sheet上
                Row row1 = sheet.createRow(rowNum);
                rowNum++;
                ProjectExportExcelVo vo = list.get(i);
                List<ImsProjectDeviceDto> flist= vo.getDeviceDtoList();

                // 在row行上创建一个方格
                for (short j = 0; j < columnNamesOne.size(); j++) {
                    Cell cell = row1.createCell(j);
                    ExportExcelAnnota an = columnNamesOne.get(j);
                    //获取值
                    Object value = getFieldValueByName(an.name(),list.get(i));
                    String cellvalue = "";
                    //转换:日期转换;字典转换
                    if(an.date()){
                        //日期
                        cellvalue = DateUtil.formatDate((Date)value,an.pattern());
                        cell.setCellValue(cellvalue);
                    }else if(an.dic()){
                        //从字典中获取值
                        cellvalue = getDictMapName(dictMap, an.dicName(), String.valueOf(value));
                        cell.setCellValue(cellvalue);
                    }else{
                        setCellValue(cell,value);
                    }
                    cell.setCellStyle(cs2);
                }

                if(CollectionUtils.isNotEmpty(flist)){
                    for(int k=0 ; k < flist.size();k++){
                        if(k > 0){
                            row1 = sheet.createRow(rowNum);
                            rowNum++;
                            for (short j = 0; j < columnNamesOne.size(); j++) {
                                Cell cell = row1.createCell(j);
                                cell.setCellValue("");
                                cell.setCellStyle(cs2);
                            }
                        }
                        Cell cell = null;
                        for (short j = 0; j < columnNamesTwo.size(); j++) {
                            cell = row1.createCell(columnNamesOne.size() + j);
                            //获取值
                            Object value = getFieldValueByName(columnNamesTwo.get(j).name(),flist.get(k));
                            String cellvalue = "";
                            //转换:日期转换;字典转换
                            if(columnNamesTwo.get(j).date()){
                                //日期
                                cellvalue = DateUtil.formatDate((Date)value,columnNamesTwo.get(j).pattern());
                                cell.setCellValue(cellvalue);
                            }else if(columnNamesTwo.get(j).dic()){
                                //从字典中获取值
                                cellvalue = getDictMapName(dictMap, columnNamesTwo.get(j).dicName(), String.valueOf(value));
                                cell.setCellValue(cellvalue);
                            }else{
                                setCellValue(cell,value);
                            }
                            cell.setCellStyle(cs2);
                        }
                    }
                    //合并单元格
                    if(flist.size() > 1){
                        for (short j = 0; j < columnNamesOne.size(); j++) {
                            sheet.addMergedRegion(new CellRangeAddress(sindex,rowNum-1,j,j));
                        }
                    }
                }else{
                    Cell cell = null;
                    for (short j = 0; j < columnNamesTwo.size(); j++) {
                        cell = row1.createCell(columnNamesOne.size() + j);
                        cell.setCellValue("");
                        cell.setCellStyle(cs2);
                    }
                }
            }
        }
        return wb;
    }

    /**
     * 项目管理-列表excel导出
     * @param sheetName
     * @param response
     * @param dictMap
     * @param list
     */
    public static void exportExcelDemo(String sheetName, HttpServletResponse response,ApiResult<Map<String, List<SysDictResVo>>> dictMap,List<?> list){
        XSSFWorkbook wb = null;
        ServletOutputStream outputStream = null;
        try {
            wb = ExcelExportListUtil.createWorkBook(sheetName,list,dictMap,ProjectExportExcelVo.class);
            list.clear();
            outputStream = response.getOutputStream();
            response.setContentType("application/vnd.ms-excel");
            response.setHeader("Content-disposition", "attachment;filename=project.xlsx");
            wb.write(outputStream);
        } catch (IOException e) {
            log.error("导出项目数据失败. msg={}", e.getMessage());
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.flush();
                    outputStream.close();
                } catch (IOException e) {
                    log.error(e.getMessage());
                }
            }
            if (wb != null) {
                try {
                    wb.close();
                } catch (IOException e) {
                    log.error(e.getMessage());
                }
            }
        }
    }

    /**
     * 单对象导出(通用)
     * 单sheet报表
     * @param sheetName sheet名称
     * @param list
     * @param targetClass
     * @return
     */
    public static XSSFWorkbook createWorkBook(String sheetName,List<?> list,ApiResult<Map<String, List<SysDictResVo>>> dictMap,Class<?> targetClass){
        XSSFWorkbook wb = new XSSFWorkbook();
        Sheet sheet = wb.createSheet(sheetName);
        //获取列数据
        List<ExportExcelAnnota> columnNames = getColumnNames(null,targetClass);
        if(CollectionUtils.isNotEmpty(columnNames)){
            // 手动设置列宽。第一个参数表示要为第几列设;第二个参数表示列的宽度,n为列高的像素数。
            for (int i = 0; i < columnNames.size(); i++) {
                sheet.setColumnWidth((short) i, (short) (35.7 * 150));
            }
        }
        // 设置表头
        int rownum = setCellTitle(0,columnNames, wb, sheet);
        //创建内容行
        if (list != null && list.size() > 0) {
            CellStyle cs2 = getCellStyle(wb);
            //设置每行每列的值
            for (int i = 0; i < list.size(); i++) {
                // Row 行,Cell 方格 , Row 和 Cell 都是从0开始计数的
                // 创建一行,在页sheet上
                Row row1 = sheet.createRow(rownum);
                rownum++;
                // 在row行上创建一个方格
                for (short j = 0; j < columnNames.size(); j++) {
                    Cell cell = row1.createCell(j);
                    //获取值
                    Object value = getFieldValueByName(columnNames.get(j).name(),list.get(i));
                    String cellvalue = "";
                    //转换:日期转换;字典转换
                    if(columnNames.get(j).date()){
                        //日期
                        cellvalue = DateUtil.formatDate((Date)value,columnNames.get(j).pattern());
                        cell.setCellValue(cellvalue);
                    }else if(columnNames.get(j).dic()){
                        //从字典中获取值
                        cellvalue = getDictMapName(dictMap, columnNames.get(j).dicName(), String.valueOf(value));
                        cell.setCellValue(cellvalue);
                    }else{
                        setCellValue(cell,value);
                    }
                    cell.setCellStyle(cs2);
                }
            }
        }
        return wb;
    }

    /**
     * 给单元格赋值
     * @param cell
     * @param obj
     */
    private static void setCellValue(Cell cell, Object obj) {
        if(obj == null){
            cell.setCellValue("");
        }else if(obj instanceof BigDecimal || obj instanceof Double) {
            cell.setCellValue(Double.valueOf(obj.toString()));
        } else if (obj instanceof String) {
            cell.setCellValue(String.valueOf(obj));
        } else if (obj instanceof Integer || isNumber(obj.toString())) {
            cell.setCellValue(Integer.valueOf(obj.toString()));
        } else if (obj instanceof Date) {
            cell.setCellValue(LocalDateUtil.format((Date) obj));
        } else {
            cell.setCellValue(String.valueOf(obj));
        }
    }

    /**
     * 判断是否是数字
     * @param str
     * @return
     */
    public static boolean isNumber(String str) {
        java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("[1-9]{1}\d{0,9}");
        java.util.regex.Matcher match = pattern.matcher(str);
        if (match.matches()) {
            return true;
        } else {
            return false;
        }
    }

    /**
     *
     * @param rownum
     * @param columnNames
     * @param wb
     * @param sheet
     * @return  行数
     */
    private static int setCellTitle(int rownum, List<ExportExcelAnnota> columnNames, XSSFWorkbook wb, Sheet sheet) {
        if (CollectionUtils.isEmpty(columnNames)) {
            return rownum;
        }
        // 创建第一行
        Row row = sheet.createRow(rownum);
        rownum++;
        CellStyle cs = wb.createCellStyle();
        // 创建字体
        Font f = wb.createFont();
        // 创建第一种字体样式(用于列名)
        f.setFontHeightInPoints((short) 10);
        f.setColor(IndexedColors.BLACK.getIndex());
        f.setBold(true);
        setStyle(cs, f);
        //设置列名
        for (int i = 0; i < columnNames.size(); i++) {
            Cell cell = row.createCell(i);
            cell.setCellValue(columnNames.get(i).value());
            cell.setCellStyle(cs);
        }
        return rownum;
    }

    /**
     * 设置单元格样式
     * @param cs
     * @param f
     */
    private static void setStyle(CellStyle cs, Font f) {
        cs.setFont(f);
        cs.setBorderLeft(BorderStyle.THIN);
        cs.setBorderRight(BorderStyle.THIN);
        cs.setBorderTop(BorderStyle.THIN);
        cs.setBorderBottom(BorderStyle.THIN);
        cs.setAlignment(HorizontalAlignment.LEFT);
        cs.setWrapText(true);//设置自动换行
    }

    private static CellStyle getCellStyle(XSSFWorkbook wb) {
        // 创建两种单元格格式
        CellStyle cs = wb.createCellStyle();
        // 创建两种字体
        Font f = wb.createFont();
        // 创建第二种字体样式(用于值)
        f.setFontHeightInPoints((short) 10);
        f.setColor(IndexedColors.BLACK.getIndex());
        // 设置第二种单元格的样式(用于值)
        setStyle(cs, f);
        return cs;
    }


    /**
     * @param columnNames
     * @param targetClass
     * @return
     */
    public static List<ExportExcelAnnota> getColumnNames(List<ExportExcelAnnota> columnNames, Class<?> targetClass){
        if(CollectionUtils.isEmpty(columnNames)){
            columnNames = new ArrayList<>();
        }
        //获取类的属性对象
        Field[] fields = targetClass.getDeclaredFields();
        for(int i=0;i<fields.length;i++){
            ExportExcelAnnota api = fields[i].getAnnotation(ExportExcelAnnota.class);
            if(api != null){
                columnNames.add(api);
            }
        }
        return columnNames;
    }

    /**
     * 获取属性名数组
     * */
    private static String[] getFiledName(Class<?> targetClass){
        Field[] fields = targetClass.getDeclaredFields();
        String[] fieldNames=new String[fields.length];
        for(int i=0;i<fields.length;i++){
            fieldNames[i]=fields[i].getName();
            ApiModelProperty api = fields[i].getAnnotation(ApiModelProperty.class);
            System.out.println(api.value());
        }
        return fieldNames;
    }

    /**
     * 根据属性名获取属性值
     * @param fieldName
     * @param o
     * @return
     */
    private static Object getFieldValueByName(String fieldName, Object o) {
        try {
            String firstLetter = fieldName.substring(0, 1).toUpperCase();
            String getter = "get" + firstLetter + fieldName.substring(1);
            Method method = o.getClass().getMethod(getter, new Class[] {});
            Object value = method.invoke(o, new Object[] {});
            return value;
        } catch (Exception e) {

            return null;
        }
    }

    /**
     * 获取字典值
     * @param dictMap
     * @param keyName
     * @param value
     * @return
     */
    public static String getDictMapName(ApiResult<Map<String, List<SysDictResVo>>> dictMap, String keyName, String value){
        if (dictMap!=null && dictMap.getData()!=null){
            Map<String, List<SysDictResVo>> data = dictMap.getData();
            if (data.get(keyName)!=null){
                List<SysDictResVo> resVoList = data.get(keyName);
                Map<String, SysDictResVo> valueMap = resVoList.stream().collect(Collectors.toMap(SysDictResVo::getNum, dict -> dict));
                if (valueMap.get(value)!=null){
                    return valueMap.get(value).getName();
                }
            }

        }
        return "";
    }
}