EasyExcel_动态表头的导入导出

文章目录

  • 前言
  • 一、EasyExcel
  • 二、使用步骤
    • 1.引入jar包
    • 2.数据准备
      • 2.1 数据库
    • 3.方法实例
      • 3.1 无实体的导入
        • 3.1.1 Controller
        • 3.1.2 Service
        • 3.1.3 Listener
        • 3.1.4 Utils
        • 3.1.5 无实体导入数据返回说明
      • 3.2 无实体的导出
        • 3.2.1 无实体导出数据(这里只贴出关键代码,Service代码处理)
        • 3.2.2 Controller
        • 3.2.2 无实体导出总结
      • 原创不易,望一键三连 (^ _ ^)


前言

今天产品提了个需求,导入导的Excel文件表头根据数据库的配置来。
因为之前大部分的导入和导出都是有固定表头或者固定的模板来做导入导出,这个需求。。。嗯,搞起!!!


一、EasyExcel

EasyExcel前面已经有过介绍了,这里不做具体介绍,大家看EasyExcel官网: EasyExcel官网
这里主要参考EasyExcel不创建对象的读和不创建对象的写

二、使用步骤

1.引入jar包

	<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.1.1</version></dependency>

2.数据准备

2.1 数据库

数据库表及数据

3.方法实例

3.1 无实体的导入

