下面我们以用户信息的导出导入为例,分别介绍两种处理方式。
com.alibaba
easyexcel
2.1.6
创建一个用来 读取 excel的实体类
实体类的属性可以用
@ExcelProperty(index = 0),index=0,找的是上图 A列(第一列)
@ExcelProperty(value = “标号”)
两种都可以用,但是不要两个一起用
实体类中可以使用@DateFormat(阿里包下的)注解:
要使用String类型来接收数据才有用
@Data
public class TemplateEntity {
@ExcelProperty("标号")
private Integer label;
@ExcelProperty("字符串")
private String str;
@ExcelProperty("数字")
private Integer num;
@ExcelProperty("时间")
// 这里需要用string接收才会格式化
@DateTimeFormat("yyyy-MM-dd")
private String date;
}
————————————————
public class TemplateListener extends AnalysisEventListener {
private List list = new ArrayList<>();
// 一条一条读取数据,全部添加到list集合里
@Override
public void invoke(TemplateEntity data, AnalysisContext analysisContext) {
list.add(data);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {}
public List getData() {
return list;
}
}
public interface TemplateService {
/**
* 导入excel
*/
Result importExcel(MultipartFile file) throws IOException;
}
@Service
public class TemplateServiceImpl implements TemplateService {
@Override
public Result importExcel(MultipartFile file) throws IOException{
List entities = getTemplateEntities(file);
// 处理数据
System.out.println(entities);
return Result.success(entities);
}
// 读取 excel 数据
private List getTemplateEntities(MultipartFile file) throws IOException {
TemplateListener listener = new TemplateListener(); // 定义的 listener
EasyExcel.read(file.getInputStream(), TemplateEntity.class, listener).sheet().doRead();
// 返回 所有数据
return listener.getData();
}
}
@RestController
@RequestMapping("/sys")
public class TemplateController {
@Autowired
private TemplateService templateService;
@RequestMapping("/import")
public Result importData(@RequestPart("file") MultipartFile file) throws IOException{
return templateService.importExcel(file);
}
}
{
"code": 200,
"msg": "处理成功",
"data": [
{
"label": 1,
"str": "a",
"num": 20
},
{
"label": 2,
"str": "b",
"num": 30
},
{
"label": 3,
"str": "c",
"num": 40
},
...
}
————————————————
这里为了演示效果,sheet1和sheet3是不同表头的,sheet2目前是空的数据表
读取时,指定不同的监听类,excel接收数据的实体类对象,然后放入map中返回即可
实体类
TemplateEntity接收sheet1
@Data
public class TemplateEntity {
@ExcelProperty("标号")
private Integer label;
@ExcelProperty("字符串")
private String str;
@ExcelProperty("数字")
private Integer num;
@ExcelProperty(value = "时间")
@DateTimeFormat("yyyy-MM-dd")
private String date;
}
OtherTemplateEntity接收sheet3
@Data
public class OtherTemplateEntity {
@ExcelProperty("标号")
private String label;
@ExcelProperty("名称")
private String name;
@ExcelProperty("类型")
private String type;
@ExcelProperty(value = "时间")
@DateTimeFormat("yyyy-MM-dd")
private String date;
}
同上,只是写两个各自的
@PostMapping("/importMany")
public R importMany(@RequestPart("file") MultipartFile file) throws IOException {
return easyExcelService.importManyExcel(file);
}
public R importManyExcel(MultipartFile file) throws IOException {
Map map = getTemplateEntitiesMany(file);
List data1 = (List) map.get("data1");
List data2 = (List) map.get("data2");
log.info("data1数据=={}", data1);
log.info("data2数据=={}", data2);
return R.success(map);
}
private Map getTemplateEntitiesMany(MultipartFile file) throws IOException {
Map map = new HashMap<>();
TemplateListener listener = new TemplateListener(); // 定义的 listener
OtherTemplateListener otherListener = new OtherTemplateListener();
ExcelReader excelReader = EasyExcel.read(file.getInputStream()).build();
// 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener
// readSheet参数设置读取sheet的序号
// 读取sheet1
ReadSheet readSheet1 =
EasyExcel.readSheet(0).head(TemplateEntity.class).registerReadListener(listener).build();
// 读取sheet3
ReadSheet readSheet2 =
EasyExcel.readSheet(2).head(OtherTemplateEntity.class).registerReadListener(otherListener).build();
excelReader.read(readSheet1, readSheet2);
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
// 取出数据放入map中,然后返回
List data1 = listener.getData();
List data2 = otherListener.getData();
map.put("data1", data1);
map.put("data2", data2);
return map;
}
{
"code": 200,
"msg": "OK",
"message": null,
"data": {
"data2": [
{
"label": "a",
"name": "a1",
"type": "t1",
"date": "2022-01-07"
},
{
"label": "b",
"name": "b1",
"type": "t2",
"date": "2022-01-07"
}
......
],
"data1": [
{
"label": 1,
"str": "a",
"num": 20,
"date": "2021-12-20"
},
{
"label": 2,
"str": "b",
"num": 30,
"date": "2021-12-20"
}
......
]
}
}
headRowNumber是头行数,如下是设置头行数2,那么读取时会从第三行开始读取数据
private List getTemplateEntities(MultipartFile file) throws IOException {
TemplateListener listener = new TemplateListener(); // 定义的 listener
EasyExcel.read(file.getInputStream(), TemplateEntity.class, listener).sheet(0).headRowNumber(2).doRead();
// 返回 所有数据
return listener.getData();
}
在监听类中重写invokeHeadMap方法,将表头数据也添加即可
public class TemplateListener extends AnalysisEventListener {
private List list = new ArrayList<>();
@Override
public void invoke(TemplateEntity data, AnalysisContext context) {
list.add(data);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {}
public List getData() {
return list;
}
@Override
public void invokeHeadMap(Map headMap, AnalysisContext context) {
// 读取到头数据
LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap))
}
————————————————
实体类省略,还是上面的TemplateEntity
导出excel数据,这里有两种写法,拟定好文件名称直接传入方法,会自动创建一个文件
// 模拟数据
private List exportData() {
List entities = new ArrayList<>();
for (int i = 0; i< 10; i++) {
TemplateEntity entity = new TemplateEntity();
entity.setStr("字符串" + i);
entity.setDate("数据" + i);
entity.setLabel(i+1);
entity.setNum(i);
entities.add(entity);
}
return entities;
}
public R export() {
String path = "C:\\Users\\EDZ\\Desktop\\";
// 写法1
String fileName = path + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, TemplateEntity.class).sheet("模板").doWrite(exportData());
// 写法2
// 这里 需要指定写用哪个class去写
ExcelWriter excelWriter = EasyExcel.write(fileName, TemplateEntity.class).build();
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
excelWriter.write(exportData(), writeSheet);
// 千万别忘记finish 会帮忙关闭流
excelWriter.finish();
return R.success();
}
————————————————
public R export() {
String path = "C:\\Users\\EDZ\\Desktop\\";
String fileName = path + System.currentTimeMillis() + ".xlsx";
// 加入要忽略date字段
Set excludeColumnFiledNames = new HashSet();
excludeColumnFiledNames.add("date");
EasyExcel.write(fileName,TemplateEntity.class).excludeColumnFiledNames(excludeColumnFiledNames).sheet("模板").doWrite(exportData());
}
@Data
public class TemplateEntity {
@ExcelProperty({"主标题", "标号"})
private Integer label;
@ExcelProperty({"主标题", "字符串"})
private String str;
@ExcelProperty({"主标题", "数字"})
private Integer num;
@ExcelProperty({"主标题", "时间"})
@DateTimeFormat("yyyy-MM-dd")
private String date;
}
在实际使用开发中,我们不可能每来一个 excel 导入导出需求,就编写一个实体类,很多业务需求需要根据不同的字段来动态导入导出,没办法基于实体类注解的方式来读取文件或者写入文件。
因此,基于EasyExcel提供的动态参数化生成文件和动态监听器读取文件方法,我们可以单独封装一套动态导出导出工具类,省的我
们每次都需要重新编写大量重复工作,我在实际使用过程,封装出来的工具类如下:
com.google.guava
guava
20.0
com.alibaba
easyexcel
3.0.5
package com.ltkj.common.excel;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.ltkj.common.utils.JsonUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 动态导出工具类测试
*
* @author wangl
* @date 2023-10-10
*/
public class DynamicEasyExcelExportUtilTest {
private static final Logger log = LoggerFactory.getLogger(DynamicEasyExcelExportUtilTest.class);
private static final String DEFAULT_SHEET_NAME = "sheet1";
/**
* 动态生成导出模版(单表头)
*
* @param headColumns 列名称
* @return excel文件流
*/
public static byte[] exportTemplateExcelFile(List headColumns) {
List> excelHead = Lists.newArrayList();
byte[] stream = createExcelFile(excelHead, new ArrayList<>());
return stream;
}
/**
* 动态生成模版(复杂表头)
*
* @param excelHead 列名称
* @return
*/
public static byte[] exportTemplateExcelFileCustomHead(List> excelHead) {
byte[] stream = createExcelFile(excelHead, new ArrayList<>());
return stream;
}
/**
* 动态导出文件(通过map方式计算)
*
* @param headColumnMap 有序列头部
* @param dataList 数据体
* @return
*/
public static byte[] exportExcelFile(LinkedHashMap headColumnMap, List
//导出包含数据内容的文件(方式二)
//头部,第一层
List head1 = new ArrayList<>();
head1.add("第1行头部列1-序号");
head1.add("第1行头部列2-缴纳单位");
head1.add("第1行头部列3-姓名");
head1.add("第1行头部列4-身份证号");
// 第一行:一直是列1 列1 列1:就是合并列的意思
head1.add("第1行头部列5-五险一金信息");
head1.add("第1行头部列5-五险一金信息");
head1.add("第1行头部列5-五险一金信息");
head1.add("第1行头部列5-五险一金信息");
head1.add("第1行头部列5-五险一金信息");
head1.add("第1行头部列5-五险一金信息");
//头部,第二层
List head2 = new ArrayList<>();
// 2行 和 1行 行头相同: 就是合并行的意思
head2.add("第1行头部列1-序号");
head2.add("第1行头部列2-缴纳单位");
head2.add("第1行头部列3-姓名");
// 2-3行合并,不和1行合并
head2.add("第1行头部列3-身份证号");
head2.add("第2行头部列5-养老保险");
head2.add("第2行头部列5-养老保险");
head2.add("第2行头部列5-养老保险");
head2.add("第2行头部列8-医疗保险");
head2.add("第2行头部列8-医疗保险");
head2.add("第2行头部列8-医疗保险");
//头部,第三层
List head3 = new ArrayList<>();
// 3行 和 2行 行头相同: 就是合并行的意思
head3.add("第1行头部列1-序号");
head3.add("第1行头部列2-缴纳单位");
head3.add("第1行头部列3-姓名");
// 2-3行合并,不和1行合并
head3.add("第1行头部列3-身份证号");
head3.add("第3行头部列5-缴费基数");
head3.add("单位缴纳比例");
head3.add("第3行头部列7-个人缴纳比例");
head3.add("第3行头部列8-缴费基数");
head3.add("单位缴纳比例");
head3.add("第3行头部列10-个人缴纳比例");
//封装头部
List> allHead = new ArrayList<>();
allHead.add(head1);
allHead.add(head2);
allHead.add(head3);
//封装数据体
//第一行数据
List
com.alibaba
fastjson
2.0.1
com.google.guava
guava
20.0
package com.ltkj.common.excel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson2.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* EasyExcel 导入需要一个监听器,导出不需要
*
* @author wangl
* @date 2023-10-14
*/
public class DynamicEasyExcelListener extends AnalysisEventListener
package com.ltkj.common.excel;
import com.ali
baba.excel.EasyExcelFactory;
import com.alibaba.excel.util.IoUtils;
import com.alibaba.fastjson.JSONArray;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 编写导入工具类
*
* @author wangl
* @date 2023-10-14
*/
public class DynamicEasyExcelImportUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(DynamicEasyExcelListener.class);
/**
* 动态获取全部列和数据体,默认只有1行标题
*
* @param stream
* @return
*/
public static List> parseExcelToView(byte[] stream) {
return parseExcelToView(stream, 1);
}
/**
* 动态获取全部列和数据体,指定有几行标题
*
* @param stream excel文件流
* @param parseRowNumber 指定有几行标题
* @return
*/
public static List> parseExcelToView(byte[] stream, Integer parseRowNumber) {
LOGGER.info("指定有[" + parseRowNumber + "]行标题");
DynamicEasyExcelListener readListener = new DynamicEasyExcelListener();
EasyExcelFactory.read(new ByteArrayInputStream(stream)).registerReadListener(readListener).headRowNumber(parseRowNumber).sheet("sheet1").doRead();
List> headList = readListener.getHeadList();
if (CollectionUtils.isEmpty(headList)) {
throw new RuntimeException("Excel未包含表头");
}
List> dataList = readListener.getDataList();
if (CollectionUtils.isEmpty(dataList)) {
throw new RuntimeException("Excel未包含数据");
}
//获取头部,取最后一次解析的列头数据
Map excelHeadIdxNameMap = headList.get(headList.size() - 1);
//封装数据体
List> excelDataList = Lists.newArrayList();
for (Map dataRow : dataList) {
Map rowData = new LinkedHashMap<>();
excelHeadIdxNameMap.entrySet().forEach(columnHead -> {
rowData.put(columnHead.getValue(), dataRow.get(columnHead.getKey()));
});
excelDataList.add(rowData);
}
return excelDataList;
}
/**
* 文件导入测试
*
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
FileInputStream inputStream = new FileInputStream(new File("easyexcel-export-user1.xlsx"));
byte[] stream = IoUtils.toByteArray(inputStream);
List> dataList = parseExcelToView(stream, 2);
// 打印最后结果 全部数据 不含标题
System.out.println(JSONArray.toJSONString(dataList));
inputStream.close();
}
}
所有数据解析完成!
[{"姓名":"张三0","性别":"男","职称名称":"律师","职称等级":"0 级"},
{"姓名":"张三1","性别":"男","职称名称":"律师","职称等级":"10 级"},
{"姓名":"张三2","性别":"男","职称名称":"律师","职称等级":"20 级"},
{"姓名":"张三3","性别":"男","职称名称":"律师","职称等级":"30 级"},
{"姓名":"张三4","性别":"男","职称名称":"律师","职称等级":"40 级"}]
为了方便后续的操作流程,在解析数据的时候,会将列名作为key!
三、小结
在实际的业务开发过程中,根据参数动态实现 Excel 的导出导入还是非常广的。