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 |
存储器 | 数据持久化 | 保管员 |
这种设计实现了开闭原则:
-
对扩展开放:可以传入不同的验证、转换、保存逻辑
-
对修改关闭:核心处理流程不需要修改
优点:
-
解耦:验证、转换、保存逻辑分离
-
复用:可以复用处理框架
-
灵活:可以动态替换处理策略
-
可测试:每个函数都可以单独测试
这是函数式编程在 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);
}
}