EasyExcel_动态表头的导入导出

文章目录

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

前言

今天产品提了个需求,导入导的Excel文件表头根据数据库的配置来。

因为之前大部分的导入和导出都是有固定表头或者固定的模板来做导入导出,这个需求。。。嗯,搞起!!!


一、EasyExcel

EasyExcel前面已经有过介绍了,这里不做具体介绍,大家看EasyExcel官网: EasyExcel官网

这里主要参考EasyExcel不创建对象的读不创建对象的写

二、使用步骤

1.引入jar包

xml 复制代码
	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>easyexcel</artifactId>
		<version>3.1.1</version>
	</dependency>

2.数据准备

2.1 数据库

3.方法实例

3.1 无实体的导入

3.1.1 Controller
java 复制代码
@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
java 复制代码
@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
java 复制代码
/**
 * 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
java 复制代码
/**
 * 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代码处理)
java 复制代码
		// 可用国家分组配置(动态表头数据来源)
        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
java 复制代码
@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代替,否则就会出现错位问题

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

相关推荐
Theodore_10223 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
冰帝海岸4 小时前
01-spring security认证笔记
java·笔记·spring
世间万物皆对象4 小时前
Spring Boot核心概念:日志管理
java·spring boot·单元测试
没书读了5 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
小二·5 小时前
java基础面试题笔记(基础篇)
java·笔记·python
开心工作室_kaic5 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
懒洋洋大魔王5 小时前
RocketMQ的使⽤
java·rocketmq·java-rocketmq
武子康5 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
转世成为计算机大神6 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式
qq_327342736 小时前
Java实现离线身份证号码OCR识别
java·开发语言