• easypoi导入Excel最佳实践


    前言

    本文原文链接地址:http://nullpointer.pw/easypoi%E5%AF%BC%E5%85%A5Excel%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5.html

    一直以来,使用EasyPOI做了不少导入导出的需求,但是每次做完都是临时去看官方文档现学现用,正巧最近朋友遇到这么个需求,用到了EasyPOI来完成导入,我也正好整理整理EasyPOI的导入用法。

    本文原文链接地址:http://nullpointer.pw/easypoi%E5%AF%BC%E5%85%A5Excel%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5.html

    需求是这样的:现在要在后台导入用户的简历,简历的格式是这样子的:

    一个人有多个属性,某些属性如申请职位、薪资是单一属性,即只会有一个值;某些属性如工作经历、教育经历、获奖情况是一组属性,可能会有多组值。现在要将这批简历数据导入到库中。

    零、文件准备:

    • 示例Excel以及示例Excel2

    • 加入 EasyPOI 的依赖

      <dependency>
        <groupId>cn.afterturn</groupId>
        <artifactId>easypoi-web</artifactId>
        <version>3.2.0</version>
      </dependency>
      

    一、定义EasyPOI导入实体类

    首先定义一个 EasyPOI 实体类,单个属性使用 @Excel注解声明,name 属性需要与Excel中的表头保持一致,比如 姓名*中的 * 号就不能省略掉。一对多关系使用 @Collection 注解声明,name 是最上方的表头,对应的集合元素类型需要另外定义一个对象,这里因为篇幅问题,只展示一个工作经历以及教育经历对应的集合对象。

    @Data
    public class TalentUserInputEntity{
    
        @Excel(name = "姓名*")
        private String name;
    
        @Excel(name = "性别*")
        private Integer gender;
    
        @Excel(name = "手机号*")
        private String phone;
    
        @Excel(name = "开始工作时间*")
        private Date workTime;
    
        @Excel(name = "民族*")
        private String national;
    
        @Excel(name = "语言水平*")
        private String languageProficiency;
    
        @Excel(name = "出生日期*")
        private Date birth;
    
        @Excel(name = "职位*")
        private String jobsName;
    
        @Excel(name = "职位类型*")
        private String categoryName;
    
        @Excel(name = "薪资*")
        private Integer salary;
    
        @Excel(name = "工作地点*")
        private String workArea;
    
        @ExcelCollection(name = "工作经历*")
        private List<ExperienceInputEntity> experienceList;
    
        @ExcelCollection(name = "教育经历*")
        private List<EducationInputEntity> educationList;
    
        @ExcelCollection(name = "获奖情况")
        private List<AwardsInputEntity> awardList;
    
        @ExcelCollection(name = "技能证书")
        private List<PunishmentInputEntity> punishmentList;
    
        @Excel(name = "特长")
        private String specialty;
    }
    
    // 工作经历对象
    @Data
    public class ExperienceInputEntity {
        @Excel(name = "公司名称*")
        private String companyName;
    
        @Excel(name = "所在行业*")
        private String industry;
    
        @Excel(name = "开始时间*")
        private Date beginTime;
    
        @Excel(name = "结束时间*")
        private Date finishTime;
    
        @Excel(name = "职位名称*")
        private String jobTitle;
    
        @Excel(name = "所属部门*")
        private String department;
    
        @Excel(name = "工作内容*")
        private String description;
    }
    // 教育经历对象
    @Data
    public class EducationInputEntity {
        @Excel(name = "学校*")
        private String schoolName;
    
        @Excel(name = "学历*")
        private Integer record;
    
        @Excel(name = "开始年份*")
        private Date beginTime;
    
        @Excel(name = "毕业年份*")
        private Date finishTime;
    
        @Excel(name = "专业*")
        private String profession;
    }
    // 省略其他
    

    二、EasyPOI基础导入

    这里为方便演示,直接将导入结果转成JSON打印输出。

    @PostMapping("/upload")
    public Boolean upload(@RequestParam("file") MultipartFile multipartFile) throws Exception {
      ImportParams params = new ImportParams();
      params.setHeadRows(2);
      // params.setTitleRows(0);
      List<TalentUserInputEntity> result = ExcelImportUtil.importExcel(multipartFile.getInputStream(),
                                                                       TalentUserInputEntity.class, params);
      System.out.println(JSONUtil.toJsonStr(result));
      return true;
    }
    

    这里需要注意表头的行数设置一定要正确!否则集合数据将无法读取,可以通过WPS或者office查看实际表头所占用的行数,一定要区分表头与标题的区别,表头是列名称,标题是表头上面的文字,本文示例文件中没有标题,所以setTitleRows为0

    三、值替换

    使用 postman 或者 Talend API Tester 等工具进行上传 示例文件.xlsx,结果控制台输出了异常,异常如下:

    java.lang.NumberFormatException: For input string: "男"
    ...
    cn.afterturn.easypoi.exception.excel.ExcelImportException: Excel 值获取失败
    

    原因是因为数据库中性别字段类型为 Integer 类型的,所以导入对象也设置成了 Integer,而 Excel 中填写的是男/女汉字,自然就出错了,这里就需要使用 easypoi 的 replace 方式来替换最终值,修改 gender 字段上的注解为:

    // replace格式为 "替换前的值_替换后的值"
    @Excel(name = "性别*", replace = {"男_0", "女_1"})
    private Integer gender;
    

    同理,薪资类型和教育经历中的学历需要替换需要的值

    @Excel(name = "薪资*", replace = {"3K以下_1", "3K-5K_2", "5K-10K_3", "10K-20K_4", "20K-50K_5", "50K以上_6"})
    private Integer salary;
    
    @Excel(name = "学历*", replace ={"初中及以下_1","中专_2","高中_3","大专_4","本科_5","硕士_6","博士_7"})
    private Integer record;
    

    再重新尝试导入,控制台输出了导入的JSON内容

    [
        {
            "experienceList": [
                {
                    "finishTime": 1571673600000,
                    "companyName": "科技公司1",
                    "jobTitle": "运营",
                    "description": "运营方案处理",
                    "industry": "互联网",
                    "beginTime": 1546358400000,
                    "department": "运营部"
                },
                {
                    "finishTime": 1571673600000,
                    "companyName": "财务公司",
                    "jobTitle": "会计",
                    "description": "审计",
                    "industry": "财务",
                    "beginTime": 1574179200000,
                    "department": "会计部"
                }
            ],
            "gender": 0,
            "languageProficiency": "英语四级",
            "jobsName": "销售",
            "educationList": [
                {
                    "profession": "计算机",
                    "finishTime": 1530374400000,
                    "record": 5,
                    "beginTime": 1409500800000,
                    "schoolName": "山东大学"
                },
                {
                    "profession": "计算机",
                    "finishTime": 1593532800000,
                    "record": 6,
                    "beginTime": 1409500800000,
                    "schoolName": "山东大学"
                }
            ],
            "birth": 851097600000,
            "salary": 4,
            "workTime": 1549900800000,
            "categoryName": "销售",
            "phone": "13122223333",
            "workArea": "浙江省金华市义乌市",
            "name": "张无忌",
            "national": "汉族",
            "punishmentList": [
                {},
                {}
            ],
            "awardList": [
                {
                    "date": 1530374400000,
                    "name": "国家奖学金",
                    "description": "国家一级奖学金"
                },
                {}
            ]
        },
        {
            "specialty": "特长就是太多特长",
            "experienceList": [
                {
                    "finishTime": 1571673600000,
                    "companyName": "科技公司1",
                    "jobTitle": "java开发",
                    "description": "code",
                    "industry": "互联网",
                    "beginTime": 1546358400000,
                    "department": "开发部门"
                }
            ],
            "gender": 0,
            "languageProficiency": "英语八级",
            "jobsName": "java",
            "educationList": [
                {
                    "profession": "计算机",
                    "finishTime": 1530374400000,
                    "record": 5,
                    "beginTime": 1409500800000,
                    "schoolName": "安徽大学"
                }
            ],
            "birth": 851097600000,
            "salary": 4,
            "workTime": 1549900800000,
            "categoryName": "开发",
            "phone": "18311111111",
            "workArea": "浙江省金华市义乌市",
            "name": "张小凡",
            "national": "汉族",
            "punishmentList": [
                {
                    "date": 1530374400000,
                    "description": "技能没有"
                }
            ],
            "awardList": [
                {
                    "date": 1530374400000,
                    "name": "国家奖学金",
                    "description": "国家一级奖学金"
                }
            ]
        },
        // 省略其他...
    ]
    

    四、导入之基础校验

    现在产品需要对导入的Excel进行校验,不合法的Excel不允许入库,需要返回具体的错误信息给前端,提示给用户,错误信息中需要包含行号以及对应的错误。

    因为 EasyPOI 支持 Hibernate Validator ,所以直接使用就可以了,因为要将错误信息以及错误行号返回,所以需要用到 EasyPOI 的高级用法,实现 IExcelDataModelIExcelModel接口,IExcelDataModel负责设置行号,IExcelModel 负责设置错误信息。

    修改导入实体类,增加字段 rowNumerrorMsg以及增加校验注解。

    如果使用到了 @Pattern 注解,则字段类型必须是 String 类型,否则会抛出异常,本文中的原 Integer 类型的 gender 修改成为 String 类型的 genderStrrecord 字段也修改为了 String 类型的 recordStr等等。同理如果校验 Date 类型字段,先将类型改成String,正则表达式参考下文写法。

    这里需要注意,如果@Excel注解中设置了 replace 属性,则Hibernate Validator 校验的是替换后的值

    @Data
    public class TalentUserInputEntity implements IExcelDataModel, IExcelModel {
        // 时间格式校验正则
        public static final String DATE_REGEXP = "(Mon|Tue|Wed|Thu|Fri|Sat|Sun)( )(Dec|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov)( )\d{2}( )(00:00:00)( )(CST)( )\d{4}";
    
        /**
         * 行号
         */
        private int rowNum;
    
        /**
         * 错误消息
         */
        private String errorMsg;
    
        @Excel(name = "姓名*")
        @NotBlank(message = "[姓名]不能为空")
        private String name;
    
        @Excel(name = "性别*", replace = {"男_0", "女_1"})
        @Pattern(regexp = "[01]", message = "性别错误")
        private String genderStr;
    
        @Excel(name = "手机号*")
        private String phone;
    
        @Excel(name = "开始工作时间*")
        @Pattern(regexp = DATE_REGEXP, message = "[开始工作时间]时间格式错误")
        private String workTimeStr;
    
        @Excel(name = "民族*")
        @NotBlank(message = "[民族]不能为空")
        private String national;
    
        @Excel(name = "语言水平*")
        @NotBlank(message = "[语言水平]不能为空")
        private String languageProficiency;
    
        @Excel(name = "出生日期*")
        @Pattern(regexp = DATE_REGEXP, message = "[出生日期]时间格式错误")
        private String birthStr;
    
        @Excel(name = "职位*")
        @NotBlank(message = "[职位]不能为空")
        private String jobsName;
    
        @Excel(name = "职位类型*")
        @NotBlank(message = "[职位类型]不能为空")
        private String categoryName;
    
        @Excel(name = "薪资*", replace = {"3K以下_1", "3K-5K_2", "5K-10K_3", "10K-20K_4", "20K-50K_5", "50K以上_6"})
        @Pattern(regexp = "[123456]", message = "薪资信息错误")
        private String salaryStr;
    
        @Excel(name = "工作地点*")
        @NotBlank(message = "[工作地点]不能为空")
        private String workArea;
    
        @ExcelCollection(name = "工作经历*")
        private List<ExperienceInputEntity> experienceList;
    
        @ExcelCollection(name = "教育经历*")
        private List<EducationInputEntity> educationList;
    
        @ExcelCollection(name = "获奖情况")
        private List<AwardsInputEntity> awardList;
    
        @ExcelCollection(name = "技能证书")
        private List<PunishmentInputEntity> punishmentList;
    
        @Excel(name = "特长")
        private String specialty;
    }
    
    // 工作经历
    @Data
    public class ExperienceInputEntity {
        @Excel(name = "公司名称*")
        private String companyName;
    
        @Excel(name = "所在行业*")
        private String industry;
    
        @Excel(name = "开始时间*")
        @Pattern(regexp = DATE_REGEXP, message = "[工作经历][开始时间]时间格式错误")
        private String beginTimeStr;
    
        @Excel(name = "结束时间*")
        @Pattern(regexp = DATE_REGEXP, message = "[工作经历][结束时间]时间格式错误")
        private String finishTimeStr;
    
        @Excel(name = "职位名称*")
        private String jobTitle;
    
        @Excel(name = "所属部门*")
        private String department;
    
        @Excel(name = "工作内容*")
        private String description;
    }
    
    // 教育经历
    @Data
    public class EducationInputEntity {
    
        @Excel(name = "学校*")
        private String schoolName;
    
        @Excel(name = "学历*", replace = {"初中及以下_1", "中专_2", "高中_3", "大专_4", "本科_5", "硕士_6", "博士_7"})
        @Pattern(regexp = "[1234567]", message = "学历信息错误")
        private String recordStr;
    
        @Excel(name = "开始年份*")
        @Pattern(regexp = DATE_REGEXP, message = "[教育经历][开始年份]时间格式错误")
        private String beginTimeStr;
    
        @Excel(name = "毕业年份*")
        @Pattern(regexp = DATE_REGEXP, message = "[教育经历][毕业年份]时间格式错误")
        private String finishTimeStr;
    
        @Excel(name = "专业*")
        private String profession;
    }
    // 其他省略...
    

    修改完实体类后,修改导入处的代码

    @PostMapping("/upload")
    public Boolean upload(@RequestParam("file") MultipartFile multipartFile) throws Exception {
      ImportParams params = new ImportParams();
      // 表头设置为2行
      params.setHeadRows(2);
      // 标题行设置为0行,默认是0,可以不设置
      params.setTitleRows(0);
      // 开启Excel校验
      params.setNeedVerfiy(true);
      ExcelImportResult<TalentUserInputEntity> result = ExcelImportUtil.importExcelMore(multipartFile.getInputStream(),
                                                                                        TalentUserInputEntity.class, params);
      System.out.println("是否校验失败: " + result.isVerfiyFail());
      System.out.println("校验失败的集合:" + JSONObject.toJSONString(result.getFailList()));
      System.out.println("校验通过的集合:" + JSONObject.toJSONString(result.getList()));
      for (TalentUserInputEntity entity : result.getFailList()) {
        String msg = "第" + entity.getRowNum() + "行的错误是:" + entity.getErrorMsg();
        System.out.println(msg);
      }
      return true;
    }
    

    为了方便测试,我基于正确的Excel另存一份个别字段有误的 示例Excel2 并上传,解析结果为:

    这里贴出部分校验失败集合数据,可以看到第50行和61行是原Excel中错误的数据,也已经打印了出来,但是教育经历中的教育水平也是错误的,却没被打印出来,查看源码发现,EasyPOI 对 Collection 中的对象并没有进行校验,我们在下文中解决。

    五、导入值自定义校验之重复值校验

    上文所作的校验只是一些基本的校验,可能会有诸如Excel中重复行校验,Excel中数据与数据库重复校验等等。这种校验就无法通过 Hibernate Validator 来完成,只能写代码来实现校验逻辑了。

    首先从简单的Excel数据与数据库值重复校验开始。为了便于演示,就不引入数据库了,直接Mock一些数据用来判断是否重复。

    @Service
    public class MockTalentDataService {
        private static List<TalentUser> talentUsers = new ArrayList<>();
        static {
            TalentUser u1 = new TalentUser(1L, "凌风", "18311342567");
            TalentUser u2 = new TalentUser(2L, "张三", "18512343567");
            TalentUser u3 = new TalentUser(3L, "李四", "18902343267");
            talentUsers.add(u1);
            talentUsers.add(u2);
            talentUsers.add(u3);
        }
    
        /**
         * 校验是否重复
         */
        public boolean checkForDuplicates(String name, String phone) {
            // 姓名与手机号相等个数不等于0则为重复
            return talentUsers.stream().anyMatch(e -> e.getName().equals(name) && e.getPhone().equals(phone));
        }
    }
    

    其中Mock数据中 ID 为 1 的数据与示例Excel2 中的数据是重复的。
    EasyPOI 提供了校验的接口,这需要我们自己写一个用于校验的类。在这个类中,可以对导入时的每一行数据进行校验,框架通过 ExcelVerifyHandlerResult 对象来判断是否校验通过,校验不通过需要传递 ErrorMsg。

    @Component
    public class TalentImportVerifyHandler implements IExcelVerifyHandler<TalentUserInputEntity> {
    
        @Resource
        private MockTalentDataService mockTalentDataService;
    
        @Override
        public ExcelVerifyHandlerResult verifyHandler(TalentUserInputEntity inputEntity) {
            StringJoiner joiner = new StringJoiner(",");
            // 根据姓名与手机号判断数据是否重复
            String name = inputEntity.getName();
            String phone = inputEntity.getPhone();
            // mock 数据库
            boolean duplicates = mockTalentDataService.checkForDuplicates(name, phone);
            if (duplicates) {
                joiner.add("数据与数据库数据重复");
            }
            if (joiner.length() != 0) {
                return new ExcelVerifyHandlerResult(false, joiner.toString());
            }
            return new ExcelVerifyHandlerResult(true);
        }
    }
    

    修改校验处代码,设置校验类对象。

    @Resource
    private TalentImportVerifyHandler talentImportVerifyHandler;
    
    @PostMapping("/upload")
    public Boolean upload(@RequestParam("file") MultipartFile multipartFile) throws Exception {
      ImportParams params = new ImportParams();
      // 表头设置为2行
      params.setHeadRows(2);
      // 标题行设置为0行,默认是0,可以不设置
      params.setTitleRows(0);
      // 开启Excel校验
      params.setNeedVerfiy(true);
      params.setVerifyHandler(talentImportVerifyHandler);
      ExcelImportResult<TalentUserInputEntity> result = ExcelImportUtil.importExcelMore(multipartFile.getInputStream(),
                                                                                        TalentUserInputEntity.class, params);
      System.out.println("是否校验失败: " + result.isVerfiyFail());
      System.out.println("校验失败的集合:" + JSONObject.toJSONString(result.getFailList()));
      System.out.println("校验通过的集合:" + JSONObject.toJSONString(result.getList()));
      for (TalentUserInputEntity entity : result.getFailList()) {
        int line = entity.getRowNum() + 1;
        String msg = "第" + line + "行的错误是:" + entity.getErrorMsg();
        System.out.println(msg);
      }
      return true;
    }
    

    上传 示例Excel2 文件测试,结果输出:

    而第七行的数据正是与Mock中的数据相重复的。

    六、导入值自定义校验之Collection对象校验

    上文中还有一个待解决的问题,就是Collection中的对象添加了Hibernate Validator 注解校验但是并未生效的问题,现在就来解决一下。上一步中实现了导入对象的校验类,校验类会校验Excel中的每一条数据, 那我是不是可以直接在校验类中校验Collection中对象了呢?实践证明行不通,因为这个校验类的verifyHandler方法只会被调用一次,所以Collection中只有一条记录。既然这里行不通的话,就只能对导入结果再进行校验了。

    因为Collection中的数据EasyPOI校验不到,所以有问题的数据也可能会被框架放到result.getList()中而不是result.getFailList() 中,为了校验需要将两个集合合并为一个集合,使用 EasyPOI 自带的工具类 PoiValidationUtil 进行校验 Collection 中的对象。

    @Resource
    private TalentImportVerifyHandler talentImportVerifyHandler;
    
    @PostMapping("/upload")
    public Boolean upload(@RequestParam("file") MultipartFile multipartFile) throws Exception {
      ImportParams params = new ImportParams();
      // 表头设置为2行
      params.setHeadRows(2);
      // 标题行设置为0行,默认是0,可以不设置
      params.setTitleRows(0);
      // 开启Excel校验
      params.setNeedVerfiy(true);
      params.setVerifyHandler(talentImportVerifyHandler);
      ExcelImportResult<TalentUserInputEntity> result = ExcelImportUtil.importExcelMore(multipartFile.getInputStream(),
                                                                                        TalentUserInputEntity.class, params);
      System.out.println("是否校验失败: " + result.isVerfiyFail());
      System.out.println("校验失败的集合:" + JSONObject.toJSONString(result.getFailList()));
      System.out.println("校验通过的集合:" + JSONObject.toJSONString(result.getList()));
    
      // 合并结果集
      List<TalentUserInputEntity> resultList = new ArrayList<>();
      resultList.addAll(result.getFailList());
      resultList.addAll(result.getList());
      for (TalentUserInputEntity inputEntity : resultList) {
        StringJoiner joiner = new StringJoiner(",");
        joiner.add(inputEntity.getErrorMsg());
        // 校验Collection的元素
        inputEntity.getExperienceList().forEach(e -> verify(joiner, e));
        inputEntity.getEducationList().forEach(e -> verify(joiner, e));
        inputEntity.getAwardList().forEach(e -> verify(joiner, e));
        inputEntity.getPunishmentList().forEach(e -> verify(joiner, e));
        inputEntity.setErrorMsg(joiner.toString());
      }
    
      for (TalentUserInputEntity entity : result.getFailList()) {
        int line = entity.getRowNum() + 1;
        String msg = "第" + line + "行的错误是:" + entity.getErrorMsg();
        System.out.println(msg);
      }
      return true;
    }
    
    private void verify(StringJoiner joiner, Object object) {
      String validationMsg = PoiValidationUtil.validation(object, null);
      if (StringUtils.isNotEmpty(validationMsg)) {
        joiner.add(validationMsg);
      }
    }
    

    上传 示例Excel2 ,结果如下:

    七、导入值自定义校验之Excel重复行校验

    上文中对Excel中数据与数据库数据进行重复校验,可有些需求是要求数据库在入库前需要对Excel的的重复行进行校验。这需要在校验类中完成,但校验类中并没有全部行的数据,该如何实现呢?博主的做法是将导入的数据放到 ThreadLocal 中进行暂存,从而达到在校验类中校验Excel重复行的目的。ThreadLocal使用注意完之后一定要及时清理!

    首先定义什么叫重复行,完全相同的两行是重复行,本文中设定name 与 phone 相同的行为重复行,由于只需要比较这两个字段,所以我们需要重写导入对象的equals与hashCode方法。

    @Data
    public class TalentUserInputEntity implements IExcelDataModel, IExcelModel {
        // 时间格式校验正则
        public static final String DATE_REGEXP = "(Mon|Tue|Wed|Thu|Fri|Sat|Sun)( )(Dec|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov)( )\d{2}( )(00:00:00)( )(CST)( )\d{4}";
    
        /**
         * 行号
         */
        private int rowNum;
    
        /**
         * 错误消息
         */
        private String errorMsg;
    
        @Excel(name = "姓名*")
        @NotBlank(message = "[姓名]不能为空")
        private String name;
    
        @Excel(name = "性别*", replace = {"男_0", "女_1"})
        @Pattern(regexp = "[01]", message = "性别错误")
        private String genderStr;
    
        @Excel(name = "手机号*")
        @Pattern(regexp = "[0-9]{11}", message = "手机号不正确")
        private String phone;
    
        @Excel(name = "开始工作时间*")
        @Pattern(regexp = DATE_REGEXP, message = "[开始工作时间]时间格式错误")
        private String workTimeStr;
    
        @Excel(name = "民族*")
        @NotBlank(message = "[民族]不能为空")
        private String national;
    
        @Excel(name = "语言水平*")
        @NotBlank(message = "[语言水平]不能为空")
        private String languageProficiency;
    
        @Excel(name = "出生日期*")
        @Pattern(regexp = DATE_REGEXP, message = "[出生日期]时间格式错误")
        private String birthStr;
    
        @Excel(name = "职位*")
        @NotBlank(message = "[职位]不能为空")
        private String jobsName;
    
        @Excel(name = "职位类型*")
        @NotBlank(message = "[职位类型]不能为空")
        private String categoryName;
    
        @Excel(name = "薪资*", replace = {"3K以下_1", "3K-5K_2", "5K-10K_3", "10K-20K_4", "20K-50K_5", "50K以上_6"})
        @Pattern(regexp = "[123456]", message = "薪资信息错误")
        private String salaryStr;
    
        @Excel(name = "工作地点*")
        @NotBlank(message = "[工作地点]不能为空")
        private String workArea;
    
        @ExcelCollection(name = "工作经历*")
        private List<ExperienceInputEntity> experienceList;
    
        @ExcelCollection(name = "教育经历*")
        private List<EducationInputEntity> educationList;
    
        @ExcelCollection(name = "获奖情况")
        private List<AwardsInputEntity> awardList;
    
        @ExcelCollection(name = "技能证书")
        private List<PunishmentInputEntity> punishmentList;
    
        @Excel(name = "特长")
        private String specialty;
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            TalentUserInputEntity that = (TalentUserInputEntity) o;
            return Objects.equals(name, that.name) &&
                    Objects.equals(phone, that.phone);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(name, phone);
        }
    }
    

    修改校验类代码,实现重复行的校验逻辑

    @Component
    public class TalentImportVerifyHandler implements IExcelVerifyHandler<TalentUserInputEntity> {
    
        private final ThreadLocal<List<TalentUserInputEntity>> threadLocal = new ThreadLocal<>();
    
        @Resource
        private MockTalentDataService mockTalentDataService;
    
        @Override
        public ExcelVerifyHandlerResult verifyHandler(TalentUserInputEntity inputEntity) {
            StringJoiner joiner = new StringJoiner(",");
            // 根据姓名与手机号判断数据是否重复
            String name = inputEntity.getName();
            String phone = inputEntity.getPhone();
            // mock 数据库
            boolean duplicates = mockTalentDataService.checkForDuplicates(name, phone);
            if (duplicates) {
                joiner.add("数据与数据库数据重复");
            }
    
            List<TalentUserInputEntity> threadLocalVal = threadLocal.get();
            if (threadLocalVal == null) {
                threadLocalVal = new ArrayList<>();
            }
    
            threadLocalVal.forEach(e -> {
                if (e.equals(inputEntity)) {
                    int lineNumber = e.getRowNum() + 1;
                    joiner.add("数据与第" + lineNumber + "行重复");
                }
            });
            // 添加本行数据对象到ThreadLocal中
            threadLocalVal.add(inputEntity);
            threadLocal.set(threadLocalVal);
            if (joiner.length() != 0) {
                return new ExcelVerifyHandlerResult(false, joiner.toString());
            }
            return new ExcelVerifyHandlerResult(true);
        }
    
        public ThreadLocal<List<TalentUserInputEntity>> getThreadLocal() {
            return threadLocal;
        }
    }
    

    由于校验类中使用了ThreadLocal,因此需要及时释放,修改导入处的代码。

    @Resource
    private TalentImportVerifyHandler talentImportVerifyHandler;
    
    @PostMapping("/upload")
    public Boolean upload(@RequestParam("file") MultipartFile multipartFile) throws Exception {
      ExcelImportResult<TalentUserInputEntity> result;
      try {
        ImportParams params = new ImportParams();
        // 表头设置为2行
        params.setHeadRows(2);
        // 标题行设置为0行,默认是0,可以不设置
        params.setTitleRows(0);
        // 开启Excel校验
        params.setNeedVerfiy(true);
        params.setVerifyHandler(talentImportVerifyHandler);
        result = ExcelImportUtil.importExcelMore(multipartFile.getInputStream(),
                                                 TalentUserInputEntity.class, params);
      } finally {
        // 清除threadLocal 防止内存泄漏
        ThreadLocal<List<TalentUserInputEntity>> threadLocal = talentImportVerifyHandler.getThreadLocal();
        if (threadLocal != null) {
          threadLocal.remove();
        }
      }
      System.out.println("是否校验失败: " + result.isVerfiyFail());
      System.out.println("校验失败的集合:" + JSONObject.toJSONString(result.getFailList()));
      System.out.println("校验通过的集合:" + JSONObject.toJSONString(result.getList()));
    
      // 合并结果集
      List<TalentUserInputEntity> resultList = new ArrayList<>();
      resultList.addAll(result.getFailList());
      resultList.addAll(result.getList());
      for (TalentUserInputEntity inputEntity : resultList) {
        StringJoiner joiner = new StringJoiner(",");
        joiner.add(inputEntity.getErrorMsg());
        // 校验Collection的元素
        inputEntity.getExperienceList().forEach(e -> verify(joiner, e));
        inputEntity.getEducationList().forEach(e -> verify(joiner, e));
        inputEntity.getAwardList().forEach(e -> verify(joiner, e));
        inputEntity.getPunishmentList().forEach(e -> verify(joiner, e));
        inputEntity.setErrorMsg(joiner.toString());
      }
    
      for (TalentUserInputEntity entity : result.getFailList()) {
        int line = entity.getRowNum() + 1;
        String msg = "第" + line + "行的错误是:" + entity.getErrorMsg();
        System.out.println(msg);
      }
      return true;
    }
    
    private void verify(StringJoiner joiner, Object object) {
      String validationMsg = PoiValidationUtil.validation(object, null);
      if (StringUtils.isNotEmpty(validationMsg)) {
        joiner.add(validationMsg);
      }
    }
    

    导入示例Excel2,结果如下:

    至此,我们就完成了导入的大部分需求。

    结语

    这篇博客花费了我不少时间来写,文中的代码也上传到了GitHub上,并且在写文章的同时,学习了一下Git tag 的使用,读者可以在 GitHub 的分支切换可以切换到不同tag看代码。

    本文原文链接地址:http://nullpointer.pw/easypoi%E5%AF%BC%E5%85%A5Excel%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5.html

  • 相关阅读:
    轻松搞定CentOS+Nginx+PHP+MySQL标准生产环境 厚燃涂想 ITeye技术网站
    djangoceleryemail 1.0.3 : Python Package Index
    nodejs thinking
    God A Process Monitoring Framework in Ruby
    Beanstalkd 一个高性能分布式内存队列系统
    让tar解压到指定文件夹 » Xeno Joshua | Xeno Joshua
    Check to see if python script is running Stack Overflow
    Django | Model field reference | Django documentation
    深入Django(2):自定义ORM 心内求法 博客频道 CSDN.NET
    第五章:模型
  • 原文地址:https://www.cnblogs.com/vcmq/p/12149673.html
Copyright © 2020-2023  润新知