活动介绍

@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. 引入分布式锁防止重复计算
阅读全文

相关推荐

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.dao.PpmAssembleOutputTaskDao; 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 @Lazy private SysAdapter sysAdapter; @Resource @Lazy private BasicAdapter basicAdapter; @Resource @Lazy private PpmAssembleOutputSelfCheckService assembleOutputSelfCheckService; @Resource @Lazy private PpmAssembleOutputMaterialService assembleOutputMaterialService; @Resource @Lazy private PpmAssembleOutputTaskDao assembleOutputTaskDao; @Resume(tableName = PpmConsts.ProductionExecute.PpmAssembleOutputTask.TABLE) @Override 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 outputMaterialList = assembleOutputMaterialService.lambdaQuery() .eq(PpmAssembleOutputMaterial::getAssembleOutputTaskId, outputTask.getId()) .eq(PpmAssembleOutputMaterial::getIsDel, YesOrNoEnum.NO.getCode()) .eq(PpmAssembleOutputMaterial::getBranchCompanyId, outputTask.getBranchCompanyId()) .list(); //更新mbom的缺料数量以及是否缺料状态 assembleOutputTaskDao.batchUpdateLackMaterials(outputMaterialList.stream() .map(PpmAssembleOutputMaterial::getMbomSonId) // 提取字段 .collect(Collectors.toList()),String.valueOf(YesOrNoEnum.NO.getCode()),YesOrNoEnum.NO.getCode()); List upmaterialList = outputMaterialList.stream().map(m -> { m.setIsLackMaterials(YesOrNoEnum.NO.getCode()); m.setLackItemNum(YesOrNoEnum.NO.getCode()); return m; }).collect(Collectors.toList()); List outputSelfCheckList = assembleOutputSelfCheckService.lambdaQuery() .eq(PpmAssembleOutputSelfCheck::getAssembleOutputTaskId, outputTask.getId()) .eq(PpmAssembleOutputSelfCheck::getIsDel, YesOrNoEnum.NO.getCode()) .eq(PpmAssembleOutputSelfCheck::getBranchCompanyId, UserTokenUtils.getBranchCompanyId()) .list(); List 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("履历保存操作失败!"); } } } } 帮我看一下SOLD.getCode()这个枚举明明是1,为什么到更新的时候就变成2了

docx
内容概要:本文提出了一种融合多尺度Wavelet模型的跨文化英语交际智能模型系统(FL-DP-Wavelet),旨在通过多模态数据融合、多尺度特征提取与跨文化适应性建模,提升智能系统的文化敏感性和语境理解能力。该模型通过结合小波变换与深度学习优化语言信号的时频特征提取,基于跨文化敏感性发展模型(DMIS)构建文化适应性评估模块,并设计多模态数据融合框架,增强跨文化场景下的语义解析鲁棒性。实验结果显示,系统在跨文化语境下的语义理解准确率提升12.7%,文化适应性评分优于基线模型15.3%。 适合人群:从事跨文化交流、国际商务、外语教育的研究人员和技术开发者,特别是对智能系统在跨文化场景中的应用感兴趣的学者和工程师。 使用场景及目标:①跨文化商务谈判、教育合作和公共外交等场景中,需要提升智能系统的文化敏感性和语境理解能力;②帮助系统实现实时文化适应,减少因文化差异引起的语义误判和非语言行为冲突;③通过多模态数据融合,增强智能系统在复杂跨文化环境中的语义解析能力。 其他说明:该研究不仅提出了新的理论框架和技术路径,还在实际应用中验证了其有效性和优越性。未来将聚焦于小波-Transformer耦合、联邦学习隐私保护和在线学习算法,进一步推动系统向自主文化融合演进。
txt

最新推荐

recommend-type

Springboot项目使用Slf4j将日志保存到本地目录的实现代码

Springboot 项目使用 Slf4j 将日志保存到本地目录的实现代码 Slf4j 是一个 Java 日志记录门面,它提供了一个通用的日志记录API,允许开发者使用不同的日志记录框架,例如 Logback、Log4j 等。在 Springboot 项目中...
recommend-type

阿达啊是的租出去水电费水电费

企鹅请问阿西重置成本v啊阿萨达
recommend-type

Typora下载问题解决:资源安装包实测可用

