当前位置: 首页 > news >正文

java每日精进 5.19【Excel 导入导出】

基于 EasyExcel 实现 Excel 的读写操作,可用于实现最常见的 Excel 导入导出等功能。

Excel 导入导出功能涉及前后端协作,后端处理数据查询、文件生成和解析,前端提供用户交互和文件下载/上传界面。以下是全流程解析,分为导出流程和导入流程。

1.1 导出流程解析
1.1.1 后端导出(PostController.java)

代码

@GetMapping("/export")
@Operation(summary = "岗位管理")
@PreAuthorize("@ss.hasPermission('system:post:export')")
@ApiAccessLog(operateType = EXPORT)
public void export(HttpServletResponse response, @Validated PostPageReqVO reqVO) throws IOException {// ① 查询数据reqVO.setPageSize(PageParam.PAGE_SIZE_NONE);List<PostDO> list = postService.getPostPage(reqVO).getList();// ② 导出 ExcelExcelUtils.write(response, "岗位数据.xls", "岗位列表", PostRespVO.class,BeanUtils.toBean(list, PostRespVO.class));
}

解析

  • 功能:导出岗位数据为 Excel 文件,供前端下载。
  • 步骤
    1. 查询数据
      • reqVO.setPageSize(PageParam.PAGE_SIZE_NONE):禁用分页,获取所有符合条件的数据。
      • postService.getPostPage(reqVO):调用服务层查询岗位数据,返回 PageResult<PostDO>。
      • getList():提取数据列表(List<PostDO>)。
    2. 数据转换
      • BeanUtils.toBean(list, PostRespVO.class):将 PostDO 列表转换为 PostRespVO 列表,适配 Excel 导出格式。
    3. 导出 Excel
      • ExcelUtils.write:将数据写入 Excel 文件,设置文件名(岗位数据.xls)、Sheet 名(岗位列表)和响应头。
  • 权限控制
    • @PreAuthorize("@ss.hasPermission('system:post:export')"):确保用户有导出权限。
  • 日志记录
    • @ApiAccessLog(operateType = EXPORT):记录导出操作日志。
1.1.2 VO 类(PostRespVO.java)

代码

@Schema(description = "管理后台 - 岗位信息 Response VO")
@Data
@ExcelIgnoreUnannotated
public class PostRespVO {@Schema(description = "岗位序号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")@ExcelProperty("岗位序号")private Long id;@Schema(description = "岗位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小土豆")@ExcelProperty("岗位名称")private String name;@Schema(description = "岗位编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")@ExcelProperty("岗位编码")private String code;@Schema(description = "显示顺序不能为空", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")@ExcelProperty("岗位排序")private Integer sort;@Schema(description = "状态,参见 CommonStatusEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")@ExcelProperty(value = "状态", converter = DictConvert.class)@DictFormat(DictTypeConstants.COMMON_STATUS)private Integer status;@Schema(description = "备注", example = "快乐的备注")private String remark;@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)private LocalDateTime createTime;
}

解析

  • 功能:定义 Excel 导出的数据结构和格式。
  • 注解
    • @ExcelIgnoreUnannotated:忽略未标注 @ExcelProperty 的字段(如 remark、createTime),不导出到 Excel。
    • @ExcelProperty:指定 Excel 列名(如 岗位序号、岗位名称)。
    • @ExcelProperty(value = "状态", converter = DictConvert.class):使用 DictConvert 转换器,将 status(如 1)转换为文字(如 开启)。
    • @DictFormat(DictTypeConstants.COMMON_STATUS):指定字典类型,映射 status 值(0=禁用, 1=开启)。
  • 作用:确保导出的 Excel 列名和数据格式符合预期,字段值经过转换(如状态从数字转为文字)。
1.1.3 工具类(ExcelUtils.java - 导出部分)

代码

public static <T> void write(HttpServletResponse response, String filename, String sheetName,Class<T> head, List<T> data) throws IOException {EasyExcel.write(response.getOutputStream(), head).autoCloseStream(false).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).registerWriteHandler(new SelectSheetWriteHandler(head)).registerConverter(new LongStringConverter()).sheet(sheetName).doWrite(data);response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));response.setContentType("application/vnd.ms-excel;charset=UTF-8");
}

