经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
《优化接口设计的思路》系列:第十一篇—表格的导入导出接口优化
来源:cnblogs  作者:sum墨  时间:2024/7/3 14:41:55  对本文有异议

一、前言

大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。

作为一名从业已达六年的老码农,我的工作主要是开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。

在后端开发中,我们经常处理增删改查的接口,这些操作已经非常熟悉了。然而,有时产品经理会要求增加一个表格数据的导入和导出功能,让用户可以离线处理数据。这类操作也很常见,但处理起来不太简单。尽管一些前端表格组件可以直接实现这类功能,但往往不够灵活,因为前端的数据通常已经过处理。如果要获取原始数据,还是得依靠后端处理。

开源的框架也有,如Apache POI、Alibaba EasyExcel等,我使用的最多的是Alibaba EasyExcel框架,非常好用,这里介绍一下我平时是怎么使用它的。

二、组件介绍

首先放出官网地址,欢迎大家star(目前已经24K):
https://alibaba-easyexcel.github.io/docs/current/

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

64M内存20秒读取75M(46W行25列)的Excel(3.0.2+版本)

Alibaba EasyExcel的核心类是EasyExcel

  1. /**
  2. * 最简单的读
  3. * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
  4. * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
  5. * <p>3. 直接读即可
  6. */
  7. @Test
  8. public void simpleRead() {
  9. String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
  10. // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
  11. EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
  12. }
  1. /**
  2. * 最简单的写
  3. * <p>1. 创建excel对应的实体对象 参照{@link com.alibaba.easyexcel.test.demo.write.DemoData}
  4. * <p>2. 直接写即可
  5. */
  6. @Test
  7. public void simpleWrite() {
  8. String fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx";
  9. // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
  10. // 如果这里想使用03 则 传入excelType参数即可
  11. EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
  12. }

三、尝试一下

maven导入

  1. <!-- easyexcel -->
  2. <dependency>
  3. <groupId>com.alibaba</groupId>
  4. <artifactId>easyexcel</artifactId>
  5. <version>3.1.1</version>
  6. </dependency>

1. 表格导出

(1)测试类代码

ExportController.java

  1. package com.example.springbooteasyexcel.controller;
  2. import com.alibaba.excel.EasyExcel;
  3. import com.alibaba.excel.ExcelWriter;
  4. import com.alibaba.excel.write.metadata.WriteSheet;
  5. import com.example.springbooteasyexcel.data.Mock;
  6. import com.example.springbooteasyexcel.sheet.CitySheet;
  7. import com.example.springbooteasyexcel.sheet.CompanySheet;
  8. import com.example.springbooteasyexcel.sheet.UserSheet;
  9. import org.springframework.web.bind.annotation.RequestMapping;
  10. import org.springframework.web.bind.annotation.RestController;
  11. import javax.servlet.http.HttpServletResponse;
  12. import java.io.IOException;
  13. import java.net.URLEncoder;
  14. @RestController
  15. @RequestMapping("/export")
  16. public class ExportController {
  17. /**
  18. * @param response
  19. * @url <a>http://localhost:8080/export/test1</a>
  20. * 在Excel中写入单个sheet
  21. */
  22. @RequestMapping("/test1")
  23. public void test1(HttpServletResponse response) {
  24. //从HttpServletResponse中获取OutputStream输出流
  25. try {
  26. // 设置响应类型
  27. response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
  28. // 设置编码格式
  29. response.setCharacterEncoding("utf-8");
  30. // 设置URLEncoder.encode 防止中文乱码
  31. String fileName = URLEncoder.encode("用户信息表", "UTF-8").replaceAll("\\+", "%20");
  32. // 设置响应头
  33. response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
  34. // 写出Excel
  35. EasyExcel.write(response.getOutputStream(), UserSheet.class).inMemory(true).sheet("用户信息表").doWrite(Mock.userList());
  36. } catch (IOException e) {
  37. throw new RuntimeException("数据或文件损坏,无法下载");
  38. }
  39. }
  40. /**
  41. * 在Excel中写入多个sheet
  42. *
  43. * @url <a>http://localhost:8080/export/test2</a>
  44. */
  45. @RequestMapping("/test2")
  46. public void test2(HttpServletResponse response) throws Exception {
  47. // 设置响应类型
  48. response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
  49. // 设置编码格式
  50. response.setCharacterEncoding("utf-8");
  51. // 设置URLEncoder.encode 防止中文乱码
  52. String fileName = URLEncoder.encode("信息表", "UTF-8").replaceAll("\\+", "%20");
  53. // 设置响应头
  54. response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
  55. // 多个sheet的输出需要使用ExcelWriter类,这里想要下载成功,需要输出到OutputStream中
  56. try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).inMemory(true).build()) {
  57. // 创建用户信息表的sheet,写入用户信息数据,1代表sheet的位置是第一个
  58. WriteSheet userInfoSheet = EasyExcel.writerSheet(0, "用户信息表").head(UserSheet.class).build();
  59. excelWriter.write(Mock.userList(), userInfoSheet);
  60. // 创建城市信息表的sheet,写入城市信息数据,2代表sheet的位置是第二个
  61. WriteSheet cityInfoSheet = EasyExcel.writerSheet(1, "城市信息表").head(CitySheet.class).build();
  62. excelWriter.write(Mock.cityList(), cityInfoSheet);
  63. // 创建公司信息表的sheet,写入公司信息数据,3代表sheet的位置是第三个
  64. WriteSheet companyInfoSheet = EasyExcel.writerSheet(2, "公司信息表").head(CompanySheet.class).build();
  65. excelWriter.write(Mock.companyList(), companyInfoSheet);
  66. }
  67. }
  68. }

