通用的导入、导出方法

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);
        }
    }
相关推荐
qq_12498707532 小时前
基于Spring Boot的微信小程序的智慧商场系统的设计与实现
java·spring boot·spring·微信小程序·小程序·毕业设计·计算机毕业设计
yaoxin5211232 小时前
277. Java Stream API - 去重与排序:Stream 中的 distinct() 与 sorted()
java·开发语言
幽络源小助理2 小时前
SpringBoot+Vue多维分类知识管理系统源码 | Java知识库项目免费下载 – 幽络源
java·vue.js·spring boot
不吃葱的胖虎2 小时前
根据Excel模板,指定单元格坐标填充数据
java·excel
k***92162 小时前
C语言模拟面向对象三大特性与C++实现对比
java·c语言·c++
野生技术架构师2 小时前
SpringBoot健康检查完整指南,避免线上事故
java·spring boot·后端
疯狂成瘾者2 小时前
Lombok 可以生成哪些类方法
java·tomcat·maven
Light602 小时前
MyBatis-Plus 全解:从高效 CRUD 到云原生数据层架构的艺术
spring boot·云原生·架构·mybatis·orm·代码生成·数据持久层
于樱花森上飞舞2 小时前
【多线程】CAS和哈希表
java·数据结构·java-ee