解析

  • 功能:将数据列表写入 Excel 文件,返回给前端。
  • 步骤
    1. 初始化 EasyExcel
      • EasyExcel.write(response.getOutputStream(), head):使用 EasyExcel 写入响应流,指定 VO 类(如 PostRespVO)。
    2. 配置写入
      • autoCloseStream(false):不自动关闭流,由 Servlet 管理。
      • LongestMatchColumnWidthStyleStrategy:自动调整列宽。
      • SelectSheetWriteHandler:支持下拉框(基于 VO 注解)。
      • LongStringConverter:防止 Long 类型精度丢失。
    3. 写入数据
      • sheet(sheetName).doWrite(data):写入数据到指定 Sheet(如 岗位列表)。
    4. 设置响应头
      • Content-Disposition:指定文件名(如 岗位数据.xls)。
      • Content-Type:设置为 Excel 格式(application/vnd.ms-excel)。
  • 作用:将 PostRespVO 列表转换为 Excel 文件,自动生成表头和数据行。
1.1.4 前端导出(post/index.vue)

代码

handleExport() {const queryParams = this.queryParams;this.$modal.confirm('是否确认导出所有岗位数据项?').then(() => {this.exportLoading = true;return exportPost(queryParams);}).then(response => {this.$download.excel(response, '岗位数据.xls');}).finally(() => {this.exportLoading = false;});
}

解析

  • 功能:触发岗位数据导出,下载 Excel 文件。
  • 步骤
    1. 用户交互
      • 用户点击“导出”按钮,弹出确认框。
    2. 发送请求
      • exportPost(queryParams):调用后端 /admin-api/system/post/export,传递查询参数(如岗位名称、状态)。
    3. 处理响应
      • this.$download.excel(response, '岗位数据.xls'):将响应(Excel 文件流)保存为本地文件。
    4. 状态管理
      • exportLoading:控制加载状态,防止重复点击。
  • 作用:提供用户界面,触发导出请求,处理下载。
1.2 导入流程解析
1.2.1 后端导入(UserController.java)

代码

@PostMapping("/import")
@Operation(summary = "导入用户")
@Parameters({@Parameter(name = "file", description = "Excel 文件", required = true),@Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true")
})
@PreAuthorize("@ss.hasPermission('system:user:import')")
public CommonResult<UserImportRespVO> importExcel(@RequestParam("file") MultipartFile file,@RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception {List<UserImportExcelVO> list = ExcelUtils.read(file, UserImportExcelVO.class);return success(userService.importUserList(list, updateSupport));
}

解析

  • 功能:从 Excel 文件导入用户数据,保存到数据库。
  • 步骤
    1. 解析 Excel
      • ExcelUtils.read(file, UserImportExcelVO.class):读取 Excel 文件,转换为 List<UserImportExcelVO>。
    2. 处理数据
      • userService.importUserList(list, updateSupport):将解析的数据导入数据库,支持更新现有用户(updateSupport=true)。
    3. 返回结果
      • CommonResult<UserImportRespVO>:返回导入结果(如成功/失败记录数)。
  • 参数
    • file:上传的 Excel 文件(MultipartFile)。
    • updateSupport:是否更新已有用户(默认 false)。
  • 权限控制
    • @PreAuthorize("@ss.hasPermission('system:user:import')"):确保用户有导入权限。
1.2.2 VO 类(UserImportExcelVO.java)

code

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = false)
public class UserImportExcelVO {@ExcelProperty("登录名称")private String username;@ExcelProperty("用户名称")private String nickname;@ExcelProperty("部门编号")private Long deptId;@ExcelProperty("用户邮箱")private String email;@ExcelProperty("手机号码")private String mobile;@ExcelProperty(value = "用户性别", converter = DictConvert.class)@DictFormat(DictTypeConstants.USER_SEX)private Integer sex;@ExcelProperty(value = "账号状态", converter = DictConvert.class)@DictFormat(DictTypeConstants.COMMON_STATUS)private Integer status;
}

解析

  • 功能:定义 Excel 导入的数据结构。
  • 注解
    • @ExcelProperty:指定 Excel 列名(如 登录名称、用户名称)。
    • @ExcelProperty(value = "用户性别", converter = DictConvert.class):将文字(如 男)转换为数字(如 1),基于 USER_SEX 字典。
    • @DictFormat:指定字典类型(如 USER_SEX、COMMON_STATUS)。
    • @Accessors(chain = false):禁用链式 setter,防止导入时序列化问题。
  • 作用:映射 Excel 列到 Java 对象,确保导入数据正确解析。
