Skip to content

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 示例代码
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 方法示例代码
java
  @SneakyThrows
  @Override
  public void importExcel(ImportExcelDTO dto) {
...        
      ExcelResult<TeacherStatisticsImportDTO> excelResult = ExcelUtils.importExcel(dto.getFile().getInputStream(), TeacherStatisticsImportDTO.class, true);
...
  }
  • 步骤三:处理导入结果
excelResult 示例代码
java
...
      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 示例代码
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方法示例代码
java
  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 操作。

以下是如何使用这些注解的详细说明,以“讲师区分类型”字典为例。

KeyValue字典类型
1000001正常讲师区分类型
1000002禁用讲师区分类型
1000003禁言讲师区分类型

导入字典映射

在导入实体的对应字段上应用 @DictFormat 注解,以指定相应的字典项。这一操作确保在导入过程中,字典中的显示值(Value)如“正常”将被自动转换为对应的键值(Key),例如从 “正常” 转换为 1000001

java
...
    @ExcelProperty(value = "讲师区分类型")
    @DictFormat(dictType = "account_status")
    @Schema(description = "讲师区分类型")
    private String teacherCommonType;    
...

效果

import-excel-column

import-excel-console.png

导出字典映射

在导出实体的对应字段上添加@DictFormat注解指定字典项。这一操作确保在导入过程中,字典中的键值(Key)将被自动转换为对应的显示值(Value),例如从 1000001转换为 “正常”

java
...
    @ExcelProperty(value = "讲师区分类型")
    @DictFormat(dictType = "account_status")
    @Schema(description = "讲师区分类型")
    private String teacherCommonType;
...

效果

export-excel-sql.png

export-excel-xls.png

合并单元格相同项

为了在导出的 Excel 文件中对具有相同值的列进行单元格合并,您可以在导出实体的对应字段上添加 @CellMerge 注解。这将自动合并列中相同的项,从而提供一个更加整洁和易于阅读的表格视图。

java
...
    @ExcelProperty(value = "统计年月")
    @Schema(description = "统计年月")
    @CellMerge
    private String duringTime;  
...

效果

export-cell-merge.png

导入表头验证

在处理 Excel 文件导入时,确保数据的准确性和完整性是非常重要的。sz-admin 提供了表头验证功能,以确保导入的 Excel 文件包含所有必要的列。在ExcelUtils.importExcel()方法中,通过设置第三个参数validateHeadertrue,可以启用这一验证机制。

java
...    
	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();
    }
...

效果

sz-admin Excel 导入模板表头缺少颜色标注错误示例截图

导入模板生成

NOTE

以下功能为 v1.3.4-beta 新增,向后兼容,无需迁移

sz-admin 提供三个注解,配合 AbstractExcelImportTemplate 导入框架,实现导入模板自动生成与统一的导入流程管理。

@ExcelTemplate

标注在导入 DTO 类上,将该类注册为可下载的导入模板。

属性说明默认值
alias模板唯一标识,同时作为下载文件名(如 "教师统计导入模板.xlsx"必填
validRowsExcel 数据验证覆盖行数(从第 2 行起)1000
java
@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 支持 * 通配符。

java
@SpringBootApplication
@EnableExcelTemplateScan(basePackages = "com.sz.sso.*.pojo.dto")
public class AdminApplication { ... }

模板自动生成三级查找策略

配置好注解后,前端通过模板下载接口即可获得自动生成的空白导入模板,无需手动维护 Excel 文件。

按以下优先级查找:

  1. classpath:/templates/{templateName} — 静态模板文件(优先)
  2. sys_temp_file 表 — 手动上传到系统的模板文件
  3. ExcelTemplateScanRegistryalias 动态生成 — 兜底策略,根据 DTO 注解实时生成

必填列效果@ImportColumn(required = true) 的字段在生成的模板中,表头文字前自动追加红色 * 前缀,且选中该列数据格时弹出"此列为必填字段"的输入提示。

Excel 导入模板表头,必填列红色 * 标注

统一导入框架

框架提供抽象基类 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)批次异常时回调空实现
完整使用示例
java
// ① 导入 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 参数)

参数类型必填说明
titlestring弹窗标题(显示为"导入:{title}")
aliasstring对应 @ExcelTemplate.alias,用于模板下载查找
fileNamestring下载的模板文件名
templateNamestring模板展示名(不传则与 fileName 相同)
importApiFunction调用导入接口的方法
tempApiFunction下载模板接口方法(不传则使用框架默认)
getTableListFunction导入成功后刷新表格的回调
paramobject透传给导入接口的额外参数
fileSizenumber最大上传文件大小(MB,默认 5
resultTipstring有失败条数时显示的引导提示文案

调用示例

typescript
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(可复制)、成功/失败条数,以及引导提示。

导入结果弹窗