easy-excel导入

该博客介绍了一种使用EasyExcel处理Excel数据导入的解决方案,包括自定义注解进行数据校验,如数字范围、字符串长度、正则表达式等。此外,还提供了异常处理类和监听器来处理导入过程中的错误,将错误数据存储以便后续导出。业务处理类实现了数据的正常保存和异常数据处理。整个流程确保了数据导入的完整性和准确性。
摘要由CSDN通过智能技术生成

使用easy excel导入excel数据到系统

1、自定义校验注解

/**
 * <p>Excel导入Decimal类型校验</p>
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelDecimalValid {
    String min();
    String max();
    String message() default "小数类型数字填写超出范围";
}
/**
* <p>Excel导入正则表达式校验</p>
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelPatternValid {

    String regexp() default "";

    String name() default "";

    String message() default "不符合规则";
}

/**
 * <p>Excel导入Int类型校验</p>
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelIntValid {
    int min();
    int max();
    String message() default "整数数字填写超出范围";
}

/**
 * <p>Excel导入字符串长度校验</p>
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelStrValid {
    int length() default 0;
    String message() default "文字填写超出长度要求";
}

/**
 * <p>Excel导入必填校验注解</p>
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelValid {
    String message() default "导入有未填入的字段";

    /**
     * 表头字段
     */
    String name() default "";
}

2、校验类

/**
 * <p>Excel导入字段校验</p
 */
public class ExcelImportValid {

    /**
     * Excel导入字段校验
     *
     * @param object 校验的JavaBean 其属性须有自定义注解
     */
    @SneakyThrows
    public static void valid(Object object) throws ExcelValidException {
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            //设置可访问
            field.setAccessible(true);
            //属性的值
            Object fieldValue = null;
            try {
                fieldValue = field.get(object);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }

            //是否包含必填校验注解
            boolean isExcelValid = field.isAnnotationPresent(ExcelValid.class);
            if (isExcelValid && Objects.isNull(fieldValue)) {
                String name = field.getAnnotation(ExcelValid.class).name();
                throw new ExcelValidException("【" + name + "】列有未填值项");
            }
            //是否包含字符串长度校验注解
            boolean isExcelStrValid = field.isAnnotationPresent(ExcelStrValid.class);
            if (isExcelStrValid) {
                String cellStr = fieldValue.toString();
                int length = field.getAnnotation(ExcelStrValid.class).length();
                if (StringUtils.isNotBlank(cellStr) && cellStr.length() > length) {
                    throw new ExcelValidException(field.getAnnotation(ExcelStrValid.class).message());
                }
            }
            //是否包含int类型校验注解
            boolean isExcelIntValid = field.isAnnotationPresent(ExcelIntValid.class);
            if (isExcelIntValid) {
                if (fieldValue instanceof Integer) {
                    int cellInt = Integer.parseInt(fieldValue.toString());
                    int min = field.getAnnotation(ExcelIntValid.class).min();
                    int max = field.getAnnotation(ExcelIntValid.class).max();
                    if (cellInt < min || cellInt > max) {
                        throw new ExcelValidException(field.getAnnotation(ExcelIntValid.class).message());
                    }
                }
            }
            //是否包含decimal类型注解
            boolean isExcelDecimalValid = field.isAnnotationPresent(ExcelDecimalValid.class);
            if (isExcelDecimalValid) {
                if (isBigDecimal(fieldValue.toString())) {
                    BigDecimal cellDecimal = new BigDecimal(fieldValue.toString());
                    BigDecimal min = new BigDecimal(field.getAnnotation(ExcelDecimalValid.class).min());
                    BigDecimal max = new BigDecimal(field.getAnnotation(ExcelDecimalValid.class).max());
                    if (cellDecimal.compareTo(min) < 0 || cellDecimal.compareTo(max) > 0) {
                        throw new ExcelValidException(field.getAnnotation(ExcelDecimalValid.class).message());
                    }
                } else {
                    throw new ExcelValidException("不是小数数字类型");
                }
            }

            // 是否包含正则校验
            boolean isExcelPatternValid = field.isAnnotationPresent(ExcelPatternValid.class);
            if (isExcelPatternValid) {
                String cellStr = fieldValue.toString();
                String pattern = field.getAnnotation(ExcelPatternValid.class).regexp();
                String name = field.getAnnotation(ExcelPatternValid.class).name();
                if (StringUtils.isBlank(name)) {
                    throw new ExcelValidException("【" + name + "】列有未填值项");
                }
                if (StringUtils.isNotBlank(cellStr) && !Pattern.matches(pattern, cellStr)) {
                    throw new ExcelValidException("【" + name + "】不符合规则");
                }
            }
        }
    }

    private static boolean isBigDecimal(String decimal) {
        try {
            BigDecimal bd = new BigDecimal(decimal);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }
}