1.2.3 工具类(ExcelUtils.java - 导入部分)

Code

public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException {return EasyExcel.read(file.getInputStream(), head, null).autoCloseStream(false).doReadAllSync();
}

解析

  • 功能:从 Excel 文件读取数据,转换为 Java 对象列表。
  • 步骤
    1. 初始化 EasyExcel
      • EasyExcel.read(file.getInputStream(), head, null):读取 Excel 文件,指定 VO 类(如 UserImportExcelVO)。
    2. 读取数据
      • doReadAllSync():同步读取所有数据,返回 List<T>。
    3. 流管理
      • autoCloseStream(false):不自动关闭流,由 Servlet 管理。
  • 作用:将 Excel 行转换为 UserImportExcelVO 对象,供后端处理。
1.2.4 前end导入(post/index.vue - 用户导入部分)

Code

<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body><el-upload ref="upload" :limit="1" accept=".xlsx, .xls" :headers="upload.headers":action="upload.url + '?updateSupport=' + upload.updateSupport" :disabled="upload.isUploading":on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag><i class="el-icon-upload"></i><div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div><div class="el-upload__tip text-center" slot="tip"><div class="el-upload__tip" slot="tip"><el-checkbox v-model="upload.updateSupport" /> 是否更新已经存在的用户数据</div><span>仅允许导入xls、xlsx格式文件。</span><el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link></div></el-upload><div slot="footer" class="dialog-footer"><el-button type="primary" @click="submitFileForm">确 定</el-button><el-button @click="upload.open = false">取 消</el-button></div>
</el-dialog>

解析

  • 功能:提供用户界面,上传 Excel 文件,触发导入。
  • 步骤
    1. 用户交互
      • 用户点击“导入”按钮,打开对话框,选择 Excel 文件(.xls 或 .xlsx)。
      • 勾选 updateSupport(是否更新现有用户)。
    2. 上传配置
      • accept=".xlsx, .xls":限制文件类型。
      • action="upload.url + '?updateSupport=' + upload.updateSupport":动态拼接 URL(如 /admin-api/system/user/import?updateSupport=true)。
      • headers:携带认证 token(Authorization: Bearer xxx)。
      • on-progress:显示上传进度。
      • on-success:处理导入结果,显示成功/失败信息。
    3. 触发上传
      • submitFileForm:调用 this.$refs.upload.submit(),发送 POST /admin-api/system/user/import 请求。
    4. 模板下载
      • importTemplate:下载 Excel 模板,引导用户按格式填写。
  • 作用:提供用户友好的导入界面,支持文件选择和上传。

Excel 全流程总结

导出流程

  1. 前端触发:用户在 post/index.vue 点击“导出”按钮,调用 handleExport,发送 GET /admin-api/system/post/export 请求。
  2. 后端查询:PostController 查询岗位数据(PostDO),转换为 PostRespVO。
  3. 后端生成 Excel:ExcelUtils.write 使用 EasyExcel 将 PostRespVO 列表写入 Excel,设置响应头。
  4. 前端下载:前端接收 Excel 文件流,调用 $download.excel 保存为 岗位数据.xls。

导入流程

  1. 前端触发:用户在 post/index.vue 打开导入对话框,选择 Excel 文件,设置 updateSupport,点击“确定”触发上传。
  2. 前端上传:<el-upload> 发送 POST /admin-api/system/user/import 请求,携带 Excel 文件和参数。
  3. 后端解析:UserController 使用 ExcelUtils.read 解析 Excel 为 List<UserImportExcelVO>。
  4. 后一致性:userService.importUserList 处理数据,保存到数据库,返回导入结果(UserImportRespVO)。
  5. 前端反馈:前端显示导入结果(如成功/失败记录数)。

字段转换原理解析

EasyExcel Converter 接口

  • EasyExcel 的 Converter 接口用于在 Excel 和 Java 对象之间进行字段值转换,处理数据格式不一致的情况(如 Excel 中的文字与 Java 中的数字)。
  • 核心方法
    1. convertToJavaData:将 Excel 单元格值转换为 Java 对象字段值。
      • 示例:Excel 的“开启”转换为 Java 的 status = 1。
    2. convertToExcelData:将 Java 对象字段值转换为 Excel 单元格值。
      • 示例:Java 的 status = 1转换为 Excel 的“开启”。