3.1.1 Controller
@RestController
@RequestMapping("/product")
@Slf4j
@Api(tags = "产品控制器")
public class ProductController {@PostMapping("/v1/importCountryGroupConf")@ApiOperation(value = "批量导入国家分组配置", httpMethod = "POST")public ResponseBean importCountryGroupConf(@RequestParam("file") MultipartFile file){try {return productService.importCountryGroupConf(file);} catch (Exception e) {log.error("国家分组配置导入报错:具体报错信息:{}",e.getMessage(),e);return ResponseBean.buildFail(50002,"导入失败!!!");}}
}
3.1.2 Service
@Slf4j
@Service
public class ProductService extends BaseService<Product> {public ResponseBean<Void> importCountryGroupConf(MultipartFile file) {// 文件解析及返回List<Map<String, String>> readResults = DynamicHeadImportUtils.importExcel(file);log.info("导入内容====>{}", JSONObject.toJSONString(readResults));// 获取所有国家分组List<CountryGroupConf> groupConfList = groupConfService.lambdaQuery().eq(CountryGroupConf::getDelFlag, DelFlagEnums.NORMAL.getCode()).list();// 将国家分组配置按照国家名称进行分组,返回的Map,key为国家名称,value为国家分组配置的idMap<String, Long> countryGroupConfMap = groupConfList.stream().collect(Collectors.toMap(CountryGroupConf::getGroupName, CountryGroupConf::getId));// 商品Code列表List<String> productCodes = new ArrayList<>();List<Product> updateCondition = new ArrayList<>();for (int i = 0; i < readResults.size(); i++) {int lineNum = i + 2;Product product = new Product();List<Long> groupConfIds = new ArrayList<>();for (Map.Entry<String, String> entry : readResults.get(i).entrySet()) {String key = entry.getKey();String value = entry.getValue();if ("SKU".equals(key)) {if (productCodes.contains(value)) {return ResponseBean.buildFail("第" + lineNum + "行,SKU:" + value + "存在重复!!!");} else {product.setProductCode(value);productCodes.add(value);}} else {if (ObjectUtil.isNotNull(countryGroupConfMap.get(key)) && StringUtils.isNotBlank(value)) {if (!"可售".equals(value)) {return ResponseBean.buildFail("第" + lineNum + "行" + key + "分组请正确填写!!!");} else {groupConfIds.add(countryGroupConfMap.get(key));}}}}if (CollectionUtil.isNotEmpty(groupConfIds)) {product.setCountryGroupConfIds(groupConfIds);updateCondition.add(product);}}// 是否有属性不为"成品"的SKUList<Product> productList = this.getDao().queryByProductCodes(productCodes);if (CollectionUtil.isEmpty(productList)) {return ResponseBean.buildFail("SKU不存在,请确认数据是否正确!!!");}List<String> filterResults = productList.stream().filter(s -> Integer.valueOf(s.getAttributeCode()) != 1).map(Product::getProductCode).distinct().collect(Collectors.toList());if (CollectionUtil.isNotEmpty(filterResults)) {return ResponseBean.buildFail("SKU:" + String.join(",", filterResults) + "属性不为成品!!!");}// 计算productCodes和filterResults的单差集List<String> queryCodes = productList.stream().map(Product::getProductCode).distinct().collect(Collectors.toList());List<String> diff = CollectionUtil.subtractToList(productCodes, queryCodes);if (CollectionUtil.isNotEmpty(diff)) {return ResponseBean.buildFail("SKU:" + String.join(",", diff) + "不存在,请确认数据是否填写正确!!!");}// productList按照productCode分组Map<String, Long> productIdMap = productList.stream().collect(Collectors.toMap(Product::getProductCode, Product::getId));// 更新产品信息updateCondition.forEach(x -> {if (ObjectUtil.isNotEmpty(productIdMap.get(x.getProductCode()))) {Date now = new Date();String nickName = BaseContextHandler.getNickName();Product product = new Product();product.setId(productIdMap.get(x.getProductCode()));product.setCountryGroupConfIds(x.getCountryGroupConfIds());product.setUpdateBy(nickName);product.setUpdateTime(now);getDao().update(product);// 日志ProductCodeLog codeLog = new ProductCodeLog();codeLog.setProductId(product.getId());codeLog.setOperateType("批量更新");codeLog.setContent("修改可售国家/地区配置");codeLog.setCreateUser(nickName);codeLog.setCreateTime(now);productCodeLogMapper.insert(codeLog);}});return ResponseBean.buildSuccess();}
}
3.1.3 Listener
/*** NoModelDataListener class.** @author zs* @program: naikai* @description: EasyExcel_不创建对象的读_监听器* @date 2024/10/21*/
@Slf4j
public class NoModelDataListener extends AnalysisEventListener<Map<Integer, String>> {// 表头数据(存储所有的表头数据)private List<Map<Integer, String>> headList = new ArrayList<>();// 数据体private List<Map<Integer, String>> dataList = new ArrayList<>();@Override       // 这里会返回一行行的返回头public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {// 存储全部表头数据log.info("获取表头Start=====>");headList.add(headMap);log.info("=====>获取表头End");}@Override       // 处理每一行数据public void invoke(Map<Integer, String> data, AnalysisContext analysisContext) {dataList.add(data);}@Override       // 全部处理结束执行public void doAfterAllAnalysed(AnalysisContext context) {}public List<Map<Integer, String>> getHeadList() {return headList;}public List<Map<Integer, String>> getDataList() {return dataList;}}
3.1.4 Utils
/*** ExcelDynamicHeadImportUtils class.** @author zs* @program: naikai* @description: EasyExcel_动态表头导入_工具类* @date 2024/10/21*/
@Slf4j
public class DynamicHeadImportUtils {/*** 动态表头导入功能_无实体** @param file 文件* @return*/public static List<Map<String, String>> importExcel(MultipartFile file) {try {// Sept 1: 校验传入文件是否为空if (file == null) {throw new CheckException("传入数据为空");}// Sept 2: 引入监听器(此处需注意,监听器不可被Spring管理)NoModelDataListener readListener = new NoModelDataListener();// Sept 3: 开始处理excelEasyExcelFactory.read(file.getInputStream(), readListener).sheet(0).doRead();// 获取表头(判空)List<Map<Integer, String>> headList = readListener.getHeadList();if (CollectionUtil.isEmpty(headList)) {throw new CheckException("Excel表头不能为空");}// 获取表数据(判空)List<Map<Integer, String>> dataList = readListener.getDataList();if (CollectionUtil.isEmpty(dataList)) {throw new CheckException("Excel数据内容不能为空");}// 获取头部,取最后一次解析的列头数据Map<Integer, String> excelHeadIdxNameMap = headList.get(headList.size() - 1);// 封装数据体List<Map<String, String>> excelDataList = new ArrayList<>();for (Map<Integer, String> dataRow : dataList) {HashMap<String, String> rowData = new HashMap<>();excelHeadIdxNameMap.entrySet().forEach(columnHead -> {rowData.put(columnHead.getValue(), dataRow.get(columnHead.getKey()));});excelDataList.add(rowData);}return excelDataList;} catch (Exception e) {log.error("解析文件失败===>{}", e.getMessage(), e);throw new RuntimeException("导入失败=====>" + e.getMessage());}}
}
3.1.5 无实体导入数据返回说明

参考3.1.2方法中的返回类型:
导入内容

3.2 无实体的导出

3.2.1 无实体导出数据(这里只贴出关键代码,Service代码处理)
		// 可用国家分组配置(动态表头数据来源)List<CountryGroupConf> confs = groupConfService.lambdaQuery().eq(CountryGroupConf::getDelFlag, com.smallrig.middleground.common.enums.DelFlagEnums.NORMAL.getCode()).orderByAsc(CountryGroupConf::getId).list();Map<String, Long> countryGroupConfMap = confs.stream().collect(Collectors.toMap(CountryGroupConf::getGroupName, CountryGroupConf::getId));// 动态表头生成,第一列写死SKU,其他的根据查询的国家分组配置结果写入,查询结果是按照国家分组配置id排序(升序)List<List<String>> dynamicHeads = ListUtils.newArrayList();dynamicHeads.add(Arrays.asList("SKU"));confs.forEach(x -> dynamicHeads.add(Arrays.asList(x.getGroupName())));exportProduct.setCountryGroupConfDynamicHeads(dynamicHeads);//  商品的国家分组配置idsMap<String, List<Long>> groupConfMap = products.stream().filter(x -> CollectionUtil.isNotEmpty(x.getCountryGroupConfIds())).collect(Collectors.toMap(Product::getProductCode, Product::getCountryGroupConfIds));// 动态数据的写入,注意:表头和数据体的数据必须顺序一致,没有数据用null代替,否则就会出现错位问题List<List<Object>> datas = ListUtils.newArrayList();for (Map.Entry<String, List<Long>> entry : groupConfMap.entrySet()) {String productCode = entry.getKey();List<Long> confIds = entry.getValue();// 动态数据体List<Object> dataList = new ArrayList<>();// 表头第一列是SKU,所以对应数据体第一列必须是SKUdataList.add(productCode);// 根据表头的顺序,依次写入对应数据,如果商品没有表头国家配置,则置空for (int i = 1; i < dynamicHeads.size(); i++) {// 获取具体表头String head = dynamicHeads.get(i).get(0);Long id = countryGroupConfMap.get(head);// 判断商品的国家分组配置id是否包含了当前表头对应的国家分组配置id,如果有就写入可售,没有就置空if (confIds.contains(id)) {dataList.add("可售");}else {dataList.add(null);}}datas.add(dataList);}
3.2.2 Controller
@RestController
@RequestMapping("/product")
@Slf4j
@Api(tags = "产品控制器")
public class ProductController {
@PostMapping("/v1/export")public ResponseBean export(HttpServletResponse response,HttpServletRequest requests, @RequestBody ExportProductRequest request) throws Exception {try {//创建Excel文件File file = new File(path + "/" + fileName);if (!file.getParentFile().exists()) {file.getParentFile().mkdirs();}file.createNewFile();// 查询数据结果ExportProduct exportProduct = productService.exportObjectV2(conversionRequest, Long.valueOf(currentUserId));// 使用EasyExcel写入数据try (OutputStream out = new FileOutputStream(file)) {ExcelWriter excelWriter = EasyExcel.write(out).build();// 可售国家地区WriteSheet sheet11 = EasyExcel.writerSheet(10, "可售国家地区").head(exportProduct.getCountryGroupConfDynamicHeads()).build();excelWriter.write(exportProduct.getCountryGroupConfDatas(), sheet11);// 完成写入excelWriter.finish();// 修改down文件为成功downClient.updateDown(downId);log.info("成品编码异步导出=====>end");}} catch (IOException e) {log.error("成品编码异步导出异常", e);throw new RuntimeException(e);}return ResponseBean.buildSuccess();}
}
3.2.2 无实体导出总结

无实体导出关键在于表头和数据体的数据必须顺序一致,没有数据用null代替,否则就会出现错位问题

原创不易,望一键三连 (^ _ ^)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/456248.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kafka Tool(Offset Explorer)在windows下配置访问kerberos认证Kafka

Author : Spinach | GHB Link : http://blog.csdn.net/bocai8058文章目录 前言准备配置文件配置软件参数及启动 前言 Offset Explorer&#xff08;以前称为Kafka Tool&#xff09;是一个用于管理和使用Apache Kafka集群的图形用户界面&#xff08;GUI&#xff09;应用程序。…

登录时用户名密码加密传输(包含前后端代码)

页面输入用户名密码登录过程中&#xff0c;如果没有对用户名密码进行加密处理&#xff0c;可能会导致传输过程中数据被窃取&#xff0c;就算使用https协议&#xff0c;在浏览器控制台的Request Payload中也是能直接看到传输的明文&#xff0c;安全感是否还是不足。 大致流程&a…

FreeRTOS代码规范(3)

数据类型 portmacro.h : 在里面定义了Free RTOS 用到的相关数据类型 在 CM-3 内核中 short类型是16位&#xff0c;long 类型是32位 在 portmacro.h 中有两个最基本的数据类型 &#xff1a; Base type_t Tick type_t 这两个数据类型的存在是基于执行效率考虑的&#xff0c;…

如何使用 NumPy 和 Matplotlib 进行数据可视化

如何使用 NumPy 和 Matplotlib 进行数据可视化 在数据科学领域&#xff0c;NumPy 和 Matplotlib 是 Python 中最常用的两个库。NumPy 用于科学计算和数据处理&#xff0c;而 Matplotlib 提供了丰富的图表工具来展示数据。本文将介绍如何将这两个库结合使用&#xff0c;轻松进行…

腾讯会议pc端3.29.11开启悬浮窗口

之前是&#xff1a;pc端每次最小化&#xff0c;它就自动收回到任务栏里了 版本&#xff1a;3.29.11 解决办法&#xff1a; 打开腾讯会议&#xff0c;点击左上角的【头像】。 单击【设置】。 选择【显示当前说话者】来管理麦克风浮窗。 再进入会议&#xff0c;点击最小化一哈&…

中小企业设备资源优化:Spring Boot系统实现

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

RabbitMQ常见问题持续汇总

文章目录 消息分发不公平分发限流-basic.qos主要功能使用场景示例代码 消费者默认concurrency数量prefetch和concurrency结合&#xff1f; spring.rabbitmq.template.retry.enabledtrue和spring.rabbitmq.listener.simple.retry.enabledtrue有什么区别1. spring.rabbitmq.templ…

Appium环境搭建全流程(含软件)

目录 1.node.js的安装 1--中文下载地址 2--node.js设置镜像源 2.安装appium 1--全局安装appium 2--安装驱动 uiautomator2 3--查看插件的命令 4--安装 images插件 5--安装 execute-driver 插件 6--安装 relaxed-caps插件 7--安装 universal-xml 插件 3.python安装 1--python下…

Excel功能区变灰是什么原因造成?怎么解决?

Microsoft Excel是广泛使用的电子表格软件&#xff0c;但有时用户可能会遇到功能区变灰的问题&#xff0c;这可能导致功能无法使用&#xff0c;影响工作效率和用户体验。本文将深入探讨Excel功能区灰色的原因及解决方案&#xff0c;帮助读者快速解决这一问题。 一、Excel功能区…

Windows 使用命令行开启热点(无线网卡连接下)

winr 打开命令提示符框&#xff08;管理员模式&#xff09;在cmd中输入 netsh wlan show drivers 检查无线网卡是否已插入继续输入 ssid后面是wifi名称&#xff0c;key后面是密码&#xff0c;可以自行更改 netsh wlan set hostednetwork modeallow ssidWDW keyWDWWDWWDW 设置热…

开源模型应用落地-Qwen2.5-7B-Instruct与vllm实现推理加速的正确姿势-Gradio

一、前言 目前&#xff0c;Qwen模型已经升级到了2.5版本。无论是语言模型还是多模态模型&#xff0c;它们都是在大规模的多语言和多模态数据上进行预训练的&#xff0c;并通过高质量的数据进行后期微调&#xff0c;以更好地符合人类的需求。 Gradio作为一个强大的工具&#xff…

APP专项测试-冷启动-流量-电量-内存

1、响应时间 1.1怎么获取冷启动时间&#xff08;热启动&#xff0c;就是后台不关后台再次打开&#xff09; 方法一 1.2怎么获取包名 与 启动页 方法三soloPi&#xff1a;启动时间(用户角度出发&#xff0c;页面差异进行计算时间)&#xff1a; 然后默认配置。点击开始录制 1开…

云计算行业应用实训室建设方案

一、引言 云计算作为信息技术领域的重要分支&#xff0c;正在深刻影响着各行各业的发展。随着云计算技术的不断成熟和应用领域的不断拓展&#xff0c;对云计算专业人才的需求日益增长。实训室作为培养学生实践能力和创新能力的重要场所&#xff0c;其建设对于提高教育质量和满…

模拟信号采集显示器+GPS同步信号发生器制作全过程(焊接、问题、代码、电路)

1、制作最小系统板 在制作最小系统板的时候&#xff0c;要用USB转TTL给板子供电&#xff0c;留了一个电源输入的四个接口&#xff0c;同时又用排针引出来VCC和GND用于后续其他外设的电源供应&#xff0c;电源配有电源指示灯和保护电容&#xff0c; 当时在焊接的时候把接口处的…

数学建模与优化算法:从基础理论到实际应用

数学建模和优化算法&#xff0c;它们不仅帮助我们理解和描述复杂系统的行为&#xff0c;还能找到系统性能最优化的解决方案。本文将从基础的数学理论出发&#xff0c;逐步深入到各种优化算法&#xff0c;并探讨它们在实际问题中的应用。 思维导图文件可获取&#xff1a;https:…

51单片机应用开发(进阶)---外部中断(按键+数码管显示0-F)

实现目标 1、巩固数码管、外部中断知识 2、具体实现&#xff1a;按键K4&#xff08;INT1&#xff09;每按一次&#xff0c;数码管从0依次递增显示至F&#xff0c;再按则循环显示。 一、共阳数码管 1.1 共阳数码管结构 1.2 共阳数码管码表 共阳不带小数点0-F段码为&#xff…

Python异常检测- DBSCAN

系列文章目录 Python异常检测- Isolation Forest&#xff08;孤立森林&#xff09; python异常检测 - 随机离群选择Stochastic Outlier Selection (SOS) python异常检测-局部异常因子&#xff08;LOF&#xff09;算法 文章目录 系列文章目录前言一、DBSCAN算法原理二、DBSCAN算…

【小白学机器学习16】 概率论的世界观2: 从正态分布去认识世界

目录 1 从正态分布说起 1.1 正态分布的定义 1.2 正态分布的名字 1.3 正态分布的广泛&#xff0c;和基础性 2 正态分布的公式和图形 2.1 正态分布 2.2 标准正态分布 3 正态分布的认识的3个层次 3.1 第1层次&#xff1a;个体的某个属性的样本值&#xff0c;服从正态分布…

《IDE 巧用法宝:使用技巧全解析与优质插件推荐》

在日常撸代码的时候&#xff0c;相信兄弟们在IDEA 中用到不少插件&#xff0c;利用插件&#xff0c;不仅可以提高工具效率&#xff0c;撸起代码来&#xff0c;也格外的娃哈哈…… 一、IntelliJ IDEA 作为一个资深 Java 程序员&#xff0c;除了 IDEA 中默认的插件&#xff0c;我…

重学SpringBoot3-Reactive-Streams规范

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-Reactive-Streams规范 1. 什么是 Reactive-Streams 规范&#xff1f;2. Reactive-Streams 的核心组件2.1 Publisher&#xff08;发布者&#xff09;2.2 …