3、自定义异常处理

/**
 * 导入excel自定义异常类
 */
public class ExcelValidException extends RuntimeException {
    private static final long serialVersionUID = 1L;

    private int code;

    public ExcelValidException(String msg) {
        this(100, msg, null);
    }


    public ExcelValidException(int code, String msg) {
        this(code, msg, null);
    }

    public ExcelValidException(int code, String msg, Exception nestedEx) {
        super(msg, nestedEx);
        this.code = code;
    }
}

4、监听器

@Slf4j
public class ImportExcelDataListener<T> extends AnalysisEventListener<T> {
    //service的名称
    private final String serviceName;

    // 校验通过数据处理方法
    private final String accessMethodName;

    // 异常数据处理
    private final String errorMethodName;

    private final String version;

    private Map<String, Object> paramMap = new HashMap<>();

    public ImportExcelDataListener(String serviceName, Map<String, Object> param) {
        this.serviceName = serviceName;
        this.accessMethodName = "savaSuccessForExcel";
        this.errorMethodName = "saveErrorForExcel";
        version = UUID.randomUUID().toString();
        paramMap = param;
    }

    public ImportExcelDataListener(String serviceName) {
        this.serviceName = serviceName;
        this.accessMethodName = "savaSuccessForExcel";
        this.errorMethodName = "saveErrorForExcel";
        version = UUID.randomUUID().toString();
        paramMap = new HashMap<>();
    }

    // 校验通过数据
    private final List<T> successList = new LinkedList<>();

    //异常数据  (key:原因,value:data项)
    private final Map<String, List<T>> errorMap = new HashMap<>();

    /**
     * <p>所有数据解析完成后调用此方法</p>
     */
    @SneakyThrows
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) throws ExcelValidException {
        //保存数据,确保最后的遗留数据也能保存到数据库或者进行其他操作
        this.execute();
        if (errorMap.size() > 0) {
            this.executeError();
        }
    }

    /**
     * <p>数据转换异常时的处理,比如required是Integer,provided是"sss"的字符串</p>
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        exception.printStackTrace();
        if (exception instanceof ExcelDataConvertException) {
            Integer columnIndex = ((ExcelDataConvertException) exception).getColumnIndex() + 1;
            Integer rowIndex = ((ExcelDataConvertException) exception).getRowIndex() + 1;
            String message = String.format("第%s行,第%s列数据格式有误,请核实", rowIndex, columnIndex);
            throw new RuntimeException(message);
        } else {
            super.onException(exception, context);
        }
    }

    @Override
    public void invoke(T data, AnalysisContext analysisContext) {
        //通用方法数据校验
        try {
            ExcelImportValid.valid(data);
        } catch (ExcelValidException e) {
            List<T> ts = errorMap.get(e.getMessage());
            if (CollectionUtils.isEmpty(ts)) {
                ts = new LinkedList<>();
            }
            ts.add(data);
            errorMap.put(e.getMessage(), ts);
            return;
        }
        //将解析完的数据加入到list中
        successList.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (successList.size() >= 600) {
            try {
                this.execute();
            } catch (Exception e) {
                e.printStackTrace();
            }
            // 存储完成清理 list
            successList.clear();
        }

    }

    //执行数据保存的操作
    public void execute() throws Exception {
        try {
            Object beanService = SpringUtil.getBean(serviceName + "Impl");
            Class<?> beanClass = beanService.getClass();
            Method method = beanClass.getMethod(accessMethodName, List.class, String.class, Map.class);
            method.invoke(beanService, successList, version, paramMap);
        } catch (NoSuchMethodException | IllegalAccessException e) {
            log.warn("import error:", e);
            throw new RuntimeException("导入执行保存失败");
        } catch (InvocationTargetException e) {
            e.printStackTrace();
            throw new ExcelValidException("有异常数据");
        }
    }

    //执行错误数据处理的操作
    public void executeError() {
        try {
            Object beanService = SpringUtil.getBean(serviceName + "Impl");
            Class<?> beanClass = beanService.getClass();
            Method method = beanClass.getMethod(errorMethodName, Map.class, String.class);
            method.invoke(beanService, errorMap, version);
        } catch (NoSuchMethodException | IllegalAccessException e) {
            log.warn("execute error:", e);
            throw new RuntimeException("处理错误数据失败");
        } catch (InvocationTargetException e) {
            throw new ExcelValidException("有异常数据");
        }
    }
}

5、数据处理接口,使用范型

/**
 * 导入数据处理接口
 */
