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

EasyExcel 实现国际化导入导出

新项目涉及国际化。不想每个语言都建一个pojo文件。就将字段信息放到了数据库中。因为数据体量小,都放在了一个表中,可以根据需要改成一个语言一个表,或者是一个类多个语言一个表。
思路:
下载模版: 查询导入模版的字段,使用EasyExcel实现文件导出
导入: 查询导入模版的字段,利用EasyExcel 回调监听,获取到excel的数据。使用反射将表头转换成字段名,构造对像并给对象赋值,获得对象数据。之后就可以根据实际需求对数据进行新增或修改。
导出:查询导出模版字段,查询导出数据,使用EasyExcel实现文件导出。比下载模版多一个查询导出数据。

新建数据库表

entity_header 表字段

字段名描述
id主键ID
entity对象名,也可以认为是关键字,用来区分这个字段是属于谁
lang语言,例如:en、zh-CN
field字段名,实体类中的字段,如 name
translate字段的翻, 例如: 姓名、Name
sort排序,文件导出是字段的顺序。如果顺序要求严格,也可以用作导入的列字段判断
remark备注信息
CREATE TABLE `entity_header` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',`entity` varchar(50) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '对象',`lang` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '语言标识',`field` varchar(50) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '字段名称',`translate` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '翻译后的字段名称',`sort` int(11) DEFAULT NULL COMMENT '显示顺序',`remark` varchar(200) DEFAULT NULL COMMENT '备注',`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='对象表头字段翻译';

后端实现

新建导入导出VO


