一、前言
项目开发过程中,免不了必要数据导出功能,常见的导出工具包有poi,easypoi,easyexcel,它们各有优缺点,简要来说:
- poi:功能强大,利用起来相对复杂,大数据时大概导致内存溢出
- easypoi:基于poi实现,功能强大,简单易用,大数据时大概导致内存溢出,小数据量时推荐
- easyexcel:基于poi实现,性能更优,大数据量时推荐
- 本文只介绍easyexcel,并介绍常见题目比如字典、样式的办理方案
二、业务流程
介绍数据导出业务流程之前,先梳理下数据导出,什么是数据导出,为什么必要数据导出?
我们都知道数据存储在数据库中,数据库中的数据可以通过前端页面或者APP出现给利用者,其实通过excel将数据导出也是一种出现方式,而且可以通过excel对数据做更复杂的处理惩罚。
前面说了,excel导出和前端页面、APP一样都是一种出现方式,所以前端页面和APP在将数据出现给用户的过程中遇到的题目,像属性转换(数据库存储的name,要出现给利用者名称)、字典转换(数据库存储的1/0,要出现给利用者启用/停用)等等题目,excel导出一样会遇到。下面介绍下数据出现必须要经过业务流程
- 获取必要导出的数据集合
- 数据属性和自然语言映射关系,将数据对象的属性转为利用者可以理解的自然语言
- 数据字典值和自然语言映射关系,将属性的字典值转为利用者可以理解的自然语言(非字典值,是什么值就出现什么值)
- 数据样式和自然语言样式映射关系,将数据样式转为利用者可以理解的自然语言样式
- 设置表格样式
- 将数据集合按照上述映射关系和表格样式,写入到excel中
- 用户下载excel
三、实现
1、引入easyexcel、fastjson、lombok包
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>easyexcel</artifactId>
- <version>4.0.3</version>
- </dependency>
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.46</version>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <version>1.18.0</version>
- </dependency>
复制代码 2、创建Json工具类
- package com.yu.demo.tools;
- import com.alibaba.fastjson.JSON;
- import com.fasterxml.jackson.core.type.TypeReference;
- import java.lang.reflect.Type;
- import java.util.Map;
- /**
- * JSON工具类
- *
- * @author admin
- */
- public abstract class JsonUtil {
- private JsonUtil() {
- }
- public final static Type MAP_INTEGER_STRING = new TypeReference<Map<Integer, String>>() {
- }.getType();
- /**
- * json串转Map(Map的value类型一致时使用)
- *
- * @param jsonString json串
- * @return 对象
- */
- public static <K, V> Map<K, V> json2Map(String jsonString, Type type) {
- return JSON.parseObject(jsonString, type);
- }
- }
复制代码 3、创建自定义字典转换注解
- package com.yu.demo.tools;
- import java.lang.annotation.*;
- @Target({ElementType.FIELD})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface DictSource {
- /**
- * 字典类型主键
- */
- String dictTypeId() default "";
- /**
- * 字典内容json串
- */
- String dictContentJson() default "";
- }
复制代码 4、创建字典转换实现类
- package com.yu.demo.tools;
- import com.alibaba.excel.converters.Converter;
- import com.alibaba.excel.enums.CellDataTypeEnum;
- import com.alibaba.excel.metadata.GlobalConfiguration;
- import com.alibaba.excel.metadata.data.WriteCellData;
- import com.alibaba.excel.metadata.property.ExcelContentProperty;
- import org.apache.poi.util.StringUtil;
- import java.lang.reflect.Field;
- import java.util.Map;
- public class IntegerDictConverter implements Converter<Integer> {
- @Override
- public CellDataTypeEnum supportExcelTypeKey() {
- return CellDataTypeEnum.STRING;
- }
- @Override
- public WriteCellData<?> convertToExcelData(Integer value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
- //属性值为空时,直接返回
- if (value == null) {
- //为空时的处理,与前端展示保持一致即可
- return new WriteCellData<>("");
- }
- //获取添加@ExcelProperty注解且converter = IntegerDictConverter.class的属性
- Field field = contentProperty.getField();
- //获取该属性的DictConverter注解信息
- DictSource dictSource = field.getAnnotation(DictSource.class);
- //配置了converter = IntegerDictConverter.class的属性,但是没有添加DictSource注解的直接返回
- if (dictSource == null) {
- return new WriteCellData<>(String.valueOf(value));
- }
- //获取配置的dictTypeId
- String dictTypeId = dictSource.dictTypeId();
- //获取配置的dictContentJson
- String dictContentJson = dictSource.dictContentJson();
- //判断dictTypeId是否为空
- boolean nullDictType = StringUtil.isBlank(dictTypeId);
- //判断nullDictContentJson是否为空
- boolean nullDictContentJson = StringUtil.isBlank(dictContentJson);
- //字典配置都为空时,将属性值转为字符串直接返回
- if (nullDictType && nullDictContentJson) {
- return new WriteCellData<>(String.valueOf(value));
- }
- //优先使用dictTypeId处理转换
- if (!nullDictType) {
- //通过dictTypeId获取字典内容集合:List<DictContent> dictContents = dictContentService.listByDictTypeId(dictTypeId);//主键是数值的,将dictTypeId转为数值
- //遍历字典内容,匹配属性值与字典值:value.equals(dictContent.getValue())
- //匹配成功后获取字典名称返回:return new WriteCellData<>(dictContent.getName());
- //如果没有匹配成功使用dictContentJson处理转换
- }
- if (!nullDictContentJson) {
- Map<Integer, String> dictContentMap = JsonUtil.json2Map(dictContentJson, JsonUtil.MAP_INTEGER_STRING);
- String cnName = dictContentMap.get(value);
- if (StringUtil.isNotBlank(cnName)) {
- return new WriteCellData<>(cnName);
- }
- }
- //没有转换成功时使用默认属性值
- return new WriteCellData<>(String.valueOf(value));
- }
- }
复制代码 5、创建数据对象类
- package com.yu.demo.web.easyexcel.entity;
- import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
- import com.alibaba.excel.annotation.ExcelProperty;
- import com.alibaba.excel.annotation.format.DateTimeFormat;
- import com.alibaba.excel.annotation.write.style.ColumnWidth;
- import com.alibaba.excel.converters.date.DateStringConverter;
- import com.yu.demo.web.easyexcel.component.DictSource;
- import com.yu.demo.web.easyexcel.component.IntegerDictConverter;
- import lombok.Getter;
- import lombok.Setter;
- import lombok.ToString;
- import java.util.Date;
- @Setter
- @Getter
- @ToString
- //类上添加@ExcelIgnoreUnannotated时,属性没有@ExcelProperty注解时不导出
- //类上未添加@ExcelIgnoreUnannotated,属性没有@ExcelProperty注解时也导出
- @ExcelIgnoreUnannotated
- public class User {
- /**
- * 名称
- */
- @ExcelProperty("名称")
- private String name;
- /**
- * 密码
- * 类添加@ExcelIgnoreUnannotated,属性未添加@ExcelProperty,不导出
- */
- private String password;
- /**
- * 生日
- * 日期样式处理
- * 1.使用@DateTimeFormat设置导出样式
- * 2.使用DateStringConverter处理导出
- */
- @DateTimeFormat("yyyy-MM-dd HH:mm:ss")
- @ExcelProperty(value = "生日", converter = DateStringConverter.class)
- private Date birthday;
- /**
- * 性别
- * 字典转换处理
- */
- @ColumnWidth(7)//指定列宽度,优先级高于LongestMatchColumnWidthStyleStrategy
- @ExcelProperty(value = "性别", converter = IntegerDictConverter.class)
- @DictSource(dictContentJson = "{0:'女',1:'男',2:'保密'}")
- private Integer sex;
- }
复制代码 6、创建多sheet页封装对象
- package com.yu.demo.tools;
- import lombok.Getter;
- import lombok.Setter;
- import lombok.ToString;
- import java.util.List;
- /**
- * excel导入导出数据对象
- */
- @Setter
- @Getter
- @ToString
- public class SheetEntity<T> {
- /**
- * sheet页名称(导出参数)
- * 可以为空,为空时,单sheet页没有名称,多sheet页序号为名称
- */
- private String sheetName;
- /**
- * 数据类型(导入导出参数)
- */
- private Class<T> head;
- /**
- * 数据(导出参数)
- */
- private List<T> data;
- }
复制代码 7、创建Excel导出工具类
导出的数据有如下三种及其说明
- 通过全路径文件名导出,easyexcel通过全路径文件名创建文件,将数据写入文件,当路径不存在时报错,适合场景:一次导出,多次下载
- 通过文件导出,将数据写入文件,当路径不存在报错,适合场景:一次导出,多次下载
- 通过输出流导出,将数据写入输出流,适合场景:导出一次下载一次
- package com.yu.demo.tools;
- import com.alibaba.excel.EasyExcel;
- import com.alibaba.excel.ExcelWriter;
- import com.alibaba.excel.support.ExcelTypeEnum;
- import com.alibaba.excel.write.builder.ExcelWriterBuilder;
- import com.alibaba.excel.write.handler.WriteHandler;
- import com.alibaba.excel.write.metadata.WriteSheet;
- import com.alibaba.excel.write.metadata.style.WriteCellStyle;
- import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
- import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
- import org.apache.poi.ss.usermodel.HorizontalAlignment;
- import org.apache.poi.ss.usermodel.VerticalAlignment;
- import org.apache.poi.util.StringUtil;
- import java.io.File;
- import java.io.OutputStream;
- import java.util.List;
- /**
- * excel导入导出工具类(easyExcel实现)
- * easyPoi:并发量和数据量都不大时推荐,定制化的导出支持非常的丰富
- * easyExcel:高并发、大数据量时推荐
- */
- public abstract class ExcelUtil {
- // 设置居中对齐的样式
- private static final WriteCellStyle CONTENT_WRITE_CELL_STYLE;
- private static final WriteHandler HORIZONTAL_CELL_STYLE_STRATEGY;
- static {
- CONTENT_WRITE_CELL_STYLE = new WriteCellStyle();
- //水平居中
- CONTENT_WRITE_CELL_STYLE.setHorizontalAlignment(HorizontalAlignment.CENTER);
- //垂直居中
- CONTENT_WRITE_CELL_STYLE.setVerticalAlignment(VerticalAlignment.CENTER);
- HORIZONTAL_CELL_STYLE_STRATEGY = new HorizontalCellStyleStrategy(null, CONTENT_WRITE_CELL_STYLE);
- }
- private ExcelUtil() {
- }
- /**
- * 使用EasyExcel导出
- *
- * @param fullFileName 文件路径+文件名+后缀(文件已存在时覆盖)
- * @param sheetName sheet名称(为空时使用默认值0)
- * @param head 数据类型(为空时没有表头,只有数据)
- * @param exportData 需要导出的数据(为空时,没有数据)
- */
- public static void exportByEasyExcel(String fullFileName, String sheetName, Class<?> head, List<?> exportData) {
- File targetFile = new File(fullFileName);
- // 判断文件父目录是否存在
- if (!targetFile.getParentFile().exists()) {
- boolean mkdirResult = targetFile.getParentFile().mkdirs();
- if (!mkdirResult) {
- return;
- }
- }
- ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(targetFile, head);
- if (fullFileName.endsWith(ExcelTypeEnum.XLS.getValue())) {
- excelWriterBuilder.excelType(ExcelTypeEnum.XLS);
- } else if (fullFileName.endsWith(ExcelTypeEnum.CSV.getValue())) {
- excelWriterBuilder.excelType(ExcelTypeEnum.CSV);
- } else {
- excelWriterBuilder.excelType(ExcelTypeEnum.XLSX);
- }
- excelWriterBuilder
- //设置列按最大长度调整
- .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
- //设置水平垂直居中
- .registerWriteHandler(HORIZONTAL_CELL_STYLE_STRATEGY)
- .sheet(sheetName)
- .doWrite(exportData);
- }
- /**
- * 使用EasyExcel导出
- *
- * @param outputStream 输出流
- * @param sheetName sheet名称(为空时使用默认值0)
- * @param head 数据类型(为空时没有表头,只有数据)
- * @param exportData 需要导出的数据(为空时,没有数据)
- */
- public static void exportByEasyExcel(OutputStream outputStream, ExcelTypeEnum excelType, String sheetName, Class<?> head, List<?> exportData) {
- EasyExcel.write(outputStream, head)
- .excelType(excelType)
- //设置列按最大长度调整,非线程安全,每次都需要new
- .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
- //设置水平垂直居中
- .registerWriteHandler(HORIZONTAL_CELL_STYLE_STRATEGY)
- .sheet(sheetName)
- .doWrite(exportData);
- }
- /**
- * 使用EasyExcel导出多sheet页数据
- *
- * @param outputStream 输出流
- * @param sheetEntities 导出数据对象集合
- */
- public static void exportByEasyExcel(OutputStream outputStream, ExcelTypeEnum excelType, List<SheetEntity<?>> sheetEntities) {
- ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(outputStream).excelType(excelType);
- writeSheets(excelWriterBuilder, sheetEntities);
- }
- private static void writeSheets(ExcelWriterBuilder excelWriterBuilder, List<SheetEntity<?>> sheetEntities) {
- excelWriterBuilder
- .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
- .registerWriteHandler(HORIZONTAL_CELL_STYLE_STRATEGY);
- ExcelWriter excelWriter = excelWriterBuilder.build();
- for (int i = 0; i < sheetEntities.size(); i++) {
- SheetEntity<?> sheetEntity = sheetEntities.get(i);
- Class<?> head = sheetEntity.getHead();
- List<?> exportData = sheetEntity.getData();
- String sheetName = StringUtil.isBlank(sheetEntity.getSheetName()) ? String.valueOf(i + 1) : sheetEntity.getSheetName();
- WriteSheet writeSheet = EasyExcel.writerSheet(i + 1, sheetName).head(head).build();
- excelWriter.write(exportData, writeSheet);
- }
- excelWriter.finish();
- }
- }
复制代码 8、创建测试类
- package com.yu.demo.web.easyexcel.web;
- import com.alibaba.excel.support.ExcelTypeEnum;
- import com.yu.demo.web.easyexcel.entity.SheetEntity;
- import com.yu.demo.web.easyexcel.entity.User;
- import com.yu.demo.web.easyexcel.util.ExcelUtil;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.http.HttpHeaders;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
- import javax.annotation.PostConstruct;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
- import java.net.URLEncoder;
- import java.nio.charset.StandardCharsets;
- import java.util.ArrayList;
- import java.util.Date;
- import java.util.List;
- @RestController
- @RequestMapping("user")
- public class UserController {
- @Value("${download.path}")
- private String filePath;
- private List<User> users;
- private List<SheetEntity<?>> sheetEntities;
- @PostConstruct
- public void init() {
- users = new ArrayList<>(5);
- for (int i = 0; i < 5; i++) {
- User user = new User();
- user.setName(i + "号用户");
- user.setPassword(String.valueOf(i * 1000));
- user.setBirthday(new Date());
- user.setSex(i % 3);
- users.add(user);
- }
- sheetEntities = new ArrayList<>(2);
- for (int i = 0; i < 2; i++) {
- SheetEntity<User> sheetEntity = new SheetEntity<>();
- sheetEntity.setSheetName(i + "号sheet");
- sheetEntity.setHead(User.class);
- sheetEntity.setData(users);
- sheetEntities.add(sheetEntity);
- }
- }
- /**
- * 单sheet页通过全路径文件名导出测试接口(也可以通过文件流导出)
- * 返回文件名,前端通过web路径+文件名下载文件
- */
- @GetMapping("/filePath")
- public String filePath() {
- String fileName = "用户.xlsx";
- String fullFileName = filePath + fileName;
- ExcelUtil.exportByEasyExcel(fullFileName, "用户", User.class, users);
- return fileName;
- }
- /**
- * 多sheet页通过文件流导出(也可以通过全路径文件名导出)
- */
- @GetMapping("/download")
- public void download(HttpServletResponse response) throws IOException {
- String fileName = "用户";
- response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
- response.setCharacterEncoding(StandardCharsets.UTF_8.name());
- String encodeFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
- response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=*=utf-8''" + encodeFileName + ExcelTypeEnum.XLSX.getValue());
- ExcelUtil.exportByEasyExcel(response.getOutputStream(), ExcelTypeEnum.XLSX, sheetEntities);
- }
- }
复制代码 9、application.yml中添加文件下载路径配置
- download:
- #配置文件下载路径
- path: C:\Users\Administrator\Desktop
- spring:
- web:
- resources:
- static-locations:
- #注册文件下载路径
- - file:${download.path}
- #系统默认配置
- - classpath:/META-INF/resources/
- - classpath:/resources/
- - classpath:/static/
- - classpath:/public/
复制代码 四、接口测试
1、启动项目
2、利用全路径文件名方式导出
3、利用文件流方式导出
五、总结
- 利用Entity对象作为关系映射的载体,利用@ExcelProperty注解映射属性名称,并可以指定转换器、序号等信息;利用@DateTimeFormat注解和指定转换器设置时间格式;利用自定义注解@DictSource注解和指定转换器转换字典值
- 利用@ColumnWidth或其同目录下的其他注解、WriteHandler设置Excel样式
- 利用全路径文件名、文件、文件流作为数据导出的载体导出数据。
- SpringBoot集成easyexcel数据导出案例下载
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |