【EasyExcel】excel表格的导入和导出

【EasyExcel】excel表格的导入和导出

  • 【一】EasyExcel简介
  • 【二】EasyExcel使用
    • 【1】EasyExcel相关依赖
    • 【2】写Excel
      • (1)最简单的写(方式一)
      • (2)最简单的写(方式二)
      • (3)排除模型中的属性字段
      • (4)向表格中导出指定属性
      • (5)插入指定的列
      • (6)复杂头数据写入
      • (7)重复写到Excel的同一个Sheet中
      • (8)写到Excel的不同Sheet中
      • (9)日期/数字类型格式化
      • (10)写入图片到Excel
      • (11)设置写入Excel的列宽和行高
      • (12)通过注解形式设置写入Excel样式
      • (13)合并单元格
      • (14)写的数据转换器
    • 【3】读Excel
      • (1)读API的拆分
      • (2)最简单的读(方式一)
      • (3)最简单的读(方式二)
      • (4)格式化Excel中的数据格式
      • (5)读取多个sheet表格
      • (6)读的数据转换器
    • 【4】填充Excel
      • (1)简单填充
      • (2)列表填充
      • (3)组合填充
      • (4)水平填充
      • (5)报表导出案例
    • 【5】Web操作(Excel上传/下载)
      • (1)Excel文件下载
      • (2)Excel文件上传
  • 【三】EasyExcel使用优化
    • 【1】监听器优化
  • 【四】一次实际使用案例
    • 【1】添加pom依赖
    • 【2】监听器
    • 【3】实体类
    • 【4】数据类型转换器
    • 【5】导入接口和业务代码
    • 【6】导出接口和业务代码

【一】EasyExcel简介

EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。

github地址: https://github.com/alibaba/easyexcel
官方文档: https://www.yuque.com/easyexcel/doc/easyexcel
B站视频: https://www.bilibili.com/video/BV1Ff4y1U7Qc

Excel解析流程图:
在这里插入图片描述

EasyExcel读取Excel的解析原理:

在这里插入图片描述

【二】EasyExcel使用

【1】EasyExcel相关依赖

添加maven依赖, 依赖的poi最低版本3.17

<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.1.2</version>
</dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.17</version>
</dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>3.17</version>
</dependency>

【2】写Excel

(1)最简单的写(方式一)

创建实体类,下面也用这个数据模型

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class User {@ExcelProperty(value = "用户编号")private Integer userId;@ExcelProperty(value = "姓名")private String userName;@ExcelProperty(value = "性别")private String gender;@ExcelProperty(value = "工资")private Double salary;@ExcelProperty(value = "入职时间")private Date hireDate;// lombok 会生成getter/setter方法
}

写入

// 根据user模板构建数据
private List<User> getUserData() {List<User> users = new ArrayList<>();for (int i = 1; i <= 10; i++) {User user = User.builder().userId(i).userName("admin" + i).gender(i % 2 == 0 ? "男" : "女").salary(i * 1000.00).hireDate(new Date()).build();users.add(user);}return users;
}
@Test
public void testWriteExcel() {String filename = "D:\\study\\excel\\user1.xlsx";// 向Excel中写入数据 也可以通过 head(Class<?>) 指定数据模板EasyExcel.write(filename, User.class).sheet("用户信息").doWrite(getUserData());
}

在这里插入图片描述

(2)最简单的写(方式二)

@Test
public void testWriteExcel2() {String filename = "D:\\study\\excel\\user2.xlsx";// 创建ExcelWriter对象ExcelWriter excelWriter = EasyExcel.write(filename, User.class).build();// 创建Sheet对象WriteSheet writeSheet = EasyExcel.writerSheet("用户信息").build();// 向Excel中写入数据excelWriter.write(getUserData(), writeSheet);// 关闭流excelWriter.finish();
}

在这里插入图片描述

(3)排除模型中的属性字段

指定字段不写入excel

@Test
public void testWriteExcel3() {String filename = "D:\\study\\excel\\user3.xlsx";// 设置排除的属性 也可以在数据模型的字段上加@ExcelIgnore注解排除Set<String> excludeField = new HashSet<>();excludeField.add("hireDate");excludeField.add("salary");// 写ExcelEasyExcel.write(filename, User.class).excludeColumnFiledNames(excludeField).sheet("用户信息").doWrite(getUserData());
}

在这里插入图片描述

(4)向表格中导出指定属性

@Test
public void testWriteExcel4() {String filename = "D:\\study\\excel\\user4.xlsx";// 设置要导出的字段Set<String> includeFields = new HashSet<>();includeFields.add("userName");includeFields.add("hireDate");// 写ExcelEasyExcel.write(filename, User.class).includeColumnFiledNames(includeFields).sheet("用户信息").doWrite(getUserData());
}

在这里插入图片描述

(5)插入指定的列

将Java对象中指定的属性, 插入到Eexcel表格中的指定列(在Excel表格中进行列排序), 使用index属性指定列顺序。

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class User {@ExcelProperty(value = "用户编号", index = 0)private Integer userId;@ExcelProperty(value = "姓名", index = 1)private String userName;@ExcelProperty(value = "性别", index = 3)private String gender;@ExcelProperty(value = "工资", index = 4)private Double salary;@ExcelProperty(value = "入职时间", index = 2)private Date hireDate;// lombok 会生成getter/setter方法
}
@Test
public void testWriteExcel5() {String filename = "D:\\study\\excel\\user5.xlsx";// 向Excel中写入数据EasyExcel.write(filename, User.class).sheet("用户信息").doWrite(getUserData());
}

在这里插入图片描述

(6)复杂头数据写入

@ExcelProperty注解的value属性是一个数组类型, 设置多个head时会自动合并

数据模板:

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class ComplexHeadUser {@ExcelProperty(value = {"group1", "用户编号"}, index = 0)private Integer userId;@ExcelProperty(value = {"group1", "姓名"}, index = 1)private String userName;@ExcelProperty(value = {"group2", "入职时间"}, index = 2)private Date hireDate;// lombok 会生成getter/setter方法
}

写excel代码

@Test
public void testWriteExcel6() {String filename = "D:\\study\\excel\\user6.xlsx";List<ComplexHeadUser> users = new ArrayList<>();for (int i = 1; i <= 10; i++) {ComplexHeadUser user = ComplexHeadUser.builder().userId(i).userName("大哥" + i).hireDate(new Date()).build();users.add(user);}// 向Excel中写入数据EasyExcel.write(filename, ComplexHeadUser.class).sheet("用户信息").doWrite(users);
}

在这里插入图片描述

(7)重复写到Excel的同一个Sheet中

@Test
public void testWriteExcel7() {String filename = "D:\\study\\excel\\user7.xlsx";// 创建ExcelWriter对象ExcelWriter excelWriter = EasyExcel.write(filename, User.class).build();// 创建Sheet对象WriteSheet writeSheet = EasyExcel.writerSheet("用户信息").build();// 向Excel的同一个Sheet重复写入数据for (int i = 0; i < 2; i++) {excelWriter.write(getUserData(), writeSheet);}// 关闭流excelWriter.finish();
}

(8)写到Excel的不同Sheet中

