前言
文章是根据easyexcel官方文档,加上自己实战总结出来的,希望对大家有所帮助!!!
一、实战截图
1、导入数据列表
2、导出数据
3、导出数据耗时
EasyExcel官方文档:
https://easyexcel.opensource.alibaba.com/docs/current/
EasyExcel的maven依赖
<!--Easy Excel-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.3</version>
</dependency>
二、导入Excel
导入实体类对象:
package com.tb.easyexcel.entity;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* WriteUser 用户信息实体类
*
* @author tanb
* @version 1.0
* @date 2024/3/9 22:34
*/
@Data //生成getter、setter、toString、equals、hashCode等方法
@NoArgsConstructor
@AllArgsConstructor
//@Accessors(chain = true) //该注解不能加,
@TableName("t_user")
public class ReadUser implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO) //主键,自增长
@ExcelIgnore //导出时忽略这个字段
private Long id;
/**
* 姓名
*/
@TableField("xm")
@ExcelProperty({"用户信息","姓名"}) //导出复杂表头名称,用户信息为一级表头,姓名为二级表头
private String xm;
/**
* 手机号
*/
@TableField("phone")
@ExcelProperty({"用户信息","手机号"}) //导出复杂表头名称
private String phone;
/**
* 邮箱
*/
@TableField("email")
@ExcelProperty({"用户信息","邮箱"}) //导出复杂表头名称
private String email;
/**
* 生日
*/
@TableField("birthday")
@ExcelProperty({"用户信息","出生日期"})
private String birthday;
/**
* 薪资
*/
@TableField("salary")
//@NumberFormat("#.##") //导出时数字格式处理
@ExcelProperty({"用户信息","薪资"}) //导出复杂表头名称
private BigDecimal salary;
/**
* 入职时间
*/
@TableField("join_time")
@DateTimeFormat("yyyy-MM-dd HH:mm:ss") //导出时日期格式处理
@ExcelProperty({"用户信息","加入时间"}) //导出复杂表头名称
private LocalDateTime joinTime;
}
1、读Excel 写法一 简单方法
/**
* 读Excel 写法一 简单方法
* @return
*/
@Override
public ResponseResult importSimple() {
String userInfoFile = getExcelUrl("userList.xlsx");
// 这里默认每次会读取1000条数据 然后返回过来 直接调用使用数据就行
// 具体需要返回多少行可以在`PageReadListener`的构造函数设置
EasyExcel.read(userInfoFile, ReadUser.class, new PageReadListener<ReadUser>(dataList ->{
log.info("{}条数据,开始存储数据库!", dataList.size());
//保存入库
boolean b = this.saveBatch(dataList);
log.info("保存入库状态{}", b);
}, 1000)).sheet().doRead();
return ResponseResult.okResult();
}
2、读Excel 写法二 匿名内部类
/**
* 读Excel 写法二 匿名内部类
* @return
*/
@Override
public ResponseResult importByAnonymousInnerClass() {
String fileName = getExcelUrl("userList.xlsx");
EasyExcel.read(fileName, ReadUser.class, new ReadListener<ReadUser>() {
//单次缓存数据量
public static final int BATCH_COUNT = 1000;
//临时存储
private List<ReadUser> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
@Override
public void invoke(ReadUser readUser, AnalysisContext analysisContext) {
cachedDataList.add(readUser);
if(cachedDataList.size() >= BATCH_COUNT){
saveData();
//存储完成,清理list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
saveData();
}
//模拟数据存储(这里就是简单的打印一下)
private void saveData(){
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
//保存入库
boolean b = saveBatch(cachedDataList);
log.info("保存入库状态{}", b);
log.info("存储数据库成功!");
}
}).sheet().doRead();
return ResponseResult.okResult();
}
3、读Excel 写法三(使用自定义的ReadListener的实现类)
/**
* 读Excel 写法三(使用自定义的ReadListener的实现类)
* @return
*/
@Override
public ResponseResult importByReadListener() {
//写法三
String fileName = getExcelUrl("userList.xlsx");
//有个很重要的点 ReadExcelDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, ReadUser.class, new ReadExcelDataListener(this)).sheet().doRead();
return ResponseResult.okResult();
}
package com.tb.easyexcel.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson.JSON;
import com.tb.easyexcel.entity.ReadUser;
import com.tb.easyexcel.service.IReadUserService;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Map;
/**
* 读取excel数据监听 ReadExcelDataListener
*
* @author tanb
* @version 1.0
* @date 2024/3/10 20:13
*/
@Slf4j //日志输出
public class ReadExcelDataListener implements ReadListener<ReadUser> {
private IReadUserService ReadUserService;
public ReadExcelDataListener(IReadUserService readUserService) {
this.ReadUserService = readUserService;
}
//每隔1000条存储数据库,然后清理list,方便内存的回收
private static final int BATCH_COUNT = 1000;
//缓存数据
private List<ReadUser> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
//在解析 Excel 过程中发生异常时调用的方法。可以在该方法中记录日志或者进行异常处理等操作
@Override
public void onException(Exception e, AnalysisContext analysisContext) throws Exception {
}
//在读取 Excel 文件表头时调用的方法。可用于对表头进行校验或者记录日志等操作
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
log.info("读取Excel文件表头!");
}
//在读取到一条数据时调用的方法。T 表示读取到的数据类型。可以在该方法中对读取到的数据进行处理或者记录日志等操作
@Override
public void invoke(ReadUser user, AnalysisContext analysisContext) {
log.info("解析到一条数据:{}", JSON.toJSONString(user));
cachedDataList.add(user);
//达到BATCH_COUNT了,清空缓存,并可以去做一些处理(如存储一次数据库)
//目的:防止几万条数据在内存中,容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
//一些业务操作(如存储数据库)
//……
saveData();
//清除缓存
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
//在读取 Excel 文件中除数据外的其他内容时调用的方法。例如,批注、超链接等。可以在该方法中进行相应的处理
@Override
public void extra(CellExtra cellExtra, AnalysisContext analysisContext) {
}
//在读取数据完成后调用的方法。可以在该方法中进行一些资源清理工作或者记录日志等操作
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
//一些业务操作(如存储数据库,简单打印剩余数据)
saveData();
log.info("所有数据解析完成!");
}
//判断是否还有下一条数据需要读取。如果返回 true,会自动调用 invoke(T data, AnalysisContext analysisContext) 方法来读取下一条数据;
// 如果返回 false,则结束读取数据的过程。
@Override
public boolean hasNext(AnalysisContext context) {
return ReadListener.super.hasNext(context);
}
//模拟数据存储(这里就是简单的打印一下)
private void saveData() {
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
ReadUserService.saveBatch(cachedDataList);
log.info("存储数据库成功!");
// 清空缓存数据
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
4、读Excel 写法四(使用 build 方法构建 ExcelReader 对象)
/**
* 读Excel 写法四(使用 build 方法构建 ExcelReader 对象)
* @return
*/
@Override
public ResponseResult importByBuild() {
//写法四
String fileName = getExcelUrl("userList.xlsx");
// 一个文件一个reader
try (ExcelReader excelReader = EasyExcel.read(fileName, ReadUser.class, new ReadExcelDataListener(this)).build()) {
// 构建一个sheet 这里可以指定名字或者no
ReadSheet readSheet = EasyExcel.readSheet(0).build();
// 读取一个sheet
excelReader.read(readSheet);
}
return ResponseResult.okResult();
}
5、读Excel 写法五(通过 doReadAll 读取全部 sheet)
/**
* 读Excel 写法五(通过 doReadAll 读取全部 sheet)
* 读多个或者全部sheet,这里注意一个sheet不能读取多次,多次读取需要重新读取文件
* @return
*/
@Override
public ResponseResult importByDoReadAll() {
// 写法一:读取全部sheet
// 获取Excel表的路径
String fileName = getExcelUrl("userList.xlsx");
// 这里需要注意 DemoDataListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。
// 然后所有sheet都会往同一个DemoDataListener里面写
EasyExcel.read(fileName, ReadUser.class, new ReadExcelDataListener(this)).doReadAll();
return ResponseResult.okResult();
}
6、读Excel 写法六(通过 注册 ReadListener 的方式读取各个 Sheet 的信息)
/**
* 读Excel 写法六(通过 注册 ReadListener 的方式读取各个 Sheet 的信息)
* @return
*/
@Override
public ResponseResult importByRegisterReadListener() {
//写法二:分开读取各个Sheet的信息
String fileName = getExcelUrl("userList.xlsx");
try (ExcelReader excelReader = EasyExcel.read(fileName).build()) {
// 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener
// 注意复杂表头一定要加跳过表头 headRowNumber(2)
ReadSheet readSheet1 =
EasyExcel.readSheet(0).head(ReadUser.class).headRowNumber(2).registerReadListener(new ReadExcelDataListener(this)).build();
ReadSheet readSheet2 =
EasyExcel.readSheet(1).head(ReadUser.class).headRowNumber(2).registerReadListener(new ReadExcelDataListener(this)).build();
// 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能
excelReader.read(readSheet1, readSheet2);
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
}
return ResponseResult.okResult();
}
三、导出Excel
导入实体类对象:
package com.tb.easyexcel.entity;
import com.alibaba.excel.annotation.ExcelIgnore;
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.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.tb.easyexcel.aop.annotation.SensitivityEncrypt;
import com.tb.easyexcel.enums.SensitivityTypeEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* WriteUser 用户信息实体类
*
* @author tanb
* @version 1.0
* @date 2024/3/9 22:34
*/
@Data //生成getter、setter、toString、equals、hashCode等方法
@NoArgsConstructor
@AllArgsConstructor
@ContentRowHeight(20) //所有内容的行高
@HeadRowHeight(25) //所有头部标题的行高
@ColumnWidth(25) //所有列宽
@Accessors(chain = true)
@TableName("t_user")
public class WriteUser implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO) //主键,自增长
@ExcelIgnore //导出时忽略这个字段
private Long id;
/**
* 姓名
*/
@TableField("xm")
@SensitivityEncrypt(type = SensitivityTypeEnum.NAME) //姓名进行脱敏处理
@ExcelProperty({"用户信息","姓名"}) //导出复杂表头名称,用户信息为一级表头,姓名为二级表头
private String xm;
/**
* 手机号
*/
@TableField("phone")
@SensitivityEncrypt(type = SensitivityTypeEnum.PHONE) //手机号进行脱敏处理
@ExcelProperty({"用户信息","手机号"}) //导出复杂表头名称
private String phone;
/**
* 邮箱
*/
@TableField("email")
@SensitivityEncrypt(type = SensitivityTypeEnum.EMAIL) //邮箱进行脱敏处理
@ExcelProperty({"用户信息","邮箱"}) //导出复杂表头名称
private String email;
/**
* 生日
*/
@TableField("birthday")
@ExcelProperty({"用户信息","出生日期"}) //导出复杂表头名称
private String birthday;
/**
* 薪资
*/
@TableField("salary")
//@NumberFormat("#.##") //导出时数字格式处理
@ExcelProperty({"用户信息","薪资"}) //导出复杂表头名称
private BigDecimal salary;
/**
* 入职时间
*/
@TableField("join_time")
@DateTimeFormat("yyyy-MM-dd HH:mm:ss") //导出时日期格式处理
@ColumnWidth(50) //所有列宽
@ExcelProperty({"用户信息","加入时间"}) //导出复杂表头名称
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime joinTime;
}
1、写Excel 写法一 简单导出
/**
* 导出全部人员列表
* 写Excel 写法一
*
* @param userDto
* @return
*/
@Override
public ResponseResult exportAllList(UserDto userDto) {
LambdaQueryWrapper<WriteUser> queryWrapper = getUserLambdaQueryWrapper(userDto);
//查询所有用户列表
List<WriteUser> users = userMapper.selectList(queryWrapper);
// 脱敏操作
List<WriteUser> result = users.stream().map(user -> JSON.parseObject(DesensitizedUtils.getJson(user), WriteUser.class)).collect(Collectors.toList());
//导出excel
// 写法1 JDK8+
// since: 3.0.0-beta1
String fileName = this.getExcelUrl("excelSimpleWrite" + System.currentTimeMillis() + ".xlsx");
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
long startTime = new Date().getTime();
log.info("开始时间start:{}", startTime);
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, WriteUser.class)
.sheet("用户信息")
.doWrite(() -> {
// 分页查询数据
return result;
});
long endTime = new Date().getTime();
log.info("结束时间end:{}", endTime);
log.info("共耗时:{} ms", endTime - startTime);
return ResponseResult.okResult(users);
}
2、写Excel 写法二 多Sheet页导出
/**
* 导出人员列表(多sheet)
* 写Excel 写法二
*
* @return
*/
public ResponseResult exportMultipleSheet(UserDto userDto) {
LambdaQueryWrapper<WriteUser> queryWrapper = getUserLambdaQueryWrapper(userDto);
//查询所有用户列表
List<WriteUser> users = userMapper.selectList(queryWrapper);
// 脱敏操作
List<WriteUser> result = users.stream().map(user -> JSON.parseObject(DesensitizedUtils.getJson(user), WriteUser.class)).collect(Collectors.toList());
//获取多个Sheet数据
List<WriteUser> sheet1 = result.subList(0, 5000);
List<WriteUser> sheet2 = result.subList(5000, 10000);
JSONObject sheetJson = new JSONObject();
sheetJson.put("sheet1", sheet1);
sheetJson.put("sheet2", sheet2);
// 方法2 如果写到不同的sheet 不同的对象
String fileName = this.getExcelUrl("excelMultipleSheetWrite" + System.currentTimeMillis() + ".xlsx");
long startTime = new Date().getTime();
log.info("开始时间start:{}", startTime);
// 这里 指定文件
try (ExcelWriter excelWriter = EasyExcel.write(fileName).build()) {
// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
for (int i = 0; i < 2; i++) {
// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样。这里注意User.class 可以每次都变,我这里为了方便 所以用的同一个class
// 实际上可以一直变
WriteSheet writeSheet = EasyExcel.writerSheet(i, "用户信息" + i).head(WriteUser.class).build();
// 分页去数据库查询数据 这里可以去数据库查询每一页的数据
JSONArray data = sheetJson.getJSONArray("sheet" + (i + 1));
List<WriteUser> sheetData = JSONObject.parseArray(JSONObject.toJSONString(data, SerializerFeature.WriteClassName), WriteUser.class);
excelWriter.write(sheetData, writeSheet);
}
}
long endTime = new Date().getTime();
log.info("结束时间end:{}", endTime);
log.info("共耗时:{} ms", endTime - startTime);
return ResponseResult.okResult(users);
}
四、脱敏注解及工具类
1、脱敏注解SensitivityEncrypt
package com.tb.easyexcel.aop.annotation;
/**
* SensitivityEncrypt 自定义数据脱敏注解
*
* @author tanb
* @version 1.0
* @date 2024/3/9 22:34
*/
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.tb.easyexcel.enums.SensitivityTypeEnum;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义数据脱敏注解
*
* @author 10419
*/
// 作用在字段上
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = com.tb.easyexcel.handler.SensitivitySerializer.class) // 该注解使用序列化的方式
public @interface SensitivityEncrypt {
/**
* 脱敏数据类型(必须指定类型)
*/
SensitivityTypeEnum type();
/**
* 前面有多少不需要脱敏的长度
*/
int prefixNoMaskLen() default 1;
/**
* 后面有多少不需要脱敏的长度
*/
int suffixNoMaskLen() default 1;
/**
* 用什么符号进行打码
*/
String symbol() default "*";
}
2、脱敏序列化类SensitivitySerializer
package com.tb.easyexcel.handler;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.tb.easyexcel.aop.annotation.SensitivityEncrypt;
import com.tb.easyexcel.enums.SensitivityTypeEnum;
import com.tb.easyexcel.utils.SensitivityUtil;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Objects;
/**
* SensitivitySerializer 自定义脱敏序列化
*
* @author tanb
* @version 1.0
* @date 2024/3/9 22:34
*/
@NoArgsConstructor
@AllArgsConstructor
@Component
public class SensitivitySerializer extends JsonSerializer<String> implements ContextualSerializer {
// 脱敏类型
private SensitivityTypeEnum sensitivityTypeEnum;
// 前几位不脱敏
private Integer prefixNoMaskLen;
// 最后几位不脱敏
private Integer suffixNoMaskLen;
// 用什么打码
private String symbol;
/**
* 序列化 数据处理
*/
@Override
public void serialize(final String origin, final JsonGenerator jsonGenerator,
final SerializerProvider serializerProvider) throws IOException {
//判断是否为空脱敏类型
if (StringUtils.isNotBlank(origin) && null != sensitivityTypeEnum) {
//判断脱敏类型,进入对应类型的数据处理
switch (sensitivityTypeEnum) {
case CUSTOMER:
jsonGenerator.writeString(SensitivityUtil.desValue(origin, prefixNoMaskLen, suffixNoMaskLen, symbol));
break;
case NAME:
jsonGenerator.writeString(SensitivityUtil.hideChineseName(origin));
break;
case ID_CARD:
jsonGenerator.writeString(SensitivityUtil.hideIDCard(origin));
break;
case PHONE:
jsonGenerator.writeString(SensitivityUtil.hidePhone(origin));
break;
case EMAIL:
jsonGenerator.writeString(SensitivityUtil.hideEmail(origin));
break;
default:
throw new IllegalArgumentException("unknown privacy type enum " + sensitivityTypeEnum);
}
} else {
//如果脱敏类型为空则赋值空,要不然会导致序列化错误
jsonGenerator.writeString("");
}
}
/**
* 读取自定义注解SensitivityEncrypt 创建上下文所需
*/
@Override
public JsonSerializer<?> createContextual(final SerializerProvider serializerProvider,
final BeanProperty beanProperty) throws JsonMappingException {
if (beanProperty != null) {
if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
SensitivityEncrypt sensitivityEncrypt = beanProperty.getAnnotation(SensitivityEncrypt.class);
if (sensitivityEncrypt == null) {
sensitivityEncrypt = beanProperty.getContextAnnotation(SensitivityEncrypt.class);
}
if (sensitivityEncrypt != null) {
return new SensitivitySerializer(sensitivityEncrypt.type(), sensitivityEncrypt.prefixNoMaskLen(),
sensitivityEncrypt.suffixNoMaskLen(), sensitivityEncrypt.symbol());
}
}
return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
}
return serializerProvider.findNullValueSerializer(null);
}
}
3、数据脱敏工具类SensitivityUtil
package com.tb.easyexcel.utils;
import org.springframework.stereotype.Component;
/**
* SensitivityUtil 数据脱敏工具类
*
* @author tanb
* @version 1.0
* @date 2024/3/9 22:34
*/
@Component
public class SensitivityUtil {
/**
* 【中文姓名】只显示第一个汉字,其他隐藏为星号,比如:才**
*/
public static String hideChineseName(String chineseName) {
if (chineseName == null) {
return null;
}
return desValue(chineseName, 1, 0, "*");
}
/**
* 隐藏邮箱
*/
public static String hideEmail(String email) {
return email.replaceAll("(\\w?)(\\w+)(\\w)(@\\w+\\.[a-z]+(\\.[a-z]+)?)", "$1****$3$4");
}
/**
* 隐藏手机号中间四位
*/
public static String hidePhone(String phone) {
return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
}
/**
* 隐藏身份证
*/
public static String hideIDCard(String idCard) {
return idCard.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1*****$2");
}
/**
* 对字符串进行脱敏操作
*
* @param origin 原始字符串
* @param prefixNoMaskLen 左侧需要保留几位明文字段
* @param suffixNoMaskLen 右侧需要保留几位明文字段
* @param maskStr 用于遮罩的字符串, 如'*'
* @return 脱敏后结果
*/
public static String desValue(String origin, int prefixNoMaskLen, int suffixNoMaskLen, String maskStr) {
if (origin == null) {
return null;
}
StringBuilder sb = new StringBuilder();
for (int i = 0, n = origin.length(); i < n; i++) {
if (i < prefixNoMaskLen) {
sb.append(origin.charAt(i));
continue;
}
if (i > (n - suffixNoMaskLen - 1)) {
sb.append(origin.charAt(i));
continue;
}
sb.append(maskStr);
}
return sb.toString();
}
}
五、实战导入导出方法
1、导入
/**
* 导入人员数据(通过 文件上传)
* @param file
* @return
*/
@Override
public ResponseResult importFile(MultipartFile file) {
//获取文件的输入流
InputStream inputStream = null;
try {
inputStream = file.getInputStream();
EasyExcel.read(inputStream) //调用read方法
//注册自定义监听器,字段校验可以在监听器内实现
.registerReadListener(new ReadExcelDataListener(this))
.head(ReadUser.class) //对应导入的实体类
.sheet(0) //导入数据的sheet页编号,0代表第一个sheet页,如果不填,则会导入所有sheet页的数据
.headRowNumber(2) //列表头行数,2代表列表头有2行,第3行开始为数据行
.doRead(); //开始读Excel,返回一个List<T>集合,继续后续入库操作
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return ResponseResult.okResult();
}
2、导出
/**
* 导出人员列表
*
* @return
*/
public void exportFile(UserDto userDto, HttpServletResponse response) throws IOException {
LambdaQueryWrapper<WriteUser> queryWrapper = getUserLambdaQueryWrapper(userDto);
//查询所有用户列表
List<WriteUser> users = userMapper.selectList(queryWrapper);
// 脱敏操作
List<WriteUser> result = users.stream().map(user -> JSON.parseObject(DesensitizedUtils.getJson(user), WriteUser.class)).collect(Collectors.toList());
ExcelUtils.exportExcel(result, Optional.of("用户信息"), WriteUser.class, response);
}
3、前端页面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>人员列表</title>
<script src="./vue.js"></script>
<!--引入 element-ui 的样式,-->
<link rel="stylesheet" href="./node_modules/element-ui/lib/theme-chalk/index.css">
<!-- 必须先引入vue, 后使用element-ui -->
<script src="./vue-resource.min.js"></script>
<!-- 引入element 的组件库-->
<script src="./node_modules/element-ui/lib/index.js"></script>
</head>
<body>
<div id="app">
<template>
<div>
<el-form :model="searchFrom" :inline="true" label-width="100px">
<el-row>
<el-col :span="24">
<el-form-item label="姓名">
<el-input v-model="searchFrom.xm"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getList(1)">查询</el-button>
<el-button type="primary" @click="exportExcel()">导出</el-button>
<el-upload
class="upload-demo"
style="display: inline-block;margin-left: 10px;"
action="readUser/importFile"
:before-upload="handleBefore"
:on-success="handleSuccess"
:limit="1"
:show-file-list="false"
>
<el-button type="primary">导入</el-button>
</el-upload>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-table :data="tableData.data" stripe style="width: 100%">
<el-table-column type="index" label="序号" align="center" width="100"></el-table-column>
<el-table-column prop="xm" label="姓名" align="center"></el-table-column>
<el-table-column prop="phone" label="手机号" align="center"></el-table-column>
<el-table-column prop="email" label="邮箱" align="center"></el-table-column>
<el-table-column prop="birthday" label="生日" align="center"></el-table-column>
<el-table-column prop="salary" label="薪资" align="center"></el-table-column>
<el-table-column prop="joinTime" label="入职时间" align="center"></el-table-column>
</el-table>
<div class="block">
<el-pagination
background
@current-change="handleCurrentChange"
:current-page.sync="tableData.pageNum"
:page-size="tableData.pageSize"
layout="total, prev, pager, next"
:total="tableData.total">
</el-pagination>
</div>
</div>
</template>
</div>
<script>
new Vue({
el: '#app',
data: {
searchFrom: {
xm: '',
pageNum: 1,
pageSize: 10,
},
tableData: {
data: [],
pageNum: 1,
pageSize: 10,
total: 0
},
},
mounted: function () {
this.getList(1);
},
methods: {
getList: function (page) {
if (page) {
this.searchFrom.pageNum = page;
}
this.$http.get('writeUser/pageList', {params: this.searchFrom}).then(res => {
this.tableData.data = res.body.data.list;
this.tableData.total = res.body.data.total;
this.tableData.pageNum = res.body.data.pageNum;
})
},
// 页码切换
handleCurrentChange(val) {
this.getList(val);
},
// 导出
exportExcel: function () {
window.location.href = 'writeUser/exportFile';
},
// 文件上传前回调
handleBefore(file) {
if (file.name.substring(file.name.lastIndexOf(".") + 1).toLowerCase() !== "xlsx") {
this.$message.info("上传文件格式不正确");
return false;
}
;
},
// 文件上传成功时回调
handleSuccess(response, file, fileList) {
if (response.code === 200) {
this.$message.info("导入成功!");
this.getList(1);
}
}
},
})
</script>
</body>
</html>
六、开发常见问题
数据无法解析常见问题处理方案
原因一:实体对象中使用注解@Accessors
@Accessors(chain = true),全属性为空,与chain为true/false无关(-1是自个写的未匹配(也就是null)返回-1)
原因二:实体类字段不符合驼峰命名规范: 如手首个字母为小写的情况
原因三:@ExcelProperty中index或者value值错误
原因四:自定义Listener中hasNext方法返回值为false,应该返回true
原因五:sheet表名中有空格
原因六:Excle表格中多表头未添加headRowNumber参数的问题
七、项目源码
下载地址:
https://download.csdn.net/download/ilywq/88957314
在此特别感谢easyexcel团队提供详细的开发文档。
你的鼓励是我续更的动力💪💪