Mock.java

  1. package com.example.springbooteasyexcel.data;
  2. import com.alibaba.excel.enums.CellDataTypeEnum;
  3. import com.alibaba.excel.metadata.data.RichTextStringData;
  4. import com.alibaba.excel.metadata.data.WriteCellData;
  5. import com.alibaba.excel.write.metadata.style.WriteFont;
  6. import com.example.springbooteasyexcel.sheet.CitySheet;
  7. import com.example.springbooteasyexcel.sheet.CompanySheet;
  8. import com.example.springbooteasyexcel.sheet.UserSheet;
  9. import org.apache.poi.ss.usermodel.IndexedColors;
  10. import java.util.ArrayList;
  11. import java.util.Calendar;
  12. import java.util.List;
  13. /**
  14. * 以下数据均来自于网络,如有侵权,请联系删除
  15. */
  16. public class Mock {
  17. public static List<UserSheet> userList() {
  18. List<UserSheet> list = new ArrayList<>(10);
  19. list.add(UserSheet.builder().userId(001L).userName("张三").userPhone("11112223123").userEmail("zhansan@163.com").userAddress("北京朝阳区").gender(buildCellData("男")).registerTime(Calendar.getInstance().getTime()).build());
  20. list.add(UserSheet.builder().userId(002L).userName("李四").userPhone("11112223123").userEmail("lisi@qq.com").userAddress("南京玄武门").gender(buildCellData("女")).registerTime(Calendar.getInstance().getTime()).build());
  21. list.add(UserSheet.builder().userId(003L).userName("王五").userPhone("11112223123").userEmail("wangwu@google.com").userAddress("杭州未来科技城").gender(buildCellData("男")).registerTime(Calendar.getInstance().getTime()).build());
  22. list.add(UserSheet.builder().userId(004L).userName("赵六").userPhone("11112223123").userEmail("zhaoliu@baidu.com").userAddress("上海徐家汇").gender(buildCellData("女")).registerTime(Calendar.getInstance().getTime()).build());
  23. return list;
  24. }
  25. private static WriteCellData<String> buildCellData(String gender) {
  26. // 设置单个单元格多种样式
  27. WriteCellData<String> cellData = new WriteCellData<>();
  28. // 设置单个单元格的填充类型
  29. cellData.setType(CellDataTypeEnum.RICH_TEXT_STRING);
  30. RichTextStringData richTextStringData = new RichTextStringData();
  31. cellData.setRichTextStringDataValue(richTextStringData);
  32. richTextStringData.setTextString(gender);
  33. WriteFont writeFont = new WriteFont();
  34. if ("男".equalsIgnoreCase(gender)) {
  35. //设置颜色为红色
  36. writeFont.setColor(IndexedColors.RED.getIndex());
  37. } else if ("女".equalsIgnoreCase(gender)) {
  38. //设置颜色为绿色
  39. writeFont.setColor(IndexedColors.GREEN.getIndex());
  40. }
  41. //应用颜色字体
  42. richTextStringData.applyFont(writeFont);
  43. return cellData;
  44. }
  45. public static List<CitySheet> cityList() {
  46. List<CitySheet> list = new ArrayList<>(10);
  47. list.add(CitySheet.builder().cityName("杭州市").cityDesc("杭州市一般指杭州。 杭州,简称“杭”,古称临安、钱塘,浙江省辖地级市、省会、副省级市、特大城市、国务院批复确定的浙江省经济、文化、科教中心,长江三角洲中心城市之一,环杭州湾大湾区核心城市、G60科创走廊中心城市。").build());
  48. list.add(CitySheet.builder().cityName("合肥市").cityDesc("合肥市一般指合肥。 合肥,简称“庐”或“合”,古称庐州、庐阳、合淝,安徽省辖地级市、省会,是合肥都市圈中心城市,国务院批复确定的中国长三角城市群副中心城市,全国四大科教基地、现代制造业基地和综合交通枢纽。").build());
  49. list.add(CitySheet.builder().cityName("武汉市").cityDesc("武汉市一般指武汉。 武汉,简称“汉”,别称江城,是湖北省省会,中部六省唯一的副省级市,超大城市,中国中部地区的中心城市,全国重要的工业基地、科教基地和综合交通枢纽,联勤保障部队机关驻地。").build());
  50. list.add(CitySheet.builder().cityName("深圳市").cityDesc("深圳市一般指深圳。 深圳,简称“深”,别称鹏城,广东省辖地级市,是广东省副省级市,国家计划单列市,超大城市,国务院批复确定的中国经济特区、全国性经济中心城市、国际化城市、科技创新中心、区域金融中心、商贸物流中心。").build());
  51. return list;
  52. }
  53. public static List<CompanySheet> companyList() {
  54. List<CompanySheet> list = new ArrayList<>(10);
  55. list.add(CompanySheet.builder().companyName("阿里巴巴").companyBoss("马云").companyBase("杭州市").companyDesc("阿里巴巴集团经营多项业务,另外也从关联公司的业务和服务中取得经营商业生态系统上的支援。业务和关联公司的业务包括:淘宝网、天猫、聚划算、全球速卖通、阿里巴巴国际交易市场、1688、阿里妈妈、阿里云、蚂蚁集团 [408] 、菜鸟网络等。").build());
  56. list.add(CompanySheet.builder().companyName("字节跳动").companyBoss("张一鸣").companyBase("北京市").companyDesc("字节跳动的全球化布局始于2015年 [3] ,“技术出海”是字节跳动全球化发展的核心战略 [4] ,其旗下产品有今日头条、西瓜视频、抖音、头条百科、皮皮虾、懂车帝、悟空问答等。").build());
  57. list.add(CompanySheet.builder().companyName("腾讯").companyBoss("马化腾").companyBase("深圳市").companyDesc("社交和通信服务QQ及微信/WeChat、社交网络平台QQ空间、腾讯游戏旗下QQ游戏平台、门户网站腾讯网、腾讯新闻客户端和网络视频服务腾讯视频等。").build());
  58. list.add(CompanySheet.builder().companyName("百度").companyBoss("李彦宏").companyBase("北京市").companyDesc("百度(Baidu)是拥有强大互联网基础的领先AI公司。百度愿景是:成为最懂用户,并能帮助人们成长的全球顶级高科技公司。").build());
  59. return list;
  60. }
  61. }

