文章目录
- 前言
- 一、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代替,否则就会出现错位问题