EasyExcel_动态表头的导入导出

news2024/10/25 14:30:42

文章目录

  • 前言
  • 一、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为国家分组配置的id
        Map<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);
            }
        }

        // 是否有属性不为"成品"的SKU
        List<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: 开始处理excel
            EasyExcelFactory.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);

        //  商品的国家分组配置ids
        Map<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,所以对应数据体第一列必须是SKU
            dataList.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.coloradmin.cn/o/2223335.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

凸优化学习

认为学习凸优化理论比较合适的路径是&#xff1a; 学习/复习线性代数和&#xff08;少量&#xff09;高等数学的知识。 实际上&#xff0c;凸优化理论综合使用了线性代数和微积分的相关知识&#xff0c;比如方向导数&#xff0c;雅克比矩阵&#xff0c;海森矩阵&#xff0c;KKT…

大数据-189 Elasticsearch - ELK 日志分析实战 - 环境配置启动 Nginx、ZK、Kafka、ES、Kibana

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

写出Windows操作系统内核的程序员,70多岁,还去办公室敲代码

大家好&#xff0c;我是二哥呀。 微软前 CEO 鲍尔默曾说过一句言简意赅的话&#xff1a;“没有 Dave&#xff0c;就没有今天的微软”。 可见 Dave 在微软的分量。Dave 的全名叫 Dave Cutler&#xff0c;微软的超级程序员&#xff0c;Windows NT 操作系统内核的缔造者&#xf…

【Java】ArrayList相关操作及其案例

ArrayList相当于集合&#xff0c;作为一种容器存储数据&#xff0c;与数组类似。不同的是&#xff0c;ArrayList中长度可变&#xff0c;而数组长度不可变。 ArrayList相关API 构造器 public ArrayList() 创建一个空的集合对象 ArrayList<String>arrnew ArrayList<>…

RHCE【web服务器】

目录 一、web服务器简介 1、什么是www 2、网址及HTTP简介 3、http协议请求的工作过程&#xff1a; 二、web服务器的类型 1、仅提供用户浏览的单向静态网页 2、提供用户互动接口的动态网站 三、web服务器基本配置 四、虚拟主机配置实战 1、搭建静态网站--基于http协议…

草地杂草数据集野外草地数据集田间野草数据集YOLO格式VOC格式目标检测计算机视觉数据集

一、数据集概述 数据集名称&#xff1a;杂草图像数据集 数据集是一个包含野草种类的集合&#xff0c;其中每种野草都有详细的特征描述和标记。这些数据可以包括野草的图片、生长习性、叶片形状、颜色等特征。 1.1可能应用的领域 农业领域: 农业专家和农民可以利用这一数据集来…

Spring Boot:植物健康监测的智能先锋

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了植物健康系统的开发全过程。通过分析植物健康系统管理的不足&#xff0c;创建了一个计算机管理植物健康系统的方案。文章介绍了植物健康系统的系统分析部分&…

VScode分文件编写C++报错 | 如何进行VScode分文件编写C++ | 不懂也能轻松解决版

分文件编写遇到的问题 分文件编写例子如下所示&#xff1a; 但是直接使用 Run Code 或者 调试C/C文件 会报错如下&#xff1a; 正在执行任务: C/C: g.exe 生成活动文件 正在启动生成… cmd /c chcp 65001>nul && D:\Librarys\mingw64\bin\g.exe -fdiagnostics-col…

RabbitMQ常见问题持续汇总

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

中药大数据(二)中药方剂表设计与导入

中药大数据&#xff08;二&#xff09;中药方剂表设计与导入 最近在做一个中药大数据的单子&#xff0c;已经爬取到了中药和方剂的数据&#xff0c;现在根据爬取到的数据设计数据库和导入neo4j形成知识图谱。 1 中药方剂数据表设计 爬取到的字段有 方剂名 title 处方 presc…

自动化部署-01-jenkins安装

文章目录 前言一、下载安装二、启动三、问题3.1 jdk版本问题3.2 端口冲突3.3 系统字体配置问题 四、再次启动五、配置jenkins5.1 解锁5.2 安装插件5.3 创建管理员用户5.4 实例配置5.5 开始使用5.6 完成 总结 前言 spingcloud微服务等每次部署到服务器上&#xff0c;都需要本地…

【判断推理】逻辑论证之数量论证

3.1 比例类论证 看比例而不是单看分子&#xff01; 逻辑类似于抛开剂量谈毒性没有价值。不明确基数大小&#xff0c;单纯比较数量没有价值。 本题中&#xff0c;平民总数可能有1000万&#xff0c;军队综述可能就50万&#xff0c;死亡率不可能相似。 论点&#xff1a;家人吸…

利用Pixabay API获取免费图片和视频的完整指南

视觉内容在吸引受众和有效传达信息方面发挥着举足轻重的作用。然而&#xff0c;获取这些内容往往需要付出高昂的代价。 幸运的是&#xff0c;Pixabay 提供了 440 多万种免费资产&#xff0c;从令人惊叹的照片到引人入胜的视频&#xff0c;所有这些都可以通过其 API 访问。 在…

处理Hutool的Http工具上传大文件报OOM

程序环境 JDK版本&#xff1a; 1.8Hutool版本&#xff1a; 5.8.25 问题描述 客服端文件上传主要代码&#xff1a; HttpRequest httpRequest HttpUtil.createPost(FILE_UPLOAD_URL); Resource urlResource new UrlResource(url, fileName); httpRequest.form("file&q…

nrm之npm镜像源管理工具(NPMRegistryManager)

1. Whats is nrm? 1. 官网地址 https://github.com/Pana/nrm https://www.npmjs.com/package/nrm 2. 关于nrm nrm can help you easy and fast switch between different npm registries, now include: npm, cnpm, taobao, nj(nodejitsu). nrm可以帮助您在不同的 npm 注册表…

智能AI监测系统燃气安全改造方案的背景及应用价值

随着燃气行业的迅速发展和城市化进程的加快&#xff0c;燃气安全管理成为企业运营和城市管理中不可忽视的关键领域。燃气泄漏、管道破损等事故的发生不仅会造成严重的经济损失&#xff0c;还威胁到人民生命财产安全。传统的安全管理方法往往依赖人工巡检和手动监测&#xff0c;…

Discuz发布原创AI帖子内容生成:起尔 | AI原创帖子内容生成插件开发定制

Discuz发布原创AI帖子内容生成&#xff1a;起尔 | AI原创帖子内容生成插件开发定制 在当今互联网快速发展的时代&#xff0c;内容创作成为了网站运营、社交媒体管理和个人博客维护不可或缺的一部分。然而&#xff0c;高质量内容的创作往往耗时耗力&#xff0c;特别是对于需要频…

webpack 老项目升级记录:从 node-sass 限制的的 node v8 提升至支持 ^node v22

老项目简介 技术框架 vue 2.5.17webpack 4.16.5"webpack-cli": "3.1.0""node-sass": "^4.7.2" 几个阶段 第一步&#xff1a;vue2 升级到最新 第一步&#xff1a;升级 vue2 至最新版本&#xff0c;截止到目前&#xff08;2024-10-…

用js+css实现圆环型的进度条——js+css基础积累

如果用jscss实现圆环型的进度条&#xff1a; 直接上代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><met…

通信协议——UART

目录 基础概念串行&并行串行的优缺点 单工&双工 UART基本概念时序图思考&#xff1a;接收方如何确定01和0011 基础概念 串行&并行 串行为8车道&#xff0c;并行为1车道 串行的优缺点 通行速度快浪费资源布线复杂线与线之间存在干扰 单工&双工 单工&#xf…