CitySheet.java

  1. package com.example.springbooteasyexcel.sheet;
  2. import com.alibaba.excel.annotation.ExcelProperty;
  3. import com.alibaba.excel.annotation.write.style.ColumnWidth;
  4. import lombok.Builder;
  5. import lombok.Data;
  6. @Data
  7. @Builder
  8. public class CitySheet {
  9. @ExcelProperty(value = "城市名称", index = 0)
  10. @ColumnWidth(10)
  11. private String cityName;
  12. @ExcelProperty(value = "城市介绍", index = 1)
  13. @ColumnWidth(60)
  14. private String cityDesc;
  15. }

CompanySheet.java

  1. package com.example.springbooteasyexcel.sheet;
  2. import com.alibaba.excel.annotation.ExcelProperty;
  3. import com.alibaba.excel.annotation.write.style.ColumnWidth;
  4. import lombok.Builder;
  5. import lombok.Data;
  6. @Data
  7. @Builder
  8. public class CompanySheet {
  9. @ExcelProperty(value = "公司名称", index = 0)
  10. @ColumnWidth(10)
  11. private String companyName;
  12. @ExcelProperty(value = "公司创始人", index = 1)
  13. @ColumnWidth(10)
  14. private String companyBoss;
  15. @ExcelProperty(value = "公司总基地", index = 2)
  16. @ColumnWidth(10)
  17. private String companyBase;
  18. @ExcelProperty(value = "公司简介", index = 3)
  19. @ColumnWidth(50)
  20. private String companyDesc;
  21. }

