EasyPoi导入导出Excel最全案例

假设现在有这样一个需求:
1) 批量导入用户,需要校验用户的信息
2) 如果有错误的数据支持导出,有错误信息的单元格用特殊颜色标出,并将错误信息设置在单元格批注里
针对以上需求,笔者对EasyPoi进行了封装,下面将依次介绍

1 导入

excel导入数据如下图
在这里插入图片描述
EasyPoi支持hibernate-validator注解式校验,如下图
在这里插入图片描述
如果要获取校验没通过的错误信息及行号需要实现IExcelDataModel和IExcelModel接口。这些都是基本校验,在实际开发过程中可能会遇到需要写代码来校验,比如校验用户名是否重复,EasyPoi给出了校验处理器,可以实现自定义校验。

@Component
public class UserImportVerifyHandler implements IExcelVerifyHandler<UserExcel> {

    @Resource
    private UserMapper userMapper;

    @Override
    public ExcelVerifyHandlerResult verifyHandler(UserExcel excelBo) {
        StringJoiner joiner = new StringJoiner(",");
        UserInfo userInfo = userMapper.findByName(excelBo.getUserName());
        if (userInfo != null) {
            joiner.add("用户名不允许重复#0");
        }
        return new ExcelVerifyHandlerResult(false, joiner.toString());
    }
}

以上校验需要在导入参数里开启

public void importUser(MultipartFile file) {
		//导入参数
        ImportParams params = new ImportParams();
        //开启校验
        params.setNeedVerify(true);
        //校验处理器
        params.setVerifyHandler(verifyHandler);
        //调用模板方法导入excel
        this.importExcel(file, UserExcel.class, params);
    }

在上面的校验信息里面加了后缀,比如用户名不允许重复#0,这里面的#0是用来标识错误信息所在的列,0就表示列,#是方便将0截取出来,在导出的时候会用到。读者可以采用其它的方式,比如将错误信息与列做个映射。一整行的错误信息都会存到errorMsg这个字段里,并用“,”分隔。
下面看下封装的抽象类

public abstract class AbstractImportService<T> {

    @Resource
    private TransactionalHelper transactionalHelper;

    /**
     * 导入excel
     * @param file 文件
     * @param pojoClass excel模板类
     * @param params excel导入参数
     */
    public void importExcel(MultipartFile file, Class<?> pojoClass, ImportParams params) {
        ExcelImportResult<T> result = null;
        try {
        	//调用EasyPoi的导入接口
            result = ExcelImportUtil.importExcelMore(file.getInputStream(), pojoClass, params);
        } catch (Exception e) {
            //此处抛异常
        }

        if (result != null) {
            this.checkTitleCell(result, params.getTitleRows(), reqDto.getTitleCells());
            if (!CollectionUtils.isEmpty(result.getList())) {
                this.findDuplicate(result.getList(), result.getFailList());
            }
            //开启事务保存数据,这种用法主要是为了解决事务失效的问题,见下面描述
            transactionalHelper.apply(this::saveData, result);
        }
    }

	//校验标题格式是否正确
    private void checkTitleCell(ExcelImportResult<T> result, int titleRows, int titleCells) {
        Row row = result.getWorkbook().getSheetAt(0).getRow(titleRows);
        if (row.getLastCellNum() < titleCells) {
            //此处抛异常
        }

        for (int i=0; i<row.getLastCellNum(); i++) {
            Cell cell = row.getCell(i);
            if (cell == null || StringUtils.isBlank(cell.getStringCellValue())) {
            //此处抛异常
            }
        }
    }

	//该抽象方法主要是为了找出excel中重复的数据,重复的数据放在failList里
    protected abstract void findDuplicate(List<T> importBos, List<T> failList);

	//该抽象方法保存解析出来的数据
    protected abstract void saveData(ExcelImportResult<T> result);
}

代码中简化了很多东西,读者可以自己去细化。代码中用了一个TransactionalHelper,参见解决事务失效的工具类

2 导出

导出采用模板方式导出,实体类还是用上文中的UserExcel。导出只介绍封装的抽象类

public abstract class AbstractExportService<T> {