DictConvert 作用

  • DictConvert 是自定义转换器,基于字典框架(DictFrameworkUtils)实现字段值与字典标签的转换。
  • 用途:将数字(如 1)与文字(如 开启)相互映射,常用于状态、性别等枚举字段。

2. DictConvert 代码解析

以下是 DictConvert 的代码和逐行解析:

@Slf4j
public class DictConvert implements Converter<Object> {@Overridepublic Class<?> supportJavaTypeKey() {throw new UnsupportedOperationException("暂不支持,也不需要");}@Overridepublic CellDataTypeEnum supportExcelTypeKey() {throw new UnsupportedOperationException("暂不支持,也不需要");}@Overridepublic Object convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty,GlobalConfiguration globalConfiguration) {// 使用字典解析String type = getType(contentProperty);String label = readCellData.getStringValue();String value = DictFrameworkUtils.parseDictDataValue(type, label);if (value == null) {log.error("[convertToJavaData][type({}) 解析不掉 label({})]", type, label);return null;}// 将 String 的 value 转换成对应的属性Class<?> fieldClazz = contentProperty.getField().getType();return Convert.convert(fieldClazz, value);}@Overridepublic WriteCellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty,GlobalConfiguration globalConfiguration) {// 空时,返回空if (object == null) {return new WriteCellData<>("");}// 使用字典格式化String type = getType(contentProperty);String value = String.valueOf(object);String label = DictFrameworkUtils.getDictDataLabel(type, value);if (label == null) {log.error("[convertToExcelData][type({}) 转换不了 label({})]", type, value);return new WriteCellData<>("");}// 生成 Excel 小表格return new WriteCellData<>(label);}private static String getType(ExcelContentProperty contentProperty) {return contentProperty.getField().getAnnotation(DictFormat.class).value();}
}

解析

  • 接口实现
    • 实现 Converter<Object>,支持任意类型的字段转换。
    • supportJavaTypeKey 和 supportExcelTypeKey 未实现,表明 DictConvert 不限制字段类型,依靠注解动态处理。
  • convertToJavaData(Excel -> Java):
    1. 获取字典类型
      • getType(contentProperty):从字段的 @DictFormat 注解获取字典类型(如 COMMON_STATUS)。
    2. 读取 Excel 值
      • readCellData.getStringValue():获取 Excel 单元格的字符串值(如 开启)。
    3. 字典解析
      • DictFrameworkUtils.parseDictDataValue(type, label):将文字(如 开启)转换为字典值(如 1)。
      • 示例:COMMON_STATUS 字典可能定义 { "0": "禁用", "1": "开启" }。
    4. 类型转换
      • Convert.convert(fieldClazz, value):将字符串值(如 "1")转换为字段类型(如 Integer)。
    5. 错误处理
      • 如果 value 为 null(字典未找到对应值),记录错误日志,返回 null。
  • convertToExcelData(Java -> Excel):
    1. 空值处理
      • 如果 object 为 null,返回空字符串。
    2. 获取字典类型
      • 同上,通过 @DictFormat 获取 type。
    3. 字典转换
      • DictFrameworkUtils.getDictDataLabel(type, value):将 Java 值(如 1)转换为字典标签(如 开启)。
    4. 返回结果
      • WriteCellData<String>(label):将标签写入 Excel 单元格。
    5. 错误处理
      • 如果 label 为 null,记录错误,返回空字符串。
  • getType
    • 提取字段上的 @DictFormat 注解值(如 COMMON_STATUS),确定字典类型。

作用

  • 实现 Excel 和 Java 之间的双向转换,确保状态、性别等字段在导入导出时保持一致(如 1 <-> 开启)。
  • 依赖 DictFrameworkUtils 提供的字典数据(如 COMMON_STATUS、USER_SEX)。

4. EasyExcel 注解解析

以下是文档中提到的 EasyExcel 注解,结合代码和上下文说明其作用,并提供实例。

4.1 @ExcelProperty
  • 作用:指定 Excel 列名、序号或转换器。
  • 参数
    • value:列名(如 "岗位名称")。
    • index:列序号(与 value 二选一)。
    • converter:转换器(如 DictConvert.class)。
  • 示例

    @ExcelProperty("岗位名称") private String name;

    • 输出:Excel 列名为 岗位名称,值来自 name 字段。
