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

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

相关推荐
程序猿阿伟4 分钟前
《C++中的魔法:实现类似 Python 的装饰器模式》
java·c++·装饰器模式
Mr. zhihao6 分钟前
装饰器模式详解:动态扩展对象功能的优雅解决方案
java·开发语言·装饰器模式
百流10 分钟前
Pyspark中pyspark.sql.functions常用方法(4)
1024程序员节
2401_8576009513 分钟前
商场应急管理:SpringBoot技术解决方案
java·spring boot·后端
qq210846295313 分钟前
【Ubuntu】Ubuntu22双网卡指定网关
1024程序员节
想做白天梦25 分钟前
多级反馈队列
java·windows·算法
潇雷26 分钟前
算法Day12|226-翻转二叉树;101-对称二叉树;104-二叉树最大深度;111-二叉树最小深度
java·算法·leetcode
YueTann31 分钟前
APS开源源码解读: 排程工具 optaplanner II
1024程序员节
一颗甜苞谷34 分钟前
开源一套基于若依的wms仓库管理系统,支持lodop和网页打印入库单、出库单的源码
java·开源