当前位置: 首页 > 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;先疏通…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…...

大话软工笔记—需求分析概述

需求分析&#xff0c;就是要对需求调研收集到的资料信息逐个地进行拆分、研究&#xff0c;从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要&#xff0c;后续设计的依据主要来自于需求分析的成果&#xff0c;包括: 项目的目的…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

Python实现prophet 理论及参数优化

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

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍

文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结&#xff1a; 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析&#xff1a; 实际业务去理解体会统一注…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...

CSS | transition 和 transform的用处和区别

省流总结&#xff1a; transform用于变换/变形&#xff0c;transition是动画控制器 transform 用来对元素进行变形&#xff0c;常见的操作如下&#xff0c;它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...

0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化

是不是受够了安装了oracle database之后sqlplus的简陋&#xff0c;无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话&#xff0c;配置.bahs_profile后也能解决上下翻页这些&#xff0c;但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可&#xff0c…...

在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例

目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码&#xff1a;冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...

2.3 物理层设备

在这个视频中&#xff0c;我们要学习工作在物理层的两种网络设备&#xff0c;分别是中继器和集线器。首先来看中继器。在计算机网络中两个节点之间&#xff0c;需要通过物理传输媒体或者说物理传输介质进行连接。像同轴电缆、双绞线就是典型的传输介质&#xff0c;假设A节点要给…...