UserSheet.java

  1. package com.example.springbooteasyexcel.sheet;
  2. import com.alibaba.excel.annotation.ExcelIgnore;
  3. import com.alibaba.excel.annotation.ExcelProperty;
  4. import com.alibaba.excel.annotation.write.style.ColumnWidth;
  5. import com.alibaba.excel.metadata.data.WriteCellData;
  6. import lombok.Builder;
  7. import lombok.Data;
  8. import java.util.Date;
  9. @Data
  10. @Builder
  11. public class UserSheet {
  12. @ExcelProperty(value = "用户ID", index = 0)
  13. @ColumnWidth(10)
  14. private Long userId;
  15. @ExcelProperty(value = "用户名称", index = 1)
  16. @ColumnWidth(10)
  17. private String userName;
  18. @ExcelProperty(value = {"基本信息", "手机号码"}, index = 2)
  19. @ColumnWidth(20)
  20. private String userPhone;
  21. @ExcelProperty(value = {"基本信息", "电子邮箱"}, index = 3)
  22. @ColumnWidth(20)
  23. private String userEmail;
  24. @ExcelProperty(value = {"基本信息", "地址"}, index = 4)
  25. @ColumnWidth(20)
  26. private String userAddress;
  27. @ExcelProperty(value = "注册时间", index = 5)
  28. @ColumnWidth(20)
  29. private Date registerTime;
  30. @ExcelProperty(value = "性别,男:红色/女:绿色")
  31. @ColumnWidth(30)
  32. private WriteCellData<String> gender;
  33. /**
  34. * 忽略这个字段
  35. */
  36. @ExcelIgnore
  37. private Integer age;
  38. }

(2)效果展示

单个sheet&表头合并

多个sheet导出



2. 表格导入

Excel导入到数据库,我用一张流程图展示一下

EasyExcel有一个AnalysisEventListener,可以自定义一个Listener继承AnalysisEventListener,里面有一个invoke方法,每条数据都会进入这个方法。我们可以在这里做校验、存储、抛异常等动作,EasyExcel将这些都流程化了,写起代码来非常舒服。当然也有一些点需要注意下,比如自定义Listener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去。

(1)测试类代码

UserData.java

  1. package com.example.springbooteasyexcel.read;
  2. import java.util.Date;
  3. import com.alibaba.excel.annotation.ExcelIgnore;
  4. import com.alibaba.excel.annotation.ExcelProperty;
  5. import com.alibaba.excel.annotation.write.style.ColumnWidth;
  6. import com.alibaba.excel.metadata.data.WriteCellData;
  7. import lombok.AllArgsConstructor;
  8. import lombok.Builder;
  9. import lombok.Data;
  10. import lombok.NoArgsConstructor;
  11. import lombok.ToString;
  12. @Data
  13. @Builder
  14. @ToString
  15. @AllArgsConstructor
  16. @NoArgsConstructor
  17. public class UserData {
  18. private Long userId;
  19. private String userName;
  20. private Integer age;
  21. private String userPhone;
  22. private String userEmail;
  23. private String userAddress;
  24. private Date registerTime;
  25. private String gender;
  26. }

