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

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代替,否则就会出现错位问题

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

相关文章:

EasyExcel_动态表头的导入导出

文章目录 前言一、EasyExcel二、使用步骤1.引入jar包2.数据准备2.1 数据库 3.方法实例3.1 无实体的导入3.1.1 Controller3.1.2 Service3.1.3 Listener3.1.4 Utils3.1.5 无实体导入数据返回说明 3.2 无实体的导出3.2.1 无实体导出数据(这里只贴出关键代码,Service代码处理)3.2.2…...

uni-app简单模拟人脸识别

uni-app使用live-pusher简单模拟人脸识别页面样式 实现想法调起手机摄像头设置圆形 实现想法 公司的需求是模拟一个人脸识别&#xff0c;不用第三发插件&#xff0c;简单模拟样式即可。 基本思路是调起手机前置摄像头&#xff0c;再设置一个圆形的样式来达到一个基本样式 调起…...

华为HCIE-OpenEuler认证详解

华为HCIE认证&#xff08;Huawei Certified ICT Expert&#xff09;是华为提供的最高级别的专业认证&#xff0c;它旨在培养和认证在特定技术领域具有深厚理论知识和丰富实践经验的专家级工程师。对于华为欧拉&#xff08;OpenEuler&#xff09;方向的HCIE认证&#xff0c;即HC…...

从零开始的Go语言之旅(2 Go by Example: Values)

Go 语言有多种值类型&#xff0c;包括字符串、整数、浮点数、布尔值等。以下是一些基本示例。 package mainimport "fmt"func main() {fmt.Println("go" "lang")fmt.Println("11 ", 11)fmt.Println("7.0/3.0 ", 7.0/3.0)f…...

XShell 中实现免密登录 Linux 服务器的详细流程

个人主页&#xff1a;Jason_from_China-CSDN博客 所属栏目&#xff1a;Linux系统性学习_Jason_from_China的博客-CSDN博客 所属栏目&#xff1a;Linux知识点的补充_Jason_from_China的博客-CSDN博客 XShell 中实现免密登录 Linux 服务器的详细流程&#xff1a; 一、在本地生成…...

跨界创新|使用自定义YOLOv11和Ollama(Llama 3)增强OCR文本识别

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…...

一些关于 WinCC Comfort 和 WinCC Advanced 脚本编程语言 VBS 的实用技巧

为什么一个由内部变量的 “数值更变” 事件触发的脚本不执行&#xff1f; 如果使用一个内部变量调用另外一个内部变量&#xff0c;以此&#xff0c;例如被调用的变量又去执行一个脚本&#xff08;比如&#xff0c;根据变量变化&#xff09;&#xff0c;此时一个安全机制会阻止这…...

Java|乐观锁和悲观锁在自旋的时候分别有什么表现?

乐观锁和悲观锁是两种不同的并发控制策略&#xff0c;各自采用不同的机制来处理线程之间的资源竞争。 乐观锁 1. 定义 乐观锁是一种假设冲突不会发生的并发控制策略&#xff0c;通常不对资源进行加锁&#xff0c;而是在操作前不加锁&#xff0c;操作后再进行验证。乐观锁通常…...

Linux定时器定时任务清理log日志文件

首先&#xff0c;创建xx.sh文件&#xff0c;内容如下 #!/bin/bash sfecho "" > /var/lib/docker/containers/12379e809ea1294eea9b117368181cff1dd3915fdb1611f940c5cf3d6077d734/12379e809ea1294eea9b117368181cff1dd3915fdb1611f940c5cf3d6077d734-json.log 打…...

美国大学生数学建模竞赛(MCM/ICM)介绍

美国大学生数学建模竞赛(MCM/ICM)是一项具有较高影响力的国际赛事。以下是一份美赛教程: 一、前期准备 组队 寻找合适的队友,最好具备不同的专业技能,如数学、计算机、工程等。团队成员应具备良好的沟通能力、合作精神和责任心。明确各自的分工,例如有人负责建模、有人负…...

【独家:AI编程助手Cursor如何revolutionize Java设计模式学习】

【独家:AI编程助手Cursor如何revolutionize Java设计模式学习】 导语 在Java高级编程的世界里,设计模式是每个开发者必须掌握的利器。但是,如何快速理解并灵活运用这些模式呢?让我们一起探索如何借助AI编程助手Cursor,轻松掌握设计模式,提升Java编程技能! 正文 设计模式:J…...

数据仓库宽表概述

宽表是指一种将多个相关数据集整合到一个表中的数据建模方法&#xff0c;具有减少连接操作、提高查询性能、简化数据管理的优点。 一、宽表的定义 宽表&#xff0c;顾名思义&#xff0c;是一种在数据仓库中使用的表格形式&#xff0c;其特征是包含了大量的列。这种表格设计的…...

