一、前言
大家好!我是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
1package com.example.springbooteasyexcel.controller; 2 3import com.alibaba.excel.EasyExcel; 4import com.alibaba.excel.ExcelWriter; 5import com.alibaba.excel.write.metadata.WriteSheet; 6import com.example.springbooteasyexcel.data.Mock; 7import com.example.springbooteasyexcel.sheet.CitySheet; 8import com.example.springbooteasyexcel.sheet.CompanySheet; 9import com.example.springbooteasyexcel.sheet.UserSheet; 10import org.springframework.web.bind.annotation.RequestMapping; 11import org.springframework.web.bind.annotation.RestController; 12 13import javax.servlet.http.HttpServletResponse; 14import java.io.IOException; 15import java.net.URLEncoder; 16 17@RestController 18@RequestMapping("/export") 19public class ExportController { 20 21 /** 22 * @param response 23 * @url <a>http://localhost:8080/export/test1</a> 24 * 在Excel中写入单个sheet 25 */ 26 @RequestMapping("/test1") 27 public void test1(HttpServletResponse response) { 28 //从HttpServletResponse中获取OutputStream输出流 29 try { 30 // 设置响应类型 31 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); 32 // 设置编码格式 33 response.setCharacterEncoding("utf-8"); 34 // 设置URLEncoder.encode 防止中文乱码 35 String fileName = URLEncoder.encode("用户信息表", "UTF-8").replaceAll("\\+", "%20"); 36 // 设置响应头 37 response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); 38 // 写出Excel 39 EasyExcel.write(response.getOutputStream(), UserSheet.class).inMemory(true).sheet("用户信息表").doWrite(Mock.userList()); 40 } catch (IOException e) { 41 throw new RuntimeException("数据或文件损坏,无法下载"); 42 } 43 } 44 45 /** 46 * 在Excel中写入多个sheet 47 * 48 * @url <a>http://localhost:8080/export/test2</a> 49 */ 50 @RequestMapping("/test2") 51 public void test2(HttpServletResponse response) throws Exception { 52 // 设置响应类型 53 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); 54 // 设置编码格式 55 response.setCharacterEncoding("utf-8"); 56 // 设置URLEncoder.encode 防止中文乱码 57 String fileName = URLEncoder.encode("信息表", "UTF-8").replaceAll("\\+", "%20"); 58 // 设置响应头 59 response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); 60 // 多个sheet的输出需要使用ExcelWriter类,这里想要下载成功,需要输出到OutputStream中 61 try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).inMemory(true).build()) { 62 // 创建用户信息表的sheet,写入用户信息数据,1代表sheet的位置是第一个 63 WriteSheet userInfoSheet = EasyExcel.writerSheet(0, "用户信息表").head(UserSheet.class).build(); 64 excelWriter.write(Mock.userList(), userInfoSheet); 65 // 创建城市信息表的sheet,写入城市信息数据,2代表sheet的位置是第二个 66 WriteSheet cityInfoSheet = EasyExcel.writerSheet(1, "城市信息表").head(CitySheet.class).build(); 67 excelWriter.write(Mock.cityList(), cityInfoSheet); 68 // 创建公司信息表的sheet,写入公司信息数据,3代表sheet的位置是第三个 69 WriteSheet companyInfoSheet = EasyExcel.writerSheet(2, "公司信息表").head(CompanySheet.class).build(); 70 excelWriter.write(Mock.companyList(), companyInfoSheet); 71 } 72 } 73}
Mock.java
1package com.example.springbooteasyexcel.data; 2 3 4import com.alibaba.excel.enums.CellDataTypeEnum; 5import com.alibaba.excel.metadata.data.RichTextStringData; 6import com.alibaba.excel.metadata.data.WriteCellData; 7import com.alibaba.excel.write.metadata.style.WriteFont; 8import com.example.springbooteasyexcel.sheet.CitySheet; 9import com.example.springbooteasyexcel.sheet.CompanySheet; 10import com.example.springbooteasyexcel.sheet.UserSheet; 11import org.apache.poi.ss.usermodel.IndexedColors; 12 13import java.util.ArrayList; 14import java.util.Calendar; 15import java.util.List; 16/** 17 * 以下数据均来自于网络,如有侵权,请联系删除 18 */ 19public class Mock { 20 21 public static List<UserSheet> userList() { 22 List<UserSheet> list = new ArrayList<>(10); 23 list.add(UserSheet.builder().userId(001L).userName("张三").userPhone("11112223123").userEmail("zhansan@163.com").userAddress("北京朝阳区").gender(buildCellData("男")).registerTime(Calendar.getInstance().getTime()).build()); 24 list.add(UserSheet.builder().userId(002L).userName("李四").userPhone("11112223123").userEmail("lisi@qq.com").userAddress("南京玄武门").gender(buildCellData("女")).registerTime(Calendar.getInstance().getTime()).build()); 25 list.add(UserSheet.builder().userId(003L).userName("王五").userPhone("11112223123").userEmail("wangwu@google.com").userAddress("杭州未来科技城").gender(buildCellData("男")).registerTime(Calendar.getInstance().getTime()).build()); 26 list.add(UserSheet.builder().userId(004L).userName("赵六").userPhone("11112223123").userEmail("zhaoliu@baidu.com").userAddress("上海徐家汇").gender(buildCellData("女")).registerTime(Calendar.getInstance().getTime()).build()); 27 return list; 28 } 29 30 private static WriteCellData<String> buildCellData(String gender) { 31 // 设置单个单元格多种样式 32 WriteCellData<String> cellData = new WriteCellData<>(); 33 // 设置单个单元格的填充类型 34 cellData.setType(CellDataTypeEnum.RICH_TEXT_STRING); 35 36 RichTextStringData richTextStringData = new RichTextStringData(); 37 cellData.setRichTextStringDataValue(richTextStringData); 38 richTextStringData.setTextString(gender); 39 WriteFont writeFont = new WriteFont(); 40 if ("男".equalsIgnoreCase(gender)) { 41 //设置颜色为红色 42 writeFont.setColor(IndexedColors.RED.getIndex()); 43 } else if ("女".equalsIgnoreCase(gender)) { 44 //设置颜色为绿色 45 writeFont.setColor(IndexedColors.GREEN.getIndex()); 46 } 47 //应用颜色字体 48 richTextStringData.applyFont(writeFont); 49 return cellData; 50 } 51 52 public static List<CitySheet> cityList() { 53 List<CitySheet> list = new ArrayList<>(10); 54 list.add(CitySheet.builder().cityName("杭州市").cityDesc("杭州市一般指杭州。 杭州,简称“杭”,古称临安、钱塘,浙江省辖地级市、省会、副省级市、特大城市、国务院批复确定的浙江省经济、文化、科教中心,长江三角洲中心城市之一,环杭州湾大湾区核心城市、G60科创走廊中心城市。").build()); 55 list.add(CitySheet.builder().cityName("合肥市").cityDesc("合肥市一般指合肥。 合肥,简称“庐”或“合”,古称庐州、庐阳、合淝,安徽省辖地级市、省会,是合肥都市圈中心城市,国务院批复确定的中国长三角城市群副中心城市,全国四大科教基地、现代制造业基地和综合交通枢纽。").build()); 56 list.add(CitySheet.builder().cityName("武汉市").cityDesc("武汉市一般指武汉。 武汉,简称“汉”,别称江城,是湖北省省会,中部六省唯一的副省级市,超大城市,中国中部地区的中心城市,全国重要的工业基地、科教基地和综合交通枢纽,联勤保障部队机关驻地。").build()); 57 list.add(CitySheet.builder().cityName("深圳市").cityDesc("深圳市一般指深圳。 深圳,简称“深”,别称鹏城,广东省辖地级市,是广东省副省级市,国家计划单列市,超大城市,国务院批复确定的中国经济特区、全国性经济中心城市、国际化城市、科技创新中心、区域金融中心、商贸物流中心。").build()); 58 return list; 59 } 60 61 public static List<CompanySheet> companyList() { 62 List<CompanySheet> list = new ArrayList<>(10); 63 list.add(CompanySheet.builder().companyName("阿里巴巴").companyBoss("马云").companyBase("杭州市").companyDesc("阿里巴巴集团经营多项业务,另外也从关联公司的业务和服务中取得经营商业生态系统上的支援。业务和关联公司的业务包括:淘宝网、天猫、聚划算、全球速卖通、阿里巴巴国际交易市场、1688、阿里妈妈、阿里云、蚂蚁集团 [408] 、菜鸟网络等。").build()); 64 list.add(CompanySheet.builder().companyName("字节跳动").companyBoss("张一鸣").companyBase("北京市").companyDesc("字节跳动的全球化布局始于2015年 [3] ,“技术出海”是字节跳动全球化发展的核心战略 [4] ,其旗下产品有今日头条、西瓜视频、抖音、头条百科、皮皮虾、懂车帝、悟空问答等。").build()); 65 list.add(CompanySheet.builder().companyName("腾讯").companyBoss("马化腾").companyBase("深圳市").companyDesc("社交和通信服务QQ及微信/WeChat、社交网络平台QQ空间、腾讯游戏旗下QQ游戏平台、门户网站腾讯网、腾讯新闻客户端和网络视频服务腾讯视频等。").build()); 66 list.add(CompanySheet.builder().companyName("百度").companyBoss("李彦宏").companyBase("北京市").companyDesc("百度(Baidu)是拥有强大互联网基础的领先AI公司。百度愿景是:成为最懂用户,并能帮助人们成长的全球顶级高科技公司。").build()); 67 return list; 68 } 69} 70
CitySheet.java
1package com.example.springbooteasyexcel.sheet; 2 3import com.alibaba.excel.annotation.ExcelProperty; 4import com.alibaba.excel.annotation.write.style.ColumnWidth; 5import lombok.Builder; 6import lombok.Data; 7 8@Data 9@Builder 10public class CitySheet { 11 12 @ExcelProperty(value = "城市名称", index = 0) 13 @ColumnWidth(10) 14 private String cityName; 15 16 @ExcelProperty(value = "城市介绍", index = 1) 17 @ColumnWidth(60) 18 private String cityDesc; 19 20} 21
CompanySheet.java
1package com.example.springbooteasyexcel.sheet; 2 3 4import com.alibaba.excel.annotation.ExcelProperty; 5import com.alibaba.excel.annotation.write.style.ColumnWidth; 6import lombok.Builder; 7import lombok.Data; 8 9@Data 10@Builder 11public class CompanySheet { 12 13 @ExcelProperty(value = "公司名称", index = 0) 14 @ColumnWidth(10) 15 private String companyName; 16 17 @ExcelProperty(value = "公司创始人", index = 1) 18 @ColumnWidth(10) 19 private String companyBoss; 20 21 @ExcelProperty(value = "公司总基地", index = 2) 22 @ColumnWidth(10) 23 private String companyBase; 24 25 @ExcelProperty(value = "公司简介", index = 3) 26 @ColumnWidth(50) 27 private String companyDesc; 28} 29
UserSheet.java
1package com.example.springbooteasyexcel.sheet; 2 3import com.alibaba.excel.annotation.ExcelIgnore; 4import com.alibaba.excel.annotation.ExcelProperty; 5import com.alibaba.excel.annotation.write.style.ColumnWidth; 6import com.alibaba.excel.metadata.data.WriteCellData; 7import lombok.Builder; 8import lombok.Data; 9 10import java.util.Date; 11 12@Data 13@Builder 14public class UserSheet { 15 16 @ExcelProperty(value = "用户ID", index = 0) 17 @ColumnWidth(10) 18 private Long userId; 19 20 @ExcelProperty(value = "用户名称", index = 1) 21 @ColumnWidth(10) 22 private String userName; 23 24 @ExcelProperty(value = {"基本信息", "手机号码"}, index = 2) 25 @ColumnWidth(20) 26 private String userPhone; 27 28 @ExcelProperty(value = {"基本信息", "电子邮箱"}, index = 3) 29 @ColumnWidth(20) 30 private String userEmail; 31 32 @ExcelProperty(value = {"基本信息", "地址"}, index = 4) 33 @ColumnWidth(20) 34 private String userAddress; 35 36 @ExcelProperty(value = "注册时间", index = 5) 37 @ColumnWidth(20) 38 private Date registerTime; 39 40 @ExcelProperty(value = "性别,男:红色/女:绿色") 41 @ColumnWidth(30) 42 private WriteCellData<String> gender; 43 44 /** 45 * 忽略这个字段 46 */ 47 @ExcelIgnore 48 private Integer age; 49}
(2)效果展示
单个sheet&表头合并

多个sheet导出

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

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

四、总结一下
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 5 @ExcelProperty(value = {"基本信息", "电子邮箱"}, index = 3) 6 @ColumnWidth(20) 7 private String userEmail; 8 9 @ExcelProperty(value = {"基本信息", "地址"}, index = 4) 10 @ColumnWidth(20) 11 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 12 // 写法1 JDK8+ 13 // since: 3.0.0-beta1 14 String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; 15 // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 16 // 如果这里想使用03 则 传入excelType参数即可 17 EasyExcel.write(fileName, DemoData.class) 18 .sheet("模板") 19 .doWrite(() -> { 20 // 分页查询数据 21 return data(); 22 }); 23 24 // 写法2 25 fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; 26 // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 27 // 如果这里想使用03 则 传入excelType参数即可 28 EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data()); 29 30 // 写法3 31 fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; 32 // 这里 需要指定写用哪个class去写 33 try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) { 34 WriteSheet writeSheet = EasyExcel.writerSheet("模板").build(); 35 excelWriter.write(data(), writeSheet); 36 } 37 }
(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可以很好的优化代码,并且逻辑也可以变得更清晰。
