@Slf4j @Component public class RoleMenuManager { @Resource private SysRoleMenuDao sysRoleMenuDao; @Transactional(rollbackFor = Exception.class) public void batchRoleMenu(Long roleId, List<Long> menuIds) { List<SysRoleMenuEntity> sysRoleMenuEntityList = new ArrayList<>(); for (Long menuId : menuIds) { SysRoleMenuEntity roleMenuEntity = new SysRoleMenuEntity(); roleMenuEntity.setRoleId(roleId); roleMenuEntity.setMenuId(menuId); sysRoleMenuEntityList.add(roleMenuEntity); } if (CollUtil.isNotEmpty(sysRoleMenuEntityList)) { sysRoleMenuDao.batchRoleMenu(sysRoleMenuEntityList); } } }
时间: 2024-02-16 20:26:29 浏览: 103
这是一个 Java 类,使用了注解和依赖注入。@Slf4j 是 Lombok 提供的注解,用于自动生成日志记录器。@Component 是 Spring 提供的注解,用于将该类作为组件注册到 Spring 容器中,以便进行依赖注入。该类中有一个 batchRoleMenu 方法,通过传入 roleId 和 menuIds 两个参数,将其转换为 SysRoleMenuEntity 对象,并且将这些对象封装到一个集合中,然后调用 sysRoleMenuDao 的 batchRoleMenu 方法进行批量添加或更新角色与菜单的关联关系。该方法使用 @Transactional 注解声明事务,当出现异常时进行回滚操作。
相关问题
package com.ever.digitalfactory.ppm.usage.rabbitmq.impl; import com.ever.beeboat.runtime.config.exception.AppError; import com.ever.beeboat.runtime.config.exception.AppErrorException; import com.ever.common.util.BpmUtils; import com.ever.common.util.UserTokenUtils; import com.ever.digitalfactory.ppm.base.consts.PpmConsts; import com.ever.digitalfactory.ppm.usage.adapter.BasicAdapter; import com.ever.digitalfactory.ppm.usage.adapter.SysAdapter; import com.ever.digitalfactory.ppm.usage.adapter.param.ManufacturingBomSonIsLackMaterialsUpdateParam; import com.ever.digitalfactory.ppm.usage.enums.YesOrNoEnum; import com.ever.digitalfactory.ppm.usage.productionexecute.domain.PpmAssembleOutputMaterial; import com.ever.digitalfactory.ppm.usage.productionexecute.domain.PpmAssembleOutputSelfCheck; import com.ever.digitalfactory.ppm.usage.productionexecute.domain.PpmAssembleOutputTask; import com.ever.digitalfactory.ppm.usage.productionexecute.service.PpmAssembleOutputMaterialService; import com.ever.digitalfactory.ppm.usage.productionexecute.service.PpmAssembleOutputSelfCheckService; import com.ever.digitalfactory.ppm.usage.productionexecute.service.PpmAssembleOutputTaskService; import com.ever.digitalfactory.ppm.usage.rabbitmq.ApprovalParamEntity; import com.ever.digitalfactory.ppm.usage.rabbitmq.enums.MqStatusEnum; import com.ever.digitalfactory.ppm.usage.rabbitmq.enums.MqTypeEnum; import com.ever.digitalfactory.ppm.usage.rabbitmq.factory.ConsumeFlowMessage; import com.ever.digitalfactory.system.commons.component.Resume; import com.ever.digitalfactory.system.commons.sys.param.SystemResumeSaveListBatchParam; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import static com.ever.digitalfactory.ppm.usage.enums.OutputStatusEnum.SOLD; /** * 销项送审回调接口 */ @Slf4j @Service @RefreshScope public class CheckOutputTaskToMaterialAndSelfCheckResult implements ConsumeFlowMessage { @Resource @Lazy private PpmAssembleOutputTaskService assembleOutputTaskService; @Resource private SysAdapter sysAdapter; @Resource private PpmAssembleOutputSelfCheckService assembleOutputSelfCheckService; @Resource private PpmAssembleOutputMaterialService assembleOutputMaterialService; @Resource private BasicAdapter basicAdapter; @Resume(tableName = PpmConsts.ProductionExecute.PpmAssembleOutputTask.TABLE) @Override @Transactional(rollbackFor = Exception.class) public void dealBusinessByApproveResult(ApprovalParamEntity approvalParam) { PpmAssembleOutputTask outputTask = assembleOutputTaskService.getById(approvalParam.getBizId()); if (outputTask == null) { throw new AppErrorException(AppError.INTERNAL_ERROR.getCode(), "数据不存在"); } //审批通过 if (MqStatusEnum.AGREE.getCode().equals(approvalParam.getActionCode())) { outputTask.setOutputStatus(SOLD.getCode()); assembleOutputTaskService.updateById(outputTask); List<PpmAssembleOutputMaterial> outputMaterialList = assembleOutputMaterialService.lambdaQuery() .eq(PpmAssembleOutputMaterial::getAssembleOutputTaskId, outputTask.getId()) .eq(PpmAssembleOutputMaterial::getIsDel, YesOrNoEnum.NO.getCode()) .eq(PpmAssembleOutputMaterial::getBranchCompanyId, outputTask.getBranchCompanyId()) .list(); //更新mbom的缺料数量以及是否缺料状态 outputMaterialList.forEach(result -> { ManufacturingBomSonIsLackMaterialsUpdateParam updateParam = new ManufacturingBomSonIsLackMaterialsUpdateParam(); updateParam.setId(result.getMbomSonId()); updateParam.setIsLackMaterials(String.valueOf(YesOrNoEnum.NO.getCode())); updateParam.setLackItemNum(YesOrNoEnum.NO.getCode()); basicAdapter.isLackMaterialsUpdateAdapter(updateParam); }); List<PpmAssembleOutputMaterial> upmaterialList = outputMaterialList.stream().map(m -> { m.setIsLackMaterials(YesOrNoEnum.NO.getCode()); m.setLackItemNum(YesOrNoEnum.NO.getCode()); return m; }).collect(Collectors.toList()); List<PpmAssembleOutputSelfCheck> outputSelfCheckList = assembleOutputSelfCheckService.lambdaQuery() .eq(PpmAssembleOutputSelfCheck::getAssembleOutputTaskId, outputTask.getId()) .eq(PpmAssembleOutputSelfCheck::getIsDel, YesOrNoEnum.NO.getCode()) .eq(PpmAssembleOutputSelfCheck::getBranchCompanyId, UserTokenUtils.getBranchCompanyId()) .list(); List<PpmAssembleOutputSelfCheck> upoutputSelfCheckList = outputSelfCheckList.stream().map(m -> { m.setCheckResult(YesOrNoEnum.NO.getCode()); return m; }).collect(Collectors.toList()); //更新销项物料表 assembleOutputMaterialService.updateBatchById(upmaterialList); //更新销项检验表 assembleOutputSelfCheckService.updateBatchById(upoutputSelfCheckList); } //被驳回 if (MqStatusEnum.REJECT.getCode().equals(approvalParam.getActionCode())) { SystemResumeSaveListBatchParam param = BpmUtils.saveSystemResume(new ArrayList<>(Arrays.asList(approvalParam.getBizId())), approvalParam, MqStatusEnum.REJECT.getMsg(), MqTypeEnum.ASSEMBLE_OUTPUT_TASK_APPROVE.getMsg()); try { sysAdapter.saveResumeListBatch(param); } catch (Exception e) { log.error("履历保存操作失败!"); } } //撤回 改回送审之前的状态 if (MqStatusEnum.REVOKE.getCode().equals(approvalParam.getActionCode())) { SystemResumeSaveListBatchParam param = BpmUtils.saveSystemResume(new ArrayList<>(Arrays.asList(approvalParam.getBizId())), approvalParam, MqStatusEnum.REJECT.getMsg(), MqTypeEnum.ASSEMBLE_OUTPUT_TASK_APPROVE.getMsg()); try { sysAdapter.saveResumeListBatch(param); } catch (Exception e) { log.error("履历保存操作失败!"); } } //中间节点通过 if (MqStatusEnum.NODE_AGREE.getCode().equals(approvalParam.getActionCode())) { SystemResumeSaveListBatchParam param = BpmUtils.saveSystemResume(new ArrayList<>(Arrays.asList(approvalParam.getBizId())), approvalParam, MqStatusEnum.REJECT.getMsg(), MqTypeEnum.ASSEMBLE_OUTPUT_TASK_APPROVE.getMsg()); try { sysAdapter.saveResumeListBatch(param); } catch (Exception e) { log.error("履历保存操作失败!"); } } //修改审批人 if (MqStatusEnum.MODIFY_APPROVER.getCode().equals(approvalParam.getActionCode())) { SystemResumeSaveListBatchParam param = BpmUtils.saveSystemResume(new ArrayList<>(Arrays.asList(approvalParam.getBizId())), approvalParam, MqStatusEnum.REJECT.getMsg(), MqTypeEnum.ASSEMBLE_OUTPUT_TASK_APPROVE.getMsg()); try { sysAdapter.saveResumeListBatch(param); } catch (Exception e) { log.error("履历保存操作失败!"); } } } } 帮我检查一下
<think>根据提供的代码,我们重点检查`basicAdapter.isLackMaterialsUpdateAdapter(updateParam);`出现空指针异常的原因。由于其他部分也调用了`basicAdapter`的方法而没有问题,我们需要分析当前上下文下`basicAdapter`可能为null的原因。观察代码,我们发现`basicAdapter`是通过`@Resource`注入的。在Spring中,依赖注入通常不会有问题,但以下情况可能导致空指针:1.当前类没有被Spring管理(但代码有`@Service`注解,排除)2.当前类被多次实例化(例如通过new创建),导致注入失效3.在Spring容器完全初始化之前调用了该方法(但这是通过消息队列触发的,通常容器已初始化完成)4.多线程环境下,在依赖注入完成前调用了方法(但消息处理一般是单线程的,除非有异步)然而,我们注意到在类上有一个注解`@RefreshScope`,这是SpringCloud用于配置刷新的作用域。在配置刷新时,会重新创建Bean,此时如果正在处理消息,可能会遇到Bean正在重新创建而不可用的情况。另外,我们注意到`basicAdapter`字段上并没有`@Lazy`注解,而其他一些字段(如`assembleOutputTaskService`)有。但是,`@Lazy`注解在这里的作用是延迟注入,通常用于解决循环依赖。如果`basicAdapter`在配置刷新时被重新加载,而当前Bean还在使用旧的引用,就可能出现空指针。此外,还要考虑方法执行时,Spring可能正在重新加载Bean。由于`@RefreshScope`的存在,当配置更新时,这个Bean会被销毁并重新创建。如果此时有消息正在处理,那么注入的`basicAdapter`可能已经被销毁(变为null)或者被重新创建(但当前实例持有的还是旧的引用)。因此,最可能的原因是:**在配置刷新期间,当前Bean被重新创建,而旧实例的`basicAdapter`已经被置为null(或者旧实例的`basicAdapter`所指向的Bean已经被销毁)**。###解决方案1.**避免在配置刷新时处理消息**:可以通过消息重试机制,在配置刷新完成后重新处理消息。2.**使用代理模式**:通过`@Lazy`注解延迟注入,确保每次调用时都从Spring容器中获取最新的实例。3.**检查`BasicAdapter`的注入方式**:将`basicAdapter`也使用`@Lazy`注入,避免因为配置刷新导致引用失效。###修改代码将`basicAdapter`的注入改为`@Lazy`:```java@Resource@LazyprivateBasicAdapterbasicAdapter;```这样,`basicAdapter`会被包装成一个代理,每次调用时都会从Spring容器中获取最新的实例。###其他可能原因虽然配置刷新是最可能的原因,但我们也应该检查其他可能性:-**多线程问题**:如果`outputMaterialList`是一个很大的列表,并且使用了并行流,那么可能会在多个线程中同时调用`basicAdapter`。但代码中并没有使用并行流,所以排除。-**`basicAdapter`本身的方法实现**:如果`isLackMaterialsUpdateAdapter`方法内部出现了空指针,那么异常堆栈会指向该方法内部。所以需要确认异常堆栈。###调试建议在出现异常时,打印日志来确认`basicAdapter`是否为null:```javaoutputMaterialList.forEach(result->{//检查basicAdapter是否为nullif(basicAdapter==null){log.error("basicAdapterisnullinCheckOutputTaskToMaterialAndSelfCheckResult");}else{//...原有代码}});```###修改后的代码片段```javaoutputMaterialList.forEach(result->{ManufacturingBomSonIsLackMaterialsUpdateParamupdateParam=newManufacturingBomSonIsLackMaterialsUpdateParam();updateParam.setId(result.getMbomSonId());updateParam.setIsLackMaterials(String.valueOf(YesOrNoEnum.NO.getCode()));updateParam.setLackItemNum(YesOrNoEnum.NO.getCode());//添加非空检查if(basicAdapter!=null){basicAdapter.isLackMaterialsUpdateAdapter(updateParam);}else{log.error("basicAdapterisnull,cannotupdatematerialstatusformbomSonId:{}",result.getMbomSonId());}});```但请注意,这只是临时规避,根本原因还是依赖注入的问题。因此,加上`@Lazy`是更合适的做法。###总结推荐做法:1.给`basicAdapter`加上`@Lazy`注解。2.在代码中增加非空判断和日志,以便快速发现问题。修改后的注入代码:```java@Resource@LazyprivateBasicAdapterbasicAdapter;```这样修改后,即使Bean被重新创建,代理也能保证每次调用时都使用最新的实例。§§相关问题§§:1.SpringCloud的`@RefreshScope`机制是如何工作的?2.如何解决Spring配置刷新导致的空指针异常?3.在Spring中,`@Lazy`注解的作用是什么?4.如何设计消息处理以应对SpringBean的重新加载?</think>### 代码问题分析与修复
根据您提供的完整代码,我进行了详细分析。`basicAdapter.isLackMaterialsUpdateAdapter(updateParam)` 报空指针异常的主要原因如下:
#### 根本原因分析:
1. **`@RefreshScope` + `@Lazy` 组合问题**:
- 类使用了 `@RefreshScope`(配置刷新作用域)
- `basicAdapter` 没有 `@Lazy` 注解
- 当配置刷新时,`@RefreshScope` Bean 会被销毁重建,但 `@Lazy` 的依赖可能未及时更新
2. **依赖加载顺序问题**:
- `assembleOutputTaskService` 有 `@Lazy` 注解
- `basicAdapter` 没有 `@Lazy`
- 当 Spring 初始化时,非 `@Lazy` 的依赖会优先加载
3. **潜在的多线程问题**:
- 消息队列处理可能并发执行
- 配置刷新期间依赖可能被重置
#### 修复方案:
##### 方案1:给 `basicAdapter` 添加 `@Lazy`
```java
@Resource
@Lazy // 添加此注解
private BasicAdapter basicAdapter;
```
##### 方案2:使用构造函数注入(推荐)
```java
private final BasicAdapter basicAdapter;
@Autowired
public CheckOutputTaskToMaterialAndSelfCheckResult(
@Lazy PpmAssembleOutputTaskService assembleOutputTaskService,
SysAdapter sysAdapter,
PpmAssembleOutputSelfCheckService assembleOutputSelfCheckService,
PpmAssembleOutputMaterialService assembleOutputMaterialService,
BasicAdapter basicAdapter) {
this.assembleOutputTaskService = assembleOutputTaskService;
this.sysAdapter = sysAdapter;
this.assembleOutputSelfCheckService = assembleOutputSelfCheckService;
this.assembleOutputMaterialService = assembleOutputMaterialService;
this.basicAdapter = basicAdapter;
}
```
##### 方案3:添加空指针防护(临时方案)
```java
outputMaterialList.forEach(result -> {
// 添加空指针检查
if (basicAdapter == null) {
log.error("basicAdapter is null! Skipping update for mbomSonId: {}", result.getMbomSonId());
return;
}
ManufacturingBomSonIsLackMaterialsUpdateParam updateParam = new ManufacturingBomSonIsLackMaterialsUpdateParam();
// ...参数设置
basicAdapter.isLackMaterialsUpdateAdapter(updateParam);
});
```
#### 完整修复后的代码片段:
```java
@Slf4j
@Service
@RefreshScope
public class CheckOutputTaskToMaterialAndSelfCheckResult implements ConsumeFlowMessage {
// 改为构造函数注入
private final PpmAssembleOutputTaskService assembleOutputTaskService;
private final SysAdapter sysAdapter;
private final PpmAssembleOutputSelfCheckService assembleOutputSelfCheckService;
private final PpmAssembleOutputMaterialService assembleOutputMaterialService;
private final BasicAdapter basicAdapter;
@Autowired
public CheckOutputTaskToMaterialAndSelfCheckResult(
@Lazy PpmAssembleOutputTaskService assembleOutputTaskService,
SysAdapter sysAdapter,
PpmAssembleOutputSelfCheckService assembleOutputSelfCheckService,
PpmAssembleOutputMaterialService assembleOutputMaterialService,
BasicAdapter basicAdapter) {
this.assembleOutputTaskService = assembleOutputTaskService;
this.sysAdapter = sysAdapter;
this.assembleOutputSelfCheckService = assembleOutputSelfCheckService;
this.assembleOutputMaterialService = assembleOutputMaterialService;
this.basicAdapter = basicAdapter;
}
// ... 其他方法保持不变
}
```
### 其他潜在问题检查:
1. **事务边界问题**:
```java
@Transactional(rollbackFor = Exception.class)
public void dealBusinessByApproveResult(ApprovalParamEntity approvalParam) {
// 长事务风险:包含外部服务调用
basicAdapter.isLackMaterialsUpdateAdapter(updateParam); // 外部调用
}
```
**建议**:将外部调用移到事务外或添加重试机制
2. **批量更新优化**:
```java
// 当前是循环单次调用
outputMaterialList.forEach(result -> {
basicAdapter.isLackMaterialsUpdateAdapter(updateParam);
});
// 建议改为批量接口
basicAdapter.batchUpdateMaterials(outputMaterialList);
```
3. **分支公司ID获取**:
```java
.eq(PpmAssembleOutputSelfCheck::getBranchCompanyId, UserTokenUtils.getBranchCompanyId())
```
**注意**:确保消息处理时有有效的用户上下文
### 日志增强建议:
添加更详细的错误日志:
```java
try {
basicAdapter.isLackMaterialsUpdateAdapter(updateParam);
} catch (NullPointerException e) {
log.error("NPE in basicAdapter call. Service state: {}, Hash: {}",
basicAdapter == null ? "NULL" : "INJECTED",
System.identityHashCode(basicAdapter));
throw e;
}
```
### 总结修复步骤:
1. **优先采用构造函数注入**
2. **给所有依赖添加 `@Lazy` 注解**
3. **添加空指针防护日志**
4. **检查 Spring Cloud 配置刷新事件**
5. **考虑拆分长事务**
我代码能力很差,你解释一下package com.foonsu.efenxiao.biz.handler; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.text.csv.CsvReadConfig; import cn.hutool.core.text.csv.CsvReader; import cn.hutool.core.text.csv.CsvRow; import cn.hutool.core.util.ObjectUtil; import com.alibaba.excel.EasyExcel; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.foonsu.efenxiao.biz.config.CidReadListener; import com.foonsu.efenxiao.biz.feign.OrderClientWrapper; import com.foonsu.efenxiao.biz.util.OrderUtil; import com.foonsu.efenxiao.order.dto.CidOrder; import com.foonsu.efenxiao.biz.vo.ImportCidAccountVO; import com.foonsu.efenxiao.biz.vo.ProgressVo; import com.foonsu.efenxiao.common.utils.CollectionsUtil; import com.foonsu.efenxiao.order.dto.OrderDTO; import com.foonsu.efenxiao.order.dto.OrderExtendDto; import com.foonsu.efenxiao.order.dto.OrderGoodsDto; import com.foonsu.efenxiao.order.dto.OrderTradeDto; import com.foonsu.efenxiao.order.dubbo.CidClient; import com.foonsu.efenxiao.order.dubbo.OrderClient; import com.foonsu.efenxiao.order.entity.CidAccount; import com.foonsu.efenxiao.order.entity.CidAccountingDetail; import com.foonsu.efenxiao.order.entity.CidPlan; import com.foonsu.efenxiao.order.entity.Order; import com.foonsu.efenxiao.order.vo.CidAccountSearchVO; import com.foonsu.efenxiao.order.vo.CidOrderConsumption; import com.foonsu.efenxiao.order.vo.CidPlanSearchResp; import com.foonsu.efenxiao.order.vo.CidPlanSearchVo; import com.foonsu.efenxiao.order.vo.req.AdvertiserAccount; import com.foonsu.efenxiao.order.vo.req.HistoryPushAccountReq; import com.foonsu.efenxiao.platform.dubbo.GoodsClient; import com.foonsu.efenxiao.platform.entity.CidGoodsPriceHistory; import com.foonsu.efenxiao.platform.entity.PlatformGoods; import com.foonsu.efenxiao.platform.vo.PlatformGoodsSkuVO; import com.foonsu.efenxiao.platform.vo.PlatformGoodsVO; import com.foonsu.efenxiao.search.dubbo.OrderSearchClient; import com.foonsu.efenxiao.search.vo.ordersearch.*; import com.foonsu.efenxiao.webutil.progress.impl.CidImportAccountProgress; import com.foonsu.efenxiao.webutil.progress.impl.CidImportStatementProgress; import com.foonsu.framework.boot.common.utils.MD5Utils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.apache.dubbo.config.annotation.DubboReference; import org.springblade.core.tool.api.R; import org.springblade.core.tool.utils.BeanUtil; import org.springblade.core.tool.utils.StringUtil; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.io.IOException; import java.io.InputStreamReader; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @Component @Slf4j public class CidShopBillHandler { private static final Pattern PATTERN = Pattern.compile("\\d{6}-\\d{15}"); @Resource @Qualifier("cidHandleThreadPoolTaskExecutor") ThreadPoolTaskExecutor cidHandleThreadPoolTaskExecutor; @DubboReference private OrderClient orderClient; @DubboReference private CidClient cidClient; @DubboReference private OrderSearchClient orderSearchClient; @DubboReference private GoodsClient goodsClient; @Resource private OrderClientWrapper orderClientWrapper; @Resource private CidImportStatementProgress cidImportStatementProgress; @Resource private CidImportAccountProgress cidImportAccountProgress; @Value("${cid.tenant-id}") private String tenantId; public static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); public CidSearchOrderResp searchOrder(SearchOrderReq req, String tenantId) { SearchOrderBackendParam backendParam = new SearchOrderBackendParam(); backendParam.setTenantId(tenantId); EsSearchOrderResp resp = orderSearchClient.searchCidOrder(req, backendParam); CidSearchOrderResp cidSearchOrderResp = new CidSearchOrderResp(); if (!resp.getOrderNos().isEmpty()) { List<String> allOrderNos = resp.getOrderNos().stream().flatMap(Collection::stream).collect(Collectors.toList()); List<Order> ordersData = orderClientWrapper.queryByOrderNos(allOrderNos); List<CidOrderVO> cidOrderVOS = this.convertData(resp, ordersData); cidSearchOrderResp.setData(cidOrderVOS); } cidSearchOrderResp.setTotal(resp.getTotalCount().intValue()); cidSearchOrderResp.setCurrent(req.getPageNo()); cidSearchOrderResp.setSize(req.getPageSize()); cidSearchOrderResp.setPages(cidSearchOrderResp.getTotal() / req.getPageSize() + (cidSearchOrderResp.getTotal() % req.getPageSize() > 0 ? 1 : 0)); return cidSearchOrderResp; } private List<CidOrderVO> convertData(EsSearchOrderResp esSearchOrderResp, List<Order> orders) { List<CidOrderVO> cidOrderVOS = new ArrayList<>(); for (List<String> orderNos : esSearchOrderResp.getOrderNos()) { List<Order> onelineOrders = orders.stream().filter(t -> orderNos.contains(t.getOrderNo())).collect(Collectors.toList()); if (!onelineOrders.isEmpty()) { Map<String, List<Order>> tradeNoOrders = onelineOrders.stream().collect(Collectors.groupingBy(Order::getTradeNo)); for (String tradeNo : tradeNoOrders.keySet()) { List<Order> splitOrders = tradeNoOrders.get(tradeNo); List<OrderDTO> orderDTOS = OrderUtil.convertToDto(splitOrders); // cid默认单商品订单 OrderDTO orderDTO = orderDTOS.get(0); CidOrderVO cidOrderVO = new CidOrderVO(); cidOrderVO.setShopName(orderDTO.getShopName()); cidOrderVO.setJlId(orderDTO.getOrderGoodsDtoList().get(0).getBindJlId()); cidOrderVO.setTradeNo(orderDTO.getTradeNo()); cidOrderVO.setOrderNo(orderDTO.getOrderNo()); cidOrderVO.setPayAmount(orderDTO.getOrderTradeDto().getPayAmount()); CidOrderExtendVO cidOrderExtendVO = JSON.parseObject(orderDTO.getExtendInfo(), CidOrderExtendVO.class); cidOrderVO.setCidOrderExtendVO(cidOrderExtendVO); List<CidGoodsVo> goodsVos = JSON.parseArray(orderDTO.getGoods(), CidGoodsVo.class); cidOrderVO.setGoods(goodsVos); cidOrderVOS.add(cidOrderVO); } } } return cidOrderVOS; } public void compute(String tenantId, List<String> tradeNos) { Map<String, List<CidAccountingDetail>> accountDetailGroupByTradeNo = cidClient.queryAllImportCidTradeNo(tenantId, null, tradeNos) .stream().collect(Collectors.groupingBy(CidAccountingDetail::getTradeNo)); List<String> importCidTradeNos = new ArrayList<>(accountDetailGroupByTradeNo.keySet()); List<List<String>> reqsList = CollectionsUtil.splitListToThreads(importCidTradeNos, 1500, 20); for (List<String> orders : reqsList) { cidHandleThreadPoolTaskExecutor.submit(() -> { for (String order : orders) { this.handleOrder(order, tenantId, accountDetailGroupByTradeNo.get(order)); } }); } } public void compute(List<CidAccountingDetail> accountingDetails) { accountingDetails = accountingDetails.stream().sorted(Comparator.comparing(CidAccountingDetail::getTradeNo)).collect(Collectors.toList()); CollectionsUtil.batchDeal(accountingDetails, 500, accountingDetailList -> { List<CidOrder> cidOrders = new ArrayList<>(); Set<String> tradeNos = accountingDetailList.stream().map(CidAccountingDetail::getTradeNo).collect(Collectors.toSet()); Map<String, List<LocalDateTime>> occurTimeByTradeNos = cidClient.queryOccurentTimeByTradeNos(tradeNos); Map<String, List<CidAccountingDetail>> accountDetailGroupByTradeNo = accountingDetailList.stream().collect(Collectors.groupingBy(CidAccountingDetail::getTradeNo)); List<Order> orders = orderClient.queryOrderByTradeNos(new ArrayList<>(tradeNos)); if (CollectionsUtil.isEmpty(orders)) return; Map<String, Order> ordergroupbyTradeNo = orders.stream().collect(Collectors.toMap(Order::getTradeNo, Function.identity())); Map<String, String> ordersGroupByProductIdMap = orders.stream().collect(Collectors.toMap(Order::getTradeNo, order -> { List<OrderGoodsDto> orderGoodsDtos = JSONArray.parseArray(order.getGoods(), OrderGoodsDto.class); return orderGoodsDtos.get(0).getProductId(); })); List<String> skuIds = orders.stream().map(order -> { List<OrderGoodsDto> orderGoodsDtos = JSONArray.parseArray(order.getGoods(), OrderGoodsDto.class); return orderGoodsDtos.get(0).getSkuId(); }).distinct().collect(Collectors.toList()); Map<String, List<CidGoodsPriceHistory>> cidGoodsPriceGroupBySkuIdMap = goodsClient.selectCidGoodsPriceHistory(tenantId, null, skuIds) .stream().collect(Collectors.groupingBy(CidGoodsPriceHistory::getSkuId)); List<String> productIds = ordersGroupByProductIdMap.values().stream().distinct().collect(Collectors.toList()); Map<String, PlatformGoodsVO> goodsGroupByProductId = goodsClient.selectCidGoods(tenantId, productIds, null) .stream().collect(Collectors.toMap(PlatformGoodsVO::getProductId, Function.identity())); accountDetailGroupByTradeNo.forEach((tradeNo, accountDetails) -> { if (ordergroupbyTradeNo.containsKey(tradeNo)) { Order order = ordergroupbyTradeNo.get(tradeNo); String productId = ordersGroupByProductIdMap.get(tradeNo); if (goodsGroupByProductId.containsKey(productId)) { PlatformGoodsVO platformGoodsVO = goodsGroupByProductId.get(productId); OrderExtendDto orderExtendDto = computeGoodsProfit(platformGoodsVO, order, cidGoodsPriceGroupBySkuIdMap); cidOrders.addAll(covertCidOrders(accountDetails, orderExtendDto, tradeNo, platformGoodsVO.getBindJlId(), occurTimeByTradeNos.get(tradeNo))); } } }); if (cidOrders.isEmpty()) return; orderSearchClient.saveCidOrders(cidOrders); }, cidHandleThreadPoolTaskExecutor); } private CidOrder createEsCidOrder(String tenantId, String bindJlId, String tradeNO) { CidOrder esCidOrder = new CidOrder(); esCidOrder.setAccountId(bindJlId); esCidOrder.setTenantId(tenantId); esCidOrder.setTradeNo(tradeNO); return esCidOrder; } private List<CidOrder> covertCidOrders(List<CidAccountingDetail> accountDetails, OrderExtendDto orderExtendDto, String tradeNO, String bindJlId, List<LocalDateTime> occurTimes) { List<CidOrder> esCidOrders = accountDetails.stream().map(accountingDetail -> { CidOrder cidOrder = createEsCidOrder(tenantId, bindJlId, tradeNO); cidOrder.setId(accountingDetail.getUniqueId()); cidOrder.setOccurrenceTime(Collections.singletonList(LocalDateTime.parse(accountingDetail.getOccurrenceTime(), DATETIME_FORMATTER))); cidOrder.setRefundType(orderExtendDto.getRefundType()); BigDecimal incomeAmount = accountingDetail.getIncomeAmount() == null ? BigDecimal.ZERO : accountingDetail.getIncomeAmount(); BigDecimal expenditureAmount = accountingDetail.getExpenditureAmount() == null ? BigDecimal.ZERO : accountingDetail.getExpenditureAmount(); cidOrder.setOrderProfit(incomeAmount.add(expenditureAmount)); switch (accountingDetail.getAccountingType()) { case "扣款": cidOrder.setDeductionSum(accountingDetail.getExpenditureAmount()); switch (accountingDetail.getServiceType()) { case "0040002": cidOrder.setSalesCompensate(accountingDetail.getExpenditureAmount()); break; case "0040003": cidOrder.setExpressCompensate(accountingDetail.getExpenditureAmount()); break; case "0040004": cidOrder.setDelaySendGoods(accountingDetail.getExpenditureAmount()); break; case "0040005": cidOrder.setFalseSendGoods(accountingDetail.getExpenditureAmount()); break; case "0040006": cidOrder.setOutOfStock(accountingDetail.getExpenditureAmount()); } break; case "技术服务费": cidOrder.setTechnologyFee(accountingDetail.getExpenditureAmount()); break; case "多多进宝": cidOrder.setDdjb(accountingDetail.getExpenditureAmount()); } return cidOrder; }).collect(Collectors.toList()); CidOrder esCidOrder = createEsCidOrder(tenantId, bindJlId, tradeNO); esCidOrder.setOrderProfit(BigDecimal.ZERO.subtract(orderExtendDto.getPurchaseExpressFee())); esCidOrder.setId(tradeNO); esCidOrder.setOccurrenceTime(occurTimes.stream().distinct().collect(Collectors.toList())); esCidOrders.add(esCidOrder); return esCidOrders; } private OrderExtendDto computeGoodsProfit(PlatformGoodsVO platformGoods, Order order, Map<String, List<CidGoodsPriceHistory>> cidGoodsPriceGroupBySkuIdMap) { OrderTradeDto orderTradeDto = JSON.parseObject(order.getTrade(), OrderTradeDto.class); List<OrderGoodsDto> orderGoodsDtos = JSONArray.parseArray(order.getGoods(), OrderGoodsDto.class); OrderGoodsDto orderGoodsDto = orderGoodsDtos.get(0); OrderExtendDto orderExtendDto = new OrderExtendDto(); List<PlatformGoodsSkuVO> skuVOS = platformGoods.getGoodsSkuList().stream().filter(sku -> sku.getSkuId().equals(orderGoodsDto.getSkuId())).collect(Collectors.toList()); if (!skuVOS.isEmpty()) { PlatformGoodsSkuVO platformGoodsSkuVO = skuVOS.get(0); BigDecimal purchasePrice = platformGoodsSkuVO.getPurchaseUnitPrice(); BigDecimal expressFee = platformGoodsSkuVO.getExpressFee(); String skuId = platformGoodsSkuVO.getSkuId(); if (cidGoodsPriceGroupBySkuIdMap.containsKey(skuId)) { List<CidGoodsPriceHistory> cidGoodsPriceHistories = cidGoodsPriceGroupBySkuIdMap.get(skuId); for (CidGoodsPriceHistory cidGoodsPriceHistory : cidGoodsPriceHistories) { if (cidGoodsPriceHistory.getType() == 2 && DateUtil.toLocalDateTime(new Date(orderTradeDto.getSourceCreateDate())).isAfter(cidGoodsPriceHistory.getUpdateTime())) { purchasePrice = cidGoodsPriceHistory.getPrice(); } if (cidGoodsPriceHistory.getType() == 3 && DateUtil.toLocalDateTime(new Date(orderTradeDto.getSourceCreateDate())).isAfter(cidGoodsPriceHistory.getUpdateTime())) { expressFee = cidGoodsPriceHistory.getPrice(); } } } orderExtendDto.setPurchasePrice(purchasePrice == null ? BigDecimal.ZERO : purchasePrice); orderExtendDto.setExpressFee(expressFee == null ? BigDecimal.ZERO : expressFee); } if (orderGoodsDto.getRefundType() != null) { orderExtendDto.setPurchaseExpressFee(orderExtendDto.getExpressFee()); orderExtendDto.setRefundType(orderGoodsDto.getRefundType()); } else { if (orderExtendDto.getPurchasePrice() != null && orderExtendDto.getExpressFee() != null) { orderExtendDto.setPurchaseExpressFee(orderExtendDto.getPurchasePrice().add(orderExtendDto.getExpressFee())); } } return orderExtendDto; } private void handleOrder(String importCidTradeNo, String tenantId, List<CidAccountingDetail> cidAccountingDetails) { if (StringUtil.isBlank(importCidTradeNo)) { return; } List<Order> orders = orderClient.queryOrderByTradeNo(importCidTradeNo); if (orders.isEmpty()) { log.info("对账订单为空"); return; } // 默认都是单商品订单 Order order = orders.get(0); List<CidAccountingDetail> accountingDetails = cidClient.queryAccountingDetailByTradeNo(tenantId, importCidTradeNo); if (accountingDetails.isEmpty()) { log.info("对账明细为空"); return; } OrderTradeDto orderTradeDto = JSON.parseObject(order.getTrade(), OrderTradeDto.class); List<OrderGoodsDto> orderGoodsDtos = JSONArray.parseArray(order.getGoods(), OrderGoodsDto.class); OrderGoodsDto orderGoodsDto = orderGoodsDtos.get(0); List<PlatformGoodsVO> platformGoodsVOS = goodsClient.selectCidGoods(tenantId, null, null); if (platformGoodsVOS.isEmpty()) { return; } PlatformGoodsVO platformGoods = platformGoodsVOS.get(0); OrderExtendDto orderExtendDto = StringUtils.isBlank(order.getExtendInfo()) ? new OrderExtendDto() : JSON.parseObject(order.getExtendInfo(), OrderExtendDto.class); BigDecimal orderNum = BigDecimal.ZERO; BigDecimal deductionSum = BigDecimal.ZERO; BigDecimal technologyFee = BigDecimal.ZERO; BigDecimal ddjb = BigDecimal.ZERO; BigDecimal salesCompensate = BigDecimal.ZERO;// 售后补偿 BigDecimal expressCompensate = BigDecimal.ZERO;// 运费补偿 BigDecimal delaySendGoods = BigDecimal.ZERO;// 延迟发货 BigDecimal falseSendGoods = BigDecimal.ZERO;// 虚假发货 BigDecimal outOfStock = BigDecimal.ZERO;// 缺货 for (CidAccountingDetail accountingDetail : accountingDetails) { orderNum = orderNum.add(accountingDetail.getIncomeAmount()).add(accountingDetail.getExpenditureAmount()); boolean serviceFlag = false; switch (accountingDetail.getAccountingType()) { case "扣款": deductionSum = deductionSum.add(accountingDetail.getExpenditureAmount()); serviceFlag = true; break; case "技术服务费": technologyFee = technologyFee.add(accountingDetail.getExpenditureAmount()); break; case "多多进宝": ddjb = ddjb.add(accountingDetail.getExpenditureAmount()); } if (serviceFlag) { switch (accountingDetail.getServiceType()) { case "0040002": salesCompensate = salesCompensate.add(accountingDetail.getExpenditureAmount()); break; case "0040003": expressCompensate = expressCompensate.add(accountingDetail.getExpenditureAmount()); break; case "0040004": delaySendGoods = delaySendGoods.add(accountingDetail.getExpenditureAmount()); break; case "0040005": falseSendGoods = falseSendGoods.add(accountingDetail.getExpenditureAmount()); break; case "0040006": outOfStock = outOfStock.add(accountingDetail.getExpenditureAmount()); } } } orderExtendDto.setDeductionSum(deductionSum); orderExtendDto.setTechnologyFee(technologyFee); orderExtendDto.setDdjb(ddjb); orderExtendDto.setSalesCompensate(salesCompensate); orderExtendDto.setExpressCompensate(expressCompensate); orderExtendDto.setDelaySendGoods(delaySendGoods); orderExtendDto.setFalseSendGoods(falseSendGoods); orderExtendDto.setOutOfStock(outOfStock); orderExtendDto.setPurchasePrice(BigDecimal.ZERO); orderExtendDto.setExpressFee(BigDecimal.ZERO); List<PlatformGoodsSkuVO> skuVOS = platformGoods.getGoodsSkuList().stream().filter(sku -> sku.getSkuId().equals(orderGoodsDto.getSkuId())).collect(Collectors.toList()); if (!skuVOS.isEmpty()) { PlatformGoodsSkuVO platformGoodsSkuVO = skuVOS.get(0); BigDecimal purchasePrice = platformGoodsSkuVO.getPurchaseUnitPrice(); BigDecimal expressFee = platformGoodsSkuVO.getExpressFee(); List<CidGoodsPriceHistory> cidGoodsPriceHistories = goodsClient.selectCidGoodsPriceHistory(tenantId, null, null); if (!cidGoodsPriceHistories.isEmpty()) { for (CidGoodsPriceHistory cidGoodsPriceHistory : cidGoodsPriceHistories) { if (cidGoodsPriceHistory.getType() == 2 && DateUtil.toLocalDateTime(new Date(orderTradeDto.getSourceCreateDate())).isAfter(cidGoodsPriceHistory.getUpdateTime())) { purchasePrice = cidGoodsPriceHistory.getPrice(); } if (cidGoodsPriceHistory.getType() == 3 && DateUtil.toLocalDateTime(new Date(orderTradeDto.getSourceCreateDate())).isAfter(cidGoodsPriceHistory.getUpdateTime())) { expressFee = cidGoodsPriceHistory.getPrice(); } } } orderExtendDto.setPurchasePrice(purchasePrice == null ? BigDecimal.ZERO : purchasePrice); orderExtendDto.setExpressFee(expressFee == null ? BigDecimal.ZERO : expressFee); } if (orderGoodsDto.getRefundType() != null) { orderExtendDto.setPurchaseExpressFee(orderExtendDto.getExpressFee()); orderExtendDto.setRefundType(orderGoodsDto.getRefundType()); } else { if (orderExtendDto.getPurchasePrice() != null && orderExtendDto.getExpressFee() != null) { orderExtendDto.setPurchaseExpressFee(orderExtendDto.getPurchasePrice().add(orderExtendDto.getExpressFee())); } } orderExtendDto.setOrderSum(orderNum); orderExtendDto.setOrderProfit(orderExtendDto.getOrderSum().subtract(orderExtendDto.getPurchaseExpressFee())); orderGoodsDtos.get(0).setBindJlId(platformGoods.getBindJlId()); order.setGoods(JSON.toJSONString(orderGoodsDtos)); order.setExtendInfo(JSON.toJSONString(orderExtendDto)); orderClient.updateCidOrder(order); orderSearchClient.saveCidOrders(orderExtendDto, tenantId, accountingDetails, platformGoods.getBindJlId(), importCidTradeNo); } public void importFile(MultipartFile file, String tenantId, String taskId) { ProgressVo progressVo = new ProgressVo(); R<ProgressVo> result = R.data(progressVo); try { progressVo.setTotalCount(0); progressVo.setAllHasDone(false); cidImportStatementProgress.saveProgress(taskId, result); CsvReader csvReader = new CsvReader(new InputStreamReader(file.getInputStream(), "GBK"), new CsvReadConfig()); List<CsvRow> rows = csvReader.read().getRows(); int size = rows.size(); log.info("文件行数:{}", size); if (size == 0) { log.info("文件为空"); progressVo.setTotalCount(0); progressVo.setAllHasDone(true); cidImportStatementProgress.saveProgress(taskId, result); return; } this.handlerAccountingDetail(rows, tenantId, taskId); } catch (IOException e) { log.error("导入失败", e); throw new RuntimeException(e); } } private void handlerAccountingDetail(List<CsvRow> rows, String tenantId, String taskId) { LocalDateTime importFileTime = LocalDateTime.now(); log.info("执行处理:{}", rows.size()); AtomicInteger atomicInteger = new AtomicInteger(0); List<CidAccountingDetail> accountingDetails = new ArrayList<>(); try { for (CsvRow row : rows) { try { // log.info("row:{}", row); if (row.size() != 7) { continue; } if (StringUtil.isBlank(row.get(1))) { continue; } if ("商户订单号".equals(row.get(0))) { continue; } atomicInteger.getAndIncrement(); CidAccountingDetail accountingDetail = new CidAccountingDetail(); accountingDetail.setTenantId(tenantId); String tradeNo = row.get(0); if (StringUtil.isBlank(tradeNo)) { Matcher matcher = PATTERN.matcher(row.get(5)); if (matcher.find()) { tradeNo = matcher.group(); } else { continue; } } accountingDetail.setTradeNo(tradeNo); accountingDetail.setOccurrenceTime(row.get(1)); accountingDetail.setIncomeAmount(new BigDecimal(row.get(2))); accountingDetail.setExpenditureAmount(new BigDecimal(row.get(3))); accountingDetail.setAccountingType(row.get(4)); accountingDetail.setRemark(row.get(5)); String[] service = row.get(6).split("\\|"); accountingDetail.setServiceType(service[0]); accountingDetail.setServiceDesc(service[1]); accountingDetail.setCreateTime(importFileTime); String uniqueId = MD5Utils.encryptMD5(accountingDetail.getTradeNo() + accountingDetail.getOccurrenceTime() + accountingDetail.getAccountingType()); accountingDetail.setUniqueId(uniqueId); accountingDetails.add(accountingDetail); } catch (Exception e) { throw new RuntimeException(e); } } if (accountingDetails.isEmpty()) return; CountDownLatch countDownLatch = new CountDownLatch(accountingDetails.size() / 200 + 1); for (int j = 0; j < accountingDetails.size(); j += 200) { List<CidAccountingDetail> cidAccountList = accountingDetails.subList(j, Math.min(j + 200, accountingDetails.size())); cidHandleThreadPoolTaskExecutor.submit(() -> { cidClient.batchSaveAccountingDetail(cidAccountList); countDownLatch.countDown(); }); } countDownLatch.await(); compute(accountingDetails); } catch (Exception e) { throw new RuntimeException(e); } } public void importAccount(MultipartFile file, String tenantId, String taskId) { LocalDateTime importTime = LocalDateTime.now(); ProgressVo progressVo = new ProgressVo(); R<ProgressVo> result = R.data(progressVo); try { progressVo.setTotalCount(0); progressVo.setAllHasDone(false); cidImportAccountProgress.saveProgress(taskId, result); CidReadListener cidReadListener = new CidReadListener(); cidReadListener.setTenantId(tenantId); EasyExcel.read(file.getInputStream(), ImportCidAccountVO.class, cidReadListener).sheet().doRead(); int size = cidReadListener.getCachedDataList().size(); log.info("账户文件行数:{}", size); if (size == 0) { progressVo.setTotalCount(0); progressVo.setAllHasDone(true); cidImportAccountProgress.saveProgress(taskId, result); log.info("文件为空"); return; } int threadHandlerNum = size / 5 + (size % 5 > 0 ? 1 : 0); int fromIdx = 0; for (int i = 0; i < 5; i++) { List<CidAccount> cidAccounts = cidReadListener.getCachedDataList().subList(fromIdx, size - fromIdx >= threadHandlerNum ? fromIdx + threadHandlerNum : size); cidHandleThreadPoolTaskExecutor.submit(() -> { for (int j = 0; j < cidAccounts.size(); j += 100) { List<CidAccount> cidAccountList = cidAccounts.subList(j, Math.min(j + 100, cidAccounts.size())); cidClient.batchSaveCidAccount(cidAccountList); } log.info("处理账户完成:{}", cidAccounts.size()); return "任务完成"; }); fromIdx += cidAccounts.size(); } try { Thread.sleep(5000); } catch (InterruptedException e) { throw new RuntimeException(e); } log.info("处理账户完成, 调用生成报表:{}", importTime); this.generatePlan(tenantId, taskId, importTime, size); } catch (IOException e) { log.error("导入报表失败:{}", e.getMessage(), e); throw new RuntimeException(e); } } public void generatePlan(String tenantId, String taskId, LocalDateTime importTime, int insertCidAccountCount) { log.info("生成cid报表开始"); CidAccountSearchVO cidAccountSearchVO = new CidAccountSearchVO(); cidAccountSearchVO.setTenantId(tenantId); cidAccountSearchVO.setImportTime(importTime); Long size = 0L; for (int i = 1; i <= 10; i++) { size = cidClient.queryCidAccountCount(cidAccountSearchVO); log.info("计算cidAccount中,查询数据:{}", size); if (insertCidAccountCount == size) { break; } try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } if (size > 0L) { log.info("查询到{}条数据", size); AtomicInteger num = new AtomicInteger(0); ProgressVo progressVo = new ProgressVo(); progressVo.setTotalCount(size.intValue()); R<ProgressVo> result = R.data(progressVo); progressVo.setSuccessCount(num.get()); cidImportAccountProgress.saveProgress(taskId, result); Long finalSize = size; int pages = 1; int pageNo = 1; do { cidAccountSearchVO.setPageNo(pageNo); Page<CidAccount> cidAccountPage = cidClient.queryCidAccountPage(cidAccountSearchVO); pages = (int) cidAccountPage.getPages(); cidHandleThreadPoolTaskExecutor.submit(() -> { log.info("处理数量:{}", cidAccountPage.getRecords().size()); this.handleGroupPlan(cidAccountPage.getRecords(), taskId, finalSize.intValue(), num); }); pageNo++; } while (pageNo <= pages); } log.info("生成cid报表完成"); } private void handleGroupPlan(List<CidAccount> cidAccounts, String taskId, int accountListSize, AtomicInteger num) { ProgressVo progressVo = new ProgressVo(); progressVo.setTotalCount(accountListSize); R<ProgressVo> result = R.data(progressVo); for (CidAccount cidAccount : cidAccounts) { List<PlatformGoods> platformGoodsList = goodsClient.queryGoodsByAccountId(cidAccount.getTenantId(), cidAccount.getAccountId()); this.handlePlan(() -> { CidPlan cidPlan = new CidPlan(); cidPlan.setTenantId(cidAccount.getTenantId()); cidPlan.setAccountName(cidAccount.getAccount()); cidPlan.setChannel("巨量引擎"); cidPlan.setJlId(cidAccount.getAccountId()); cidPlan.setTotalConsumption(cidAccount.getConsume()); return cidPlan; }, platformGoodsList, null); num.getAndIncrement(); progressVo.setSuccessCount(num.get()); cidImportAccountProgress.saveProgress(taskId, result); } } private void handlePlan(Supplier<CidPlan> cidPlanSupplier, List<PlatformGoods> platformGoodsList, CidPlanSearchVo cidPlanSearchVo) { CidPlan cidPlan = cidPlanSupplier.get(); CidPlan result = orderSearchClient.statisticsCidOrder(cidPlan, DateUtil.toLocalDateTime(cidPlanSearchVo.getStartTime()), DateUtil.toLocalDateTime(cidPlanSearchVo.getEndTime())); BeanUtil.copyProperties(result, cidPlan); if (CollectionUtil.isNotEmpty(platformGoodsList)) { log.info("计划:{},没有找到商品", cidPlan.getJlId()); PlatformGoods platformGoods = platformGoodsList.get(0); cidPlan.setSalePrice(platformGoods.getSaleUnitPrice()); cidPlan.setPurchasePrice(platformGoods.getPurchaseUnitPrice()); cidPlan.setExpressFee(platformGoods.getExpressFee()); } } private List<CidOrderVO> filterOrderByDate(CidPlanSearchVo cidPlanSearchVo, List<CidOrderVO> orders) { if (cidPlanSearchVo == null) return orders; List<String> tradeNos = cidClient.selectOrderByTradeNoAndTime( orders.stream().map(CidOrderVO::getTradeNo).collect(Collectors.toList()), cidPlanSearchVo); //orderS过滤掉不包含tradeNo的订单 return orders.stream().filter(cidOrderVO -> tradeNos.contains(cidOrderVO.getTradeNo())).collect(Collectors.toList()); } private void handleOrder(List<CidOrderVO> data, Map<String, BigDecimal> param, Map<String, BigDecimal> deductionDetail, Map<String, Integer> orderParam) { int count = (int) data.stream().filter(cidOrderVO -> cidOrderVO.getCidOrderExtendVO().getRefundType() != null).count(); orderParam.put("refundOrderNum", orderParam.get("refundOrderNum") + count); orderParam.put("orderNum", orderParam.get("orderNum") + data.size()); for (CidOrderVO cidOrderVO : data) { if (cidOrderVO.getCidOrderExtendVO().getDeductionSum() != null) { param.put("deductionSum", param.get("deductionSum").add(cidOrderVO.getCidOrderExtendVO().getDeductionSum())); } if (cidOrderVO.getCidOrderExtendVO().getTechnologyFee() != null) { param.put("technologyFee", param.get("technologyFee").add(cidOrderVO.getCidOrderExtendVO().getTechnologyFee())); } if (cidOrderVO.getCidOrderExtendVO().getDdjb() != null) { param.put("ddjb", param.get("ddjb").add(cidOrderVO.getCidOrderExtendVO().getDdjb())); } if (cidOrderVO.getCidOrderExtendVO().getOrderProfit() != null) { param.put("profit", param.get("profit").add(cidOrderVO.getCidOrderExtendVO().getOrderProfit())); } if (cidOrderVO.getCidOrderExtendVO().getSalesCompensate() != null) { deductionDetail.put("salesCompensate", deductionDetail.get("salesCompensate").add(cidOrderVO.getCidOrderExtendVO().getSalesCompensate())); } if (cidOrderVO.getCidOrderExtendVO().getExpressCompensate() != null) { deductionDetail.put("expressCompensate", deductionDetail.get("expressCompensate").add(cidOrderVO.getCidOrderExtendVO().getExpressCompensate())); } if (cidOrderVO.getCidOrderExtendVO().getDelaySendGoods() != null) { deductionDetail.put("delaySendGoods", deductionDetail.get("delaySendGoods").add(cidOrderVO.getCidOrderExtendVO().getDelaySendGoods())); } if (cidOrderVO.getCidOrderExtendVO().getFalseSendGoods() != null) { deductionDetail.put("falseSendGoods", deductionDetail.get("falseSendGoods").add(cidOrderVO.getCidOrderExtendVO().getFalseSendGoods())); } if (cidOrderVO.getCidOrderExtendVO().getOutOfStock() != null) { deductionDetail.put("outOfStock", deductionDetail.get("outOfStock").add(cidOrderVO.getCidOrderExtendVO().getOutOfStock())); } } } public CidPlanSearchResp searchPlan(CidPlanSearchVo cidPlanSearchVo) { CidPlanSearchResp cidPlanSearchResp = new CidPlanSearchResp(); Page<CidPlan> cidPlanPage = cidClient.queryCidPlanPage(cidPlanSearchVo); List<CidPlan> cidPlans = cidPlanPage.getRecords(); if (ObjectUtil.isAllNotEmpty(cidPlanSearchVo.getStartTime(), cidPlanSearchVo.getEndTime())) { realTimeCompute(cidPlans, cidPlanSearchVo); } BigDecimal currentProfit = cidPlans.stream().map(CidPlan::getProfit).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_UP); cidPlanSearchResp.setCurrentProfit(currentProfit); cidPlanSearchResp.setData(cidPlanPage); CidPlan result = orderSearchClient.statisticsCidOrder(new CidPlan(), DateUtil.toLocalDateTime(cidPlanSearchVo.getStartTime()), DateUtil.toLocalDateTime(cidPlanSearchVo.getEndTime())); cidPlanSearchResp.setTotalProfit(result.getProfit()); return cidPlanSearchResp; } private void realTimeCompute(List<CidPlan> cidPlans, CidPlanSearchVo cidPlanSearchVo) { if (CollectionsUtil.isEmpty(cidPlans)) return; computeCidPlan(cidPlans, cidPlanSearchVo, cidPlans.stream().map(CidPlan::getJlId).collect(Collectors.toList())); } private void computeCidPlan(List<CidPlan> cidPlans, CidPlanSearchVo cidPlanSearchVo, List<String> accountIds) { Map<String, List<PlatformGoods>> goodsMapGroupByBindJlId = goodsClient.queryGoodsByAccountIds(cidPlanSearchVo.getTenantId(), accountIds) .stream().collect(Collectors.groupingBy(PlatformGoods::getBindJlId)); CountDownLatch countDownLatch = new CountDownLatch(cidPlans.size()); cidPlans.forEach(cidPlan -> cidHandleThreadPoolTaskExecutor.execute(() -> { this.handlePlan(() -> cidPlan, goodsMapGroupByBindJlId.get(cidPlan.getJlId()), cidPlanSearchVo); countDownLatch.countDown(); })); try { countDownLatch.await(); } catch (InterruptedException e) { log.error("realTimeCompute error: {}", e.getMessage()); } } @Transactional(rollbackFor = Exception.class) public Boolean pushAccount(List<AdvertiserAccount> data) { List<String> accountIds = data.stream().map(AdvertiserAccount::getAdvertiserId).collect(Collectors.toList()); Map<String, List<PlatformGoods>> goodsMapGroupByBindJlId = goodsClient.queryGoodsByAccountIds(tenantId, accountIds) .stream().collect(Collectors.groupingBy(PlatformGoods::getBindJlId)); Map<String, CidPlan> cidPlanMap = cidClient.queryCidPlans(tenantId, accountIds).stream().collect(Collectors.toMap(CidPlan::getJlId, Function.identity())); CopyOnWriteArrayList<CidPlan> cidPlansList = new CopyOnWriteArrayList<>(); CountDownLatch count = new CountDownLatch(accountIds.size()); List<CidAccount> cidAccounts = data.stream().map(account -> { CidAccount cidAccount = account.mapToCidAccount(new HashMap<>(), tenantId); cidHandleThreadPoolTaskExecutor.execute(() -> { String accountId = cidAccount.getAccountId(); this.handlePlan(() -> dealHistoryPlan(cidAccount, cidPlanMap, cidPlansList), goodsMapGroupByBindJlId.get(accountId), null); count.countDown(); }); return cidAccount; }).collect(Collectors.toList()); cidClient.batchSaveCidPlan(cidPlansList); orderSearchClient.saveCidConsumption(convertEsCidOrderConsumption(data, new Date())); return cidClient.batchSaveCidAccount(cidAccounts); } private CidPlan dealHistoryPlan(CidAccount cidAccount, Map<String, CidPlan> cidPlanMap, CopyOnWriteArrayList<CidPlan> cidPlansList) { String accountId = cidAccount.getAccountId(); List<CidPlan.HistoryTotalConsumption> historyTotalConsumptions = cidPlanMap.containsKey(accountId) && CollectionsUtil.isNotEmpty(cidPlanMap.get(accountId).getHistoryTotalConsumptions()) ? cidPlanMap.get(accountId).getHistoryTotalConsumptions() : new ArrayList<>(); String today = DateUtil.formatDate(new Date()); BigDecimal consume = cidAccount.getConsume(); CidPlan.HistoryTotalConsumption todayData = new CidPlan.HistoryTotalConsumption(DateUtil.parseDate(today), consume); if (historyTotalConsumptions.isEmpty() || cidPlanMap.get(accountId).getHistoryTotalConsumptions().stream() .noneMatch(historyTotalConsumption -> today.equals(DateUtil.formatDate(historyTotalConsumption.getDate())))) { historyTotalConsumptions.add(todayData); } CidPlan cidPlan = new CidPlan(); cidPlan.setTenantId(cidAccount.getTenantId()); cidPlan.setAccountName(cidAccount.getAccount()); cidPlan.setChannel("巨量引擎"); cidPlan.setJlId(accountId); cidPlan.setTotalConsumption(consume); cidPlan.setHistoryTotalConsumption(JSON.toJSONString(historyTotalConsumptions)); cidPlansList.add(cidPlan); return cidPlan; } @Transactional(rollbackFor = Exception.class) public void pushHistoryData(HistoryPushAccountReq historyPushAccountReq) { String day = historyPushAccountReq.getDate(); DateTime time = DateUtil.parseDate(day); List<AdvertiserAccount> advertiserAccounts = historyPushAccountReq.getData(); List<String> accountIds = advertiserAccounts.stream().map(AdvertiserAccount::getAdvertiserId).collect(Collectors.toList()); Map<String, BigDecimal> historyDataMap = advertiserAccounts.stream().collect(Collectors.toMap(AdvertiserAccount::getAdvertiserId, AdvertiserAccount::getStatCost)); List<CidPlan> cidPlans = cidClient.queryCidPlans(tenantId, accountIds); cidPlans.forEach(cidPlan -> { BigDecimal consume = historyDataMap.get(cidPlan.getJlId()); CidPlan.HistoryTotalConsumption todayData = new CidPlan.HistoryTotalConsumption(time, consume); List<CidPlan.HistoryTotalConsumption> historyTotalConsumptions = cidPlan.getHistoryTotalConsumptions(); if (CollectionsUtil.isNotEmpty(historyTotalConsumptions)) { if (historyTotalConsumptions.stream() .noneMatch(consumption -> day.equals(DateUtil.formatDate(consumption.getDate())))) { historyTotalConsumptions.add(todayData); historyTotalConsumptions.sort(Comparator.comparing(CidPlan.HistoryTotalConsumption::getDate)); cidPlan.setHistoryTotalConsumption(JSON.toJSONString(historyTotalConsumptions)); } } else { cidPlan.setHistoryTotalConsumption(JSON.toJSONString(Collections.singleton(todayData))); } }); if (!cidPlans.isEmpty()) { cidClient.batchSaveCidPlan(cidPlans); orderSearchClient.saveCidConsumption(convertEsCidOrderConsumption(advertiserAccounts, time)); } } private List<CidOrderConsumption> convertEsCidOrderConsumption(List<AdvertiserAccount> advertiserAccounts, Date time) { return advertiserAccounts.stream().map(advertiserAccount -> new CidOrderConsumption(advertiserAccount, time.toInstant() .atZone(ZoneId.systemDefault()) .toLocalDate())).collect(Collectors.toList()); } public void syncData(String accountId) { CollectionsUtil.batchDealWholeData(pageNo -> { Page<CidPlan> cidPlanPage = cidClient.queryCidPlanPage(new CidPlanSearchVo(pageNo, 1000, tenantId, accountId)); List<CidPlan> cidPlans = cidPlanPage.getRecords(); log.info("cidPlans:{}", JSON.toJSONString(cidPlans)); List<CidOrderConsumption> cidOrderConsumptions = cidPlans.stream().flatMap(cidPlan -> cidPlan.getHistoryTotalConsumptions().stream() .filter(historyTotalConsumption -> BigDecimal.ZERO.compareTo(historyTotalConsumption.getTotalConsumption()) < 0) .map(historyTotalConsumption -> new CidOrderConsumption(cidPlan.getJlId(), historyTotalConsumption)) ).collect(Collectors.toList()); log.info("cidOrderConsumptions:{}", JSON.toJSONString(cidOrderConsumptions)); if (CollectionUtil.isNotEmpty(cidOrderConsumptions)) { orderSearchClient.saveCidConsumption(cidOrderConsumptions); } return cidPlans; }); } } 并且告诉我每一行为什么这样写
<think>我们无法逐行解释整个类,因为代码量太大。但是,我们可以根据功能模块进行概括性解释,并重点解释关键部分。这个类名为`CidShopBillHandler`,从名字看是处理CID(可能是广告ID)相关的商店账单。它使用了Spring的`@Component`注解,表明是一个Spring管理的Bean。同时使用了Lombok的`@Slf4j`注解用于日志记录。主要功能包括:1.搜索CID相关的订单(`searchOrder`方法)2.计算账单(`compute`方法,有两个重载)3.处理订单数据(`handleOrder`方法,私有)4.导入文件(`importFile`方法,用于导入对账明细文件)5.处理导入的对账明细(`handlerAccountingDetail`方法)6.导入账户文件(`importAccount`方法)7.生成计划(报表)(`generatePlan`方法)8.搜索计划(`searchPlan`方法)9.推送账户(`pushAccount`方法)10.推送历史数据(`pushHistoryData`方法)11.同步数据到搜索服务(`syncData`方法)下面我们针对一些关键方法和代码进行解释:1. **searchOrder方法**:-根据传入的搜索条件(`SearchOrderReq`)和租户ID,调用搜索服务查询CID订单。-将查询结果转换为前端需要的格式(`CidSearchOrderResp`)。2.**compute方法(重载)**:-一个重载根据交易号列表(`tradeNos`)计算账单,另一个重载根据对账明细列表(`accountingDetails`)计算账单。-计算过程中会获取订单信息、商品信息、价格历史等,然后计算利润和各项费用。3.**handleOrder方法(私有)**:-处理单个订单,计算订单的各项费用(如扣款、技术服务费、多多进宝等)和利润,并更新订单信息。4.**importFile方法**:-导入CSV格式的对账明细文件,读取文件内容并解析为`CidAccountingDetail`对象,然后保存到数据库,最后触发计算。5. **handlerAccountingDetail方法**:-解析CSV行,构建`CidAccountingDetail`对象,并进行批量保存。然后调用`compute`方法计算账单。6.**importAccount方法**:-导入账户文件(Excel格式),使用EasyExcel读取,然后分批保存到数据库。之后调用`generatePlan`方法生成报表。7. **generatePlan方法**:-根据导入的账户数据生成报表(`CidPlan`)。它会分页查询账户数据,然后为每个账户生成报表数据。8. **handlePlan方法(私有)**:-处理单个账户的报表生成,包括统计订单数据(通过搜索服务)和设置商品价格信息。9.**searchPlan方法**:-根据条件搜索报表数据,并实时计算利润等数据。10. **pushAccount方法**:-推送账户数据,包括处理历史消费记录,并保存到数据库和搜索服务。11. **pushHistoryData方法**:-推送特定日期的历史账户消费数据。12. **syncData方法**:-将报表中的历史消费数据同步到搜索服务。由于代码量巨大,我们不可能逐行解释,但我们可以选取一个典型的方法进行详细解释,例如`importFile`方法:```javapublic voidimportFile(MultipartFilefile,String tenantId,String taskId){ProgressVoprogressVo= newProgressVo();R<ProgressVo>result =R.data(progressVo);try{progressVo.setTotalCount(0);progressVo.setAllHasDone(false);cidImportStatementProgress.saveProgress(taskId,result);//保存进度//读取CSV文件(使用GBK编码)CsvReadercsvReader= newCsvReader(newInputStreamReader(file.getInputStream(), "GBK"), newCsvReadConfig());List<CsvRow>rows =csvReader.read().getRows();int size= rows.size();log.info("文件行数:{}",size);if(size==0){log.info("文件为空");progressVo.setTotalCount(0);progressVo.setAllHasDone(true);cidImportStatementProgress.saveProgress(taskId, result);return;}this.handlerAccountingDetail(rows, tenantId,taskId); //处理对账明细}catch (IOException e) {log.error("导入失败", e);throw newRuntimeException(e);}
}```这个方法用于导入CSV文件,主要步骤:1.初始化进度对象,并设置初始状态(未完成)。2.使用`CsvReader`读取CSV文件,指定编码为GBK。3.获取所有行,如果文件为空则更新进度为完成并返回。4.调用`handlerAccountingDetail`方法处理这些行。在`handlerAccountingDetail`方法中,会逐行解析CSV数据,构建`CidAccountingDetail`对象,然后分批保存到数据库,并触发计算。另一个例子是`compute`方法(重载之一):```javapublicvoid compute(List<CidAccountingDetail> accountingDetails){//按交易号排序accountingDetails =accountingDetails.stream().sorted(Comparator.comparing(CidAccountingDetail::getTradeNo)).collect(Collectors.toList());//分批处理CollectionsUtil.batchDeal(accountingDetails,500, accountingDetailList-> {//...处理逻辑:获取交易号,查询订单,商品信息,价格历史等//计算每个对账明细对应的订单利润等//转换为ES订单对象(CidOrder)//保存到ES}, cidHandleThreadPoolTaskExecutor);
}```这个方法接收一个对账明细列表,然后分批处理(每批500条),使用线程池异步执行。处理过程中,会查询订单、商品、价格历史等信息,计算利润,然后保存到Elasticsearch。由于整个类非常复杂,涉及多个模块(数据库、搜索服务、商品服务等)和并发处理,建议根据具体需要了解某个方法的细节。注意:代码中使用了大量的工具类(如`CollectionsUtil`、`DateUtil`等)和自定义的DTO(数据传输对象),这些都需要结合项目中的其他代码才能完全理解。如果对某个具体方法有疑问,可以针对性地提问。
§§相关问题§§:1.在`compute`方法中,为什么需要对`accountingDetails`按交易号排序?2.在`handlerAccountingDetail`方法中,为什么使用`CountDownLatch`?3.在`importAccount`方法中,如何保证账户数据正确导入并生成报表?4.在`handlePlan`方法中,如何计算一个账户的报表数据?</think>### 代码解释
以下是对 `CidShopBillHandler` 类的关键部分逐行解释:
#### 1. 类定义和依赖注入
```java
@Component
@Slf4j
public class CidShopBillHandler {
// 线程池配置
@Resource
@Qualifier("cidHandleThreadPoolTaskExecutor")
ThreadPoolTaskExecutor cidHandleThreadPoolTaskExecutor;
// Dubbo服务引用
@DubboReference
private OrderClient orderClient;
@DubboReference
private CidClient cidClient;
// ...其他依赖注入...
}
```
- **`@Component`**:标记为Spring管理的Bean
- **`@Slf4j`**:Lombok提供的日志注解
- **线程池**:用于异步处理任务
- **`@DubboReference`**:注入Dubbo远程服务
#### 2. 订单搜索方法
```java
public CidSearchOrderResp searchOrder(SearchOrderReq req, String tenantId) {
// 1. 构建搜索参数
SearchOrderBackendParam backendParam = new SearchOrderBackendParam();
backendParam.setTenantId(tenantId);
// 2. 调用搜索服务
EsSearchOrderResp resp = orderSearchClient.searchCidOrder(req, backendParam);
// 3. 处理搜索结果
if (!resp.getOrderNos().isEmpty()) {
List<String> allOrderNos = resp.getOrderNos().stream().flatMap(Collection::stream).toList();
List<Order> ordersData = orderClientWrapper.queryByOrderNos(allOrderNos);
List<CidOrderVO> cidOrderVOS = convertData(resp, ordersData);
cidSearchOrderResp.setData(cidOrderVOS);
}
// 4. 设置分页信息
cidSearchOrderResp.setTotal(resp.getTotalCount().intValue());
cidSearchOrderResp.setCurrent(req.getPageNo());
// ...其他分页设置...
}
```
- **步骤**:构建参数 → 调用搜索服务 → 转换结果 → 设置分页
- **关键点**:使用`flatMap`合并嵌套列表的订单号
#### 3. 账单计算方法
```java
public void compute(List<CidAccountingDetail> accountingDetails) {
// 1. 按交易号排序
accountingDetails = accountingDetails.stream()
.sorted(Comparator.comparing(CidAccountingDetail::getTradeNo))
.collect(Collectors.toList());
// 2. 分批处理(500条/批)
CollectionsUtil.batchDeal(accountingDetails, 500, accountingDetailList -> {
// 3. 获取关联数据
Set<String> tradeNos = accountingDetailList.stream()
.map(CidAccountingDetail::getTradeNo)
.collect(Collectors.toSet());
Map<String, List<LocalDateTime>> occurTimeByTradeNos =
cidClient.queryOccurentTimeByTradeNos(tradeNos);
// 4. 计算商品利润
OrderExtendDto orderExtendDto = computeGoodsProfit(platformGoodsVO, order, cidGoodsPriceGroupBySkuIdMap);
// 5. 转换为ES存储对象
List<CidOrder> esOrders = covertCidOrders(accountDetails, orderExtendDto, ...);
// 6. 保存到ES
orderSearchClient.saveCidOrders(esOrders);
}, cidHandleThreadPoolTaskExecutor);
}
```
- **核心流程**:排序 → 分批 → 查询关联数据 → 计算利润 → 转换格式 → 存储
- **优化点**:使用分批处理避免内存溢出
#### 4. 文件导入方法
```java
public void importFile(MultipartFile file, String tenantId, String taskId) {
// 1. 初始化进度
ProgressVo progressVo = new ProgressVo();
cidImportStatementProgress.saveProgress(taskId, R.data(progressVo));
// 2. 读取CSV文件
CsvReader csvReader = new CsvReader(..., "GBK");
List<CsvRow> rows = csvReader.read().getRows();
// 3. 处理会计明细
handlerAccountingDetail(rows, tenantId, taskId);
}
```
- **编码处理**:明确指定GBK编码解决中文乱码
- **进度跟踪**:通过`ProgressVo`实现导入进度可视化
#### 5. 会计明细处理
```java
private void handlerAccountingDetail(List<CsvRow> rows, String tenantId, String taskId) {
// 1. 解析CSV行
for (CsvRow row : rows) {
// 跳过标题行
if ("商户订单号".equals(row.get(0))) continue;
// 2. 构建会计明细对象
CidAccountingDetail detail = new CidAccountingDetail();
detail.setTradeNo(extractTradeNo(row)); // 特殊格式处理
detail.setIncomeAmount(new BigDecimal(row.get(2)));
// ...其他字段设置...
// 3. 生成唯一ID
String uniqueId = MD5Utils.encryptMD5(...);
detail.setUniqueId(uniqueId);
}
// 4. 分批保存(200条/批)
CountDownLatch latch = new CountDownLatch(accountingDetails.size() / 200 + 1);
for (int j = 0; j < accountingDetails.size(); j += 200) {
cidHandleThreadPoolTaskExecutor.submit(() -> {
cidClient.batchSaveAccountingDetail(batch);
latch.countDown();
});
}
latch.await(); // 等待所有批次完成
// 5. 触发计算
compute(accountingDetails);
}
```
- **数据清洗**:使用正则提取特殊格式的订单号
- **并发控制**:`CountDownLatch`确保所有批次完成
- **幂等设计**:MD5生成唯一ID避免重复导入
#### 6. 利润计算逻辑
```java
private OrderExtendDto computeGoodsProfit(PlatformGoodsVO goods, Order order,
Map<String, List<CidGoodsPriceHistory>> priceMap) {
// 1. 获取商品SKU信息
List<OrderGoodsDto> goodsList = JSON.parseArray(order.getGoods(), OrderGoodsDto.class);
OrderGoodsDto goodsDto = goodsList.get(0); // 单商品订单
// 2. 获取当前价格
BigDecimal purchasePrice = getCurrentPrice(goodsDto, priceMap, 2); // 采购价
BigDecimal expressFee = getCurrentPrice(goodsDto, priceMap, 3); // 运费
// 3. 处理退款逻辑
if (goodsDto.getRefundType() != null) {
orderExtendDto.setRefundType(goodsDto.getRefundType());
orderExtendDto.setPurchaseExpressFee(expressFee);
} else {
orderExtendDto.setPurchaseExpressFee(purchasePrice.add(expressFee));
}
// 4. 计算订单利润
BigDecimal orderSum = ...; // 订单总额
orderExtendDto.setOrderProfit(orderSum.subtract(purchasePrice.add(expressFee)));
}
```
- **价格获取**:根据价格类型和生效时间动态获取
- **退款处理**:特殊处理退款订单的利润计算
- **精确计算**:使用`BigDecimal`避免浮点数精度问题
### 关键设计亮点
1. **异步处理**:线程池处理批量任务
2. **分批提交**:防止数据库压力过大
3. **幂等设计**:MD5唯一ID避免重复数据
4. **进度跟踪**:实时导入进度反馈
5. **动态定价**:根据时间点获取历史价格
6. **异常隔离**:单行解析错误不影响整体导入
### 改进建议
1. 增加CSV格式校验
2. 添加失败重试机制
3. 优化价格查询的批量化
4. 引入分布式锁防止重复计算
阅读全文
相关推荐