4.2 @ColumnWidth
  • 作用:设置列宽(单位:字符)。
  • 示例

    @ColumnWidth(18) @ExcelProperty("岗位编码") private String code;

    • 输出:岗位编码 列宽为 18 个字符。
4.3 @ContentFontStyle
  • 作用:设置单元格字体样式。
  • 示例

    @ContentFontStyle(fontName = "Arial", fontHeightInPoints = 12, bold = true) @ExcelProperty("岗位名称") private String name;

    • 输出:岗位名称 列使用 Arial 字体,12 磅,加粗。
4.4 @ContentLoopMerge
  • 作用:合并单元格。
  • 示例

    @ContentLoopMerge(eachRow = 2) @ExcelProperty("部门") private String dept;

    • 输出:每 2 行合并 部门 列。
4.5 @ContentRowHeight
  • 作用:设置行高。
  • 示例

    @ContentRowHeight(20) @ExcelProperty("备注") private String remark;

    • 输出:备注 行高为 20。
4.6 @ContentStyle
  • 作用:设置单元格样式(如对齐、边框、背景色)。
  • 示例

    @ContentStyle(horizontalAlignment = HorizontalAlignment.CENTER, fillBackgroundColor = IndexedColors.YELLOW) @ExcelProperty("状态") private Integer status;

    • 输出:状态 列居中,黄色背景。
4.7 @HeadFontStyle
  • 作用:设置标题字体样式。
  • 示例

    @HeadFontStyle(fontName = "Calibri", bold = true) @ExcelProperty("岗位名称") private String name;

    • 输出:岗位名称 标题使用 Calibri 字体,加粗。
4.8 @HeadRowHeight
  • 作用:设置标题行高。
  • 示例

    @HeadRowHeight(25) @ExcelProperty("岗位编码") private String code;

    • 输出:标题行高为 25。
4.9 @HeadStyle
  • 作用:设置标题样式。
  • 示例

    @HeadStyle(fillForegroundColor = IndexedColors.BLUE) @ExcelProperty("状态") private Integer status;

    • 输出:状态 标题蓝色背景。
4.10 @ExcelIgnore
  • 作用:忽略字段,不导出到 Excel。
  • 示例

    @ExcelIgnore private String internalCode;

    • 输出:internalCode 不出现在 Excel 中。
4.11 @ExcelIgnoreUnannotated
  • 作用:忽略未标注 @ExcelProperty 的字段。
  • 示例

    @ExcelIgnoreUnannotated public class PostRespVO { @ExcelProperty("岗位名称") private String name; private String remark; // 未标注 }

    • 输出:remark 不导出。

相关文章:

  • 《虚实共生:双向映射重塑具身智能决策逻辑》
  • 如何在 MongoDB 中设计文档结构?与关系型数据库的表结构设计有何不同?
  • FPGA 串口_波特率计算
  • 以用户为中心的产品才是好产品
  • 使用Python和FastAPI构建网站爬虫:Oncolo医疗文章抓取实战
  • 企业开发工具git的使用:从入门到高效团队协作
  • 【MySQL进阶】了解linux操作系统下mysql的配置文件和常用选项
  • MySQL故障排查与生产环境优化
  • 二分交互题总结
  • C# NX二次开发-求体、面的最小包容圆柱
  • 游戏引擎学习第294天:增加手套
  • 仓颉开发语言入门教程:搭建开发环境
  • Elasticsearch 深入分析三种分页查询【Elasticsearch 深度分页】
  • 2.微服务-配置
  • Jenkins 使用技巧
  • Dify-3:系统架构
  • JavaScript 中使用 Elasticsearch 的正确方式,第一部分
  • windows服务器部署jenkins工具
  • 26、DAPO论文笔记(解耦剪辑与动态采样策略优化,GRPO的改进)
  • GraphQL在.NET 8中的全面实践指南
  • 达恩当选罗马尼亚总统
  • 商务部就美国商务部调整芯片出口管制有关表述答记者问
  • 上海银行副行长汪明履新上海农商银行党委副书记
  • 不赚“快钱”的佳沛:蒋时杰解密新西兰国果如何在中国“慢养”出43亿生意
  • 国际博物馆日|航海博物馆:穿梭于海洋神话与造船工艺间
  • 南宁一学校发生伤害案件,警方通报:嫌疑人死亡,2人受伤