UserExcelReadListener.java

  1. package com.example.springbooteasyexcel.read;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import java.util.Map;
  5. import java.util.Objects;
  6. import java.util.regex.Pattern;
  7. import com.alibaba.excel.context.AnalysisContext;
  8. import com.alibaba.excel.event.AnalysisEventListener;
  9. import com.alibaba.excel.util.ListUtils;
  10. import com.alibaba.excel.util.StringUtils;
  11. import lombok.extern.slf4j.Slf4j;
  12. /**
  13. * 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
  14. */
  15. @Slf4j
  16. public class UserExcelReadListener extends AnalysisEventListener<UserData> {
  17. /**
  18. * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
  19. */
  20. private static final int BATCH_COUNT = 100;
  21. /**
  22. * 创建一个Pattern对象,使用正则表达式校验手机号格式
  23. */
  24. private static final Pattern PHONE_REGEX = Pattern.compile("^1[0-9]{10}$");
  25. /**
  26. * 缓存的数据
  27. */
  28. private List<UserData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
  29. /**
  30. * 错误信息列表
  31. */
  32. private final List<String> errorMsgList = new ArrayList<>(BATCH_COUNT);
  33. @Override
  34. public void invoke(UserData userData, AnalysisContext analysisContext) {
  35. log.info("解析到一条数据:{}", userData);
  36. int rowIndex = analysisContext.readRowHolder().getRowIndex();
  37. String name = userData.getUserName();
  38. String phone = userData.getUserPhone();
  39. String gender = userData.getGender();
  40. String email = userData.getUserEmail();
  41. Integer age = userData.getAge();
  42. String address = userData.getUserAddress();
  43. // 只有全部校验通过的对象才能被添加到下一步
  44. if (nameValid(rowIndex, name) && phoneValid(rowIndex, phone) && genderValid(rowIndex, gender) &&
  45. emailValid(rowIndex, email) && ageValid(rowIndex, age) && addressValid(rowIndex, address)) {
  46. cachedDataList.add(userData);
  47. }
  48. // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
  49. if (cachedDataList.size() >= BATCH_COUNT) {
  50. // to saveData();
  51. // 存储完成清理 list
  52. cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
  53. }
  54. }
  55. @Override
  56. public void doAfterAllAnalysed(AnalysisContext analysisContext) {
  57. log.info("所有数据解析完成!全部校验通过的数据有{}条", cachedDataList.size());
  58. // 这里也要保存数据,确保最后遗留的数据也存储到数据库saveData();
  59. // todo saveData();
  60. }
  61. @Override
  62. public void onException(Exception exception, AnalysisContext context) throws Exception {
  63. if (exception instanceof RuntimeException) {
  64. throw exception;
  65. }
  66. int index = context.readRowHolder().getRowIndex() + 1;
  67. errorMsgList.add("第" + index + "行解析错误");
  68. }
  69. @Override
  70. public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
  71. int totalRows = context.readSheetHolder().getApproximateTotalRowNumber() - 1;
  72. int maxNum = 2000;
  73. if (totalRows > maxNum) {
  74. errorMsgList.add("数据量过大,单次最多上传2000条");
  75. throw new RuntimeException("数据量过大,单次最多上传2000条");
  76. }
  77. }
  78. public List<String> getErrorMsgList() {
  79. return errorMsgList;
  80. }
  81. /**
  82. * 名称的校验
  83. *
  84. * @param rowIndex 行数
  85. * @param name 名称
  86. */
  87. private Boolean nameValid(Integer rowIndex, String name) {
  88. if (StringUtils.isBlank(name)) {
  89. errorMsgList.add("第" + rowIndex + "行,'姓名'不能为空");
  90. return Boolean.FALSE;
  91. }
  92. return Boolean.TRUE;
  93. }
  94. private Boolean phoneValid(int rowIndex, String phone) {
  95. if (StringUtils.isBlank(phone)) {
  96. errorMsgList.add("第" + rowIndex + "行,'手机号'不能为空");
  97. return Boolean.FALSE;
  98. }
  99. return Boolean.TRUE;
  100. }
  101. /**
  102. * 性别的校验
  103. *
  104. * @param rowIndex 行数
  105. * @param gender 性别
  106. */
  107. private Boolean genderValid(int rowIndex, String gender) {
  108. if (StringUtils.isBlank(gender)) {
  109. errorMsgList.add("第" + rowIndex + "行,'性别'不能为空");
  110. return Boolean.FALSE;
  111. }
  112. return Boolean.TRUE;
  113. }
  114. /**
  115. * 地址校验
  116. *
  117. * @param rowIndex 行数
  118. * @param address 地址
  119. */
  120. private Boolean addressValid(int rowIndex, String address) {
  121. // 校验地址是否为空
  122. if (StringUtils.isBlank(address)) {
  123. errorMsgList.add("第 " + rowIndex + " 行,'地址'不能为空");
  124. return Boolean.FALSE;
  125. }
  126. return Boolean.TRUE;
  127. }
  128. /**
  129. * 年龄的校验
  130. *
  131. * @param rowIndex 行数
  132. * @param age 年龄
  133. */
  134. private Boolean ageValid(int rowIndex, Integer age) {
  135. // 校验年龄是否为空
  136. if (Objects.isNull(age)) {
  137. errorMsgList.add("第 " + rowIndex + " 行'年龄'不能为空");
  138. return Boolean.FALSE;
  139. }
  140. return Boolean.TRUE;
  141. }
  142. /**
  143. * 邮箱的校验
  144. *
  145. * @param rowIndex 行数
  146. * @param email 邮箱
  147. */
  148. private Boolean emailValid(int rowIndex, String email) {
  149. // 校验邮箱是否为空
  150. if (StringUtils.isBlank(email)) {
  151. errorMsgList.add("第 " + rowIndex + " 行'邮箱'不能为空");
  152. return Boolean.FALSE;
  153. }
  154. return Boolean.TRUE;
  155. }
  156. }

