spring-excel-mapper 的演示和示例应用,包含两种推荐的集成模式。
本模块演示如何在实际的 Spring Boot 应用中使用 spring-excel-mapper。包括:
详细的 API 参考和设计文档,请参见 spring-excel-mapper README。
当您需要对每个步骤进行最大控制或在导入过程中有复杂业务逻辑时,直接使用 ExcelMapper API。
@Data
@ExcelSheet
@ExcelRow(startRowIndex = 1)
public class StudentExcelModel {
@ExcelColumn(col = "A", required = true)
private String name;
@ExcelColumn(col = "B", regex = "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$")
private String email;
@ExcelColumn(col = "C", min = 18, max = 100)
private Integer age;
}
@Data
public class StudentDTO {
private String name;
private String email;
private Integer age;
}
@Mapper(componentModel = "spring")
public interface StudentMapper {
StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);
StudentDTO toDTO(StudentExcelModel model);
List<StudentDTO> toDTOs(List<StudentExcelModel> models);
}
@Service
@Slf4j
public class StudentExcelServiceManual {
public ImportResult<StudentDTO> importStudents(MultipartFile file) throws Exception {
try (InputStream is = file.getInputStream();
Workbook workbook = WorkbookFactory.create(is)) {
// 解析 Excel 模型
ImportResult<StudentExcelModel> parseResult =
ExcelMapper.parseWorkbookWithResult(workbook, StudentExcelModel.class, false);
if (!parseResult.isSuccess()) {
// 处理错误
ImportResult<StudentDTO> result = new ImportResult<>();
result.setErrors(parseResult.getErrors());
return result;
}
// 使用 MapStruct 转换为 DTO
List<StudentDTO> dtoList = StudentMapper.INSTANCE.toDTOs(parseResult.getSuccessList());
// 可选:在此处添加业务逻辑(例如,数据库保存)
// 返回结果
ImportResult<StudentDTO> result = new ImportResult<>();
result.setSuccessList(dtoList);
result.setTotalRows(dtoList.size());
return result;
}
}
}
@RestController
@RequestMapping("/api/students")
public class StudentController {
@Autowired
private StudentExcelServiceManual excelService;
@PostMapping("/import")
public ResponseEntity<?> importStudents(@RequestParam("file") MultipartFile file) throws Exception {
ImportResult<StudentDTO> result = excelService.importStudents(file);
if (result.isSuccess()) {
return ResponseEntity.ok(Map.of(
"success", true,
"data", result.getSuccessList(),
"count", result.getTotalRows()
));
} else {
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"errors", result.getErrors()
));
}
}
}
优点:
缺点:
使用 ExcelImportService<ExcelModel, DTO> 抽象基类来获得更简洁的代码和统一的工作流。推荐用于大多数用例。
@Data
@ExcelSheet
@ExcelRow(startRowIndex = 1)
public class StudentExcelModel {
@ExcelColumn(col = "A", required = true)
private String name;
@ExcelColumn(col = "B", regex = "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$")
private String email;
@ExcelColumn(col = "C", min = 18, max = 100)
private Integer age;
}
@Data
public class StudentDTO {
private String name;
private String email;
private Integer age;
}
@Mapper(componentModel = "spring")
public interface StudentMapper {
StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);
StudentDTO toDTO(StudentExcelModel model);
List<StudentDTO> toDTOs(List<StudentExcelModel> models);
}
@Service
@Slf4j
public class StudentExcelImportService extends ExcelImportService<StudentExcelModel, StudentDTO> {
@Override
protected Class<StudentExcelModel> getExcelModelClass() {
return StudentExcelModel.class;
}
@Override
protected StudentDTO convertToDTO(StudentExcelModel model) {
return StudentMapper.INSTANCE.toDTO(model);
}
@Override
protected List<StudentDTO> convertToDTOList(List<StudentExcelModel> models) {
return StudentMapper.INSTANCE.toDTOs(models);
}
// 可选的生命周期钩子:
@Override
protected void beforeImport(Workbook workbook) {
log.info("开始导入学生,sheet 数量:{}", workbook.getNumberOfSheets());
}
@Override
protected void afterImport(List<StudentDTO> dtoList) {
log.info("导入完成,导入了 {} 条记录", dtoList.size());
// 示例:保存到数据库、发送通知等
}
@Override
protected void onImportError(Exception e) {
log.error("导入失败", e);
// 示例:发送警报、回滚事务等
}
}
@RestController
@RequestMapping("/api/students")
public class StudentController {
@Autowired
private StudentExcelImportService importService;
@PostMapping("/import")
public ResponseEntity<?> importStudents(@RequestParam("file") MultipartFile file) throws Exception {
ImportResult<StudentDTO> result = importService.importExcel(file.getInputStream(), false);
if (result.isSuccess()) {
return ResponseEntity.ok(Map.of(
"success", true,
"data", result.getSuccessList(),
"count", result.getTotalRows()
));
} else {
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"errors", result.getErrors()
));
}
}
}
优点:
缺点:
Workbook wb = WorkbookFactory.create(inputStream);
List<MyModel> list = ExcelMapper.parseWorkbook(wb, MyModel.class);
ImportResult<MyModel> result =
ExcelMapper.parseWorkbookWithResult(workbook, MyModel.class, false);
if (result.isSuccess()) {
List<MyModel> data = result.getSuccessList();
} else {
List<ValidationResult> errors = result.getErrors();
}
Map<String, List<MyModel>> perSheet =
ExcelMapper.parseWorkbookBySheet(wb, MyModel.class, true);
Sheet sheet = workbook.getSheet("Items");
ExcelExporter.fillTableRows(sheet, startRow, dataList, MyModel.class);
@ExcelSheet
@ExcelSection(
sectionKeyword = "学生列表",
keywordInCol = "A",
startRowIndex = 1,
stopKeyword = "结束"
)
public class StudentSection {
@ExcelColumn(col = "A")
private String name;
}
List<StudentSection> sections = ExcelMapper.parseSheetSection(sheet, StudentSection.class);
spring-tools-simples/
├── src/main/java/org/springtools/excel/
│ ├── controller/ # REST 控制器
│ ├── service/ # 示例服务
│ │ ├── ExcelImportService.java # 基础模板类
│ │ ├── ExcelServiceLegacy.java # 模式 1 示例
│ │ └── impl/
│ │ ├── ClassImportService.java # 模式 2 示例
│ │ ├── DepartmentImportService.java # 模式 2 示例
│ │ ├── StudentImportService.java # 模式 2 示例
│ │ └── ExcelMapperServiceImpl.java # 组合导入/导出
│ ├── assembler/ # MapStruct mappers
│ ├── dto/ # DTOs
│ └── excelmodel/ # Excel 模型类
└── src/main/resources/
└── templates/ # 示例 Excel 模板
# 构建项目
mvn clean package
# 运行 Spring Boot 应用
mvn -pl spring-tools-simples spring-boot:run
# 或运行 JAR
java -jar spring-tools-simples/target/spring-tools-simples-1.0.0-SNAPSHOT.jar
启动应用后,您可以测试端点:
# 导入学生(模式 2)
curl -X POST http://localhost:8080/api/students/import \
-F "file=@students.xlsx"
# 导入部门
curl -X POST http://localhost:8080/api/departments/import \
-F "file=@departments.xlsx"
# 导出示例
curl -X GET http://localhost:8080/api/export/template \
-o output.xlsx
从单个 Excel 文件导入多个区段:
@Service
public class ExcelMapperServiceImpl {
public CombinedResult importAll(MultipartFile file) throws Exception {
try (InputStream is = file.getInputStream();
Workbook workbook = WorkbookFactory.create(is)) {
Sheet sheet = workbook.getSheetAt(0);
// 解析不同区段
List<DepartmentExcelModel> departments =
ExcelMapper.parseSheetSection(sheet, DepartmentExcelModel.class);
List<ClassExcelModel> classes =
ExcelMapper.parseSheetSection(sheet, ClassExcelModel.class);
List<StudentExcelModel> students =
ExcelMapper.parseSheetSection(sheet, StudentExcelModel.class);
// 转换并组合
CombinedResult result = new CombinedResult();
result.setDepartments(convertDepartments(departments));
result.setClasses(convertClasses(classes));
result.setStudents(convertStudents(students));
return result;
}
}
}
使用 Excel 模板导出数据(保留格式):
@Service
public class ExportService {
public Workbook exportToTemplate(List<StudentDTO> students) throws Exception {
// 加载模板
try (InputStream template = getClass().getResourceAsStream("/templates/student_template.xlsx")) {
Workbook workbook = WorkbookFactory.create(template);
Sheet sheet = workbook.getSheet("Students");
// 将 DTO 转换为 Excel 模型
List<StudentExcelModel> models = StudentMapper.INSTANCE.toExcelModels(students);
// 填充模板(从第 1 行开始,保留第 0 行的表头)
ExcelExporter.fillTableRows(sheet, 1, models, StudentExcelModel.class);
return workbook;
}
}
}
parseWorkbookWithResult(..., failFast=false) 收集所有错误afterImport() 钩子中验证业务规则,而不是在解析期间@ExcelColumn 支持 regex、min、max 等多种验证方式