通用的导入、导出方法

1.通用导入方法

java 复制代码
    /**
     * 通用Excel导入方法(带校验)
     *
     * @param <T> Excel DTO类型
     * @param <R> 实体类型
     * @param file 上传的文件
     * @param excelClass Excel DTO类
     * @param validateFunction 校验函数(ExcelDTO, 行号)-> 校验结果
     * @param convertFunction 转换函数 ExcelDTO -> 实体
     * @param saveFunction 保存函数 List<实体> -> 保存结果
     * @return 导入结果
     */
    public static <T, R> Map<String, Object> importWithValidate(
            MultipartFile file,
            Class<T> excelClass,
            BiConsumer<T, Integer> validateFunction,
            Function<T, R> convertFunction,
            Consumer<List<R>> saveFunction) {

        if (file == null || file.isEmpty()) {
            throw new BusinessException("导入文件不能为空");
        }

        try {
            // 读取Excel数据
            List<T> excelList = EasyExcel.read(file.getInputStream())
                    .head(excelClass)
                    .sheet()
                    .doReadSync();

            if (CollUtil.isEmpty(excelList)) {
                throw new BusinessException("导入文件内容为空");
            }

            // 校验和转换数据
            List<R> entityList = new ArrayList<>();
            List<String> errorMessages = new ArrayList<>();
            int rowNum = 2; // 从第2行开始(第1行是标题)

            for (T dto : excelList) {
                try {
                    // 校验数据
                    validateFunction.accept(dto, rowNum);

                    // 转换为实体
                    R entity = convertFunction.apply(dto);
                    entityList.add(entity);
                } catch (Exception e) {
                    errorMessages.add("第" + rowNum + "行: " + e.getMessage());
                }
                rowNum++;
            }

            // 如果有错误,返回错误信息
            if (CollUtil.isNotEmpty(errorMessages)) {
                return buildErrorResult(errorMessages);
            }

            // 保存数据
            saveFunction.accept(entityList);

            // 返回成功结果
            return buildSuccessResult(entityList.size());

        } catch (IOException e) {
            log.error("导入失败", e);
            throw new BusinessException("导入失败: " + e.getMessage(), e);
        }
    }


    /**
     * 构建错误结果
     */
    private static Map<String, Object> buildErrorResult(List<String> errorMessages) {
        Map<String, Object> result = new HashMap<>();
        result.put("success", false);
        result.put("errorCount", errorMessages.size());
        result.put("errors", errorMessages);
        result.put("message", "导入失败,共" + errorMessages.size() + "条错误");
        return result;
    }

    /**
     * 构建成功结果
     */
    private static Map<String, Object> buildSuccessResult(int successCount) {
        Map<String, Object> result = new HashMap<>();
        result.put("success", true);
        result.put("successCount", successCount);
        result.put("message", "成功导入" + successCount + "条数据");
        return result;
    }

这三个都是 Java 8 中的函数式接口 ,常用于实现策略模式模板方法模式。我来详细解释每个参数的用途:

1. BiConsumer<T, Integer> validateFunction

作用:验证函数,接收两个参数并返回 void

java

复制代码
// 定义:接收对象 T 和索引 Integer,执行验证逻辑
BiConsumer<Student, Integer> validateFunction = (student, index) -> {
    // 验证逻辑
    if (student.getName() == null || student.getName().trim().isEmpty()) {
        throw new ValidationException("第" + index + "个学生姓名不能为空");
    }
    if (student.getAge() <= 0 || student.getAge() > 150) {
        throw new ValidationException("第" + index + "个学生年龄不合法");
    }
};

// 使用示例
List<Student> students = getStudents();
for (int i = 0; i < students.size(); i++) {
    validateFunction.accept(students.get(i), i);
}

2. Function<T, R> convertFunction

作用:转换函数,接收一个参数 T,返回转换后的结果 R

java

复制代码
// 定义:将 T 类型对象转换为 R 类型对象
Function<StudentDTO, StudentEntity> convertFunction = dto -> {
    // 转换逻辑
    StudentEntity entity = new StudentEntity();
    entity.setId(dto.getId());
    entity.setName(dto.getName());
    entity.setAge(dto.getAge());
    entity.setCreateTime(LocalDateTime.now());
    return entity;
};

// 使用示例
StudentDTO dto = new StudentDTO("张三", 20);
StudentEntity entity = convertFunction.apply(dto);

3. Consumer<List<R>> saveFunction