public interface EasyExcelImportService<T> {
    /**
     * 异常数据处理方法 <errorMsg,data>
     * @param errorMap
     */
    void saveErrorForExcel(Map<String, List<T>> errorMap,String version) throws ExcelValidException;

    /**
     * 正常数据处理
     * @param entityList
     * @param param 参数
     */
    void savaSuccessForExcel(List<T> entityList,String version, Map<String,Object> param);
}

6、异常数据实体类(把校验失败的数据存入数据库,方便做导出,根据自己的具体业务自定义字段)

public class ImportErrorLog {
    @TableId(type = IdType.ASSIGN_UUID)
    private String id;

    /**
     * 服务名称
     */
    private String service;

    /**
     * json数据
     */
    private String data;

    /**
     * 唯一标识
     */
    private String version;

    /**
     * 创建时间
     */
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
}

7、日期类型需要自定义转换器

/**
 * 日期转换类
 */
public class LocalDateConverter implements Converter<LocalDate> {

	@Override
	public Class<LocalDate> supportJavaTypeKey() {
		return LocalDate.class;
	}

	@Override
	public CellDataTypeEnum supportExcelTypeKey() {
		return CellDataTypeEnum.STRING;
	}

	@Override
	public LocalDate convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
		if (cellData.getType().equals(CellDataTypeEnum.NUMBER)) {
			LocalDate localDate = LocalDate.of(1900, 1, 1);
			//excel 有些奇怪的bug, 导致日期数差2
			localDate = localDate.plusDays(cellData.getNumberValue().longValue() - 2);
			return localDate;
		}else {
			return LocalDate.parse(cellData.getStringValue(), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
		}
	}

	@Override
	public WriteCellData<?> convertToExcelData(LocalDate value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
		return new WriteCellData<>(value.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
	}

}

8、业务处理类实现EasyExcelImportService接口即可

public class DemoServiceImpl implements EasyExcelImportService<Demo> {
	/**
	* 	异常数据处理
	*/
	@Override
    public void saveErrorForExcel(Map<String, List<Demo>> errorMap, String version) throws ExcelValidException {
        LinkedList<Demo> errorList = new LinkedList<>();
		/**
		* errorMap 的key是错误信息,value是excel的一行数据
		*/
        ImportErrorLog errorLog = importErrorLogMapper.selectOne(new QueryWrapper<ImportErrorLog>()
                .eq("version", version)
                .last("limit 1")
        );
        // 因为注解只能校验数据的合法性,并不能校验是否符合业务逻辑,所以这里加了版本号,同一批次的错误信息保存一条记录
        if (errorLog == null){
            errorLog = ImportErrorLog.builder()
                    .service("service")
                    .version(version)
                    .data(JSON.toJSONString(errorList))
                    .build();
            // 入库
            importErrorLogMapper.insert(errorLog);
        }else {
            String data = errorLog.getData();
            List<Demo> importDemoList = JSONObject.parseArray(data, Demo.class);
            errorList.addAll(importDemoList);
            errorLog.setData(JSON.toJSONString(errorList));
            // 入库
            importErrorLogMapper.updateById(errorLog);
        }
        // 为了提醒前端有异常数据
        throw new ExcelValidException("have error data");
    }
	// 正常信息处理
    @Override
    public void savaSuccessForExcel(List<Demo> entityList, String version, Map<String,Object> param) {
        if (CollectionUtils.isEmpty(entityList)) {
            return;
        }
        // 业务逻辑校验
        Map<String,List<Demo>> errorMap = new HashMap<>();
        Iterator<Demo> iterator = entityList.iterator();
        while (iterator.hasNext()){
            Demo next = iterator.next();
            if (projectTempByCodeMap.get(next.getProjectCode()) == null){
                iterator.remove();
                List<Demo> importDemoList = errorMap.get("项目编码错误");
                if (CollectionUtils.isEmpty(importDemoList)){
                    importDemoList = new LinkedList<>();
                }
                importPmDailyList.add(next);
                errorMap.put("项目编码错误",importPmDailyList);
            }
        }

        if (CollectionUtils.isEmpty(entityList)){
            // 存储错误信息
            if (errorMap.size()>0){
                saveErrorForExcel(errorMap, version);
            }
            return;
        }
        // 业务逻辑处理。。。。。。
}

9、如果想导出某一批次的异常数据,直接查询数据库,做导出即可

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值