Excel导入导出
sz-admin 集成了 EasyExcel 库,以简化 Excel 文件的导入和导出流程。EasyExcel 是一个高效的处理 Excel 文件的 Java 库,它提供了丰富的功能来满足各种数据处理需求。更多关于 EasyExcel 的详细信息和高级操作,请访问 EasyExcel 官网。
本文档将介绍 sz-admin中 EasyExcel 的一些扩展操作,帮助您更高效地处理 Excel 数据。!EasyExcel 已停止维护
sz-admin 现已切换至 FastExcel,一个由 EasyExcel 作者开发的升级版本,旨在提供更快速、更简洁的 Excel 文件处理能力,并有效解决大文件处理时的内存溢出问题。FastExcel 是一个高效的 Java 库,它不仅继承了 EasyExcel 的强大功能,还带来了更多的性能优化和新特性,以满足您对数据处理的各种需求。
基础操作
导入
步骤一:定义导入实体
创建一个名为
XXXImportDTO的导入结构实体,并使用@ExcelProperty等注解来标记 Excel 中的列。以下是一个示例:
TeacherStatisticsImportDTO.java 示例代码
@Data
@Schema(description = "TeacherStatistics导入DTO")
public class TeacherStatisticsImportDTO {
@ExcelProperty(value = "统计年限")
@Schema(description = "统计年限")
private String year;
@ExcelProperty(value = "统计月份")
@Schema(description = "统计月份")
private String month;
@ExcelProperty(value = "统计年月")
@Schema(description = "统计年月")
private String duringTime;
@ExcelProperty(value = "教师id")
@Schema(description = "教师id")
private String teacherId;
@ExcelProperty(value = "讲师区分类型")
@DictFormat(dictType = "account_status")
@Schema(description = "讲师区分类型")
private String teacherCommonType;
@ExcelProperty(value = "授课总数")
@Schema(description = "授课总数")
private Integer totalTeaching;
@ExcelProperty(value = "服务班次数")
@Schema(description = "服务班次数")
private Integer totalClassCount;
@ExcelProperty(value = "课时总数")
@Schema(description = "课时总数")
private BigDecimal totalHours;
@ExcelProperty(value = "核对状态")
/* @DictFormat(dictType = "account_status") */
@Schema(description = "核对状态")
private String checkStatus;
@Schema(description = "核对时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime checkTime;
@Schema(description = "最近一次同步时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime lastSyncTime;
@ExcelProperty(value = "备注")
@Schema(description = "备注")
private String remark;
}- 步骤二:读取 Excel 文件
使用 ExcelUtils.importExcel 方法来读取 Excel 文件并获取导入结果。以下是一个示例:
importExcel 方法示例代码
@SneakyThrows
@Override
public void importExcel(ImportExcelDTO dto) {
...
ExcelResult<TeacherStatisticsImportDTO> excelResult = ExcelUtils.importExcel(dto.getFile().getInputStream(), TeacherStatisticsImportDTO.class, true);
...
}- 步骤三:处理导入结果
excelResult 示例代码
...
List<TeacherStatisticsImportDTO> list = excelResult.getList();
List<String> errorList = excelResult.getErrorList();
String analysis = excelResult.getAnalysis();
System.out.println(" analysis : " + analysis);
System.out.println(" isCover : " + dto.getIsCover());
System.out.println("list ==" + JsonUtils.toJsonString(list));
// TODO 你的业务
...导出
步骤一:定义导出实体
创建一个数据模型类来定义导出的数据结构,并使用
@ExcelProperty和@ExcelIgnore注解来指定哪些字段应该被包含在 Excel 文件中。以下是一个示例:
TeacherStatisticsVO.java 示例代码
@Data
@Schema(description = "TeacherStatistics返回vo")
public class TeacherStatisticsVO {
@ExcelIgnore
@Schema(description = "id")
private Long id;
@ExcelProperty(value = "统计年限")
@Schema(description = "统计年限")
private String year;
@ExcelProperty(value = "统计月份")
@Schema(description = "统计月份")
private String month;
@ExcelProperty(value = "统计年月")
@Schema(description = "统计年月")
private String duringTime;
@ExcelProperty(value = "教师id")
@Schema(description = "教师id")
private String teacherId;
@ExcelProperty(value = "讲师区分类型")
@DictFormat(dictType = "account_status", isSelected = true)
@Schema(description = "讲师区分类型")
private String teacherCommonType;
@ExcelProperty(value = "授课总数")
@Schema(description = "授课总数")
private Integer totalTeaching;
@ExcelProperty(value = "服务班次数")
@Schema(description = "服务班次数")
private Integer totalClassCount;
@ExcelProperty(value = "课时总数")
@Schema(description = "课时总数")
private BigDecimal totalHours;
@ExcelProperty(value = "核对状态")
@Schema(description = "核对状态")
@DictFormat(dictType = "account_status")
private Integer checkStatus;
@ExcelProperty(value = "核对时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "核对时间")
private LocalDateTime checkTime;
@ExcelProperty(value = "最近一次同步时间")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "最近一次同步时间")
private LocalDateTime lastSyncTime;
@ExcelProperty(value = "备注")
@Schema(description = "备注")
private String remark;
@ExcelProperty(value = "创建人id")
@DictFormat(dictType = "dynamic_user_options")
@Schema(description = "创建人id")
private Long createId;
}- 步骤二:执行导出操作
使用 ExcelUtils.exportExcel 方法将数据导出到 Excel 文件中。以下是一个示例:
exportExcel方法示例代码
public void exportExcel(TeacherStatisticsListDTO dto, HttpServletResponse response) {
// 获取数据列表
List<TeacherStatisticsVO> list = list(dto);
// 获取响应的输出流
ServletOutputStream os = response.getOutputStream();
// 执行导出操作
ExcelUtils.exportExcel(list, "教师统计", TeacherStatisticsVO.class, os, true);
}拓展
NOTE
sz-admin 提供了 @DictFormat 和 @CellMerge 两个自定义注解,以简化常见的 Excel 操作。
以下是如何使用这些注解的详细说明,以“讲师区分类型”字典为例。
| Key | Value | 字典类型 |
|---|---|---|
| 1000001 | 正常 | 讲师区分类型 |
| 1000002 | 禁用 | 讲师区分类型 |
| 1000003 | 禁言 | 讲师区分类型 |
导入字典映射
在导入实体的对应字段上应用 @DictFormat 注解,以指定相应的字典项。这一操作确保在导入过程中,字典中的显示值(Value)如“正常”将被自动转换为对应的键值(Key),例如从 “正常” 转换为 1000001。
...
@ExcelProperty(value = "讲师区分类型")
@DictFormat(dictType = "account_status")
@Schema(description = "讲师区分类型")
private String teacherCommonType;
...效果


导出字典映射
在导出实体的对应字段上添加@DictFormat注解指定字典项。这一操作确保在导入过程中,字典中的键值(Key)将被自动转换为对应的显示值(Value),例如从 1000001转换为 “正常” 。
...
@ExcelProperty(value = "讲师区分类型")
@DictFormat(dictType = "account_status")
@Schema(description = "讲师区分类型")
private String teacherCommonType;
...效果


合并单元格相同项
为了在导出的 Excel 文件中对具有相同值的列进行单元格合并,您可以在导出实体的对应字段上添加 @CellMerge 注解。这将自动合并列中相同的项,从而提供一个更加整洁和易于阅读的表格视图。
...
@ExcelProperty(value = "统计年月")
@Schema(description = "统计年月")
@CellMerge
private String duringTime;
...效果

导入表头验证
在处理 Excel 文件导入时,确保数据的准确性和完整性是非常重要的。sz-admin 提供了表头验证功能,以确保导入的 Excel 文件包含所有必要的列。在ExcelUtils.importExcel()方法中,通过设置第三个参数validateHeader为true,可以启用这一验证机制。
...
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean validateHeader) {
Map<String, List<DictVO>> dictmap = getDictList();
ExcelListenerFactory listenerFactory = SpringApplicationContextUtils.getBean(ExcelListenerFactory.class);
DefaultExcelListener<T> listener = listenerFactory.createListener(validateHeader, clazz);
FastExcel.read(is, clazz, listener).registerConverter(new CustomStringStringConvert(dictmap)).registerConverter(new CustomIntegerStringConvert(dictmap))
.registerConverter(new CustomLongStringConvert(dictmap)).sheet().doRead();
return listener.getExcelResult();
}
...效果

导入模板生成
NOTE
以下功能为 v1.3.4-beta 新增,向后兼容,无需迁移。
sz-admin 提供三个注解,配合 AbstractExcelImportTemplate 导入框架,实现导入模板自动生成与统一的导入流程管理。
@ExcelTemplate
标注在导入 DTO 类上,将该类注册为可下载的导入模板。
| 属性 | 说明 | 默认值 |
|---|---|---|
alias | 模板唯一标识,同时作为下载文件名(如 "教师统计导入模板.xlsx") | 必填 |
validRows | Excel 数据验证覆盖行数(从第 2 行起) | 1000 |
@ExcelTemplate(alias = "教师统计导入模板.xlsx")
public class TeacherStatisticsImportDTO {
@ImportColumn(required = true, columnWidth = 100)
@ExcelProperty(value = "统计年份")
private String year;
@ImportColumn(required = true)
@ExcelProperty(value = "统计月份")
private String month;
@ExcelProperty(value = "讲师区分类型")
@DictFormat(dictType = "account_status", isSelected = true) // 字典下拉
private String teacherCommonType;
@ExcelEnumFormat(preset = ExcelEnumPreset.YES_NO) // 枚举下拉
@ExcelProperty(value = "是否无效")
private YesNoEnum hasInvalid;
@ExcelIgnore // 不出现在模板中
private String checkStatus;
}@ImportColumn
字段级注解,控制该列在导入模板中的必填校验和列宽。
| 属性 | 说明 | 默认值 |
|---|---|---|
required | 是否必填。true 时表头自动加红色 * 前缀,并在 Excel 中生成非空数据验证弹框 | false |
columnWidth | 指定列宽(字符单位)。-1 表示自动计算 | -1 |
@EnableExcelTemplateScan
标注在 Spring Boot 启动类或配置类上,启动时扫描指定包路径,将所有带 @ExcelTemplate 的 DTO 类注册到模板中心。basePackages 支持 * 通配符。
@SpringBootApplication
@EnableExcelTemplateScan(basePackages = "com.sz.sso.*.pojo.dto")
public class AdminApplication { ... }模板自动生成三级查找策略
配置好注解后,前端通过模板下载接口即可获得自动生成的空白导入模板,无需手动维护 Excel 文件。
按以下优先级查找:
classpath:/templates/{templateName}— 静态模板文件(优先)sys_temp_file表 — 手动上传到系统的模板文件ExcelTemplateScanRegistry按alias动态生成 — 兜底策略,根据 DTO 注解实时生成
必填列效果:@ImportColumn(required = true) 的字段在生成的模板中,表头文字前自动追加红色 * 前缀,且选中该列数据格时弹出"此列为必填字段"的输入提示。

统一导入框架
框架提供抽象基类 AbstractExcelImportTemplate<T>,业务侧继承后只需实现核心业务逻辑,框架自动处理:批次创建、分片执行、失败记录落库、批次状态更新。
必须实现的方法
| 方法 | 说明 |
|---|---|
importDtoClass() | 返回导入 DTO 的 Class |
bizType() | 业务类型字符串,用于失败记录归类(如 "teacher_statistics") |
bizName() | 业务名称,写入批次记录(如 "教师统计导入") |
doImport(batchId, rows) | 执行实际业务写入,返回 ExcelImportBizResult(含成功数和失败明细) |
convertExcelFailItems(failRows) | 将 FastExcel 解析失败行转换为统一的 ExcelImportFailItem |
可选钩子
| 方法 | 说明 | 默认行为 |
|---|---|---|
chunkSize() | 分片大小,0 表示不分片 | 0 |
beforeImport(batchId, rows) | 导入前回调 | 空实现 |
afterChunk(batchId, i, total, result) | 每片执行后回调 | 空实现 |
afterImport(batchId, aggregated) | 全部执行完毕后回调 | 空实现 |
onFailBatch(batchId, ex) | 批次异常时回调 | 空实现 |
完整使用示例
// ① 导入 DTO(带注解)
@ExcelTemplate(alias = "教师统计导入模板.xlsx")
public class TeacherStatisticsImportDTO {
@ImportColumn(required = true)
@ExcelProperty("统计年份")
private String year;
@ImportColumn(required = true)
@ExcelProperty("统计月份")
private String month;
@ExcelProperty("教师id")
private String teacherId;
// ... 其他字段
}
// ② 业务导入器
@Component
public class TeacherStatisticsExcelImporter
extends AbstractExcelImportTemplate<TeacherStatisticsImportDTO> {
@Autowired
private TeacherStatisticsMapper mapper;
@Override
protected Class<TeacherStatisticsImportDTO> importDtoClass() {
return TeacherStatisticsImportDTO.class;
}
@Override
protected String bizType() { return "teacher_statistics"; }
@Override
protected String bizName() { return "教师统计导入"; }
@Override
protected ExcelImportBizResult doImport(String batchId,
List<TeacherStatisticsImportDTO> rows) {
List<ExcelImportFailItem> failItems = new ArrayList<>();
List<TeacherStatisticsPO> successList = new ArrayList<>();
for (int i = 0; i < rows.size(); i++) {
TeacherStatisticsImportDTO dto = rows.get(i);
if (someValidationFail(dto)) {
failItems.add(ExcelImportFailItem.builder()
.rowNo(i + 2)
.bizKey(dto.getTeacherId())
.bizKeyLabel("教师ID")
.errorMsg("校验失败原因描述")
.build());
continue;
}
successList.add(convert(dto));
}
mapper.insertBatch(successList);
return ExcelImportBizResult.of(successList.size(), failItems);
}
@Override
protected List<ExcelImportFailItem> convertExcelFailItems(
List<ExcelFailRow<TeacherStatisticsImportDTO>> failRows) {
return failRows.stream()
.map(row -> buildExcelFailItem(row,
TeacherStatisticsImportDTO::getTeacherId, "教师ID"))
.toList();
}
}
// ③ Controller 接口
@PostMapping("/import")
@SaCheckPermission("teacher.statistics.import")
public ApiResult<ExcelImportResultVO> importExcel(@ModelAttribute ImportExcelDTO dto) {
return ApiResult.success(teacherStatisticsService.importExcel(dto));
}错误处理
导入失败分两类,框架统一收口:
- 解析失败:表头不匹配、数据格式错误等,由
convertExcelFailItems()转换 - 业务校验失败:在
doImport()中判断后加入失败列表
框架自动将批次信息和失败记录落库至 sys_import_batch / sys_import_fail_record,无需业务代码干预。
接口返回结构 ExcelImportResultVO
| 字段 | 说明 |
|---|---|
batchId | 批次 ID(UUID),唯一标识本次导入任务 |
success | 成功条数 |
fail | 失败条数 |
failDetails | 失败明细列表 |
前端导入弹窗
框架提供通用导入弹窗组件 ImportExcel,封装了模板下载、文件上传、进度反馈、结果展示的完整流程。
组件 Props(acceptParams 参数)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
title | string | ✅ | 弹窗标题(显示为"导入:{title}") |
alias | string | ✅ | 对应 @ExcelTemplate.alias,用于模板下载查找 |
fileName | string | ✅ | 下载的模板文件名 |
templateName | string | 模板展示名(不传则与 fileName 相同) | |
importApi | Function | ✅ | 调用导入接口的方法 |
tempApi | Function | 下载模板接口方法(不传则使用框架默认) | |
getTableList | Function | 导入成功后刷新表格的回调 | |
param | object | 透传给导入接口的额外参数 | |
fileSize | number | 最大上传文件大小(MB,默认 5) | |
resultTip | string | 有失败条数时显示的引导提示文案 |
调用示例
const ImportExcelRef = ref<InstanceType<typeof ImportExcel>>()
ImportExcelRef.value?.acceptParams({
title: '教师统计',
alias: '教师统计导入模板.xlsx',
fileName: '教师统计导入模板.xlsx',
tempApi: downloadTemplate,
importApi: importTeacherStatisticsExcelApi,
getTableList: proTableRef.value?.getTableList,
resultTip: '失败结果可到【Excel失败记录 - 教师统计导入】页面查看'
})导入完成后自动弹出结果弹窗,展示批次 ID(可复制)、成功/失败条数,以及引导提示。