import com.alibaba.excel.annotation.ExcelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;/*** 包装容器 Excel 导入 VO*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = false) // 设置 chain = false,避免导入有问题
public class PackingContainerImportExcelVO {private Long oemId;@ExcelProperty("代工厂")private String oemName;@ExcelProperty("包装容器号")private String code;@ExcelProperty("rfid")private String rfid;@ExcelProperty("重量下限")private String weightMin;@ExcelProperty("重量上限")private String weightMax;@ExcelProperty("皮重")private String tareWeight;@ExcelProperty("容器型号")private String packagingContainerType;@ExcelProperty("容器容积")private String packagingVolume;}
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;import java.util.List;
import java.util.Map;@Schema(description = "包装容器导入 Response VO")
@Data
@Builder
public class PackingContainerImportRespVO {@Schema(description = "创建成功的包装容器数组", requiredMode = Schema.RequiredMode.REQUIRED)private List<String> createCodes;@Schema(description = "更新成功的包装容器数组", requiredMode = Schema.RequiredMode.REQUIRED)private List<String> updateCodes;@Schema(description = "导入失败的包装容器集合,key 为容器编号,value 为失败原因", requiredMode = Schema.RequiredMode.REQUIRED)private Map<String, String> failureCodes;}

Controller文件增加方法

	@GetMapping("/get-import-template")@Operation(summary = "获得导入包装容器模板")public void importTemplate(HttpServletResponse response,@RequestAttribute(value = "lang", required = false) String lang ) throws IOException {// 输出List<EntityHeaderDO> entityHeaderList = entityHeaderService.getEntityHeaderList(PackingContainerImportExcelVO.class.getSimpleName(), lang);if(CollectionUtil.isEmpty(entityHeaderList)){throw exception(ENTITY_FIELDS_TEMPLATE_NOT_EXISTS);}List<String> headerList = entityHeaderList.stream().map(EntityHeaderDO::getTranslate).collect(Collectors.toList());ExcelUtils.writeTemplate(response,headerList,"container_data_template.xls");}@PostMapping("/import")@Operation(summary = "导入包装容器")@Parameters({@Parameter(name = "file", description = "Excel 文件", required = true),@Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true")})@PreAuthorize("@ss.hasPermission('packing-container-info:import')")@Transactional(rollbackFor = Exception.class)public CommonResult<PackingContainerImportRespVO> importExcel(@RequestParam("file") MultipartFile file,@RequestAttribute(value = "lang", required = false) String lang ,@RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception {List<EntityHeaderDO> headerList = entityHeaderService.getEntityHeaderList(PackingContainerImportExcelVO.class.getSimpleName(), lang);if(CollectionUtil.isEmpty(headerList)){throw exception(ENTITY_FIELDS_TEMPLATE_NOT_EXISTS);}Map<String, String> headMap = headerList.stream().collect(Collectors.toMap(EntityHeaderDO::getTranslate, EntityHeaderDO::getField, (v1, v2) -> v1));List<PackingContainerImportExcelVO> list = excelImportService.importExcel(file, PackingContainerImportExcelVO.class, headMap);return success(packingContainerInfoService.importContainerList(list,updateSupport));}@GetMapping("/export-excel")@Operation(summary = "导出包装容器 Excel")@PreAuthorize("@ss.hasPermission('packing-container-info:export')")@ApiAccessLog(operateType = EXPORT)public void exportInspectionStandardDetailExcel(@Valid PackingContainerPageReqVO pageReqVO,@RequestAttribute(value = "lang", required = false) String lang,HttpServletResponse response) throws IOException {pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);List<PackingContainerDO> list = packingContainerInfoService.getPackingContainerPage(pageReqVO).getList();// 输出List<EntityHeaderDO> entityHeaderList = entityHeaderService.getEntityHeaderList(InspectionStandardDetailExcelVO.class.getSimpleName(), lang);if(CollectionUtil.isEmpty(entityHeaderList)){throw exception(ENTITY_FIELDS_TEMPLATE_NOT_EXISTS);}List<String> headerList = entityHeaderList.stream().map(EntityHeaderDO::getTranslate).collect(Collectors.toList());// 导出 Excelif(CollectionUtil.isNotEmpty(list) && LanguageEnum.ENGLISH.getLang().equals(lang)){list.forEach(this::setLangAttrEn);}ExcelUtils.writeExport(response, "包装容器.xls", "data", headerList,BeanUtils.toBean(list, InspectionStandardDetailExcelVO.class));}

ExcelUtils 工具类

import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.converters.longconverter.LongStringConverter;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.mo.framework.common.util.http.HttpUtils;
import com.mo.framework.excel.core.handler.SelectSheetWriteHandler;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;/*** Excel 工具类**/
public class ExcelUtils {/*** 将列表以 Excel 响应给前端** @param response  响应* @param filename  文件名* @param sheetName Excel sheet 名* @param head      Excel head 头* @param data      数据列表哦* @param <T>       泛型,保证 head 和 data 类型的一致性* @throws IOException 写入失败的情况*/public static <T> void write(HttpServletResponse response, String filename, String sheetName,Class<T> head, List<T> data) throws IOException {// 输出 ExcelEasyExcel.write(response.getOutputStream(), head).autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度.registerWriteHandler(new SelectSheetWriteHandler(head)) // 基于固定 sheet 实现下拉框.registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度.sheet(sheetName).doWrite(data);// 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));response.setContentType("application/vnd.ms-excel;charset=UTF-8");}public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException {return EasyExcel.read(file.getInputStream(), head, null).autoCloseStream(false)  // 不要自动关闭,交给 Servlet 自己处理.doReadAllSync();}public static void writeTemplate(HttpServletResponse response,List<String> headerList,String fileName) throws IOException{// 将映射信息转换为表头列表List<List<String>> head = new ArrayList<>();for(String field : headerList){List<String> headerRow = new ArrayList<>();headerRow.add(field);head.add(headerRow);}// 设置响应头response.setContentType("application/vnd.ms-excel;charset=UTF-8");response.setCharacterEncoding("utf-8");fileName = StrUtil.isEmpty(fileName)?"import_template.xlsx":fileName;fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);// 使用 EasyExcel 写入表头并生成模板文件EasyExcel.write(response.getOutputStream()).head(head).sheet("data").doWrite(new ArrayList<>());}public static <T> void writeExport(HttpServletResponse response, String filename, String sheetName,List<String> headerList, List<T> data) throws IOException {// 将映射信息转换为表头列表List<List<String>> head = new ArrayList<>();for(String field : headerList){List<String> headerRow = new ArrayList<>();headerRow.add(field);head.add(headerRow);}// 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));response.setContentType("application/vnd.ms-excel;charset=UTF-8");// 输出 ExcelEasyExcel.write(response.getOutputStream()).head(head).autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度.registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度.sheet(sheetName).doWrite(data);}}

导入功能利用反射将excel数据转成对象数据

使用表头信息与翻译信息匹配,获取字段名称

import com.alibaba.excel.EasyExcel;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;@Service
public class ExcelImportService {public <T> List<T> importExcel(MultipartFile file, Class<T> beanClass, Map<String,String> headerFieldMap) throws Exception{ExcelDataListener listener = new ExcelDataListener();// 使用 EasyExcel 读取 Excel 数据EasyExcel.read(file.getInputStream()).registerReadListener(listener).sheet().doRead();List<T> result = new ArrayList<>();for (Map<String,String> row : listener.getDataList()) {T bean = beanClass.getDeclaredConstructor().newInstance();for (Map.Entry<String, String> entry : row.entrySet()) {String header = entry.getKey();String fieldName = headerFieldMap.get(header); // 表头 → 字段名if (fieldName != null) {Field field = beanClass.getDeclaredField(fieldName);field.setAccessible(true);field.set(bean, entry.getValue());}}result.add(bean);}return result;}public <T> List<T> convertToBeanList(List<Map<String, Object>> dataList, Class<T> beanClass) throws Exception {List<T> beans = new ArrayList<>();for (Map<String, Object> data : dataList) {T bean = beanClass.getDeclaredConstructor().newInstance();for (Map.Entry<String, Object> entry : data.entrySet()) {String fieldName = entry.getKey();Object value = entry.getValue();try {Field field = beanClass.getDeclaredField(fieldName);field.setAccessible(true);if (field.getType() == Integer.class || field.getType() == int.class) {field.set(bean, value == null ? 0 : Integer.parseInt((String) value));} else if (field.getType() == Double.class || field.getType() == double.class) {field.set(bean, value == null ? 0.0 : Double.parseDouble((String) value));} else {field.set(bean, value);}} catch (NoSuchFieldException | IllegalAccessException ignored) {}}beans.add(bean);}return beans;}
}
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import lombok.Getter;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/*** excel 回调监听*/
public class ExcelDataListener extends AnalysisEventListener<Map<Integer, String>> {private final Map<Integer, String> headers = new HashMap<>();@Getterprivate final List<Map<String, String>> dataList = new ArrayList<>();@Overridepublic void invoke(Map<Integer, String> data, AnalysisContext context) {// 构建 header -> value 映射Map<String, String> row = new HashMap<>();for (Map.Entry<Integer, String> entry : data.entrySet()) {String header = headers.get(entry.getKey());if (header != null) {row.put(header, entry.getValue());}}dataList.add(row);}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {// 所有数据解析完成}// excel头部数据@Overridepublic void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {// 存到全局的map中headers.putAll(headMap);}
}

文章转载自:

http://PFy36kOt.pswqx.cn
http://12rJ7X0K.pswqx.cn
http://fLcoGkIR.pswqx.cn
http://gv6DSE71.pswqx.cn
http://BBtOSADQ.pswqx.cn
http://r3ahqoDa.pswqx.cn
http://hvWcF8pf.pswqx.cn
http://7EUy24AW.pswqx.cn
http://1BbIGNZs.pswqx.cn
http://nmg6wuCm.pswqx.cn
http://ZrBFhyqt.pswqx.cn
http://AvE9DT0G.pswqx.cn
http://AqlnJMry.pswqx.cn
http://8rvN24EG.pswqx.cn
http://1T6ZYXnx.pswqx.cn
http://QnHB1gXG.pswqx.cn
http://vQS0IIWk.pswqx.cn
http://8Mcxc9JV.pswqx.cn
http://2sfvWFvI.pswqx.cn
http://YlQ3fl3w.pswqx.cn
http://RDuHSUz3.pswqx.cn
http://j9f9IykR.pswqx.cn
http://hQlTBJfE.pswqx.cn
http://8utU79id.pswqx.cn
http://fsXwppJa.pswqx.cn
http://8zaZtd0h.pswqx.cn
http://ndqWk7JT.pswqx.cn
http://Qak0ATPB.pswqx.cn
http://B2H3s0RA.pswqx.cn
http://ByRZViUU.pswqx.cn
http://www.dtcms.com/a/379042.html

相关文章:

