likes
comments
collection
share

EasyExcel单sheet页导入和多sheet页导入(支持多数据表)

作者站长头像
站长
· 阅读数 27

导出VO

/**
 * 文件导入不能使用 @Accessors(chain = true)注解
 * 且需要添加@NoArgsConstructor 和 @AllArgsConstructor注解
 */
@Data
@Builder
@TableName("user")
@NoArgsConstructor
@AllArgsConstructor
@ExcelIgnoreUnannotated // 自动忽略不需要导入导出的字段,可替代@ExcelIgnore注解
public class UserPO implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(type = IdType.ASSIGN_ID)
    private String id;
    @ExcelProperty(value = "姓名")
    private String name;
    @ExcelProperty(value = "年纪")
    private Integer age;
    @ExcelProperty(value = "性别")
    private String sex;
    @ExcelProperty(value = "价格")
    @JsonSerialize(using = CustomerBigDecimalSerialize.class)
    private BigDecimal price;
}

Controller接口,这里我的接口返回的是一个错误信息集合,可直接返回给前端进行展示,方便业务做导入操作一些业务相关的报错信息可以直接给业务人员查看,不用麻烦开发人员再去排查

@ApiOperation("单sheet页导入Excel")
@PostMapping("/import/simple")
public Response<Object> importExcelSimple(HttpServletRequest request) {
    try {
        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
        MultipartFile file = multipartRequest.getFile("files"); // 获取上传文件对象
        List<String> errorMsg = userService.importData(file.getInputStream());
        if (CollectionUtils.isEmpty(errorMsg)){
            return Response.ok(Lists.newArrayList(),"导入成功");
        }
        return Response.error(ApiCode.FAIL,errorMsg);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
@ApiOperation("多sheet页导入Excel")
@PostMapping("/import/sheets")
public Response<Object> importExcelSheets(HttpServletRequest request) {
    try {
        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
        MultipartFile file = multipartRequest.getFile("files"); // 获取上传文件对象
        List<String> errorMsg = userService.importDataSheets(file.getInputStream());
        if (CollectionUtils.isEmpty(errorMsg)){
            return Response.ok(Lists.newArrayList(),"导入成功");
        }
        return Response.error(ApiCode.FAIL,errorMsg);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

具体导出实现ServiceImpl

@Override
public List<String> importData(InputStream inputStream) {
    List<String> errorMessages = new ArrayList<>();

    try {
        EasyExcel.read(inputStream, UserPO.class, new AnalysisEventListener<UserPO>() {
            // 单次缓存的数据量
            private final int BATCH_COUNT = 1000;
            // 临时存储数据集
            private List<UserPO> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
            @Override
            public void invoke(UserPO user, AnalysisContext context) {
                if (Objects.isNull(user)) {
                    return;
                }
                // 自定义对个别字段进行特殊校验,返回具体报错信息
                String errorMsg = "";
                if (StringUtils.isNoneBlank(errorMsg)) {
                    String rowErrorMsg = String.format("第 %d 行导入失败, 错误信息: %s", context.readRowHolder().getRowIndex(),errorMsg);
                    errorMessages.add(rowErrorMsg);
                }
                cachedDataList.add(user);
                // 数据量较大,则走分批处理
                if (cachedDataList.size() >= BATCH_COUNT) {
                    saveBatch(cachedDataList);
                    // 存储完成清理 list
                    cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
                }
            }

            /**
             * 数据量不够大,走此处逻辑
             */
            @Override
            public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                saveBatch(cachedDataList);
            }
        }).sheet().doRead();
    } catch (ExcelDataConvertException e) {
        log.error(String.format("cell={}, msg={}",e.getCellData().getStringValue(), e.getMessage()), e);
        // 此处可以将异常包装出来返回给前端,例如返回一个集合
        String errorMsg = String.format("第 %d 行导入失败, 错误信息: %s", e.getRowIndex(), e.getMessage());
        errorMessages.add(errorMsg);
    }
    return errorMessages;
}


@Override
public List<String> importDataSheets(InputStream inputStream) {
    List<String> validateMsgList = new ArrayList<>();
    ExcelReader excelReader = EasyExcel.read(inputStream).build();
    //获取多sheet页信息
    List<ReadSheet> sheets = excelReader.excelExecutor().sheetList();
    //不同的sheet页数据源的话可以通过sheet名称来区分并走各自的监听器
    sheets.forEach(sheet -> {
        if (sheet.getSheetName().contains("用户管理1")){
            UserImportListener listener = new UserImportListener1(this, validateMsgList);
            ReadSheet readSheet = EasyExcel.readSheet(sheet.getSheetNo()).head(UserPO.class).registerReadListener(listener).build();
            excelReader.read(readSheet);
        }else if (sheet.getSheetName().contains("用户管理2")){
            UserImportListener listener = new UserImportListener2(this, validateMsgList);
            ReadSheet readSheet = EasyExcel.readSheet(sheet.getSheetNo()).head(UserPO.class).registerReadListener(listener).build();
            excelReader.read(readSheet);
        }
    });
    // 关闭流
    excelReader.finish();
    return validateMsgList;
}

最重要的一环:监听器,这里我只写一个监听器(该监听器只能被new创建,不可添加Spring相关注解进行注入),因为基本逻辑都是一致的

/**
 * 监听器只能通过new的方式来创建,不能交由spring进行单例模式管理
 **/
@Slf4j
public class UserImportListener  extends AnalysisEventListener<UserPO> {
    /**
     * 单次缓存的数据量
     */
    private static final int BATCH_COUNT = 1000;
    /**
     * 临时存储数据集
     */
    private List<UserPO> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    private UserService userService;
    /**
     * 错误信息返回结果集
     */
    private List<String> validateMsgList;
    public UserImportListener(UserService userService, List<String> validateMsgList) {
        this.userService = userService;
        this.validateMsgList = validateMsgList;
    }
    @Override
    public void invoke(UserPO data, AnalysisContext analysisContext) {
        try {
            //校验是否当前行所有数据都是空
            if (ExcelUtils.isLineNullValue(data)) {
                return;
            }
            cachedDataList.add(data);
            // 数据量较大,则走分批处理
            if (cachedDataList.size() >= BATCH_COUNT) {
                userService.saveBatch(cachedDataList);
                cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
            }
        }catch (ExcelDataConvertException e) {
            log.error(String.format("cell={}, msg={}",e.getCellData().getStringValue(), e.getMessage()), e);
            String errorMsg = String.format("第%d行导入失败, 错误信息:%s", e.getRowIndex(), e.getMessage());
            validateMsgList.add(errorMsg);
        }
    }
    /**
     * 数据量不够大,走此处逻辑
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        try {
            userService.saveBatch(cachedDataList);
        } catch (Exception e) {
            log.error("用户管理导入失败, 错误信息:{}",e.getMessage());
            String errorMsg = String.format("用户管理导入失败, 错误信息:%s", e.getMessage());
            validateMsgList.add(errorMsg);
        }
    }
}

里面的相关代码我基本上都会写上一些注释方便大家理解,这样的话方便大家阅读代码,特别是对于新手来说,新手小白接到对应的导入导出任务即可直接通过我这个demo来快速上手早点完成任务后安心摸鱼了~

这里我希望大家有更好的方法能够提出来,供所有开发者学习参考~

转载自:https://juejin.cn/post/7365830295393779731
评论
请登录