通过Log4j生成CSV格式日志时自动插入表头处理方法

本文介绍如何使用Log4j自动生成带表头的CSV格式日志文件,并提供了具体的实现方法,包括通过自定义Layout来在日志文件开头插入表头。

转载:http://ju.outofmemory.cn/entry/141660

在开发某个类似报表的功能时,感觉用log4j直接按天滚动生成csv格式的文件很方便,于是研究了一下如何在日志滚动时自动插入表头的方法。

log4j的各种layout都派生自org.apache.log4j.Layout这个抽象类。Layout有一个getHeader()方法,默认返回null,因此只要实现一个你使用的*Layout的子类,重写getHeader方法就可以了。

比如我用的是org.apache.log4j.PatternLayout,所以我的实现类是这样子:

public class CSVLayout extends PatternLayout {

    private static final String HEADER = "column1,column2,column3,column4";

    @Override
    public String getHeader() {
        return HEADER + System.getProperty("line.separator");
    }
}

同理还有getFooter()方法,可以在文件尾部追加一行内容。

如果你愿意把header放在log4j配置文件里,则可以这样实现:

public class HeaderLayout extends PatternLayout{
	private String header;
	public void setHeader(String header){
		this.header = header;
	}
	public String getHeader(){
		return header+System.getProperty("line.separator");
	}

}

同时在配置文件里加上:

log4j.appender.foo.layout.Header=foo,bar,xxx,yyy

这样就可以实现log4j每产生一个新的日志文件都在第一行追加指定内容了。不过有一个不足就是:如果重启程序,则会在当天的文件中产生额外的header。