@Test
public void testWriteExcel8() {String filename = "D:\\study\\excel\\user8.xlsx";// 创建ExcelWriter对象ExcelWriter excelWriter = EasyExcel.write(filename, User.class).build();// 向Excel的同一个Sheet重复写入数据for (int i = 0; i < 2; i++) {// 创建Sheet对象WriteSheet writeSheet = EasyExcel.writerSheet("用户信息" + i).build();excelWriter.write(getUserData(), writeSheet);}// 关闭流excelWriter.finish();
}

(9)日期/数字类型格式化

对于日期和数字,有时候需要对其展示的样式进行格式化, EasyExcel提供了以下注解

@DateTimeFormat 日期格式化

@NumberFormat 数字格式化(小数或百分数)

数据模板对象:

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class User {@ExcelProperty(value = "用户编号", index = 0)private Integer userId;@ExcelProperty(value = "姓名", index = 1)private String userName;@ExcelProperty(value = "性别", index = 3)private String gender;@ExcelProperty(value = "工资", index = 4)@NumberFormat(value = "###.#") // 数字格式化,保留1位小数private Double salary;@ExcelProperty(value = "入职时间", index = 2)@DateTimeFormat(value = "yyyy年MM月dd日 HH时mm分ss秒") // 日期格式化private Date hireDate;// lombok 会生成getter/setter方法
}

写入

@Test
public void testWriteExcel9() {String filename = "D:\\study\\excel\\user9.xlsx";// 向Excel中写入数据EasyExcel.write(filename, User.class).sheet("用户信息").doWrite(getUserData());
}

在这里插入图片描述

(10)写入图片到Excel

数据模板(Java对象)

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
@ContentRowHeight(value = 100) // 内容行高
@ColumnWidth(value = 20) // 列宽
public class ImageData {//使用抽象文件表示一个图片@ExcelProperty(value = "File类型")private File file;// 使用输入流保存一个图片@ExcelProperty(value = "InputStream类型")private InputStream inputStream;// 当使用String类型保存一个图片的时候需要使用StringImageConverter转换器@ExcelProperty(value = "String类型", converter = StringImageConverter.class)private String str;// 使用二进制数据保存为一个图片@ExcelProperty(value = "二进制数据(字节)")private byte[] byteArr;// 使用网络链接保存为一个图片@ExcelProperty(value = "网络图片")private URL url;// lombok 会生成getter/setter方法
}

写入

@Test
public void testWriteImageToExcel() throws IOException {String filename = "D:\\study\\excel\\user10.xlsx";// 图片位置String imagePath = "D:\\study\\excel\\me.jpg";// 网络图片URL url = new URL("https://cn.bing.com/th?id=OHR.TanzaniaBeeEater_ZH-CN3246625733_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp");// 将图片读取到二进制数据中byte[] bytes = new byte[(int) new File(imagePath).length()];InputStream inputStream = new FileInputStream(imagePath);inputStream.read(bytes, 0, bytes.length);List<ImageData> imageDataList = new ArrayList<>();// 创建数据模板ImageData imageData = ImageData.builder().file(new File(imagePath)).inputStream(new FileInputStream(imagePath)).str(imagePath).byteArr(bytes).url(url).build();// 添加要写入的图片模型imageDataList.add(imageData);// 写数据EasyExcel.write(filename, ImageData.class).sheet("帅哥").doWrite(imageDataList);
}

在这里插入图片描述

(11)设置写入Excel的列宽和行高

@HeadRowHeight(value = 30) // 头部行高
@ContentRowHeight(value = 25) // 内容行高
@ColumnWidth(value = 20) // 列宽, 可以作用在类或字段上

数据模板

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
@HeadRowHeight(value = 30) // 头部行高
@ContentRowHeight(value = 25) // 内容行高
@ColumnWidth(value = 20) // 列宽
public class WidthAndHeightData {@ExcelProperty(value = "字符串标题")private String string;@ExcelProperty(value = "日期标题")private Date date;@ExcelProperty(value = "数字标题")@ColumnWidth(value = 25)private Double doubleData;// lombok 会生成getter/setter方法
}

写入

@Test
public void testWrite11() {String filename = "D:\\study\\excel\\user11.xlsx";// 构建数据List<WidthAndHeightData> dataList = new ArrayList<>();WidthAndHeightData data = WidthAndHeightData.builder().string("字符串").date(new Date()).doubleData(888.88).build();dataList.add(data);// 向Excel中写入数据EasyExcel.write(filename, WidthAndHeightData.class).sheet("行高和列宽测试").doWrite(dataList);
}

在这里插入图片描述

(12)通过注解形式设置写入Excel样式

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
@HeadRowHeight(value = 30) // 头部行高
@ContentRowHeight(value = 25) // 内容行高
@ColumnWidth(value = 20) // 列宽
// 头背景设置成红色 IndexedColors.RED.getIndex()
@HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 10)
// 头字体设置成20, 字体默认宋体
@HeadFontStyle(fontName = "宋体", fontHeightInPoints = 20)
// 内容的背景设置成绿色  IndexedColors.GREEN.getIndex()
@ContentStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 17)
// 内容字体设置成20, 字体默认宋体
@ContentFontStyle(fontName = "宋体", fontHeightInPoints = 20)
public class DemoStyleData {// 字符串的头背景设置成粉红 IndexedColors.PINK.getIndex()@HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 14)// 字符串的头字体设置成20@HeadFontStyle(fontHeightInPoints = 30)// 字符串的内容背景设置成天蓝 IndexedColors.SKY_BLUE.getIndex()@ContentStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 40)// 字符串的内容字体设置成20,默认宋体@ContentFontStyle(fontName = "宋体", fontHeightInPoints = 20)@ExcelProperty(value = "字符串标题")private String string;@ExcelProperty(value = "日期标题")private Date date;@ExcelProperty(value = "数字标题")private Double doubleData;// lombok 会生成getter/setter方法
}

写入

