EasyExcel 根据实体类自动导出需要的字段

背景

  1. 开发一个通用的数据规则模板
  • 一个用于存放所有数据的表 rule_data
    image

  • 一个用于字段对照的模板 rule_template
    image

界面上的字段标头,使用template的映射,所有数据,都存在data表,通过ruleId区分所属业务

2.需求
根据不同的业务导入导出数据
要点:

  1. data表数据字段在不同的业务中表述的含义不一样,不能直接在实体中指定注解@ExcelProperty(value = "xxx")
  2. 导入导出,都要使用模板表的映射
  • 自动生成导入模板
/**
     * 根据规则ID,下载导入数据模板
     *
     * @return
     * @author zhutantan
     * @date 2022/9/23 13:47
     */
    @GetMapping("/exportTemplate/{ruleId}")
    public void exportTemplate(@PathVariable int ruleId, HttpServletResponse response) {
        try {
            //获取规则表名
            BmsRuleMain ruleMain = bmsRuleMainService.getById(ruleId);
            //根据ruleid,动态生成EXCEL模板
            List<BmsRuleTemplate> list = bmsRuleTemplateService.lambdaQuery()
                    .eq(BmsRuleTemplate::getRuleId, ruleId)
                    .orderByAsc(BmsRuleTemplate::getLineNo)
                    .list();
            List<List<String>> lists = list.stream().map((item) -> {
                List<String> temp = new ArrayList<>();
                temp.add(item.getFieldName());
                return temp;
            }).collect(Collectors.toList());

            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            String fileName = URLEncoder.encode(ruleMain.getRuleName() + "导入模板", "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
            EasyExcel.write(response.getOutputStream())
                    .head(lists).sheet("导入模板")
                    .doWrite(new ArrayList());
        } catch (Exception e) {
            e.printStackTrace();
//            R.fail();
        }
//        return R.ok();
    }
  • 导入数据时,根据template表,进行字段对照,使用表格的下标来确定字段
   /**
     * 导根据规则ID,导入数据
     *
     * @return com.kunzhi.common.core.domain.R
     * @author wanghongliang
     * @date 2023-07-07 15:40
     */
    @PostMapping("/imported/{ruleId}")
    public R importData(MultipartFile file, @PathVariable int ruleId) {
        try {
            List<BmsRuleTemplate> templates = bmsRuleTemplateService.lambdaQuery()
                    .eq(BmsRuleTemplate::getRuleId, ruleId)
                    .orderByAsc(BmsRuleTemplate::getLineNo)
                    .list();
            EasyExcel.read(file.getInputStream(), new RuleDataListener(bmsRuleDataService, templates)).sheet().doRead();
            return R.ok(null, "导入成功");
        } catch (Exception e) {
            e.printStackTrace();
            return R.fail("导入失败,原因:" + e.getMessage());
        }

    }

RuleDataListener 类

package com.kunzhi.bms.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
import com.kunzhi.bms.domain.BmsRuleData;
import com.kunzhi.bms.domain.BmsRuleTemplate;
import com.kunzhi.bms.service.BmsRuleDataService;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
//@Component
public class RuleDataListener extends AnalysisEventListener<Map<Integer, String>> {

    BmsRuleDataService bmsRuleDataService;
    /**
     * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 1000;
    List<BmsRuleData> list = new ArrayList<BmsRuleData>();
    //    List<BmsRuleData> updatelist = new ArrayList<BmsRuleData>();
    private List<BmsRuleTemplate> templates = new ArrayList<BmsRuleTemplate>();

    public RuleDataListener(BmsRuleDataService dataList, List<BmsRuleTemplate> templates) {
        this.bmsRuleDataService = dataList;
        this.templates = templates;

    }

    // 自定义表格列对照,key为自定义的字段名,value为表格列的下标
    private Map<String, Integer> columnMap = new HashMap<>();


    // 处理表头,获取表格列名和自定义字段名之间的对照关系
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        for (Map.Entry<Integer, String> entry : headMap.entrySet()) {
            String columnName = entry.getValue();
            for (BmsRuleTemplate template : templates) {
                if (template.getFieldName().equals(columnName)) {
                    columnMap.put(template.getMappingField(), entry.getKey());
                    break;
                }
            }
        }
    }

    // 处理excel每行数据
    @Override
    public void invoke(Map<Integer, String> rowData, AnalysisContext context) {
        Map<String, String> data = new HashMap<>();
        for (BmsRuleTemplate template : templates) {
            String rowDD = rowData.get(columnMap.get(template.getMappingField()));
            data.put(template.getMappingField(), rowDD);
        }
        // 每一行数据都需要带上ID
        data.put("rule_id", templates.get(0).getRuleId().toString());
        BmsRuleData brd = JSON.parseObject(JSON.toJSONString(data), BmsRuleData.class);
        list.add(brd);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size() >= BATCH_COUNT) {
            try {
                saveData();
            } finally {
                // 存储完成清理 list
                list.clear();
            }
        }

    }


    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        try {
            saveData();
        } finally {
            list.clear();
        }
        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据,开始存储数据库!", list.size());
        bmsRuleDataService.saveBatch(list);
        log.info("存储数据库成功!");
    }
    
}

  • 导出数据 标头和内容对应

    /**
     * 根据规则ID.导出数据
     *
     * @return
     * @author zhutantan
     * @date 2022/9/23 13:47
     */
    @GetMapping("/export")
    public void exportData(BmsRuleData bmsRuleData, HttpServletResponse response) {
        try {
            //获取规则表名
            BmsRuleMain ruleMain = bmsRuleMainService.getById(bmsRuleData.getRuleId());
            //重点,这里根据映射字段排序去生成标头,也是迎合导出时,默认和实体类的字段位置对应。
            List<BmsRuleTemplate> templateList = bmsRuleTemplateService.lambdaQuery()
                    .eq(BmsRuleTemplate::getRuleId, bmsRuleData.getRuleId())
                    .groupBy(BmsRuleTemplate::getMappingField)
                    .orderByAsc(BmsRuleTemplate::getMappingField)
                    .list();

            String cloms = templateList.stream().map(BmsRuleTemplate::getMappingField).collect(Collectors.joining(","));
            System.out.println(cloms);
            List<BmsRuleData> list = bmsRuleDataService.list(new QueryWrapper<>(bmsRuleData).select(cloms));
            // 定义表标题头字段
            List<List<String>> heade = new ArrayList<List<String>>();

            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            String fileName = URLEncoder.encode(ruleMain.getRuleName(), "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
            // 根据用户传入字段 假设我们只要导出 date
            Set<String> includeColumnFiledNames = new HashSet<String>();
            for (BmsRuleTemplate clom :templateList){
                List<String> temp = new ArrayList<>();
                temp.add(clom.getFieldName());
                heade.add(temp);
                includeColumnFiledNames.add(clom.getMappingField());
            }
            // 写入
            EasyExcel.write(response.getOutputStream())
                    .head(heade)
                    .includeColumnFiledNames(includeColumnFiledNames)
                    .autoCloseStream(Boolean.FALSE)
                    .sheet("数据表")
                    .doWrite(list);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

实体类

package com.kunzhi.bms.domain;

import com.alibaba.excel.annotation.ExcelIgnore;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.kunzhi.common.core.web.domain.BaseEntity;
import lombok.Data;

import javax.validation.constraints.NotNull;
import java.util.Date;

/**
 * 规则数据表
 * @TableName bms_rule_data
 */
@TableName(value ="bms_rule_data")
@Data
public class BmsRuleData extends BaseEntity {
    /**
     * 主键
     */
    @ExcelIgnore
    @TableId
    private Long id;

    /**
     * 规则表ID
     */
    @ExcelIgnore
    @NotNull(message = "规则表ID不能为空")
    private Long ruleId;

    /**
     * 版本号
     */
    @ExcelIgnore
    private String version;

    /**
     * 所属组织
     */
    @ExcelIgnore
    private String orgCode;

    /**
     * 日期值1
     */
    private Date date1;

    /**
     * 日期值2
     */
    private Date date2;

    /**
     * 日期值3
     */
    private Date date3;
    /**
     * 日期值4
     */
    private Date date4;
    /**
     * 日期值5
     */
    private Date date5;
    /**
     * 日期值6
     */
    private Date date6;
    /**
     * 日期值7
     */
    private Date date7;
    /**
     * 日期值8
     */
    private Date date8;
    /**
     * 日期值9
     */
    private Date date9;
    /**
     * 日期值10
     */
    private Date date10;
    /**
     * 日期值11
     */
    private Date date11;
    /**
     * 日期值12
     */
    private Date date12;
    /**
     * 日期值13
     */
    private Date date13;
    /**
     * 日期值14
     */
    private Date date14;
    /**
     * 日期值15
     */
    private Date date15;
    /**
     * 数值1
     */
    private Double double1;

    /**
     * 数值2
     */
    private Double double2;

    /**
     * 数值3
     */
    private Double double3;

    /**
     * 数值4
     */
    private Double double4;

    /**
     * 数值5
     */
    private Double double5;

    /**
     * 数值6
     */
    private Double double6;

    /**
     * 数值7
     */
    private Double double7;

    /**
     * 数值8
     */
    private Double double8;

    /**
     * 数值9
     */
    private Double double9;

    /**
     * 数值10
     */
    private Double double10;

    /**
     * 数值11
     */
    private Double double11;

    /**
     * 数值12
     */
    private Double double12;

    /**
     * 数值13
     */
    private Double double13;

    /**
     * 数值14
     */
    private Double double14;

    /**
     * 数值15
     */
    private Double double15;

    /**
     * 整数1
     */
    private Integer integer1;

    /**
     * 整数2
     */
    private Integer integer2;
    /**
     * 整数3
     */
    private Integer integer3;

    /**
     * 整数4
     */
    private Integer integer4;
    /**
     * 整数5
     */
    private Integer integer5;
    /**
     * 整数6
     */
    private Integer integer6;
    /**
     * 整数7
     */
    private Integer integer7;
    /**
     * 整数8
     */
    private Integer integer8;

    /**
     * 整数9
     */
    private Integer integer9;

    /**
     * 整数10
     */
    private Integer integer10;
    /**
     * 整数11
     */
    private Integer integer11;
    /**
     * 整数12
     */
    private Integer integer12;
    /**
     * 整数13
     */
    private Integer integer13;
    /**
     * 整数14
     */
    private Integer integer14;
    /**
     * 整数15
     */
    private Integer integer15;

    /**
     * 值1
     */
    private String value1;

    /**
     * 值2
     */
    private String value2;

    /**
     * 值3
     */
    private String value3;

    /**
     * 值4
     */
    private String value4;

    /**
     * 值5
     */
    private String value5;

    /**
     * 值6
     */
    private String value6;

    /**
     * 值7
     */
    private String value7;

    /**
     * 值8
     */
    private String value8;

    /**
     * 值9
     */
    private String value9;

    /**
     * 值10
     */
    private String value10;

    /**
     * 值11
     */
    private String value11;

    /**
     * 值12
     */
    private String value12;

    /**
     * 值13
     */
    private String value13;

    /**
     * 值14
     */
    private String value14;

    /**
     * 值15
     */
    private String value15;
}

拓展

    /**
     * 根据参数只导出指定列
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>
     * 2. 根据自己或者排除自己需要的列
     * <p>
     * 3. 直接写即可
     *
     * @since 2.1.1
     */
    @Test
    public void excludeOrIncludeWrite() {
        String fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里需要注意 在使用ExcelProperty注解的使用,如果想不空列则需要加入order字段,而不是index,order会忽略空列,然后继续往后,而index,不会忽略空列,在第几列就是第几列。

        // 根据用户传入字段 假设我们要忽略 date
        Set<String> excludeColumnFiledNames = new HashSet<String>();
        excludeColumnFiledNames.add("date");
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        EasyExcel.write(fileName, DemoData.class).excludeColumnFiledNames(excludeColumnFiledNames).sheet("模板")
            .doWrite(data());

        fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";
        // 根据用户传入字段 假设我们只要导出 date
        Set<String> includeColumnFiledNames = new HashSet<String>();
        includeColumnFiledNames.add("date");
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        EasyExcel.write(fileName, DemoData.class).includeColumnFiledNames(includeColumnFiledNames).sheet("模板")
            .doWrite(data());
    }

posted @ 2023-07-10 18:23  darling331  阅读(1301)  评论(0编辑  收藏  举报