赞
踩
pom引入
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.3</version>
</dependency>
@ApiOperation(value = "1.0版本-客户信息导入") @PostMapping("/clubImport") public Result<Map<String, String>> clubImport(@RequestParam("file") MultipartFile file, @ApiParam("业务渠道") @RequestParam("businessChannel") Integer businessChannel , @RequestParam("shopCode") String shopCode, @RequestParam("shopName") String shopName,@ApiParam("导入模版类型:1-普通模版,2-抖音模版") @RequestParam("importType") Integer importType) { ImportClueParam importClueParam = new ImportClueParam(); importClueParam.setFile(file); importClueParam.setShopCode(shopCode); importClueParam.setBusinessChannel(businessChannel); importClueParam.setShopName(shopName); importClueParam.setImportType(importType); importClueParam.setTest(AuthNHolderUtils.isTest()); String batchNo = LocalDateTime.now().format(DateTimeFormatter.ofPattern(DateUtils.DATE_NO_CHAR_PATTERN)); log.info("开始导入客户信息,本次导入批次号:{},店铺code:{},店铺名称:{},业务渠道:{},模版类型:{}", batchNo, shopCode, shopName, businessChannel,importType); try { //加锁,同一时间只有一个导入任务在进行 boolean tryLock = redisLock.tryLock(ImportConstant.IMPORT_LOCK_KEY + batchNo, 10); if (!tryLock) { return Result.fail("500", "存在其他正在进行的导入任务"); } //数据解析和校验 List<ClueImportTemporaryDO> covertList = clueImportService.handleExcel(importClueParam); log.info("批次号:{} 客户信息解析完成,本次导入的客户手机号:[{}]",batchNo, covertList.stream().map(ClueImportTemporaryDO::getPhone).collect(Collectors.joining(","))); //异步完成校验数据及导入任务 clueImportService.asynHandleList(covertList, batchNo, importType); log.info("clubImport结束:{},batchNo:{}", LocalDateTime.now(), batchNo); //返回批次号 HashMap<String, String> batchMap = new HashMap<>(); batchMap.put("batchNo", batchNo); return Result.success(batchMap); } catch (DccCrmException e) { log.error("error_clueImport_DccCrmException异常:{}", e.getMessage()); throw e; } catch (Exception e) { log.error("error_clueImport_Exception异常:{}", e.getMessage()); if (e.getCause() instanceof DccCrmException) { throw (DccCrmException) e.getCause(); } throw new DccCrmException(ErrorCodeEnum.EXCEL_ERROR); } finally { //释放锁 redisLock.unlock(ImportConstant.IMPORT_LOCK_KEY + batchNo); } }
返回实体对象
@Data
@ApiModel(value = "ImportCustomerEchoVO", description = "导入数据回显")
public class ImportCustomerEchoVO {
@ApiModelProperty("列表数据")
private List<ImportCustomerVO> importCustomerVOS;
@ApiModelProperty("一共识别数据")
private Integer total;
@ApiModelProperty("新增成功客户数量")
private Integer successNum;
@ApiModelProperty("新增失败客户数据数量")
private Integer failNum;
@ApiModelProperty("批次号")
private String batchUid;
}
service层–clueImportService数据解析和校验
@SneakyThrows @Override public List<ClueImportTemporaryDO> handleExcel(ImportClueParam importClueParam) { ExcelImportSimpleListener simpleListener = new ExcelImportSimpleListener(); ExcelImportDouyinListener douyinListener = new ExcelImportDouyinListener(); List<ClueImportTemporaryDO> voList = new ArrayList<>(); //根据输入的来源渠道调用对应的模版进行导入 if (importClueParam.getImportType().equals(ImportTypeEnum.DOUYIN.getCode())) { log.info("开始解析抖音excel模板的客户导入信息,导入批次号:{}", importClueParam.getBatchNo()); EasyExcel.read(importClueParam.getFile().getInputStream(), ImportClueDouyinVO.class, douyinListener).sheet().doRead(); voList = douyinListener.getList(); if (voList.size() > 1000) { throw new DccCrmException(ErrorCodeEnum.MAX_1000); } voList.forEach(x -> { x.setLastOperator(AuthNHolder.displayName()); x.setTemplateType(TemplateTypeEnum.DOUYIN.getType()); x.setShopCode(importClueParam.getShopCode()); x.setBusinessChannel(importClueParam.getBusinessChannel()); x.setShopName(importClueParam.getShopName()); x.setIsTest(importClueParam.isTest() ? 1 : 0); }); } else if (importClueParam.getImportType().equals(ImportTypeEnum.SIMPLE.getCode())) { log.info("开始解析通用excel模板的客户导入信息,导入批次号:{}", importClueParam.getBatchNo()); EasyExcel.read(importClueParam.getFile().getInputStream(), ImportClueSimpleVO.class, simpleListener).sheet().doRead(); voList = simpleListener.getList(); if (voList.size() > 1000) { throw new DccCrmException(ErrorCodeEnum.MAX_1000); } voList.forEach(x -> { x.setLastOperator(AuthNHolder.displayName()); x.setTemplateType(TemplateTypeEnum.SIMPLE.getType()); x.setShopCode(importClueParam.getShopCode()); x.setBusinessChannel(importClueParam.getBusinessChannel()); x.setShopName(importClueParam.getShopName()); x.setIsTest(importClueParam.isTest() ? 1 : 0); }); } else { throw new DccCrmException(ErrorCodeEnum.IMPORT_TYPE_ERROR); } return voList; }
//异步完成校验及导入 @Override @Async public void asynHandleList(List<ClueImportTemporaryDO> voList, String batchNo, Integer importType) { log.info("开始异步处理解析出的客户信息,批次号:{}", batchNo); if (CollectionUtils.isEmpty(voList)) { redisTemplate.opsForValue().set(ImportConstant.PROGRESS_BAR_KEY + batchNo, new ImportRedisVO().setFlag(1).setDesc("导入成功")); return; } ImportRedisVO importRedisVO = new ImportRedisVO(); //在redis中存入重复数据 //封装phones List<String> phones = voList.stream().map(ClueImportTemporaryDO::getPhone).collect(Collectors.toList()); String shopCode = voList.get(0).getShopCode(); List<String> existPhones = new ArrayList<>(); if (StringUtils.isNotBlank(shopCode) && CollectionUtils.isNotEmpty(phones)) { existPhones = customerSPI.getExistCustomerByShopAndPhone(shopCode, phones); } importRedisVO.setRepeatNum(existPhones.size()); List<ClueImportTemporaryDO> failInsertList = new ArrayList<>(); importRedisVO.setFlag(0); //校验数据 List<ClueImportTemporaryDO> list = checkList(voList, batchNo, importRedisVO, importType); //插入临时表 importRedisVO.setDesc("正在插入数据"); redisTemplate.opsForValue().set(ImportConstant.PROGRESS_BAR_KEY + batchNo, importRedisVO); //分批处理插入,异常数据存入redis List<List<ClueImportTemporaryDO>> partition = Lists.partition(list, 100); partition.stream().forEach(page -> { try { clueImportTemporaryMapper.insertList(page); } catch (Exception e) { //插入失败,则单条插入,异常数据存入redis page.forEach(p -> { try { basicDao.insert(p, ClueImportTemporaryDO.class); } catch (Exception exception) { p.setRemark(StringUtils.isNotBlank(p.getRemark()) ? p.getRemark() : "" + " 数据入库异常,请检查字段长度"); failInsertList.add(p); } }); } }); importRedisVO.setFailInsertList(failInsertList); //导入成功的调用线索服务 importRedisVO.setDesc("正在传入线索服务"); redisTemplate.opsForValue().set(ImportConstant.PROGRESS_BAR_KEY + batchNo, importRedisVO); List<ClueImportTemporaryDO> successList = list.stream().filter(x -> x.getImportState() == 1).collect(Collectors.toList()); successList.removeAll(failInsertList); List<AddClueRequest> clues = convertClueData(successList); //调用线索dubbo接口,线索中心要求每次最多传500条 List<List<AddClueRequest>> kozukiList = Lists.partition(clues, 500); kozukiList.forEach(clueSPI::batchAddClue); log.info("异步处理解析出的客户信息完成,批次号:{}", batchNo); importRedisVO.setFlag(1); importRedisVO.setDesc("导入成功"); redisTemplate.opsForValue().set(ImportConstant.PROGRESS_BAR_KEY + batchNo, importRedisVO); }
ExcelImportSimpleListener
@Slf4j public class ExcelImportSimpleListener extends AnalysisEventListener<ImportClueSimpleVO> { /** * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收 */ private static final int BATCH_COUNT = 3000; List<ClueImportTemporaryDO> list = new ArrayList<>(); private static int count = 1; @Override public void invoke(ImportClueSimpleVO data, AnalysisContext context) { try { boolean isNullObject = BeanUtils.checkFieldAllNull(data); if(isNullObject){ return; } } catch (IllegalAccessException e) { log.error("invoke 判空异常:{}",data,e); } ClueImportTemporaryDO clue = BeanUtils.convert(data, ClueImportTemporaryDO.class); String errorRemark = String.format("时间:%s 不符合标准,请输入标准格式yyyy-MM-dd HH:mm:dd;", data.getClueCollectionTime()); if (StringUtils.isBlank(data.getClueCollectionTime())) { clue.setImportState(ImportStateEnum.FAIL.getType()); clue.setRemark("线索收集日期为空"); list.add(clue); return; } // 判断时间格式 String timeStr = ExcelUtils.convertTime(data.getClueCollectionTime()); clue.setImportState(ImportStateEnum.SUCCESS.getType()); if (StringUtils.isBlank(timeStr)) { // 时间格式不符合,记录错误日志 data.setErrorRemark(errorRemark); clue.setImportState(ImportStateEnum.FAIL.getType()); } else { data.setClueCollectionTime(timeStr); boolean matches = Pattern.matches(DateUtils.TIME_PATTERN, timeStr); if (!matches) { data.setErrorRemark(errorRemark); clue.setImportState(ImportStateEnum.FAIL.getType()); } else { SimpleDateFormat sdf = new SimpleDateFormat(DateUtils.DATE_TIME_PATTERN_SLASH); try { clue.setClueCollectionTime(sdf.parse(timeStr).toInstant().atZone(ZoneOffset.ofHours(8)).toLocalDateTime()); } catch (ParseException e) { data.setErrorRemark(errorRemark); clue.setImportState(ImportStateEnum.FAIL.getType()); log.error("导入时间转化错误:时间:{},错误:{}", data.getClueCollectionTime(), e.getMessage()); } } } clue.setSourceChannel(Objects.nonNull(SourceChannelEnum.getByDesc(data.getInteractionSource())) ? SourceChannelEnum.getByDesc(data.getInteractionSource()).getCode() : null); clue.setRemark(StringUtils.isNotBlank(data.getErrorRemark()) ? data.getErrorRemark() : ""); clue.setClueCollectionTimeStr(data.getClueCollectionTime()); list.add(clue); } @Override public void doAfterAllAnalysed(AnalysisContext context) { saveData(count); System.out.println("所有数据解析完成!"); System.out.println(" count :" + count); } @Override public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) { if (headMap.size() != 13 || !headMap.values().contains("客户姓名")) { throw new DccCrmException(ErrorCodeEnum.SIMPLE_TEMPORARY_ERROR); } } /** * 加上存储数据库 */ private void saveData(int count) { System.out.println("存储数据库成功!"); } public List<ClueImportTemporaryDO> getList() { return this.list; } }
controller层
@ApiOperation(value = "1.0版本-根据批次号获取导入结果")
@GetMapping("/getResult")
public Result<ImportClueEchoVO> getResult(@RequestParam String batchNo) {
try {
ImportClueEchoVO importClueEchoVO = clueImportService.getResult(batchNo);
return Result.success(importClueEchoVO);
} catch (Exception e) {
log.error("error_getResult", e);
return Result.fail("500", e.getMessage());
}
}
service层–clueImportService
@Override public ImportClueEchoVO getResult(String batchNo) { ImportClueEchoVO importClueEchoVO = new ImportClueEchoVO(); List<ClueImportTemporaryDO> listAll = clueImportTemporaryMapper.selectListByBatchNo(new QueryClueParam().setBatchNo(batchNo)); List<ClueImportTemporaryDO> failList = listAll.stream().filter(x -> x.getImportState().equals(ImportStateEnum.FAIL.getType())).collect(Collectors.toList()); //合并redis中的失败数据 ImportRedisVO bar = (ImportRedisVO) redisTemplate.opsForValue().get(ImportConstant.PROGRESS_BAR_KEY + batchNo); if (CollectionUtils.isNotEmpty(failList)) { failList.addAll(bar.getFailInsertList()); } else { failList = CollectionUtils.isNotEmpty(bar.getFailInsertList()) ? bar.getFailInsertList() : Collections.EMPTY_LIST; } if (CollectionUtils.isNotEmpty(listAll)) { listAll.addAll(bar.getFailInsertList()); } else { listAll = CollectionUtils.isNotEmpty(bar.getFailInsertList()) ? bar.getFailInsertList() : Collections.EMPTY_LIST; } //数据Mapstruct复制 List<ImportClueVO> importClueVOS = ClueExportConvert.INSTANCE.do2ClueVoList(failList); importClueEchoVO.setImportClueVOS(importClueVOS); importClueEchoVO.setTotal(listAll.size()); importClueEchoVO.setFailNum(failList.size()); importClueEchoVO.setSuccessNum(importClueEchoVO.getTotal() - failList.size()); importClueEchoVO.setBatchNo(batchNo); //从redis中获取重复数 importClueEchoVO.setRepeatNum(bar.getRepeatNum()); return importClueEchoVO; }
controller层
@ApiOperation(value = "1.0版本-根据批次号获取进度条信息")
@GetMapping("/getProcessBar")
public Result<ImportRedisVO> getProcessBar(@RequestParam String batchNo) {
try {
ImportRedisVO bar = (ImportRedisVO) redisTemplate.opsForValue().get(ImportConstant.PROGRESS_BAR_KEY + batchNo);
return Result.success(bar);
} catch (Exception e) {
log.error("error_getResult", e);
return Result.fail("500", e.getMessage());
}
}
controller层
@ApiOperation(value = "客户信息-确认导入")
@PostMapping("/confirmImport")
public Result confirmCustomerImport(@RequestParam("batchUid") String batchUid, @RequestParam(value = "addClueFlag", required = false) Integer addClueFlag) {
return Result.success(customerService.confirmCustomerImport(batchUid, addClueFlag));
}
service层
@Override @Transactional(rollbackFor = Exception.class) public List<ImportCustomerVO> confirmCustomerImport(String batchUid, Integer addClueFlag) { //1,通过批次号获取临时表数据 List<CustomerImportTemporaryDO> customerImportTemporaryDOList = customerImportTemporaryDao.queryCustomerImportTemporaraByBatchUid(batchUid); if (CollectionUtils.isEmpty(customerImportTemporaryDOList)) { return new ArrayList<>(); } //2,组装线索接口需要的数据 待修改 //数据库customer转map Map<String, CustomerDO> customerDOMap = new HashMap<>(); //数据库customer存在的code List<String> customerCodes = new ArrayList<>(); List<String> phones = customerImportTemporaryDOList.stream().map(CustomerImportTemporaryDO::getPhone).collect(Collectors.toList()); getCustomerInfo(phones, customerDOMap, customerCodes); //临时表数据分组 Map<String, List<CustomerImportTemporaryDO>> customerImportTemporaryDOMap = customerImportTemporaryDOList.stream() .sorted(Comparator.comparing(CustomerImportTemporaryDO::getId)) .collect(Collectors.groupingBy(CustomerImportTemporaryDO::getPhone, LinkedHashMap::new, Collectors.toList())); //意向周期分组 List<Integer> cycleIds = new ArrayList<>(); Map<String, CustomerIntentionCycleDO> customerIntentionCycleDOMap = getCustomerIntentionCycleDOMap(customerCodes, cycleIds); //意向车型 Map<Integer, CustomerIntentionCarDO> customerIntentionCarDOMap = new HashMap<>(); if (CollectionUtils.isNotEmpty(cycleIds)) { List<CustomerIntentionCarDO> customerIntentionCarDOS = customerIntentionCarDao.getByCycleIds(cycleIds); customerIntentionCarDOMap = customerIntentionCarDOS.stream().collect(Collectors.toMap(CustomerIntentionCarDO::getCycleId, Function.identity(), (o, n) -> n)); } //跟进 Map<Integer, List<CustomerFollowUpDO>> customerFollowUpDOMap; if (CollectionUtils.isNotEmpty(cycleIds)) { List<CustomerFollowUpDO> customerFollowUpDOS = customerFollowUpDao.queryByCycleIds(cycleIds); customerFollowUpDOMap = customerFollowUpDOS.stream().collect(Collectors.groupingBy(CustomerFollowUpDO::getCycleId)); } else { customerFollowUpDOMap = new HashMap<>(); } List<CustomerImportTemporaryDO> customerImportTemporaryDOListAdd = new ArrayList<>(); customerImportTemporaryDOMap.forEach((phone, customerImportTemporaryDOS) -> { CustomerDO customerDO = customerDOMap.get(phone); CustomerImportTemporaryDO customerImportTemporaryDO = customerImportTemporaryDOS.get(customerImportTemporaryDOS.size() - 1); List<CustomerImportTemporaryDO> stroeList = customerImportTemporaryDOS.stream(). filter(item -> StringUtils.isNotBlank(item.getDeliverTheStoreCode())).sorted(Comparator.comparing(CustomerImportTemporaryDO::getId)).collect(Collectors.toList()); if (customerDO == null) { // String storeCode = customerImportTemporaryDO.getDeliverTheStoreCode(); if (CollectionUtils.isNotEmpty(stroeList)) { customerImportTemporaryDOListAdd.add(stroeList.get(stroeList.size() - 1)); } } else { if (customerDO.getIssuedFlag() == 0) { if (CollectionUtils.isNotEmpty(stroeList)) { customerImportTemporaryDOListAdd.add(stroeList.get(stroeList.size() - 1)); } } } }); if (CollectionUtils.isNotEmpty(customerImportTemporaryDOListAdd)) { List<AddClueRequest> clues = convertClueData(customerImportTemporaryDOListAdd); //3,调用线索dubbo接口 if (addClueFlag == null || 1 != addClueFlag) {//传入1时 不调线索 clueSPI.batchAddClue(clues); } } //4,数据处理到新表里 List<Integer> ids = customerImportTemporaryDOList.stream().map(CustomerImportTemporaryDO::getId).collect(Collectors.toList()); if (CollectionUtils.isNotEmpty(ids)) { customerImportTemporaryMapper.updateIsImport(ids); } customerServiceUtil.sureCustomersByImport(customerImportTemporaryDOMap, customerDOMap, customerIntentionCycleDOMap, customerIntentionCarDOMap, customerFollowUpDOMap, AuthNHolder.userId(), AuthNHolder.userName(), batchUid); return BeanUtils.convert(customerImportTemporaryDOList, ImportCustomerVO.class); }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。