文章目录
前言
上次介绍了使用easyexcel导出数据,本次介绍使用easyexcel导入数据。
一、业务流程
像导出数据一样,导入数据也有对应的业务场景,那就是数据输入;所以通过页面输入数据遇到的问题,导入数据也要处理。下面介绍下数据输入必须要经过业务流程
- 输入需要的数据属性
- 数据属性和自然语言映射关系,将使用者可以理解的自然语言转为数据对象的属性
- 数据字典值和自然语言映射关系,将使用者可以理解的自然语言转为属性的字典值
二、实现
1、引入easyexcel、fastjson、lombok包
<!--easy excel-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>4.0.3</version>
</dependency>
<!--fastjson-->
<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.web.easyexcel.component;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.yu.demo.web.easyexcel.util.JsonUtil;
import org.apache.poi.util.StringUtil;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Set;
public class IntegerDictConverter implements Converter<Integer> {
/**
* 导入支持的字段类型
*/
@Override
public Class<?> supportJavaTypeKey() {
return Integer.class;
}
/**
* 导出支持的字段类型
*/
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/**
* 导入转换
*/
@Override
public Integer convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
String stringValue = cellData.getStringValue();
if (StringUtil.isBlank(stringValue)) {
return null;
}
//获取添加@ExcelProperty注解且converter = IntegerDictConverter.class的属性
Field field = contentProperty.getField();
//获取该属性的DictConverter注解信息
DictSource dictSource = field.getAnnotation(DictSource.class);
//配置了converter = IntegerDictConverter.class的属性,但是没有添加DictSource注解的直接强转
if (dictSource == null) {
try {
//未配置字典时,直接强转
return Integer.parseInt(stringValue);
} catch (NumberFormatException ignored) {
//转化失败时,返回空
return null;
}
}
//获取配置的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) {
try {
return Integer.parseInt(stringValue);
} catch (NumberFormatException ignored) {
//转化失败时,返回空
return null;
}
}
//优先使用dictTypeId处理转换
if (!nullDictType) {
//通过dictTypeId获取字典内容集合:List<DictContent> dictContents = dictContentService.listByDictTypeId(dictTypeId);//主键是数值的,将dictTypeId转为数值
//遍历字典内容,匹配输入值与字典名称:name.equals(dictContent.getName())
//匹配成功后获取字典值返回:return dictContent.getValue();
//如果没有匹配成功使用dictContentJson处理转换
}
if (!nullDictContentJson) {
Map<Integer, String> dictContentMap = JsonUtil.json2Map(dictContentJson, JsonUtil.MAP_INTEGER_STRING);
Set<Map.Entry<Integer, String>> entrySet = dictContentMap.entrySet();
for (Map.Entry<Integer, String> entry : entrySet) {
if (stringValue.equals(entry.getValue())) {
return entry.getKey();
}
}
}
//没有转换成功时直接强转
try {
return Integer.parseInt(stringValue);
} catch (NumberFormatException ignored) {
//转化失败时,返回空
return null;
}
}
/**
* 导出转换
*/
@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;
/**
* 读取数据监听器(导入参数)
*/
private ReadListener<T> readListener;
}
7、创建Excel导入工具类
导入数据说明
- 通过文件或者文件流导入
- 导入的数据同步方式写入集合,适合小数据量
- 导入的数据异步方式写入集合,适合大数据量
package com.yu.demo.web.easyexcel.util;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.read.metadata.ReadSheet;
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 com.yu.demo.web.easyexcel.entity.SheetEntity;
import org.apache.commons.collections4.CollectionUtils;
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.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
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 文件路径+文件名+后缀(文件已存在时覆盖,目录不存在时Windows报错,linux不报错)
* @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);
}
/**
* 同步导入,适合小数据量
*
* @param inputStream 数据文件流
*/
public static <T> List<T> importByEasyExcel(InputStream inputStream, Class<T> head) {
return EasyExcel.read(inputStream).head(head).sheet().doReadSync();
}
/**
* 异步导入,解析的数据通过回调函数返回,适合大数据量
*
* @param inputStream 数据文件流
* @param head 数据类型
* @param readListener 回调监听器
*/
public static void importByEasyExcel(InputStream inputStream, Class<?> head, ReadListener<?> readListener) {
EasyExcel.read(inputStream, head, readListener).sheet().doRead();
}
/**
* 多sheet页导入
*
* @param inputStream 数据文件流
* @param sheetEntities 导入数据对象集合
*/
public static void importByEasyExcel(InputStream inputStream, List<SheetEntity<?>> sheetEntities) {
if (inputStream == null || CollectionUtils.isEmpty(sheetEntities)) {
return;
}
ExcelReader excelReader = EasyExcel.read(inputStream).build();
readSheets(excelReader, sheetEntities);
}
/**
* 多sheet页导入
*
* @param file 数据文件
* @param sheetEntities 导入数据对象集合
*/
public static void importByEasyExcel(File file, List<SheetEntity<?>> sheetEntities) {
if (file == null || CollectionUtils.isEmpty(sheetEntities)) {
return;
}
ExcelReader excelReader = EasyExcel.read(file).build();
readSheets(excelReader, sheetEntities);
}
private static void readSheets(ExcelReader excelReader, List<SheetEntity<?>> sheetEntities) {
List<ReadSheet> readSheets = new ArrayList<>(sheetEntities.size());
for (int i = 0; i < sheetEntities.size(); i++) {
SheetEntity<?> sheetEntity = sheetEntities.get(i);
ReadSheet readSheet = EasyExcel.readSheet(i).head(sheetEntity.getHead()).registerReadListener(sheetEntity.getReadListener()).build();
readSheets.add(readSheet);
}
excelReader.read(readSheets);
}
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.read.listener.PageReadListener;
import com.alibaba.excel.read.listener.ReadListener;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
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);
}
/**
* 上传数据
* 单sheet页,同步导入示例接口
*/
@PostMapping("/upload")
public String upload(MultipartFile file) throws IOException {
List<User> list = ExcelUtil.importByEasyExcel(file.getInputStream(), User.class);
//根据业务处理数据,这里直接打印数据
System.out.println(list);
return "success";
}
/**
* 上传数据
* 多sheet页,异步导入示例接口
*/
@PostMapping("/upload2")
public String upload2(MultipartFile file) throws IOException {
List<SheetEntity<?>> sheetEntities = new ArrayList<>(2);
sheetEntities.add(getUserImportEntity());
//多sheet页时按照顺序添加SheetEntity
sheetEntities.add(getUserImportEntity());
ExcelUtil.importByEasyExcel(file.getInputStream(), sheetEntities);
return "success";
}
private SheetEntity<User> getUserImportEntity() {
SheetEntity<User> sheetEntity = new SheetEntity<>();
sheetEntity.setHead(User.class);
//根据业务处理数据,这里直接打印数据
ReadListener<User> pageReadListener = new PageReadListener<>(System.out::println);
sheetEntity.setReadListener(pageReadListener);
return sheetEntity;
}
}
三、接口测试
1、启用项目
2、使用数据导出的文件,作为导入的文件,或者重新编写
- 多sheet页导出接口地址:http://localhost:8080/user/download,获取导入的文件
- 单sheet页导入接口地址:http://localhost:8080/user/upload
- 多sheet页导入接口地址:http://localhost:8080/user/upload2
- postman测试接口(同文件一个,单sheet页时只解析第一个sheet页)
- 测试结果
四、总结
- 使用Entity对象作为关系映射的载体,使用@ExcelProperty注解映射属性名称,并可以指定转换器、序号等信息;使用自定义注解@DictSource注解和指定转换器转换字典值
- 数据导入日期类型格式要和Entity中日期属性的注解@DateTimeFormat(“yyyy-MM-dd HH:mm:ss”)中格式保持一致
- SpringBoot集成easyexcel数据导入案例下载