  • 实现联邦学习客户端训练部分的示例
  • 从互联网医院系统源码到应用:智能医保购药平台的开发思路与实操经验
  • 伽马(gamma)变换记录
  • 第3节-使用表格数据-唯一约束
  • 深入浅出 C++20:新特性与实践
  • Java 面向对象三大核心思想:封装、继承与多态的深度解析
  • 蚁群算法详解:从蚂蚁觅食到优化利器
  • 星链计划 | 只赋能、不竞争!蓝卓“数智赋能·星链共生”重庆站沙龙成功举办
  • JavaScript 数组对象的属性、方法
  • vscode选择py解释器提示环境变量错误
  • 【2】标识符
  • Futuring robot旗下家庭机器人F1将于2025年面世
  • HTTPS 错误解析,常见 HTTPS 抓包失败、443 端口错误与 iOS 抓包调试全攻略
  • 利用数据分析提升管理决策水平
  • OC-KVC
  • Linux系统编程—基础IO
  • 考研408计算机网络2023-2024年第33题解析
  • 手眼标定之已知同名点对,求解转换RT,备份记录
  • 《MySQL事务问题与隔离级别,一篇讲透核心考点》
  • 水泵自动化远程监测与控制的御控物联网解决方案
  • Bug排查日记的技术
  • AR眼镜:化工安全生产的技术革命
  • 跨越符号的鸿沟——认知语义学对人工智能自然语言处理的影响与启示
  • 深入理解大语言模型(5)-关于token
  • Node.js-基础
  • JVM垃圾回收的时机是什么时候(深入理解 JVM 垃圾回收时机:什么时候会触发 GC?)
  • Python 版本和Quantstats不兼容的问题
  • SFINAE
  • TCP 三次握手与四次挥手
  • 【iOS】UIViewController生命周期