ReadExcelTest.java

  1. package com.example.springbooteasyexcel.read;
  2. import com.alibaba.excel.EasyExcel;
  3. public class ReadExcelTest {
  4. public static void main(String[] args) {
  5. UserExcelReadListener userExcelReadListener = new UserExcelReadListener();
  6. EasyExcel.read("用户信息表.xlsx", UserData.class, userExcelReadListener).sheet().doRead();
  7. System.out.println(userExcelReadListener.getErrorMsgList());
  8. }
  9. }

执行结果

四、总结一下

1、Alibaba EasyExcel不仅支持写Excel,还支持读Excel和填充Excel,有兴趣的话可以自己去研究,官网地址已经贴在上面了,我这里只做一个引路的。

2、常用注解有三个@ExcelProperty@ColumnWidth@ExcelIgnore
(1)@ExcelProperty不仅确定表头,还可以合并行,用法如下:

  1. @ExcelProperty(value = {"基本信息", "手机号码"}, index = 2)
  2. @ColumnWidth(20)
  3. private String userPhone;
  4. @ExcelProperty(value = {"基本信息", "电子邮箱"}, index = 3)
  5. @ColumnWidth(20)
  6. private String userEmail;
  7. @ExcelProperty(value = {"基本信息", "地址"}, index = 4)
  8. @ColumnWidth(20)
  9. private String userAddress;

效果如下:在这里插入图片描述

(2)@ColumnWidth主要是控制列宽

(3)@ExcelIgnore忽略不需要输出的字段
3、写有两种形式
(1)写到文件

  1. /**
  2. * 最简单的写
  3. * <p>
  4. * 1. 创建excel对应的实体对象 参照{@link DemoData}
  5. * <p>
  6. * 2. 直接写即可
  7. */
  8. @Test
  9. public void simpleWrite() {
  10. // 注意 simpleWrite在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入
  11. // 写法1 JDK8+
  12. // since: 3.0.0-beta1
  13. String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
  14. // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
  15. // 如果这里想使用03 则 传入excelType参数即可
  16. EasyExcel.write(fileName, DemoData.class)
  17. .sheet("模板")
  18. .doWrite(() -> {
  19. // 分页查询数据
  20. return data();
  21. });
  22. // 写法2
  23. fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
  24. // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
  25. // 如果这里想使用03 则 传入excelType参数即可
  26. EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
  27. // 写法3
  28. fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
  29. // 这里 需要指定写用哪个class去写
  30. try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
  31. WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
  32. excelWriter.write(data(), writeSheet);
  33. }
  34. }

(2)写到Web流,这里的ContentType和CharacterEncoding不要乱码,否则很容易乱码或者文件损坏

  1. /**
  2. * 文件下载(失败了会返回一个有部分数据的Excel)
  3. * <p>
  4. * 1. 创建excel对应的实体对象 参照{@link DownloadData}
  5. * <p>
  6. * 2. 设置返回的 参数
  7. * <p>
  8. * 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大
  9. */
  10. @GetMapping("download")
  11. public void download(HttpServletResponse response) throws IOException {
  12. // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman
  13. response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
  14. response.setCharacterEncoding("utf-8");
  15. // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
  16. String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
  17. response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
  18. EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data());
  19. }

(3)用好自定义Listener可以很好的优化代码,并且逻辑也可以变得更清晰。

原文链接:https://www.cnblogs.com/wlovet/p/18278663

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号