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

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为国家分组配置的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.kler.cn/news/364484.html

相关文章:

  • yolo自动化项目实例解析(八)自建UI-键鼠录制回放
  • Postgresql中和时间相关的字段类型及其适用场景
  • C#探索之路基础夯实篇(6):C#在Unity中的自定义特性
  • 什么是虚拟线程?Java 中虚拟线程的介绍与案例演示
  • COSCon'24 志愿者招募令:共创开源新生活!
  • 【Linux】总线-设备-驱动模型
  • 非强化学习的对齐方法
  • 稳啦!掌握缓存一致性与失效预防措施——使用缓存不可或缺的指南!
  • MQL实验(二)作业
  • 【蓝桥杯选拔赛真题77】python计算小球 第十五届青少年组蓝桥杯python选拔赛真题 算法思维真题解析
  • [deadlock]死锁导致的设备登录无响应问题
  • 深入解析Java中的锁
  • 【业务】群组服务功能重构测试总结
  • 基于ssm+vue的房源管理系统设计与实现
  • Vue前端播放rtsp视频流(vue+webrtc-streamer)
  • 使用 Pake 一键打包网页为桌面应用 / 客户端
  • 预算不够,怎么跟KOL砍价?(内附砍价模板)
  • 头部聚合公司源码质量测评!手把手教你打造高质量的碰一碰支付系统!
  • 设计模式 | 6大设计原则
  • NOTION 推出可定制的电子邮件产品 — NOTION MAIL
  • 15_卸载操作
  • pytorch 交叉熵损失函数 BCELoss
  • Java 代理模式详解
  • 003:无人机概述
  • 使用RabbitMQ实现延迟消息的完整指南
  • 瓜田推广:揭秘零撸项目里流量变现的技术与模式框架,新手必看!