package com.tslc.car.fileDataUpload.service.impl; import com.tslc.car.common.core.entity.CommonCurrentUserEntity; import com.tslc.car.fileDataUpload.constant.ImportConstant; import com.tslc.car.fileDataUpload.entity.ImportSheetMapping; import com.tslc.car.fileDataUpload.repository.ImportSheetMappingMapper; import com.tslc.car.fileDataUpload.repository.ImportTemplateMapper; import com.tslc.car.fileDataUpload.service.IUploadFileDataService; import com.tslc.car.fileDataUpload.service.ImportTemplateFailRecordService; import com.tslc.car.fileDataUpload.service.ImportTemplateRecordService; import com.tslc.car.fileDataUpload.service.ImportTemplateService; import com.tslc.car.fileDataUpload.vo.*; import com.tslc.common.entity.ResultMessage; import com.tslc.common.util.DataUtil; import com.tslc.common.util.IDAssignUtil; import com.tslc.core.base.constants.StatusEnum; import com.tslc.core.base.exception.ServiceException; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.xssf.usermodel.XSSFRow; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.io.*; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @BelongsProject: project-h * @BelongsPackage: com.tslc.car.controldimension.service.impl * @Author: LC20677 * @CreateTime: 2025-09-9 13:23 * @Description: 通用模板导入 * @Version: 1.0 */ @Service @Slf4j public class UploadFileDataServiceImpl implements IUploadFileDataService { @Autowired private ImportTemplateMapper importTemplateMapper; @Autowired private ImportSheetMappingMapper importSheetMappingMapper; @Autowired private ImportTemplateRecordService importTemplateRecordService; @Autowired private ImportTemplateFailRecordService importTemplateFailRecordService; // 线程池 private ExecutorService executor = Executors.newFixedThreadPool(10); /* * @MethodName importData * @Description 通用导入模板 * @Param [employeeCode] * @Return * @USER LC20677 * @Date 2025/8/27 **/ @Override @Transactional(rollbackFor = ServiceException.class) public ResultMessage uploadFileData(UploadDataVo uploadDataVo, MultipartFile file) throws ServiceException { try { ResultMessage resultMessage = ResultMessage.success("导入成功"); if (null == file) { resultMessage = ResultMessage.failed("数据插入失败,文件流为NULL。"); // TODO 插入模板导入日志 // TODO 增加失败日志 // 插入模板导入日志 insertImportTemplateLog(uploadDataVo.getTemplateCode(),uploadDataVo.getTemplateCode(), 0, 0, 0, "2", "文件流为NULL"); return resultMessage; } String fileName = file.getOriginalFilename(); // 获取传入文件格式 String fileType = fileName.substring(fileName.lastIndexOf(".") + 1); List<ImportTemplateVo> templateList = importTemplateMapper.queryImportTemplate(uploadDataVo.getTemplateCode()); if (null != templateList && templateList.size() > 0) { for (ImportTemplateVo importTemplateVo : templateList) { if (ImportConstant.FILE_TYPE_EXCEL.equalsIgnoreCase(importTemplateVo.getFileType())) { if (!ImportConstant.FILE_TYPE_XLSX.equalsIgnoreCase(fileType) && ImportConstant.FILE_TYPE_XLS.equalsIgnoreCase(fileType)) { resultMessage = ResultMessage.failed("数据插入失败,传入文件格式跟模板配置所需格式不一致。"); // TODO 插入模板导入日志 // TODO 增加失败日志 // 插入模板导入日志 insertImportTemplateLog(uploadDataVo.getTemplateCode(),uploadDataVo.getTemplateCode(), 0, 0, 0, "2", "文件格式与模板配置不一致"); return resultMessage; } } else { if (!ImportConstant.FILE_TYPE_CSV.equalsIgnoreCase(fileType)) { resultMessage = ResultMessage.failed("数据插入失败,传入文件格式跟模板配置所需格式不一致。"); // TODO 插入模板导入日志 // TODO 增加失败日志 // 插入模板导入日志 insertImportTemplateLog(uploadDataVo.getTemplateCode(),uploadDataVo.getTemplateCode(), 0, 0, 0, "2", "文件格式与模板配置不一致"); return resultMessage; } } } } else { resultMessage = ResultMessage.failed("数据插入失败,请确认模板配置sheet是否正确。"); // TODO 插入模板导入日志 // TODO 增加失败日志 // 插入模板导入日志 insertImportTemplateLog(uploadDataVo.getTemplateCode(),uploadDataVo.getTemplateCode(), 0, 0, 0, "2", "模板配置sheet不正确"); return resultMessage; } XSSFWorkbook wb = null; if (ImportConstant.FILE_TYPE_CSV.equalsIgnoreCase(fileType)) { // CSV TODO wb = new XSSFWorkbook(); this.writeDataToWorkbook(wb, this.readCsv(file.getInputStream())); } else { wb = new XSSFWorkbook(file.getInputStream()); } // 根据配置处理文件导入的数据 for (ImportTemplateVo template : templateList) { // 线程异步处理 this.fileImportHandler(uploadDataVo, wb, template, fileType); } return resultMessage; } catch (Exception e) { log.error(this.getClass().getName() + ":", e); throw new ServiceException(e.getMessage(), e); } } /** * 文件数据上传相关处理(异步处理) * * @param uploadDataVo * @param wb * @param template */ private void fileImportHandler(UploadDataVo uploadDataVo, XSSFWorkbook wb, ImportTemplateVo template, String fileType) { executor.submit(new Runnable() { @Override public void run() { String templateRecordId = insertImportTemplateLog( template.getTemplateName(), template.getTemplateCode(),// 传入实际的模板名称 0, 0, 0, "0", "导入进行中" ); XSSFSheet sheet = null; if (ImportConstant.FILE_TYPE_CSV.equalsIgnoreCase(fileType)) { // csv 只能读取第一页sheet sheet = wb.getSheetAt(0); } else { // excel根据sheet名获取数据 sheet = wb.getSheet(template.getSheetName()); } Integer totalCount = 0; Integer successCount = 0; Integer failCount = 0; for (int i = template.getStartRow(); i <= sheet.getLastRowNum(); i++) { totalCount = totalCount + 1; XSSFRow eachRow = sheet.getRow(i); List<ImportSheetMapping> sheetMappingList = importSheetMappingMapper.queryImportSheetMapping(template.getTemplateSheetId()); ImportDataVo importDataVo = new ImportDataVo(); importDataVo.setTableName(template.getTableName()); if (null != sheetMappingList && sheetMappingList.size() > 0) { List<CloumData> cloumDataList = new ArrayList<>(); try { // 获取每列中的值 for (ImportSheetMapping sheetMapping : sheetMappingList) { CloumData cloumData = new CloumData(); cloumData.setCloumName(sheetMapping.getFieldName()); if (ImportConstant.FILE_TYPE_CSV.equalsIgnoreCase(fileType)) { this.csvGetCellValue(eachRow, sheetMapping, cloumData); } else { this.excelGetCellValue(eachRow, sheetMapping, cloumData); } cloumDataList.add(cloumData); } if (null != cloumDataList && cloumDataList.size() > 0) { cloumDataList.addAll(setCreateCommonData()); importDataVo.setCloumData(cloumDataList); // 插入失败catch。后面继续 try { importTemplateMapper.importDate(importDataVo); successCount = successCount + 1; } catch (Exception e) { failCount = failCount + 1; log.error(e.getMessage(), e); // TODO 插入失败日志 // 插入失败日志 insertImportFailLog(templateRecordId, i, e.getMessage()); } } } catch (Exception e) { // 插入失败catch。后面继续 failCount = failCount + 1; log.error(e.getMessage(), e); // TODO 插入失败日志 // 插入失败日志 insertImportFailLog(templateRecordId, i, e.getMessage()); } } } // TODO 插入模板导入日志 // 插入模板导入日志 updateImportTemplateLog(template.getTemplateName(),templateRecordId,template.getTemplateCode(), totalCount, successCount, failCount, getImportStatus(successCount, failCount), "导入完成"); } /** * excel设置cellValue值 * @param eachRow * @param sheetMapping * @param cloumData */ private void excelGetCellValue(XSSFRow eachRow, ImportSheetMapping sheetMapping, CloumData cloumData) { if(DataUtil.intUtil(sheetMapping.getUseDefault()) == 1){ cloumData.setCloumValue(sheetMapping.getDefaultValue()); }else{ if (ImportConstant.CLOUM_TYPE_STRING.equals(sheetMapping.getFieldType())) { cloumData.setCloumValue(eachRow.getCell(sheetMapping.getCloumIndex()).getStringCellValue()); } else if (ImportConstant.CLOUM_TYPE_DOUBLE.equals(sheetMapping.getFieldType())) { cloumData.setCloumValue(eachRow.getCell(sheetMapping.getCloumIndex()).getNumericCellValue()); } else if (ImportConstant.CLOUM_TYPE_DATE.equals(sheetMapping.getFieldType())) { cloumData.setCloumValue(eachRow.getCell(sheetMapping.getCloumIndex()).getDateCellValue()); } else { cloumData.setCloumValue(eachRow.getCell(sheetMapping.getCloumIndex()).getStringCellValue()); } } } /** * csv设置cellValue值 * @param eachRow * @param sheetMapping * @param cloumData */ private void csvGetCellValue(XSSFRow eachRow, ImportSheetMapping sheetMapping, CloumData cloumData) { String cellValue = eachRow.getCell(sheetMapping.getCloumIndex()).getStringCellValue(); if(sheetMapping.getUseDefault() == 1){ cloumData.setCloumValue(sheetMapping.getDefaultValue()); }else{ if (ImportConstant.CLOUM_TYPE_STRING.equals(sheetMapping.getFieldType())) { cloumData.setCloumValue(cellValue); } else if (ImportConstant.CLOUM_TYPE_DOUBLE.equals(sheetMapping.getFieldType())) { cloumData.setCloumValue(DataUtil.doubleUtil(cellValue)); } else if (ImportConstant.CLOUM_TYPE_DATE.equals(sheetMapping.getFieldType())) { cloumData.setCloumValue(cellValue); } else { cloumData.setCloumValue(cellValue); } } } }); } /** * 设置基础固定栏位 * * @return */ private List<CloumData> setCreateCommonData() { List<CloumData> cloumDataList = new ArrayList<>(); CommonCurrentUserEntity entity = new CommonCurrentUserEntity(); CloumData bid = new CloumData(); bid.setCloumName(ImportConstant.CLOUM_NAME_BID); bid.setCloumValue(IDAssignUtil.uuid()); cloumDataList.add(bid); CloumData delete = new CloumData(); CloumData creator = new CloumData(); creator.setCloumName(ImportConstant.CLOUM_NAME_CREATOR); creator.setCloumValue(entity.getCurrentUserCode()); cloumDataList.add(creator); CloumData createDate = new CloumData(); createDate.setCloumName(ImportConstant.CLOUM_NAME_CREATE_DATE); createDate.setCloumValue(new Date()); cloumDataList.add(createDate); delete.setCloumName(ImportConstant.CLOUM_NAME_IS_DELETE); delete.setCloumValue(StatusEnum.RUNING.getValue()); cloumDataList.add(delete); CloumData version = new CloumData(); version.setCloumName(ImportConstant.CLOUM_NAME_VERSION); version.setCloumValue(1); cloumDataList.add(version); return cloumDataList; } /** * 读取csv数据流 * * @param inputStream * @return * @throws IOException */ public String[][] readCsv(InputStream inputStream) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); String line; StringBuilder sb = new StringBuilder(); while ((line = br.readLine()) != null) { sb.append(line).append("\n"); } br.close(); // 按行分割CSV数据 String[] rows = sb.toString().split("\n"); String[][] data = new String[rows.length][]; for (int i = 0; i < rows.length; i++) { data[i] = rows[i].split(","); // 按逗号分割列 } return data; // 返回CSV数据 } /** * 写入数据到workbook * * @param workbook * @param csvData */ public void writeDataToWorkbook(XSSFWorkbook workbook, String[][] csvData) { Sheet sheet = workbook.createSheet("Sheet1"); // 创建新的工作表 for (int i = 0; i < csvData.length; i++) { Row row = sheet.createRow(i); // 创建行 for (int j = 0; j < csvData[i].length; j++) { row.createCell(j).setCellValue(csvData[i][j]); // 写入每个单元格数据 } } } /** * 保存到excel文件不需要用 * * @param workbook * @param filePath * @throws IOException */ public void saveWorkbook(XSSFWorkbook workbook, String filePath) throws IOException { FileOutputStream outputStream = new FileOutputStream(filePath); workbook.write(outputStream); // 写入数据 outputStream.close(); workbook.close(); // 关闭 Workbook } /** * 插入导入模板日志 */ /** * 插入导入模板日志并返回导入记录ID */ private String insertImportTemplateLog(String tenokateName,String templateCode, int totalCount, int successCount, int failCount, String status, String remark) { try { ImportTemplateRecordVo templateVo = new ImportTemplateRecordVo(); templateVo.setTemplateCode(templateCode); templateVo.setTemplateName(tenokateName); templateVo.setTotalCount(String.valueOf(totalCount)); templateVo.setSuccessCount(successCount); templateVo.setFailCount(failCount); templateVo.setImportStatus(status); importTemplateRecordService.saveOrUpdate(templateVo); return templateVo.getBid(); // 返回生成的ID } catch (Exception e) { log.error("插入导入模板日志失败: ", e); return null; } } /** * 更新导入模板日志 */ private void updateImportTemplateLog(String tenokateName,String bid,String templateCode, int totalCount, int successCount, int failCount, String status, String remark) { try { ImportTemplateRecordVo templateVo = new ImportTemplateRecordVo(); templateVo.setBid(bid); templateVo.setTemplateCode(templateCode); templateVo.setTemplateName(tenokateName); templateVo.setTotalCount(String.valueOf(totalCount)); templateVo.setSuccessCount(successCount); templateVo.setFailCount(failCount); templateVo.setImportStatus(status); importTemplateRecordService.saveOrUpdate(templateVo); } catch (Exception e) { log.error("插入导入模板日志失败: ", e); } } /** * 插入导入失败日志 */ private void insertImportFailLog(String templateRecordId, int rowIndex, String errorMsg) { try { ImportTemplateFailRecordVo failRecordVo = new ImportTemplateFailRecordVo(); failRecordVo.setTemplateRecordId(templateRecordId); failRecordVo.setFailIndex(rowIndex); failRecordVo.setRemark(errorMsg != null && errorMsg.length() > 500 ? errorMsg.substring(0, 500) : errorMsg); importTemplateFailRecordService.saveImportTemplateFailRecord(failRecordVo); } catch (Exception e) { log.error("插入导入失败日志失败: ", e); } } /** * 根据成功数和失败数确定导入状态 */ private String getImportStatus(int successCount, int failCount) { if (failCount == 0) { return ImportConstant.ALL_SUCCESS; // 全部成功 } else if (successCount == 0) { return ImportConstant.ALL_FAIL; // 全部失败 } else { return ImportConstant.PART_SUCCESS; // 部分成功 } } } 我重申一下我的需求,这个东西是一个公共的导入接口,现在需要改造的点是需要增加数据方面的校验,也就是需要查询出来数据之后不要直接存入数据库,而是要放在map集合里面,然后调用一下,调用方提供的校验接口,这个的难点就在于不同的调用方提供不同的校验方法在不同的serviceimpl,并且一个模板不止一个校验方法,所以重点就在于在如何去动态的调用校验方法
10-18
<think>我们面对的核心需求是:改造公共导入接口,增加数据校验功能,将查询出的数据放入Map集合,然后动态调用不同调用方在不同ServiceImpl中提供的多个校验方法。这些校验方法分布在不同的Service实现类中,且一个模板(可能指一个导入模板)可能需要调用多个校验方法。 根据引用[1]和引用[3],我们注意到已有的工具类优势包括:通用性强、性能优化、提示清晰、扩展性高。同,引用[3]提供了使用EasyExcel进行导入和数据校验的例子,我们可以借鉴其数据接收的方式。 另外,引用[2]提到了使用Spring的Profile来根据环境切换实现,以及使用Qualifier和工厂模式来动态选择实现类。这为我们解决动态调用不同Service的校验方法提供了思路。 设计思路: 1. 首先,我们需要定义一个校验接口,让各个Service实现类中的校验方法都通过这个接口来统一调用。这样我们可以利用Spring的依赖注入来动态获取这些校验器。 2. 其次,我们需要在公共导入接口中,根据调用方的需求,获取到需要调用的校验器列表,并依次执行。 3. 为了将数据放入Map集合,我们可以考虑将每行数据转换成一个Map,键为字段名(或表头),值为对应的数据。这样校验器就可以通过字段名来获取需要校验的值。 具体步骤: 步骤1:定义校验器接口 我们可以定义一个校验器接口,例如 `DataValidator`,其中包含一个校验方法。该方法接收一个Map(代表一行数据)作为参数,并返回校验结果(可以是错误信息集合,如果校验通过则返回空集合)。 ```java public interface DataValidator { /** * 校验单行数据 * @param rowData 一行数据,键为字段名,值为字段值 * @return 错误信息列表,如果没有错误则返回空列表 */ List<String> validate(Map<String, Object> rowData); } ``` 步骤2:在各个Service实现类中实现校验器 每个调用方可以在自己的Service实现类中实现多个`DataValidator`接口(注意:一个类可以实现多个接口,但这里我们每个校验方法需要单独作为一个Bean?)。实际上,我们更希望每个校验方法独立成为一个Bean,这样我们可以按需注入多个校验器。 但注意,一个类只能实现一次接口,所以我们不能在一个类中多次实现同一个接口。因此,我们可以采用以下两种方式之一: 方式A:每个校验方法写在一个独立的Bean中(比如每个校验器一个类,这样可能类太多)。 方式B:在一个Bean中提供多个校验方法,然后我们通过其他方式来调用具体的方法。 但是,为了统一调用,我们更倾向于每个校验器是一个独立的Bean。因此,我们可以这样: 例如,调用方A需要两个校验器,那么他可以创建两个Bean,分别实现`DataValidator`接口: ```java @Service public class ValidatorA1 implements DataValidator { @Override public List<String> validate(Map<String, Object> rowData) { // 校验逻辑1 } } @Service public class ValidatorA2 implements DataValidator { @Override public List<String> validate(Map<String, Object> rowData) { // 校验逻辑2 } } ``` 步骤3:在公共导入接口中动态获取校验器 我们需要根据调用方的标识来获取一组校验器。这里我们可以使用Spring的依赖注入特性,通过`ApplicationContext`来获取所有`DataValidator`类型的Bean,但是如何知道哪些校验器是属于当前调用方的呢? 我们可以为校验器添加一个额外的注解,用来标识这个校验器适用于哪些模板(或调用方)。例如: ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface ValidatorFor { String[] value(); // 可以支持多个模板 } ``` 然后在每个校验器上指定适用的模板: ```java @Service @ValidatorFor("templateA") public class ValidatorA1 implements DataValidator { ... } @Service @ValidatorFor("templateA") public class ValidatorA2 implements DataValidator { ... } @Service @ValidatorFor("templateB") public class ValidatorB1 implements DataValidator { ... } ``` 步骤4:在公共导入接口中,根据当前模板名称(或调用方标识)获取对应的校验器 我们可以注入`ApplicationContext`,然后获取所有带有`ValidatorFor`注解并且实现了`DataValidator`接口的Bean,再根据当前模板名称过滤出需要执行的校验器。 步骤5:执行校验 对于每一行数据(已经转换为Map),依次调用每个校验器的`validate`方法,收集错误信息。 步骤6:错误信息汇总 将每一行的错误信息汇总,可以按行号记录错误,最后统一返回。 代码结构示例: 在公共导入接口的改造部分(假设是一个Service方法): ```java @Service public class CommonImportServiceImpl { @Autowired private ApplicationContext applicationContext; // 假设这个方法就是公共导入接口 public ImportResult importData(MultipartFile file, String templateName) { // 1. 使用EasyExcel读取数据,转换为List<Map<String, Object>> 或者逐行处理 // 这里我们假设逐行处理,每一行是一个Map List<Map<String, Object>> dataList = readExcelData(file); // 2. 根据templateName获取所有需要执行的校验器 List<DataValidator> validators = getValidatorsForTemplate(templateName); // 3. 逐行校验 List<RowError> errors = new ArrayList<>(); for (int i = 0; i < dataList.size(); i++) { Map<String, Object> row = dataList.get(i); List<String> rowErrors = new ArrayList<>(); for (DataValidator validator : validators) { List<String> result = validator.validate(row); if (result != null && !result.isEmpty()) { rowErrors.addAll(result); } } if (!rowErrors.isEmpty()) { errors.add(new RowError(i+1, rowErrors)); } } // 4. 如果有错误,返回错误信息;否则继续导入流程 if (!errors.isEmpty()) { return ImportResult.fail(errors); } // 5. 导入数据... return ImportResult.success(); } private List<DataValidator> getValidatorsForTemplate(String templateName) { // 获取所有DataValidator类型的Bean Map<String, DataValidator> validatorBeans = applicationContext.getBeansOfType(DataValidator.class); List<DataValidator> result = new ArrayList<>(); for (Map.Entry<String, DataValidator> entry : validatorBeans.entrySet()) { DataValidator validator = entry.getValue(); // 获取该Bean的ValidatorFor注解 ValidatorFor validatorFor = validator.getClass().getAnnotation(ValidatorFor.class); if (validatorFor != null) { // 如果该注解的value中包含当前模板名称,则加入列表 if (Arrays.asList(validatorFor.value()).contains(templateName)) { result.add(validator); } } } return result; } // 读取Excel数据的方法(使用EasyExcel) private List<Map<String, Object>> readExcelData(MultipartFile file) { // 使用EasyExcel读取,这里简化,实际需要根据表头映射 // 注意:这里可以引用[3]中的方式,但我们需要的是Map,所以可以直接用Map接收 // 具体实现略,可以参考EasyExcel的文档 } } ``` 注意事项: 1. 性能考虑:引用[1]中提到“多字段校验仅需1次数据库查询,避免N+1问题”。在我们的校验器中,如果多个校验器都需要查询数据库,可能会造成多次查询。我们可以考虑在进入校验之前,先将本行数据可能需要查询的数据库信息一次性查出来,然后放入行数据的Map中,这样校验器可以直接使用,避免各自查询。或者,我们可以设计一个上下文,在每行校验前预加载数据。 2. 错误信息清晰:每个校验器返回的错误信息应该包含字段名和错误值,这样用户能准确定位问题。 3. 扩展性:如果后续新增模板,只需要为这个模板编写新的校验器(实现DataValidator接口并标注@ValidatorFor("新模板"))即可,无需修改公共导入接口。 4. 同一个模板的多个校验器执行顺序:上面的代码是按照Spring容器中Bean的顺序执行的,如果需要指定顺序,可以在ValidatorFor注解上增加order属性,然后在getValidatorsForTemplate方法中根据order排序。 5. 线程安全:每个校验器应该是无状态的,这样能保证线程安全(引用[2]中提到无状态设计)。 总结:通过定义统一的校验器接口,利用Spring容器动态获取校验器,我们可以实现不同调用方的不同校验方法的动态调用。同,通过注解标记校验器所属的模板,实现了灵活的扩展。 关于引用[4],这里没有直接使用,但如果有需要调用第三方接口进行校验,可以在某个校验器内部调用`sendFxtHttpPost`方法,但要注意网络请求的性能和稳定性。 这样改造后,公共导入接口就具备了动态调用多个校验器的能力,同保证了通用性和扩展性。 </think>根据您的需求,我将设计一个基于Spring框架的动态校验方案,参考站内引用中的设计模式和校验实践。以下是分步改造方案: ### 1. 定义统一校验接口 ```java public interface DataValidator { /** * 动态校验方法 * @param rowData 单行数据的Map表示 * @return 校验错误信息(空列表表示通过) */ List<String> validate(Map<String, Object> rowData); } ``` ### 2. 改造公共导入接口 ```java public class CommonImportService { // 存储不同调用方的校验器映射 {模板ID: 校验器列表} private final Map<String, List<DataValidator>> validatorRegistry = new ConcurrentHashMap<>(); /** * 注册校验器 * @param templateId 模板标识 * @param validator 校验器实例 */ public void registerValidator(String templateId, DataValidator validator) { validatorRegistry.computeIfAbsent(templateId, k -> new ArrayList<>()).add(validator); } /** * 执行导入校验 * @param templateId 模板标识 * @param dataRows 数据行集合 * @return 校验错误集合 {行号: 错误信息} */ public Map<Integer, List<String>> executeValidation(String templateId, List<Map<String, Object>> dataRows) { Map<Integer, List<String>> errors = new LinkedHashMap<>(); List<DataValidator> validators = validatorRegistry.get(templateId); if (validators == null) { throw new IllegalStateException("无注册校验器: " + templateId); } for (int i = 0; i < dataRows.size(); i++) { Map<String, Object> row = dataRows.get(i); List<String> rowErrors = new ArrayList<>(); // 动态执行所有校验器 for (DataValidator validator : validators) { rowErrors.addAll(validator.validate(row)); } if (!rowErrors.isEmpty()) { errors.put(i + 1, rowErrors); // 行号从1开始 } } return errors; } } ``` ### 3. 实现调用方校验服务 参考引用[2]的工厂模式和环境隔离方案: ```java @Service public class FinanceValidatorImpl implements DataValidator { @Override public List<String> validate(Map<String, Object> row) { List<String> errors = new ArrayList<>(); // 示例: 校验金额字段 if (Double.parseDouble(row.get("amount").toString()) <= 0) { errors.add("金额必须大于0"); } return errors; } } @Service @Profile("production") public class ProductionValidatorImpl implements DataValidator { @Override public List<String> validate(Map<String, Object> row) { // 生产环境特殊校验逻辑 } } ``` ### 4. 集成到导入流程 参考引用[3]的EasyExcel集成方案: ```java @RestController public class ImportController { @Autowired private CommonImportService importService; @PostMapping("/import/{templateId}") public ResponseEntity<?> importData( @PathVariable String templateId, @RequestParam MultipartFile file) { // 1. 解析Excel为Map集合 List<Map<String, Object>> dataRows = EasyExcel.read(file.getInputStream()) .headRowNumber(1) .doReadAllSync() .stream() .map(row -> (Map<String, Object>) row) .collect(Collectors.toList()); // 2. 执行动态校验 Map<Integer, List<String>> errors = importService.executeValidation(templateId, dataRows); // 3. 处理校验结果 if (!errors.isEmpty()) { return ResponseEntity.badRequest().body(errors); } // 4. 继续业务处理... return ResponseEntity.ok("导入成功"); } } ``` ### 5. 注册校验器(关键步骤) 在应用启动自动注册各模块的校验器: ```java @Configuration public class ValidatorConfig { @Autowired public void registerValidators( CommonImportService importService, List<DataValidator> validators) { // Spring自动注入所有实现 // 示例注册(实际从配置读取) importService.registerValidator("finance_template", validators.stream() .filter(v -> v instanceof FinanceValidatorImpl) .findFirst().get() ); // 动态注册其他模板... } } ``` ### 设计优势 1. **动态扩展性** 新增校验方法只需实现`DataValidator`接口并注册,无需修改核心导入逻辑[^2] 2. **配置化注册** 支持通过配置文件动态绑定模板与校验器: ```yaml validators: finance_template: - bean: financeValidatorImpl - bean: financeCustomValidator hr_template: - bean: employeeValidator ``` 3. **校验隔离** 不同模板使用独立校验器集合,避免逻辑耦合 4. **错误定位** 返回带行号的详细错误信息: ```json { "5": ["金额格式错误", "部门不存在"], "12": ["日期格式无效"] } ``` 5. **性能优化** - 校验器复用避免重复创建 - 行级并行校验支持(可添加`@Async`)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值