欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

荐 EasyPoi导入导出Excel最全案例

程序员文章站 2022-07-10 21:24:30
现在有这样一个需求:1) 批量导入用户,需要校验用户的信息2) 如果有错误的数据支持导出,有错误信息的单元格用特殊颜色标出,并将错误信息设置在单元格批注里针对以上需求,笔者对EasyPoi进行了封装,下面将依次介绍1 导入EasyPoi支持hibernate-validator注解式校验,如下图如果要获取校验没通过的错误信息及行号需要实现IExcelDataModel和IExcelModel接口。这些都是基本校验,在实际开发过程中可能会遇到需要写代码来校验,比如校验用户名是否重复,EasyPo...

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

1 导入

excel导入数据如下图
荐
                                                        EasyPoi导入导出Excel最全案例
EasyPoi支持hibernate-validator注解式校验,如下图
荐
                                                        EasyPoi导入导出Excel最全案例
如果要获取校验没通过的错误信息及行号需要实现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);

导出模板如下图
荐
                                                        EasyPoi导入导出Excel最全案例
excel中
$fe:表示循环插入
mapList是传入map的key

本文地址:https://blog.csdn.net/weixin_45497155/article/details/107341472