【项目实战SpringBoot+Vue】基于easyexcel实现Excel大数据量的导入导出(包含数据脱敏)--简单易上手

前言

文章是根据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团队提供详细的开发文档。
你的鼓励是我续更的动力💪💪

  • 27
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现上传excel文件并将数据传输到数据库的步骤如下: 1. 前端实现文件上传功能:使用Vue.js开发前端页面,使用element-ui组件库实现文件上传组件。具体实现可以参考element-ui的文档和示例代码。 2. 后端实现文件上传功能:使用Spring Boot框架开发后端接口,使用Apache POI或者EasyExcel解析Excel文件并将数据存入数据库。具体实现可以参考Spring Boot官方文档和EasyExcel的官方文档。 3. 前后端交互:前端页面通过Ajax请求后端接口上传文件,并将文件数据以form-data格式传输到后端。后端接口接收到请求后,解析Excel文件并将数据存入数据库,最后返回上传结果给前端。 下面是一个简单的示例代码,仅供参考: 前端代码: ```vue <template> <el-upload class="upload-demo" drag action="/api/upload" :before-upload="beforeUpload" :on-success="onSuccess" :on-error="onError" > <i class="el-icon-upload"></i> <div class="el-upload__text">将 Excel 文件拖到此处,或点击上传</div> <div class="el-upload__tip" slot="tip">仅支持 .xls 和 .xlsx 格式的 Excel 文件</div> </el-upload> </template> <script> export default { methods: { beforeUpload(file) { const isExcel = file.type === 'application/vnd.ms-excel' || file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; if (!isExcel) { this.$message.error('只能上传 .xls 或 .xlsx 格式的 Excel 文件'); } return isExcel; }, onSuccess(response) { if (response.code === 0) { this.$message.success('上传成功'); } else { this.$message.error(`上传失败: ${response.msg}`); } }, onError(error) { this.$message.error(`上传失败: ${error.message}`); }, }, }; </script> ``` 后端代码: ```java @RestController @RequestMapping("/api") public class UploadController { @PostMapping("/upload") public Result<?> upload(@RequestParam("file") MultipartFile file) throws IOException { if (file.isEmpty()) { return Result.error("上传失败: 文件为空"); } String filename = file.getOriginalFilename(); String ext = FilenameUtils.getExtension(filename); if (!"xls".equals(ext) && !"xlsx".equals(ext)) { return Result.error("上传失败: 仅支持 .xls 或 .xlsx 格式的 Excel 文件"); } List<User> userList = new ArrayList<>(); Workbook workbook = WorkbookFactory.create(file.getInputStream()); Sheet sheet = workbook.getSheetAt(0); for (int i = 1; i <= sheet.getLastRowNum(); i++) { Row row = sheet.getRow(i); if (row == null) { continue; } User user = new User(); user.setName(row.getCell(0).getStringCellValue()); user.setAge((int) row.getCell(1).getNumericCellValue()); user.setGender(row.getCell(2).getStringCellValue()); userList.add(user); } userService.saveAll(userList); return Result.ok(); } } ``` 上述代码中,`UploadController`是一个Spring MVC的控制器类,用于处理上传文件的请求。`upload`方法接收一个`MultipartFile`类型的参数,即前端上传的文件数据。在方法中,我们首先判断文件是否为空,然后根据文件的扩展名判断是否为Excel文件。如果不是Excel文件,则返回上传失败的结果。否则,我们使用Apache POI库解析Excel文件,将数据转换成`User`对象并存入数据库。最后,返回上传成功的结果。 需要注意的是,上述代码中的`User`对象是一个自定义的Java类,用于存储Excel中的数据。在实际开发中,需要根据实际情况定义相应的Java类来存储数据。同时,还需要在Spring Boot的配置文件中配置数据库连接信息、数据源等相关信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值