作用:保存函数,接收一个列表参数,执行保存操作

java

复制代码
// 定义:接收转换后的列表,执行批量保存
Consumer<List<StudentEntity>> saveFunction = entities -> {
    // 批量保存逻辑
    try {
        studentRepository.batchInsert(entities);
        log.info("成功保存 {} 条学生记录", entities.size());
    } catch (Exception e) {
        log.error("保存学生记录失败", e);
        throw new BusinessException("保存失败");
    }
};

// 使用示例
List<StudentEntity> entities = convertedList;
saveFunction.accept(entities);

4. 三者结合使用的完整示例

java

复制代码
public class DataProcessor<T, R> {
    
    /**
     * 数据处理模板方法
     * @param sourceList 原始数据列表
     * @param validateFunction 验证函数
     * @param convertFunction 转换函数
     * @param saveFunction 保存函数
     */
    public void process(List<T> sourceList,
                       BiConsumer<T, Integer> validateFunction,
                       Function<T, R> convertFunction,
                       Consumer<List<R>> saveFunction) {
        
        if (CollectionUtils.isEmpty(sourceList)) {
            return;
        }
        
        List<R> resultList = new ArrayList<>();
        
        // 1. 验证并转换
        for (int i = 0; i < sourceList.size(); i++) {
            T source = sourceList.get(i);
            
            // 执行验证
            validateFunction.accept(source, i);
            
            // 执行转换
            R result = convertFunction.apply(source);
            resultList.add(result);
        }
        
        // 2. 批量保存
        saveFunction.accept(resultList);
    }
}

使用场景

java

复制代码
// 实际使用
public class StudentImportService {
    
    @Autowired
    private StudentRepository studentRepository;
    
    public void importStudents(List<StudentDTO> dtoList) {
        DataProcessor<StudentDTO, StudentEntity> processor = new DataProcessor<>();
        
        processor.process(
            dtoList,
            
            // 验证函数
            (dto, index) -> {
                validateStudent(dto, index);
            },
            
            // 转换函数
            dto -> {
                return convertToEntity(dto);
            },
            
            // 保存函数
            entities -> {
                studentRepository.batchInsert(entities);
            }
        );
    }
    
    private void validateStudent(StudentDTO dto, int index) {
        // 详细验证逻辑
    }
    
    private StudentEntity convertToEntity(StudentDTO dto) {
        // 详细转换逻辑
    }
}

5. 更优雅的链式调用写法

java

复制代码
public <T, R> void processData(List<T> sourceData,
                              BiConsumer<T, Integer> validator,
                              Function<T, R> converter,
                              Consumer<List<R>> saver) {
    
    List<R> processedData = sourceData.stream()
        .map(item -> {
            int index = sourceData.indexOf(item);
            validator.accept(item, index);  // 验证
            return converter.apply(item);   // 转换
        })
        .collect(Collectors.toList());
    
    saver.accept(processedData);  // 保存
}

// 使用示例
processData(
    excelData,
    (data, idx) -> validateExcelRow(data, idx),  // 验证Excel行数据
    ExcelRow::toEntity,                          // 转换为实体
    repository::saveAll                          // 保存到数据库
);

6. 配合 Optional 的增强版本

java

复制代码
public <T, R> void safeProcess(List<T> sourceList,
                              BiConsumer<T, Integer> validateFunction,
                              Function<T, Optional<R>> convertFunction,
                              Consumer<List<R>> saveFunction) {
    
    List<R> resultList = new ArrayList<>();
    
    for (int i = 0; i < sourceList.size(); i++) {
        T source = sourceList.get(i);
        
        try {
            validateFunction.accept(source, i);
            
            convertFunction.apply(source)
                .ifPresent(resultList::add);  // 只有转换成功才加入
            
        } catch (ValidationException e) {
            log.warn("数据验证失败,索引: {}, 原因: {}", i, e.getMessage());
        }
    }
    
    if (!resultList.isEmpty()) {
        saveFunction.accept(resultList);
    }
}

7. 实际业务场景示例

java

复制代码
@Service
public class OrderImportService {
    