### 知识点:Typora下载与安装问题解决 #### 1. Typora 简介 Typora 是一款流行的轻量级Markdown编辑器,它将实时预览功能和源代码编辑结合在一起,为用户提供了一个简洁高效的写作环境。由于其独特的设计和出色的用户体验,Typora 迅速在开发者和内容创作者之间获得了普及。 #### 2. Markdown 简介 Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成有效的XHTML(或者HTML)文档。Markdown 被广泛用于编写 README 文件、撰写文章、创建富文本内容等。其特点在于简化了传统的排版语法,让写作更加专注于内容本身。 #### 3. Typora 的特点和优势 - **所见即所得编辑器**:Typora 结合了传统Markdown编辑器和富文本编辑器的优点,使得用户在编写文档时可以直接看到最终效果。 - **跨平台兼容性**:Typora 支持Windows、macOS以及Linux等多个操作系统。 - **简洁的界面**:它拥有简洁的用户界面,没有复杂的菜单,这有助于减少分心,专注于内容创作。 - **即时预览**:Typora 提供即时预览功能,用户可以立即看到其标记语法对应的视觉效果。 - **集成度高**:支持插入图片、代码块、表格、数学公式等多种格式。 - **扩展性**:支持多种主题和插件,可以进一步增强其功能。 #### 4. 关于标题:“关于Typora下载找不到资源” 当用户在寻找Typora的下载资源时,可能会遇到找不到官方下载链接或被错误资源误导的问题。这可能是由于网络环境限制、搜索关键词不当或者不正确的网站导航等原因导致的。为了解决这个问题,重要的是要知道如何辨别官方下载渠道,以及如何查找和验证可靠的资源。 #### 5. 官方资源的识别和下载 - **访问官方网站**:访问 Typora 的官方网站(https://typora.io/)获取最新版本的下载信息。官方网站是获取软件的最安全和最可靠的方式。 - **下载安装包**:官方网站通常会提供最新版本的安装包下载链接,例如,在此案例中,压缩包子文件名列表中的 typora-setup-x64-0.9.49.exe 对应了 Typora 的一个版本号为 0.9.49 的安装程序,适用于64位Windows系统。 - **检查版本更新**:在安装之前,用户应当确认是否是当前最新版本。如果不是,可从官方网站下载最新版本。 #### 6. 安装包文件名称解析 文件名 typora-setup-x64-0.9.49.exe 中的各部分含义: - **typora**:指的是要安装的软件名。 - **setup**:通常表示这是一个安装程序。 - **x64**:表示这个安装程序支持64位系统架构。 - **0.9.49**:表示这个版本号,用户可以通过这个版本号了解其更新历史和功能改进情况。 #### 7. 实测完成 “实测完成”这一描述表明此文件已经过测试,并确认可以正常下载和安装。实测的流程包括下载安装包、运行安装程序、完成安装以及验证软件功能是否正常。 #### 8. 安装流程详解 1. **下载**:从官方网站下载对应操作系统版本的 Typora 安装包。 2. **运行安装程序**:双击下载的安装程序文件(例如 typora-setup-x64-0.9.49.exe)。 3. **安装向导**:安装向导启动后,遵循提示完成安装。可能包含选择安装路径、接受许可协议、选择开始菜单文件夹等步骤。 4. **完成安装**:完成安装向导后,可能需要重启电脑以完成安装。 5. **验证安装**:启动 Typora 程序,检查软件是否能够正常打开,并确保可以正常使用Markdown编辑功能。 #### 9. 常见问题及解决方案 - **找不到下载资源**:确保访问官方网站或使用正规的软件分发平台获取资源。 - **安装程序无法运行**:检查文件是否有损坏,重新下载安装包。确认系统环境满足安装需求,如操作系统版本兼容性、运行库等。 - **软件无法打开**:尝试以管理员权限运行 Typora,或检查是否有系统安全软件阻止其运行。 - **功能异常或错误**:检查是否有可用的软件更新,或者查看 Typora 官方论坛获取帮助。 #### 10. Typora 的未来展望 由于 Typora 不断更新迭代,功能和用户界面可能会有所改变,包括对新特性的支持和旧特性的优化。同时,开发者社区也在不断地为 Typora 开发新的主题和插件,以提供更加丰富的写作体验和扩展功能。用户应定期访问官方网站或关注官方消息,以便获取最新动态和软件更新。 通过以上介绍和问题解答,用户应该可以更好地理解 Typora 的下载与安装过程,并在遇到相关问题时快速找到解决方法,从而顺利使用这款强大的Markdown编辑器。
recommend-type

