EasyExcel 根据实体类自动导出需要的字段
背景
- 开发一个通用的数据规则模板
-
一个用于存放所有数据的表 rule_data
-
一个用于字段对照的模板 rule_template
界面上的字段标头,使用template的映射,所有数据,都存在data表,通过ruleId区分所属业务
2.需求
根据不同的业务导入导出数据
要点:
- data表数据字段在不同的业务中表述的含义不一样,不能直接在实体中指定注解
@ExcelProperty(value = "xxx")
- 导入导出,都要使用模板表的映射
- 自动生成导入模板
/**
* 根据规则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());
}
惜秦皇汉武,略输文采;唐宗宋祖,稍逊风骚。
一代天骄,成吉思汗,只识弯弓射大雕。
俱往矣,数风流人物,还看今朝