    public void importOrders(List<OrderExcelDTO> excelData) {
        // 1. 验证函数:验证Excel数据
        BiConsumer<OrderExcelDTO, Integer> validateFunction = (dto, index) -> {
            if (StringUtils.isEmpty(dto.getOrderNo())) {
                throw new ValidationException("第" + index + "行订单号为空");
            }
            if (dto.getAmount() == null || dto.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
                throw new ValidationException("第" + index + "行订单金额无效");
            }
        };
        
        // 2. 转换函数:DTO -> Entity
        Function<OrderExcelDTO, OrderEntity> convertFunction = dto -> {
            OrderEntity entity = new OrderEntity();
            BeanUtils.copyProperties(dto, entity);
            entity.setStatus(OrderStatus.PENDING);
            entity.setImportTime(LocalDateTime.now());
            return entity;
        };
        
        // 3. 保存函数:批量插入数据库
        Consumer<List<OrderEntity>> saveFunction = entities -> {
            orderRepository.batchInsert(entities);
            // 发送事件通知
            applicationEventPublisher.publishEvent(new OrderImportEvent(entities));
        };
        
        // 执行处理
        dataProcessor.process(excelData, validateFunction, convertFunction, saveFunction);
    }
}

总结三个参数的设计模式思想:

参数 角色 目的 类比
validateFunction 验证器 确保数据质量 质检员
convertFunction 转换器 数据格式转换 翻译官
saveFunction 存储器 数据持久化 保管员

这种设计实现了开闭原则

  • 对扩展开放:可以传入不同的验证、转换、保存逻辑

  • 对修改关闭:核心处理流程不需要修改

优点

  1. 解耦:验证、转换、保存逻辑分离

  2. 复用:可以复用处理框架

  3. 灵活:可以动态替换处理策略

  4. 可测试:每个函数都可以单独测试

这是函数式编程在 Java 业务开发中的典型应用,特别适合数据处理、ETL、批量导入等场景。

2.通用导出方法

java 复制代码
    /**
     * 通用Excel导出方法
     *
     * @param dataList     数据列表
     * @param excelClass   Excel对应的实体类(需要有@ExcelProperty注解)
     * @param sheetName    工作表名称
     * @param fileName     导出文件名(不含日期后缀和扩展名)
     * @param response     HttpServletResponse
     * @param <T>          数据源类型
     * @param <E>          Excel实体类型
     */
    public <T, E> void export(List<T> dataList,
                              Class<E> excelClass,
                              String sheetName,
                              String fileName,
                              HttpServletResponse response,
                              String errorMsg) {
        try {
            // 转换为Excel导出实体
            List<E> excelList = BeanUtil.copyToList(dataList, excelClass);

            // 生成文件名
            LocalDateTime dateTime = LocalDateTime.now();
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
            String fullFileName = fileName + "_" + dateTime.format(dateTimeFormatter) + ".xlsx";
            String encodedFileName = URLEncoder.encode(fullFileName, StandardCharsets.UTF_8);

            // 设置响应头
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");

            // 写入Excel
            EasyExcel.write(response.getOutputStream(), excelClass)
                    .sheet(sheetName)
                    .doWrite(excelList);

        } catch (IOException e) {
            if (StrUtil.isBlank(errorMsg)) {
                errorMsg = "导出Excel失败";
            }
            log.error(errorMsg, e);
            throw new BusinessException(errorMsg + ": " + e.getMessage(), e);
        }
    }
相关推荐
咩咩啃树皮13 小时前
第40篇:Vue3组件化开发精讲——组件拆分、复用、父子通信、工程化架构
java·前端·架构
鱟鲥鳚13 小时前
Spring Boot 集成 LangChain4j:从模型调用到 Tool Calling(Demo版)
java·spring boot
大模型码小白14 小时前
【Python零基础教程】继承、多态与魔法函数:面向对象编程三大核心特性详解
java·大数据·开发语言·人工智能·python·ai编程
腾渊信息科技公司15 小时前
Spring Boot对接MES实战:视觉检测数据自动同步方案
java·人工智能·spring boot·后端·计算机视觉·ai·软件需求
爱笑的源码基地16 小时前
高并发 Redis 缓存门诊HIS系统源码,含财务统计药房进销存
java·程序·门诊系统·诊所系统·云诊所源码
wuqingshun31415916 小时前
TCP超时重传机制是为了解决什么问题?
java
莫逸风19 小时前
【AgentScope 2.0】 0. 学习指南
java·llm·agent·agentscope
隔窗听雨眠19 小时前
Spring Boot在云原生时代的编程范式革新研究
spring boot·后端·云原生
z1234567898619 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
yaoxin52112320 小时前
470. Java 反射 - Member 接口与 AccessFlag
java·开发语言·python