网络嗅探器实战进阶:掌握高效数据捕获与准确分析的6大策略

# 1. 网络嗅探器基础原理与工具介绍 网络嗅探器,或称为网络分析器、包分析器,是一种用于捕获网络中的数据包并分析其内容的工具。它们通过直接连接到网络上,读取传输中的数据包信息,为网络安全、故障排除和网络性
recommend-type

system verilog task中用宏定义传参

<think>好的,用户现在想了解如何在SystemVerilog中使用宏定义向task传递参数。之前我已经回答了C/C++中宏定义传参的方法,现在需要迁移到SystemVerilog,同时结合用户提供的引用内容。 首先,我需要回忆SystemVerilog中宏的基本语法,确认是否与C/C++类似。SystemVerilog确实支持`define宏,带参数的形式应该也是类似的。用户提供的引用中提到`__FILE__`和`__LINE__`,这些可能和调试有关,所以可能需要考虑在宏中加入这些信息来帮助调试。 接下来,用户的问题是关于如何通过宏向task传参。这里的task应该是指System
recommend-type

Java开发的Help GUI 1.1源码:可视化组件库详解

在分析给定文件信息后,可以提取出如下知识点: 首先,标题中提到了“基于java的开发源码-帮助视图组件库 Help GUI 1.1源代码.zip”,这说明文件包含了一个Java开发的开源项目,该库被命名为Help GUI,版本为1.1。在此基础上,我们可以讨论以下几个方面: 1. Java开发: - Java是一种广泛使用的面向对象的编程语言,它具有跨平台的特性,即“一次编写,到处运行”的能力。Java通过Java虚拟机(JVM)来实现跨平台运行。 - Java的开发环境一般需要配置Java开发工具包(JDK)和集成开发环境(IDE),如Eclipse、IntelliJ IDEA或PyCharm。 - Java支持多线程编程,拥有丰富的类库和框架,如Spring、Hibernate等,用以简化开发流程。 - Java在企业级应用、移动开发(Android)、桌面应用和服务器端应用中都有广泛的应用。 2. 开源项目: - 开源项目是指源代码公开的软件项目,通常遵循特定的开源许可协议,如GPL、LGPL、Apache License等。 - 开源项目的优势在于可自由使用、修改和分发代码,能够促进技术的交流和创新。 - 通过参与开源项目,开发者可以提高自身的技术水平,贡献代码以回馈社区。 3. 组件库Help GUI 1.1: - Help GUI可能是一个为开发者提供的图形用户界面(GUI)组件库,用于简化Java桌面应用的帮助视图创建。 - 组件库一般会包含一系列预制的用户界面组件,例如按钮、文本框、列表框、对话框等,以帮助快速构建用户界面。 - 版本1.1表明这是组件库的一个更新版本,通常新版本会增加新的特性、修复bug、优化性能。 4. PyCharm配置Python环境: - 这部分描述似乎与主标题无关,但其可能涉及PyCharm这一IDE的使用。 - PyCharm是专为Python语言开发的IDE,但也可以配置Java开发环境。 - 在配置Python环境时,需要安装Python解释器,并设置相关的路径、环境变量等。 - PyCharm提供了代码高亮、代码自动完成、版本控制等功能,能够提升开发效率。 由于【压缩包子文件的文件名称列表】中只有一个“codefans.net”,这可能是一个网站地址,但没有提供足够的信息来推断与上述Java项目或组件库直接相关的内容。 综上所述,文件描述了一个基于Java的开源组件库Help GUI 1.1,该组件库用于帮助开发者创建帮助视图,且暗示了PyCharm这一IDE的使用。通过探讨Java开发、开源项目、组件库和IDE配置等知识点,可以更好地理解文件所指的项目背景和应用场景。
recommend-type

网络嗅探器全攻略:从入门到精通的15大技巧与实践案例

# 1. 网络嗅探器基础与重要性 在数字化的世界中,网络嗅探器是IT专业人员的重要工具,它允许我们监视和分析经过网络的数据包。本章将带您了解网络嗅探器的基本概念及其在网络安全和性能监控中的关键作用。 ## 网络嗅探器概述 网络嗅探器,通常被称为“抓包工具”或“协议分析器”,是一种可以监听、记录和分析网络流量的程序或设备。它们在排除网络问题、进行安全审计和协议分析时发挥着重要作用。 ##
recommend-type

RTL8720DN-VA1-CG后面的VA1-CG是什么意思

<think>嗯,用户这次的问题转向了具体的芯片型号解析,询问RTL8720DN-VA1-CG后缀的含义。这属于硬件标识领域的专业问题,需要结合芯片命名规则和行业惯例来分析。 从用户提问方式看,ta应该具备一定技术背景,可能是电子工程师或物联网开发者,正在做物料选型或故障排查。用户特意强调"参考站内引用"但实际没有引用源,说明需要权威解释而非普通科普。之前讨论Wi-Fi频段差异时用户表现出对技术参数的敏感,这次提问同样注重细节标识。 关于这个型号后缀,需要拆解三部分分析: VA1通常代表芯片修订版本(revision),比如初版叫VA0,改进版叫VA1。CG后缀可能有多种含义:最常见的是封
recommend-type

CCPD2019车牌数据集:10000张带YOLO标签图片

车牌检测是计算机视觉领域的一个重要应用,尤其在智能交通系统、监控视频分析以及车辆管理等方面具有广泛的应用。CCPD2019车牌数据集是专为车牌检测任务设计的数据集,包含了大量的实际场景中拍摄的车辆图片及其对应的标注信息,这些标注信息以YOLO(You Only Look Once)格式提供。 YOLO是一种流行的目标检测算法,因其速度和准确性相结合而受到广泛欢迎。在YOLO算法中,整个图像被一次性通过网络进行处理,同时预测出多个边界框和这些框所属的类别。YOLO将目标检测任务视为一个回归问题,直接从图像像素到边界框坐标和类别概率的映射,与其他基于区域的方法相比,YOLO在速度上有很大的优势,可以实现实时检测。 YOLO格式标签是一种特殊的标注格式,它提供了用于训练和验证模型的数据。这些标签通常包含每个目标的类别以及它的位置信息,通常在一张图片的标注文件中,对于每一个检测到的车辆,都会有一个对应的标注行,标注行中包含了该车辆车牌的位置、大小和类别信息。通常这些信息包括:标注物体在原图中的中心点坐标(x,y)、宽度、高度以及类别ID。 使用CCPD2019车牌数据集,研究人员和工程师可以进行深度学习模型的训练,特别是基于YOLO算法的车牌检测模型。数据集中的图片是精心挑选的,包含了各种光照条件、不同角度和遮挡情况下的车牌图像,这对于提高模型在现实世界中检测的准确性和鲁棒性至关重要。 在深度学习中,训练模型需要大量的标注数据。一个高质量的数据集对于模型能否成功学习到目标检测中的关键特征是至关重要的。CCPD2019车牌数据集提供的10000张图片以及它们的YOLO格式标签,能够为模型提供充足的训练材料。这些标签通常都是由人工仔细标注,确保了标注的质量和准确性。 深度学习在车牌识别系统中的应用是多方面的,不仅包括检测车牌的位置,还包括车牌字符的分割和识别。在实际应用中,经过训练的YOLO模型首先对输入图像中的车牌区域进行定位,然后可以通过其他专门设计的深度学习模型对车牌上的文字进行识别。 CCPD2019车牌数据集对于研究和开发车牌识别技术具有重要价值,它有助于推动计算机视觉领域特别是车牌检测和识别技术的发展。通过深度学习,特别是使用YOLO这样的先进算法,可以实现车牌的快速准确识别,这对于智能交通管理、车辆安全监控和交通流量分析等应用具有十分重要的意义。
recommend-type

【精准温度测量与HP303B校准】:掌握这些高级技巧,提升测量准确性

# 摘要 本文系统地探讨了精准温度测量的科学原理,并对HP303B温度传感器的特性、技术参数、校准准备和校准步骤进行了详细介绍。文章进一步讨论了提