在数据库中编程 vs 在应用程序中编程

原文地址 https://brandur.org/fragments/code-database-vs-app 数据库领域有一个长期存在的问题&#xff1a;你是更愿意将应用逻辑放在更接近数据库本身的存储过程和触发器中&#xff0c;还是置于数据库之上的应用程序代码中&#xff1f; 没有客观正确的答案&#xff0c;只有…...

【设计模式系列】装饰器模式

目录 一、什么是装饰器模式 二、装饰器模式中的角色 三、装饰器模式的典型应用场景 四、装饰器模式在BufferedReader中的应用 一、什么是装饰器模式 装饰器模式是一种结构型设计模式&#xff0c;用于在不修改对象自身的基础上&#xff0c;通过创建一个或多个装饰类来给对象…...

你真的知道TCP协议中的序列号确认、上层协议及记录标识问题吗?

引言 在前面的内容中&#xff0c;我们已经详细讲解了一系列与TCP相关的面试问题。然而&#xff0c;这些问题都是基于个别知识点进行扩展的。今天&#xff0c;我们将重点讨论一些场景问题&#xff0c;并探讨如何解决这些问题。 序列号确认问题 当A主机与B主机建立了TCP连接后…...

一家生物技术企业终止,科创属性可能不足,报告期内专利数猛增

轩凯生物九成以上营业收入来源于植物营养领域&#xff0c;收入来源结构单一&#xff0c;产品下游应用领域较为集中。报告期内公司应收账款账面价值逐年上升&#xff0c;回款比例显著低于前两年&#xff0c;遭交易所问询是否存在较大的坏账风险。 轩凯生物核心技术是否成熟以及是…...

使用 Python 的 BeautifulSoup(bs4)解析复杂 HTML

使用 Python 的 BeautifulSoup&#xff08;bs4&#xff09;解析复杂 HTML&#xff1a;详解与示例 在 Web 开发和数据分析中&#xff0c;解析 HTML 是一个常见的任务&#xff0c;尤其是当你需要从网页中提取数据时。Python 提供了多个库来处理 HTML&#xff0c;其中最受欢迎的就…...

Spring Cache Caffeine 高性能缓存库

​ Caffeine 背景 Caffeine是一个高性能的Java缓存库&#xff0c;它基于Guava Cache进行了增强&#xff0c;提供了更加出色的缓存体验。Caffeine的主要特点包括&#xff1a; 高性能&#xff1a;Caffeine使用了Java 8最新的StampedLock乐观锁技术&#xff0c;极大地提高了缓存…...

Python3入门--数据类型

文章目录 一、基础语法编码标识符注释单行注释以 # 开头多行注释用多个 # 号&#xff0c;还有 和 """ 空行行与缩进同一行显示多条语句多行语句 二、数据类型Number&#xff08;数字&#xff09;type和isinstance查询变量类型数值运算 String&#xff08;字符串…...

开发运维警示录-20241024

开发警示录 1、作为开发&#xff0c;不要私自修改业务人员给的SQL语句&#xff0c;虽然个人感觉SQL很冗余&#xff0c;效率低等。 2、开发前&#xff0c;要明确需求&#xff0c;必要时通过图和文字形成文档与需求方确认、留痕。 3、开发复杂的业务逻辑代码前&#xff0c;先疏通…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

大型活动交通拥堵治理的视觉算法应用

大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动&#xff08;如演唱会、马拉松赛事、高考中考等&#xff09;期间&#xff0c;城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例&#xff0c;暖城商圈曾因观众集中离场导致周边…...

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候&#xff0c;写过一篇简单实现&#xff0c;后期随着对该模型的深入研究&#xff0c;本次记录涉及到prophet 的公式以及参数调优&#xff0c;从公式可以更直观…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

Spring Boot面试题精选汇总

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

AI书签管理工具开发全记录(十九):嵌入资源处理

1.前言 &#x1f4dd; 在上一篇文章中&#xff0c;我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源&#xff0c;方便后续将资源打包到一个可执行文件中。 2.embed介绍 &#x1f3af; Go 1.16 引入了革命性的 embed 包&#xff0c;彻底改变了静态资源管理的…...

JS设计模式(4):观察者模式

JS设计模式(4):观察者模式 一、引入 在开发中&#xff0c;我们经常会遇到这样的场景&#xff1a;一个对象的状态变化需要自动通知其他对象&#xff0c;比如&#xff1a; 电商平台中&#xff0c;商品库存变化时需要通知所有订阅该商品的用户&#xff1b;新闻网站中&#xff0…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

搭建DNS域名解析服务器(正向解析资源文件)

正向解析资源文件 1&#xff09;准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2&#xff09;服务端安装软件&#xff1a;bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...