@Test
public void testWrite12() {String filename = "D:\\study\\excel\\user12.xlsx";// 构建数据List<DemoStyleData> dataList = new ArrayList<>();DemoStyleData data = DemoStyleData.builder().string("字符串").date(new Date()).doubleData(888.88).build();dataList.add(data);// 向Excel中写入数据EasyExcel.write(filename, DemoStyleData.class).sheet("样式设置测试").doWrite(dataList);}

在这里插入图片描述

(13)合并单元格

数据模板

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
@HeadRowHeight(value = 25) // 头部行高
@ContentRowHeight(value = 20) // 内容行高
@ColumnWidth(value = 20) // 列宽
/*** @OnceAbsoluteMerge 指定从哪一行/列开始,哪一行/列结束,进行单元格合并* firstRowIndex 起始行索引,从0开始* lastRowIndex 结束行索引* firstColumnIndex 起始列索引,从0开始* lastColumnIndex 结束列索引*/
// 例如: 第2-3行,2-3列进行合并
@OnceAbsoluteMerge(firstRowIndex = 1, lastRowIndex = 2, firstColumnIndex = 1, lastColumnIndex = 2)
public class DemoMergeData {// 每隔两行合并一次(竖着合并单元格)
//    @ContentLoopMerge(eachRow = 2)@ExcelProperty(value = "字符串标题")private String string;@ExcelProperty(value = "日期标题")private Date date;@ExcelProperty(value = "数字标题")private Double doubleData;// lombok 会生成getter/setter方法
}

写入

@Test
public void testWrite13() {String filename = "D:\\study\\excel\\user13.xlsx";// 构建数据List<DemoMergeData> dataList = new ArrayList<>();DemoMergeData data = DemoMergeData.builder().string("字符串").date(new Date()).doubleData(888.88).build();dataList.add(data);// 向Excel中写入数据EasyExcel.write(filename, DemoMergeData.class).sheet("单元格合并测试").doWrite(dataList);
}

@ContentLoopMerge
在这里插入图片描述
@OnceAbsoluteMerge
在这里插入图片描述

(14)写的数据转换器

在实际应用场景中, 我们系统db存储的数据可以是枚举, 在界面或导出到Excel文件需要展示为对于的枚举值形式.

比如: 性别, 状态等. EasyExcel提供了转换器接口Converter供我们使用, 我们只需要自定义转换器实现接口, 并将自定义转换器类型传入要转换的属性字段中. 以下面的性别gender字段为例:

(1)数据模板

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class UserModel {@ExcelProperty(value = "用户编号", index = 0)private Integer userId;@ExcelProperty(value = "姓名", index = 1)private String userName;// 性别添加了转换器, db中存入的是integer类型的枚举 0 , 1 ,2@ExcelProperty(value = "性别", index = 3, converter = GenderConverter.class)private Integer gender;@ExcelProperty(value = "工资", index = 4)@NumberFormat(value = "###.#")private Double salary;@ExcelProperty(value = "入职时间", index = 2)@DateTimeFormat(value = "yyyy年MM月dd日 HH时mm分ss秒")private Date hireDate;// lombok 会生成getter/setter方法
}

(2)自定义转换器

/*** 类描述:性别字段的数据转换器* @Author wang_qz* @Date 2021/8/15 19:16* @Version 1.0*/
public class GenderConverter implements Converter<Integer> {private static final String MALE = "男";private static final String FEMALE = "女";private static final String NONE = "未知";// Java数据类型 integer@Overridepublic Class supportJavaTypeKey() {return Integer.class;}// Excel文件中单元格的数据类型  string@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}// 读取Excel文件时将string转换为integer@Overridepublic Integer convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {String value = cellData.getStringValue();if (Objects.equals(FEMALE, value)) {return 0; // 0-女} else if (Objects.equals(MALE, value)) {return 1; // 1-男}return 2; // 2-未知}// 写入Excel文件时将integer转换为string@Overridepublic CellData convertToExcelData(Integer value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {if (value == 1) {return new CellData(MALE);} else if (value == 0) {return new CellData(FEMALE);}return new CellData(NONE); // 不男不女}
}

(3)导出到Excel的代码

@Test
public void testWriteExcel() {String filename = "D:\\study\\excel\\user1.xlsx";// 向Excel中写入数据EasyExcel.write(filename, UserModel.class).sheet("用户信息").doWrite(getUserData());
}
// 根据user模板构建数据
private List<UserModel> getUserData() {List<UserModel> users = new ArrayList<>();for (int i = 1; i <= 10; i++) {UserModel user = UserModel.builder().userId(i).userName("admin" + i).gender(i % 2 == 0 ? 0 : 2) // 性别枚举.salary(i * 1000 + 8.888).hireDate(new Date()).build();users.add(user);}return users;
}

在这里插入图片描述

【3】读Excel

(1)读API的拆分

在读取Excel表格数据时, 将读取的每行记录映射成一条LinkedHashMap记录, 而没有映射成实体类.

@Test
public void testRead() {String filename = "D:\\study\\excel\\read.xlsx";// 创建ExcelReaderBuilder对象ExcelReaderBuilder readerBuilder = EasyExcel.read();// 获取文件对象readerBuilder.file(filename);// 指定映射的数据模板
//  readerBuilder.head(DemoData.class);// 指定sheetreaderBuilder.sheet(0);// 自动关闭输入流readerBuilder.autoCloseStream(true);// 设置Excel文件格式readerBuilder.excelType(ExcelTypeEnum.XLSX);// 注册监听器进行数据的解析readerBuilder.registerReadListener(new AnalysisEventListener() {// 每解析一行数据,该方法会被调用一次@Overridepublic void invoke(Object demoData, AnalysisContext analysisContext) {// 如果没有指定数据模板, 解析的数据会封装成 LinkedHashMap返回// demoData instanceof LinkedHashMap 返回 trueSystem.out.println("解析数据为:" + demoData.toString());}// 全部解析完成被调用@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {System.out.println("解析完成...");// 可以将解析的数据保存到数据库}});readerBuilder.doReadAll();/*  // 构建读取器ExcelReader excelReader = readerBuilder.build();// 读取ExcelexcelReader.readAll();// 关闭流excelReader.finish();*/}

(2)最简单的读(方式一)

Excel数据类型

数据模板

注意: Java类中的属性字段顺序和Excel中的表头字段顺序一致, 可以不写@ExcelProperty

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class DemoData {// 根据Excel中指定列名或列的索引读取@ExcelProperty(value = "字符串标题", index = 0)private String name;@ExcelProperty(value = "日期标题", index = 1)private Date hireDate;@ExcelProperty(value = "数字标题", index = 2)private Double salary;// lombok 会生成getter/setter方法
}

读取excel代码

关键是写一个监听器,实现AnalysisEventListener, 每解析一行数据会调用invoke方法返回解析的数据, 当全部解析完成后会调用doAfterAllAnalysed方法. 我们重写invoke方法和doAfterAllAnalysed方法即可。

@Test
public void testReadExcel() {// 读取的excel文件路径String filename = "D:\\study\\excel\\read.xlsx";// 读取excelEasyExcel.read(filename, DemoData.class, new AnalysisEventListener<DemoData>() {// 每解析一行数据,该方法会被调用一次@Overridepublic void invoke(DemoData demoData, AnalysisContext analysisContext) {System.out.println("解析数据为:" + demoData.toString());}// 全部解析完成被调用@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {System.out.println("解析完成...");// 可以将解析的数据保存到数据库}}).sheet().doRead();
}

在这里插入图片描述

(3)最简单的读(方式二)

读excel的方式二代码

@Test
public void testReadExcel2() {// 读取的excel文件路径String filename = "D:\\study\\excel\\read.xlsx";// 创建一个数据格式来装读取到的数据Class<DemoData> head = DemoData.class;// 创建ExcelReader对象ExcelReader excelReader = EasyExcel.read(filename, head, new AnalysisEventListener<DemoData>() {// 每解析一行数据,该方法会被调用一次@Overridepublic void invoke(DemoData demoData, AnalysisContext analysisContext) {System.out.println("解析数据为:" + demoData.toString());}// 全部解析完成被调用@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {System.out.println("解析完成...");// 可以将解析的数据保存到数据库}}).build();// 创建sheet对象,并读取Excel的第一个sheet(下标从0开始), 也可以根据sheet名称获取ReadSheet sheet = EasyExcel.readSheet(0).build();// 读取sheet表格数据, 参数是可变参数,可以读取多个sheetexcelReader.read(sheet);// 需要自己关闭流操作,在读取文件时会创建临时文件,如果不关闭,会损耗磁盘,严重的磁盘爆掉excelReader.finish();
}

(4)格式化Excel中的数据格式

要读取的源数据, 日期格式是yyyy年MM月dd日 HH时mm分ss秒, 数字带小数点

在这里插入图片描述
数据模板

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class DemoData {@ExcelProperty(value = "字符串标题", index = 0)private String name;@ExcelProperty(value = "日期标题", index = 1)// 格式化日期类型数据@DateTimeFormat(value = "yyyy年MM月dd日 HH时mm分ss秒")private Date hireDate;@ExcelProperty(value = "数字标题", index = 2)// 格式化数字类型数据,保留一位小数@NumberFormat(value = "###.#")private String salary;//注意: @NumberFormat对于Double类型的数据格式化会失效,建议使用String类型接收数据进行格式化
//    private Double salary;// lombok 会生成getter/setter方法
}

读取excel代码同上面读取方式一样.

在这里插入图片描述

(5)读取多个sheet表格

(1)读所有sheet

读方式一:使用ExcelReaderBuilder#doReadAll方法

@Testpublic void testReadExcel() {// 读取的excel文件路径String filename = "D:\\study\\excel\\read.xlsx";// 读取excelEasyExcel.read(filename, DemoData.class, new AnalysisEventListener<DemoData>() {// 每解析一行数据,该方法会被调用一次@Overridepublic void invoke(DemoData demoData, AnalysisContext analysisContext) {System.out.println("解析数据为:" + demoData.toString());}// 全部解析完成被调用@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {System.out.println("解析完成...");// 可以将解析的数据保存到数据库}})
//         .sheet(0).doRead();.doReadAll(); // 读取全部sheet}

读方式二:使用ExcelReader#readAll方法

@Testpublic void testReadExcel2() {// 读取的excel文件路径String filename = "D:\\study\\excel\\read.xlsx";// 创建一个数据格式来装读取到的数据Class<DemoData> head = DemoData.class;// 创建ExcelReader对象ExcelReader excelReader = EasyExcel.read(filename, head, new AnalysisEventListener<DemoData>() {// 每解析一行数据,该方法会被调用一次@Overridepublic void invoke(DemoData demoData, AnalysisContext analysisContext) {System.out.println("解析数据为:" + demoData.toString());}// 全部解析完成被调用@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {System.out.println("解析完成...");// 可以将解析的数据保存到数据库}}).build();// 创建sheet对象,并读取Excel的第一个sheet(下标从0开始), 也可以根据sheet名称获取ReadSheet sheet = EasyExcel.readSheet(0).build();// 读取sheet表格数据 , 参数是可变参数,可以读取多个sheet
//        excelReader.read(sheet);excelReader.readAll(); // 读所有sheet// 需要自己关闭流操作,在读取文件时会创建临时文件,如果不关闭,会损耗磁盘,严重的磁盘爆掉excelReader.finish();}

(2)读指定的多个sheet
不同sheet表格的数据模板可能不一样,这时候就需要分别构建不同的sheet对象,分别为其指定对于的数据模板.

@Test
public void testReadExcel3() {// 读取的excel文件路径String filename = "D:\\study\\excel\\read.xlsx";// 构建ExcelReader对象ExcelReader excelReader = EasyExcel.read(filename).build();// 构建sheet对象ReadSheet sheet0 = EasyExcel.readSheet(0).head(DemoData.class) // 指定sheet0的数据模板.registerReadListener(new AnalysisEventListener<DemoData>() {// 每解析一行数据,该方法会被调用一次@Overridepublic void invoke(DemoData demoData, AnalysisContext analysisContext) {System.out.println("解析数据为:" + demoData.toString());}// 全部解析完成被调用@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {System.out.println("解析完成...");// 可以将解析的数据保存到数据库}}).build();// 读取sheet,有几个就构建几个sheet进行读取excelReader.read(sheet0);// 需要自己关闭流操作,在读取文件时会创建临时文件,如果不关闭,会损耗磁盘,严重的磁盘爆掉excelReader.finish();
} 

(6)读的数据转换器

上面的写已经提到了转换器, 读也是一样. 将Excel文件中的字符串枚举值转换成要存入db的整数类型的枚举。

和上面 (14)一样

【4】填充Excel

(1)简单填充

(1)创建Excel模板格式
填充单个属性使用{}作为占位符, 在大括号里面定义属性名称, 如果{}想不作为占位符展示出来,可以使用反斜杠进行转义
在这里插入图片描述
填充数据的Java类(数据模板)

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class FillData {private String name;private double number;// lombok 会生成getter/setter方法
}

填充的代码

@Test
public void testFillExcel() {// 根据哪个模板进行填充String template = "D:\\study\\excel\\template.xlsx";// 填充完成之后的excelString fillname = "D:\\study\\excel\\fill.xlsx";// 构建数据FillData fillData = FillData.builder().name("小米").number(888.888).build();// 填充excel 单组数据填充EasyExcel.write(fillname).withTemplate(template).sheet(0).doFill(fillData);
}

在这里插入图片描述

(2)列表填充

(1)列表填充
在这里插入图片描述
(2)填充的数据模板

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class FillData {private String name;private double number;// lombok 会生成getter/setter方法
}

(3)填充Excel代码

@Test
public void testFillExcel2() {// 根据哪个模板进行填充String template = "D:\\study\\excel\\template2.xlsx";// 填充完成之后的excelString fillname = "D:\\study\\excel\\fill2.xlsx";// 填充excel 多组数据重复填充EasyExcel.write(fillname).withTemplate(template).sheet(0).doFill(getFillData());
}
// 构建数据
private List<FillData> getFillData() {List<FillData> fillDataList = new ArrayList<>();for (int i = 1; i <= 10; i++) {// 构建数据FillData fillData = FillData.builder().name("小米" + i).number(i * 1000 + 88.88).build();fillDataList.add(fillData);}return fillDataList;}

在这里插入图片描述

(3)组合填充

(1)创建Excel填充模板
在这里插入图片描述
(2)填充的数据模板

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class FillData {private String name;private double number;// lombok 会生成getter/setter方法
}

(3)组合填充Excel代码

@Test
public void testFillExcel3() {// 根据哪个模板进行填充String template = "D:\\study\\excel\\template3.xlsx";// 填充完成之后的excelString fillname = "D:\\study\\excel\\fill3.xlsx";// 创建填充配置 换行填充FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();// 创建写对象ExcelWriter excelWriter = EasyExcel.write(fillname).withTemplate(template).build();// 创建Sheet对象WriteSheet sheet = EasyExcel.writerSheet(0).build();// 多组填充excelexcelWriter.fill(getFillData(), fillConfig, sheet);// 单组填充HashMap<String, Object> unitData = new HashMap<>();unitData.put("nickname", "张三");unitData.put("salary", 8088.66);excelWriter.fill(unitData, sheet);// 关闭流excelWriter.finish();
}

在这里插入图片描述
如果没有设置填充配置换行FillConfig为true , 效果将是单组填充的数据会覆盖所在行的多组数据填充效果.

FillConfig fillConfig = FillConfig.builder().forceNewRow(false).build();

在这里插入图片描述

(4)水平填充

(1)创建Excel填充模板
在这里插入图片描述

(2)数据模板

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class FillData {private String name;private double number;// lombok 会生成getter/setter方法
}

(3)水平填充代码

@Testpublic void testFillExcel4() {// 根据哪个模板进行填充String template = "D:\\study\\excel\\template4.xlsx";// 填充完成之后的excelString fillname = "D:\\study\\excel\\fill4.xlsx";// 创建填充配置 水平填充FillConfig fillConfig = FillConfig.builder()
//                .forceNewRow(true).direction(WriteDirectionEnum.HORIZONTAL).build();// 创建写对象ExcelWriter excelWriter = EasyExcel.write(fillname, FillData.class).withTemplate(template).build();// 创建Sheet对象WriteSheet sheet = EasyExcel.writerSheet(0).build();// 多组填充excelexcelWriter.fill(getFillData(), fillConfig, sheet);// 关闭流excelWriter.finish();}

(4)效果
在这里插入图片描述

(5)报表导出案例

(1)创建Excel填充模板

在这里插入图片描述

(2)会员数据模板

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class MemberVip {private Integer id;private String name;private String gender;private String birthday;// lombok 会生成getter/setter方法jav
}

(3)组合填充报表代码

@Test
public void testFillExcel5() {// 根据哪个模板进行填充String template = "D:\\study\\excel\\template5.xlsx";// 填充完成之后的excelString fillname = "D:\\study\\excel\\fill5.xlsx";// 创建填充配置FillConfig fillConfig = FillConfig.builder().forceNewRow(true).build();// 创建写对象ExcelWriter excelWriter = EasyExcel.write(fillname).withTemplate(template).build();// 创建Sheet对象WriteSheet sheet = EasyExcel.writerSheet(0).build();/***准备数据 start*****/HashMap<String, Object> dateMap = new HashMap<>();dateMap.put("date", "2021-08-08");HashMap<String, Object> memberMap = new HashMap<>();memberMap.put("increaseCount", 500);memberMap.put("totalCount", 999);HashMap<String, Object> curMonthMemberMap = new HashMap<>();curMonthMemberMap.put("increaseCountWeek", 100);curMonthMemberMap.put("increaseCountMonth", 200);List<MemberVip> memberVips = getMemberVips();/***准备数据 end*****/// 多组填充excelexcelWriter.fill(dateMap, sheet);excelWriter.fill(memberMap, sheet);excelWriter.fill(curMonthMemberMap, sheet);excelWriter.fill(memberVips, fillConfig, sheet);// 关闭流excelWriter.finish();
}

(4)效果
在这里插入图片描述

【5】Web操作(Excel上传/下载)

(1)Excel文件下载

(1)数据模板

@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
@HeadRowHeight(value = 30)
@ContentRowHeight(value = 25)
@ColumnWidth(value = 30)
public class UserExcel {@ExcelProperty(value = "用户编号")private Integer userId;@ExcelProperty(value = "姓名")private String username;@ExcelProperty(value = "性别")private String gender;@ExcelProperty(value = "工资")private Double salary;@ExcelProperty(value = "入职时间")private Date hireDate;
}

(2)编写controller及下载handler

*** 使用EasyExcel操作excel文件上传/下载*/
@Controller
@RequestMapping(value = "/xlsx")
public class EasyExcelController {@RequestMapping("/toExcelPage")public String todownloadPage() {return "excelPage";}/*** 下载Excel* @param request* @param response*/@RequestMapping(value = "/downloadExcel")public void downloadExcel(HttpServletRequest request, HttpServletResponse response) throws Exception {// 设置响应头response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");// 设置防止中文名乱码String filename = URLEncoder.encode("员工信息", "utf-8");// 文件下载方式(附件下载还是在当前浏览器打开)response.setHeader("Content-disposition", "attachment;filename=" + filename + ".xlsx");// 构建写入到excel文件的数据List<UserExcel> userExcels = new ArrayList<>();UserExcel userExce1 = new UserExcel(1001, "张三", "男", 1333.33, new Date());UserExcel userExce2 = new UserExcel(1002, "李四", "男", 1356.83, new Date());UserExcel userExce3 = new UserExcel(1003, "王五", "男", 1883.66, new Date());UserExcel userExce4 = new UserExcel(1004, "赵六", "男", 1393.39, new Date());userExcels.add(userExce1);userExcels.add(userExce2);userExcels.add(userExce3);userExcels.add(userExce4);// 写入数据到excelEasyExcel.write(response.getOutputStream(), UserExcel.class).sheet("用户信息").doWrite(userExcels);}
}

(3)编写jsp页面 excelPage.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head><title>测试excel文件下载</title>
</head>
<body>
<h3>点击下面链接, 进行excel文件下载</h3>
<a href="<c:url value='/xlsx/downloadExcel'/>">Excel文件下载</a>
</body>
</html>

(4)启动tomcat测试
访问 http://localhost:8080/mvc/xlsx/toExcelPage 跳转到excel文件下载界面
在这里插入图片描述

点击"Excel文件下载", 查看下载文件
在这里插入图片描述

在这里插入图片描述

(2)Excel文件上传

(1)数据模板跟上面下载一样

(2)编写上传handler

@RequestMapping("/uploadExcel")
public void uploadExcel(HttpServletRequest request, HttpServletResponse response) throws Exception {DiskFileItemFactory factory = new DiskFileItemFactory();ServletFileUpload fileUpload = new ServletFileUpload(factory);// 设置单个文件大小为3M 2的10次幂=1024fileUpload.setFileSizeMax((long) (3 * Math.pow(2, 20)));// 总文件大小为30MfileUpload.setSizeMax((long) (30 * Math.pow(2, 20)));List<FileItem> list = fileUpload.parseRequest(request);for (FileItem fileItem : list) {// 判断是否为附件if (!fileItem.isFormField()) {// 是附件InputStream inputStream = fileItem.getInputStream();EasyExcel.read(inputStream, UserExcel.class, new AnalysisEventListener<UserExcel>() {// 每解析一行数据,该方法会被调用一次@Overridepublic void invoke(UserExcel data, AnalysisContext analysisContext) {System.out.println("解析数据为:" + data.toString());}// 全部解析完成被调用@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {System.out.println("解析完成...");// 可以将解析的数据保存到数据库}}).sheet().doRead();}}
}

上面方式不知道啥原因, 通过FileItem获取不到文件, 改为下面方式Part获取上传的文件

@RequestMapping("/uploadExcel")
@ResponseBody
public String uploadExcel(@RequestParam("file") Part part) throws Exception {// 获取上传的文件流InputStream inputStream = part.getInputStream();// 读取ExcelEasyExcel.read(inputStream, UserExcel.class, new AnalysisEventListener<UserExcel>() {// 每解析一行数据,该方法会被调用一次@Overridepublic void invoke(UserExcel data, AnalysisContext analysisContext) {System.out.println("解析数据为:" + data.toString());}// 全部解析完成被调用@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {System.out.println("解析完成...");// 可以将解析的数据保存到数据库}}).sheet().doRead();return "上传Excel文件成功";
}

(3)编写jsp页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head><title>测试excel文件下载</title>
</head>
<body>
<h3>点击下面链接, 进行excel文件下载</h3>
<a href="<c:url value='/xlsx/downloadExcel'/>">Excel文件下载</a>
<hr/>
<hr/>
<h3>点击下面按钮, 进行excel文件上传</h3>
<form action="<c:url value='/xlsx/uploadExcel'/>" method="post" enctype="multipart/form-data"><input type="file" name="file"/><br/><input type="submit" value="上传Excel"/>
</form>
</body></html>

(4)启动tomcat, 测试
访问 http://localhost:8080/mvc/xlsx/toExcelPage ,跳转到Excel文件上传页面

读取前端页面上传的Excel是成功了 , 但是中文乱码问题有待解决.

中文乱码解决参考: https://blog.csdn.net/gaogzhen/article/details/107307459

【三】EasyExcel使用优化

【1】监听器优化

上面章节的读取Excel的程序弊端:
(1)每次解析不同数据模型都要新增一个监听器, 重复工作量大;
(2)即使用了匿名内部类,程序也显得臃肿;
(3)数据处理一般都会存在于项目的service中, 监听器难免会依赖dao层, 导致程序耦合度高

解决方案:
(1)通过泛型指定数据模型类型, 针对不同类型的数据模型只需要定义一个监听器即可;
(2)使用jdk8新特性中的函数式接口, 将数据处理从监听器中剥离出去, 进行解耦

监听器代码

/*** 类描述:easyexcel工具类* @Author wang_qz* @Date 2021/8/15 18:15* @Version 1.0*/
public class EasyExcelUtils<T> {/*** 获取读取Excel的监听器对象* 为了解耦及减少每个数据模型bean都要创建一个监听器的臃肿, 使用泛型指定数据模型类型* 使用jdk8新特性中的函数式接口 Consumer* 可以实现任何数据模型bean的数据解析, 不用重复定义监听器* @param consumer 处理解析数据的函数, 一般可以是数据入库逻辑的函数* @param threshold 阈值,达到阈值就处理一次存储的数据* @param <T> 数据模型泛型* @return 返回监听器*/public static <T> AnalysisEventListener<T> getReadListener(Consumer<List<T>> consumer, int threshold) {return new AnalysisEventListener<T>() {/*** 存储解析的数据 T t*/// ArrayList基于数组实现, 查询更快
//            List<T> dataList = new ArrayList<>(threshold);// LinkedList基于双向链表实现, 插入和删除更快List<T> dataList = new LinkedList<>(); /*** 每解析一行数据事件调度中心都会通知到这个方法, 订阅者1* @param data 解析的每行数据* @param context*/@Overridepublic void invoke(T data, AnalysisContext context) {dataList.add(data);// 达到阈值就处理一次存储的数据if (dataList.size() >= threshold) {consumer.accept(dataList);dataList.clear();}}/*** excel文件解析完成后,事件调度中心会通知到该方法, 订阅者2* @param context*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {// 最后阈值外的数据做处理if (dataList.size() > 0) {consumer.accept(dataList);}}};}/*** 获取读取Excel的监听器对象, 不指定阈值, 默认阈值为 2000* @param consumer* @param <T>* @return*/public static <T> AnalysisEventListener<T> getReadListener(Consumer<List<T>> consumer) {return getReadListener(consumer, 2000);}
}

再来看读取Excel的 代码:

/*** 采用解耦的自定义监听器读取Excel, 可以实现任何数据模型bean的读取*/
@Test
public void testReadExcelN() {// 读取的excel文件路径String filename = "D:\\study\\excel\\user1.xlsx";// 读取excelEasyExcel.read(filename, UserModel.class, 	EasyExcelUtils.getReadListener(dataProcess())).doReadAll(); // 读取全部sheet
}/***  传给监听器的是一个处理解析数据的函数, 当调用consumer的accept方法时就会调用传递的函数逻辑*  这里传递的函数是对解析结果集的遍历打印操作, 也可以是数据入库操作* @return*/
public Consumer<List<UserModel>> dataProcess() {Consumer<List<UserModel>> consumer = users -> users.forEach(System.out::println);return consumer;
}

遇到的问题:文件有数据, EasyExcel读取的数据全为null的坑, 看图。

在这里插入图片描述
原因及解决方案: https://blog.csdn.net/qq_19309473/article/details/111322185

【四】一次实际使用案例

【1】添加pom依赖

<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.1.2</version>
</dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.17</version>
</dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>3.17</version>
</dependency>

【2】监听器

@Data
@Slf4j
public class CloudVerifyRuleExcelListener extends AnalysisEventListener<Map<Integer, String>> {/*** cachedDataList*/private List<Map<Integer, String>> cachedDataList = Lists.newArrayList();/*** 获取excel解析结果** @param multipartFile 导入文件* @param sheetNo       页数* @param headRowNumber 表头所在行数* @return List*/public static List<Map<Integer, String>> getDataList(MultipartFile multipartFile, int sheetNo, int headRowNumber)throws IOException {log.info("解析文件开始");CloudVerifyRuleExcelListener noModelDataListener = new CloudVerifyRuleExcelListener();EasyExcel.read(multipartFile.getInputStream(), noModelDataListener).headRowNumber(headRowNumber).sheet(sheetNo).doRead();List<Map<Integer, String>> dataList = noModelDataListener.getCachedDataList();log.info("解析文件结束");return dataList;}/*** 读取excel内容,一行一行进行读取,进行业务处理*/@Overridepublic void invoke(Map<Integer, String> data, AnalysisContext analysisContext) {cachedDataList.add(data);}/*** 读取完成后执行*/@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {log.info("所有数据解析完成!");}}

【3】实体类

@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "云检验-检查规则创建请求对象")
public class CloudVerifyRuleImportRequest {/*** 云检验id*/@Schema(description = "云检验id")private Long cloudVerifyId;/*** 规则编码*/@ExcelProperty(value = "规则编码", index = 0)@Schema(description = "规则编码")private String ruleCd;/*** 系统名*/@ExcelProperty(value = "系统名", index = 1)@Schema(description = "系统名")private String sysNm;/*** 是否启用*/@ExcelProperty(value = "是否启用", converter = BooleanConverter.class, index = 6)@Schema(description = "是否启用")private Boolean initFlag;/*** 告警启始时间*/@ExcelProperty(value = "告警启始日期", converter = LocalDateStringConverter.class, index = 8)@Schema(description = "告警启始日期")private LocalDateTime warnStratTm;
}

【4】数据类型转换器

public class BooleanConverter implements Converter<Boolean> {@Overridepublic Class supportJavaTypeKey() {return null;}@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return null;}@Overridepublic Boolean convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,GlobalConfiguration globalConfiguration) throws Exception {Boolean b = false;if ("是".equals(cellData.getStringValue())) {b = true;}return b;}@Overridepublic CellData convertToExcelData(Boolean value, ExcelContentProperty contentProperty,GlobalConfiguration globalConfiguration) {return null;}}
public class LocalDateStringConverter implements Converter<LocalDateTime> {@Overridepublic Class supportJavaTypeKey() {return LocalDateTime.class;}@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}@Overridepublic LocalDateTime convertToJavaData(CellData cellData,ExcelContentProperty excelContentProperty,GlobalConfiguration globalConfiguration) throws Exception {return LocalDate.parse(cellData.getStringValue(), DateTimeFormatter.ofPattern(NORM_DATE_PATTERN)).atStartOfDay();}@Overridepublic CellData convertToExcelData(LocalDateTime localDateTime,ExcelContentProperty excelContentProperty,
GlobalConfiguration globalConfiguration) throws Exception {DateTimeFormatter formatter = DateTimeFormatter.ofPattern(NORM_DATE_PATTERN);String format = formatter.format(localDateTime);return new CellData(format);}
}

【5】导入接口和业务代码

    @PostMapping(value = "/import")@Operation(summary = "导入云检验-检查作业", description = "导入云检验-检查作业")public ApiResponse<Void> importExcel(@RequestParam("cloudVerifyJobFile") MultipartFile cloudVerifyJobFile,@RequestParam("cloudVerifyRuleFile")MultipartFile cloudVerifyRuleFile) throws IOException {return cloudVerifyJobAppService.importExcel(cloudVerifyJobFile, cloudVerifyRuleFile);}
    @Transactional(rollbackFor = Exception.class)public ApiResponse<Void> importExcel(MultipartFile cloudVerifyJobFile,MultipartFile cloudVerifyRuleFile) throws IOException {// 检验云检验-检查作业文件// 校验导入文件格式是否为xls或xlsxApiResponse<Void> fileTypeResponseJob = ExcelUtils.checkFileType(cloudVerifyJobFile);if (ComConstants.FAILED.equals(fileTypeResponseJob.getCode())) {return fileTypeResponseJob;}// 校验文件中的表头是否匹配List<Map<Integer, String>> sheetListJob = CloudVerifyJobExcelListener.getDataList(cloudVerifyJobFile, 0, 0);Map<Integer, String> indexAndNmMapSheetJob = sheetListJob.get(0);ApiResponse<Void> sheetHeadResponseJob =ExcelUtils.invokeHeadMap(indexAndNmMapSheetJob, CloudVerifyJobImportRequest.class);if (ComConstants.FAILED.equals(sheetHeadResponseJob.getCode())) {return sheetHeadResponseJob;}// 检验云检验-检查规则文件// 校验导入文件格式是否为xls或xlsxApiResponse<Void> fileTypeResponseRule = ExcelUtils.checkFileType(cloudVerifyRuleFile);if (ComConstants.FAILED.equals(fileTypeResponseRule.getCode())) {return fileTypeResponseRule;}List<Map<Integer, String>> sheetListRule = CloudVerifyRuleExcelListener.getDataList(cloudVerifyRuleFile, 0, 0);Map<Integer, String> indexAndNmMapSheetRule = sheetListRule.get(0);ApiResponse<Void> sheetHeadResponseRule =ExcelUtils.invokeHeadMap(indexAndNmMapSheetRule, CloudVerifyRuleImportRequest.class);if (ComConstants.FAILED.equals(sheetHeadResponseRule.getCode())) {return sheetHeadResponseRule;}// 解析云检验-检查作业excel数据// 第一行是说明,headRowNumber = 1List<CloudVerifyJobImportRequest> jobImportRequestList =EasyExcel.read(cloudVerifyJobFile.getInputStream()).head(CloudVerifyJobImportRequest.class).sheet().headRowNumber(1).doReadSync();// 校验文件中的数据是否为空if (CollectionUtils.isEmpty(jobImportRequestList)) {return ApiResponse.fail("导入的云检验-检查作业为空模板,请检查后重新导入");}// 解析云检验-检查规则excel数据List<CloudVerifyRuleImportRequest> ruleImportRequestList =EasyExcel.read(cloudVerifyRuleFile.getInputStream()).head(CloudVerifyRuleImportRequest.class).sheet().headRowNumber(1).doReadSync();// 校验文件中的数据是否为空if (CollectionUtils.isEmpty(ruleImportRequestList)) {return ApiResponse.fail("导入的云检验-检查规则为空模板,请检查后重新导入");}// 将云检验-检查规则的数据根据作业名分成不同的List存在Map中HashMap<String, List<CloudVerifyRuleImportRequest>> ruleMap = Maps.newHashMap();ruleImportRequestList.forEach(ruleImportRequest -> {String jobName = ruleImportRequest.getJobNm();if (ruleMap.get(jobName) == null) {List<CloudVerifyRuleImportRequest> arrayList = Lists.newArrayList();arrayList.add(ruleImportRequest);ruleMap.put(jobName, arrayList);} else {List<CloudVerifyRuleImportRequest> arrayList = ruleMap.get(jobName);arrayList.add(ruleImportRequest);ruleMap.put(jobName, arrayList);}});jobImportRequestList.forEach(jobImportRequest -> {// 云检验-检查作业表创建对象 转成 云检验-检查作业表实体CloudVerifyJob cloudVerifyJob = cloudVerifyJobDTOAssembler.assembler(jobImportRequest);long id = IdWorker.getId();cloudVerifyJob.setId(id);// 调用创建云检验-检查作业领域服务cloudVerifyJobService.create(cloudVerifyJob);// 通过关联的作业名找到检查作业对应的检查规则listString jobName = jobImportRequest.getJobNm();List<CloudVerifyRuleImportRequest> ruleImportList = ruleMap.get(jobName);if (ruleImportList != null) {ruleImportList.forEach(ruleImport -> {// 完成云检验-检查规则的插入CloudVerifyRule cloudVerifyRule = cloudVerifyRuleConfigDTOAssembler.assembler(ruleImport);cloudVerifyRule.setCloudVerifyId(cloudVerifyJob.getId());cloudVerifyRuleService.create(cloudVerifyRule);});}// 完成配置修订信息的插入ConfigRevision configRevision = new ConfigRevision();configRevision.setProjId(DEFAULT_ID);configRevision.setTaskId(DEFAULT_ID);configRevision.setFormDataId(id);configRevision.setConfigType(DevTreeNodeType.CLOUD_VERIFY_CONFIG.name());configRevision.setConfigNm(jobImportRequest.getJobNm());configRevisionService.create(configRevision);});// 领域响应结果转成API响应结果return ApiResponse.ok();}

【6】导出接口和业务代码

    @PostMapping(value = "/export")@Operation(summary = "导出云检验配置", description = "导出云检验配置")public ApiResponse<String> export(@RequestBody @Valid CloudVerifyJobQueryRequest queryRequest) throws IOException {return cloudVerifyJobAppService.export(queryRequest);}
    public DomainResponse<String> export(CleanStrategyQueryRequest queryRequest) {if (StrUtil.isBlank(queryRequest.getPath())) {throw new YTRuntimeException("导出路径不能为空");}List<CleanStrategyQueryResponse> list = this.findAll(queryRequest).getData();// 如果没有传文件名称,就用默认的文件名称String filePath;if (StrUtil.isBlank(queryRequest.getFileName())) {filePath = queryRequest.getPath() + "清理策略.xlsx";} else {filePath = queryRequest.getPath() + queryRequest.getFileName();}EasyExcel.write(filePath, CleanStrategyQueryResponse.class).registerWriteHandler(new Customhandler()).sheet("清理策略").doWrite(list);return DomainResponse.ok("导出成功!");}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/144591.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Springboot对MVC、tomcat扩展配置

Springboot在web层的开发基本都是采用Springmvc框架技术&#xff0c;但是Springmvc中的某些配置在boot是没有的&#xff0c;我们就应该根据自己的需求进行对mvc扩展配置 Springboot1.x版本如何配置 通过注解Configuration一个类&#xff0c;继承webmvcconfigureradapter&#…

Bootstrap的弹性盒子布局学习笔记

Bootstrap的弹性盒子布局学习笔记 目录 01-综述02-利用类d-flex与类d-inline-flex将容器定义为弹性盒子03-对弹性容器的的元素在水平方向上进行排列顺序设置03-对弹性容器的的元素在垂直方向上进行排列顺序设置04-弹性盒子内所有元素在主轴方向上的对齐方式05-1-弹性盒子内各行…

C#生成自定义海报

安装包 SixLabors.ImageSharp.Drawing 2.0 需要的字体&#xff1a;宋体和微软雅黑 商用的需要授权如果商业使用可以使用方正书宋、方正黑体&#xff0c;他们可以免费商用 方正官网 代码 using SixLabors.Fonts; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Draw…

redis-设置从节点

节点结构 节点配置文件 主节点 不变 6380节点 port 6380 slaveof 127.0.0.1 63796381节点 port 6381 slaveof 127.0.0.1 6380启动 指定配置文件的方式启动 D:\jiqun\redis\Redis-6380>redis-server.exe redis.windows.conf启动时&#xff0c;会触发同步数据命令 主节点…

安装ipfs-swarm-key-gen

安装ipfs-swarm-key-gen Linux安装go解释器安装ipfs-swarm-key-gen Linux安装go解释器 https://blog.csdn.net/omaidb/article/details/133180749 安装ipfs-swarm-key-gen # 编译ipfs-swarm-key-gen二进制文件 go get -u github.com/Kubuxu/go-ipfs-swarm-key-gen/ipfs-swarm…

insightface实战:画出嘴巴和眼睛的mask

今天的目标是将人脸的嘴巴和眼睛区域抠出来&#xff0c;使用insightface简单实现出来&#xff0c;为了方便批量使用多进程跑数据&#xff0c;使用多进程的方式&#xff0c;下面是代码&#xff1a; import os import cv2 from multiprocessing import Pool import numpy as n…

Visual Studio 2019 C# winform CefSharp 中播放视频及全屏播放

VS C# winform CefSharp 浏览器控件&#xff0c;默认不支持视频播放&#xff0c;好在有大佬魔改了dll&#xff0c;支持流媒体视频播放。虽然找了很久&#xff0c;好歹还是找到了一个版本100.0.230的dll&#xff08;资源放在文末&#xff09; 首先创建一个项目 第二、引入CefSha…

排序:归并(Merge)排序算法分析

1.归并操作 归并:把两个或多个已经有序的序列合并成一个。 2路归并&#xff1a;二合一k路归并&#xff1a;k合一结论:m路归并&#xff0c;每选出一个元素需要对比关键字m-1次。 2.算法思想 核心操作:把数组内的两个有序序列归并为一个。 例如&#xff1a; 3.代码实现 将…

什么是大数据可视化

在互联网高速发展的当今&#xff0c;5G的兴起加速了数据传输的速度&#xff1b;与此同时&#xff0c;智能物联网如智慧家电、可穿戴设备等产品的火热&#xff0c;进一步扩充了数据获取的渠道。不仅仅在网页上、手机和电脑应用上以秒计产生海量数据&#xff0c;智能设备同时也在…

04. 人工智能核心基础 - 导论(3)

文章目录 人工智能和其他学科的关系为什么学习人工智能怎么学好人工智能&#xff1f;一些问题 Hi&#xff0c;你好。我是茶桁。 基于上一节课咱们的整体强度有点大&#xff0c;而且咱们马上也要进入高强度内容了&#xff0c;那么这一篇咱们就稍微水一篇吧。来聊聊天&#xff0…

Nginx环境搭建、负载均衡测试

Nginx环境搭建、负载均衡测试 系统环境&#xff1a; win10&#xff0c;IDEA2020&#xff0c;JDK8 一、nginx环境搭建 1.ngxin下载 Nginx官网下载&#xff1a; http://nginx.org/en/download.html Nginx有三种版本&#xff0c;分别是Mainline version&#xff08;开发版&…

怒刷LeetCode的第19天(Java版)

目录 第一题 题目来源 题目内容 解决方法 方法一&#xff1a;遍历一次数组 方法二&#xff1a;贪心算法 方法三&#xff1a;双指针 第二题 题目来源 题目内容 解决方法 方法一&#xff1a;动态规划 方法二&#xff1a;贪婪算法 方法三&#xff1a;正则表达式 第…

玄子Share 设计模式 GOF 全23种 + 七大设计原则

玄子Share 设计模式 GOF 全23种 七大设计原则 前言&#xff1a; 此文主要内容为 面向对象七大设计原则&#xff08;OOD Principle&#xff09;GOF&#xff08;Gang Of Four&#xff09;23种设计模式拓展的两个设计模式 简单工厂模式&#xff08;Simple Factory Pattern&#x…

基于Java实现的仓库管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言功能介绍&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导…

解密PDF密码

PDF文件有两种密码&#xff0c;一个打开密码、一个限制编辑密码&#xff0c;因为PDF文件设置了密码&#xff0c;那么打开、编辑PDF文件就会受到限制。忘记了PDF密码该如何解密&#xff1f; PDF和office一样&#xff0c;可以对文件进行加密&#xff0c;但是没有提供恢复密码的功…

智能驾驶、智能家居、智能工业中的 AI 关键基础设施,半导体厂商恩智浦的角色是什么?

我们来看一条七年前的真实新闻报道&#xff0c;2016 年《福布斯》在报道中提到“2020 年会有 1000 万台的自动驾驶汽车”。然而 2023 年的现在&#xff0c;真正实现 L4 级别自动驾驶的汽车&#xff0c;仍然远远没有达到这个预测的数量。 另一边&#xff0c;数据显示&#xff0c…

【数据结构】排序算法(一)—>插入排序、希尔排序、选择排序、堆排序

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言 1.直接插入排序 2.希尔排序 3.直接选择排…

使用不同尺寸的传感器拍照时,怎么保证拍出同样视场范围的照片?

1、问题背景 使用竞品机做图像效果对比时&#xff0c;我们通常都会要求拍摄的照片要视场范围一致&#xff0c;这样才具有可比性。之前我会考虑用同样焦距、同样分辨率的设备去拍照对比就可以了&#xff0c;觉得相机的视场范围只由镜头焦距来决定。 但如果对于不同尺寸的传感器…

使用 Matter-SDK 快速搭建 Matter 环境 (Linux)

Matter 作为一个统一的智能家居互联协议&#xff0c;凭借其高兼容性的特点&#xff0c;正逐渐打破各个智能家居之间的壁垒。乐鑫作为在 Matter 项目发布之初的早期成员&#xff0c;提供了一套开源、完整、易用的 Matter-SDK。 乐鑫的 Matter-SDK 是建立在开源 Matter-SDK 之上…

IOTE 2023盛况回顾,美格智能聚连接之力促数字新生长

9月20~22日&#xff0c;IOTE国际物联网展深圳站在深圳国际会展中心正式召开。本届展会以“IoT构建数字经济底座”为主题&#xff0c;聚焦物联网技术助推数字经济发展的核心动力。美格智能携前沿技术成果亮相展会&#xff0c;与参展观众深入交流。 展会上&#xff0c;美格智能带…