    /**
     * 导出excel
     * @param dataList 需要导出的数据,继承该抽象类后自行获取
     * @param templateUrl 模板路径
     * @param fileName 文件名
     * @param startRows 存放实际数据的开始行,添加批注时需要传该值
     * @param hasComment 是否有批注
     */
    protected void exportExcel(List<T> dataList, String templateUrl, String fileName, int startRows, boolean hasComment) {

        Map<String, Object> resMap = new HashMap<>();
        resMap.put("mapList", dataList);
        try {
            ClassPathResource classPathResource = new ClassPathResource(
                    templateUrl);
            TemplateExportParams params = new TemplateExportParams(
                    classPathResource.getPath(), true);
            Workbook workbook = ExcelExportUtil.exportExcel(params,resMap);
            this.buildComment(dataList, workbook, startRows, hasComment);
            //将workbook写入到response里,读者自行实现
        }catch (Exception e){
            //此处抛异常
        }
    }

    private void buildComment(List<T> dataList, Workbook workbook, int startRows, boolean hasComment) {
        if (!hasComment) return;
        Sheet sheet = workbook.getSheetAt(0);
		//创建一个图画工具
        Drawing<?> drawing = sheet.createDrawingPatriarch();
        for (T fail : dataList) {
            Row row = sheet.getRow(startRows);
			//获取批注信息
            String commentStr = this.getCommentStr(fail);
            if (StringUtils.isNotBlank(commentStr)) {
				//解析批注,并传换成map
                Map<Integer, String> commentMap = this.getCommentMap(commentStr);
                for (Map.Entry<Integer, String> entry : commentMap.entrySet()) {
                    Cell cell = row.getCell(entry.getKey());
					//创建批注
                    Comment comment = drawing.createCellComment(this.newClientAnchor(workbook));
                    //输入批注信息
                    comment.setString(this.newRichTextString(workbook, entry.getValue()));
                    //将批注添加到单元格对象中
                    cell.setCellComment(comment);

                    //设置单元格背景颜色
                    CellStyle cellStyle = workbook.createCellStyle();
                    //设置颜色
                    cellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
                    //设置实心填充
                    cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
                    cell.setCellStyle(cellStyle);
                }
            }
            startRows++;
        }
    }

    /**
     * 批注信息,默认解析:批注#列索引,比如用户名不允许重复#0。可覆盖此方法,解析自定义的批注格式
     * @param commentStr 当前行的所有批注信息
     * @return key:列索引,value:对应列的所有批注信息
     */
    protected Map<Integer, String> getCommentMap(String commentStr) {
		//每行的所有单元格的批注都在commentStr里,并用”,”分隔
        String[] split = commentStr.split(",");
        Map<Integer, String> commentMap = new HashMap<>();
        for (String msg : split) {
            String[] cellMsg = msg.split("#");
			//如果当前列没有批注,会将该列的索引作为key存到map里;已有批注,以“,“分隔继续拼接
            int cellIndex = Integer.parseInt(cellMsg[1]);
            if (commentMap.get(cellIndex) == null) {
                commentMap.put(cellIndex, cellMsg[0]);
            } else {
                commentMap.replace(cellIndex, commentMap.get(cellIndex) + "," + cellMsg[0]);
            }
        }
        return commentMap;
    }

    private ClientAnchor newClientAnchor(Workbook workbook) {
        //xls
        if (workbook instanceof HSSFWorkbook) {
            return new HSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6);
        }
        //xlsx
        else {
            return new XSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6);
        }
    }

    private RichTextString newRichTextString(Workbook workbook, String msg) {
        //xls
        if (workbook instanceof HSSFWorkbook) {
            return new HSSFRichTextString(msg);
        }
        //xlsx
        else {
            return new XSSFRichTextString(msg);
        }
    }

    /**
     * 获取批注信息
     * @param data
     * @return
     */
    protected abstract String getCommentStr(T data);

导出模板如下图
在这里插入图片描述
excel中
$fe:表示循环插入
mapList是传入map的key

猜你喜欢

转载自blog.csdn.net/weixin_45497155/article/details/107341472
今日推荐