Ver Fonte

Merge remote-tracking branch 'origin/master'

xiari há 11 meses atrás
pai
commit
bcc1d58f29
17 ficheiros alterados com 623 adições e 347 exclusões
  1. 1 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
  2. 204 144
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/BaseBusiness.java
  3. 221 101
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/ConsumeBusiness.java
  4. 15 3
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/InitBusiness.java
  5. 0 1
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/CardConsumeValidation.java
  6. 1 1
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/CommonCheck.java
  7. 26 15
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/ConsumeUploadCheck.java
  8. 0 19
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/controller/v1/ConsumeController.java
  9. 4 3
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/controller/v1/TermsController.java
  10. 2 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/domain/convert/RemoteConsumeBoConvert.java
  11. 4 4
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/IXfTermTotalService.java
  12. 2 2
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/IXfUserTotalService.java
  13. 65 30
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/XfTermTotalServiceImpl.java
  14. 65 16
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/XfUserTotalServiceImpl.java
  15. 11 7
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/task/InitTasks.java
  16. 1 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/task/ScheduledTasks.java
  17. 1 1
      ruoyi-server/ruoyi-server-consume/src/main/resources/mapper/consume/ConsumeDetailOriginalMapper.xml

+ 1 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java

@@ -191,4 +191,5 @@ public interface CacheNames {
     String PT_MEAL_TYPE_LIST = "pt_meal_type_list";
 
     String XF_ORIGINAL_ID = "xf_original_id";
+    String XF_DETAIL_ID = "xf_detail_id";
 }

+ 204 - 144
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/BaseBusiness.java

@@ -80,8 +80,10 @@ public class BaseBusiness {
     @DubboReference
     private final RemoteCardService remoteCardService;
 
+    //region 原始消费记录处理
+
     /**
-     * 生成原始消费记录
+     * 生成原始消费记录并入库
      *
      * @param consumeBo  消费记录
      * @param accountVo  消费账户
@@ -93,8 +95,8 @@ public class BaseBusiness {
         String originalId = consumeBo.getOriginalId();
         if (ObjectUtil.isEmpty(originalId)) {
             originalId = RecordIdUtils.getRecordId(consumeBo.getConsumeDate(), consumeBo.getTermNo().shortValue(),
-                                                   consumeBo.getTermRecordId().shortValue(),
-                                                   accountVo.getUserNo().intValue(), 0);
+                consumeBo.getTermRecordId().shortValue(),
+                accountVo.getUserNo().intValue(), 0);
         }
 
         XfConsumeDetailOriginalBo originalBo = getXfConsumeDetailOriginalBo(consumeBo, accountVo, originalId);
@@ -108,6 +110,15 @@ public class BaseBusiness {
         return R.fail();
     }
 
+    /**
+     * 获取消费原始记录的原始业务对象。
+     *
+     * @param consumeBo  消费业务对象,包含与消费相关的数据
+     * @param accountVo  远程用户账户值对象,包含与用户账户相关的信息
+     * @param originalId 原始记录的ID,用于标识特定的消费明细记录
+     * @return 返回一个XfConsumeDetailOriginalBo对象,表示消费明细的原始业务数据
+     */
+
     @NotNull
     private XfConsumeDetailOriginalBo getXfConsumeDetailOriginalBo(ConsumptionBo consumeBo, RemoteUserAccountVo accountVo, String originalId) {
         XfConsumeDetailOriginalBo originalBo = new XfConsumeDetailOriginalBo();
@@ -141,6 +152,97 @@ public class BaseBusiness {
         return originalBo;
     }
 
+    //region 请求消费成功后重置相关的验证数据
+
+    /**
+     * 重置卡片消费信息。
+     * <p>
+     * 日消费次数与金额
+     * 餐消费次数与金额
+     *
+     * @param userCardVo   用户卡片值对象,包含与用户卡片相关的信息
+     * @param mealType     餐次类型,用于标识消费所属的餐次
+     * @param consumeMoney 消费金额,表示此次消费的金额数量
+     * @param consumeDate  消费日期,记录此次消费发生的具体时间
+     */
+    public void resetCardConsumeInfo(RemoteCardVo userCardVo, Long mealType, BigDecimal consumeMoney, Date consumeDate) {
+        log.info("[请求交易完成]-[更新卡片日消费数据]");
+        // 更新缓存
+        String date1 = DateUtil.format(consumeDate, DefaultConstants.DATE_FORMAT);
+        String date2 = DateUtil.format(userCardVo.getLastPay(), DefaultConstants.DATE_FORMAT);
+
+        if (ObjUtil.equals(date1, date2)) {
+            userCardVo.setDayCount(userCardVo.getDayCount() + 1);
+            userCardVo.setDayTotal(userCardVo.getDayTotal().add(consumeMoney));
+            if (userCardVo.getLastMeal().equals(mealType)) {
+                userCardVo.setMealCount(userCardVo.getMealCount() + 1);
+                userCardVo.setMealTotal(userCardVo.getMealTotal().add(consumeMoney));
+            } else {
+                userCardVo.setMealCount(1L);
+                userCardVo.setMealTotal(consumeMoney);
+            }
+        } else {
+            userCardVo.setDayCount(1L);
+            userCardVo.setDayTotal(consumeMoney);
+            userCardVo.setMealCount(1L);
+            userCardVo.setMealTotal(consumeMoney);
+        }
+
+        userCardVo.setLastMeal(mealType);
+        userCardVo.setLastPay(consumeDate);
+        RedisUtils.setCacheMapValue(CacheNames.PT_USER_CARD_NO, userCardVo.getCardNo().toString(), userCardVo);
+        RedisUtils.setCacheMapValue(CacheNames.PT_USER_CARD_USER_ID, userCardVo.getUserId().toString(), userCardVo);
+
+        // 更新数据库
+        remoteCardService.updateCardDayData(userCardVo);
+    }
+
+    /**
+     * 重置用户余额。
+     *
+     * @param userId  用户ID,用于标识特定的用户
+     * @param balance 新的余额,表示用户账户的新金额
+     */
+    public void resetUserBalance(Long userId, BigDecimal balance) {
+        log.info("[请求交易完成]-[更新人员余额数据]");
+        String strUserId = String.valueOf(userId);
+        RedisUtils.setCacheMapValue(CacheNames.USER_TOTAL_BALANCE, strUserId, balance);
+    }
+
+    /**
+     * 重置卡片限制信息。
+     * <p>
+     * 根据全局的限次、限额与折扣启用情况更新卡片对应的当日(当餐)限次、限额及折扣数据
+     *
+     * @param mapCardLimited 卡片限制的映射表,包含与卡片限制相关的键值对
+     * @param cardLimitedVo  卡片限制值对象,包含与卡片限制相关的详细信息
+     * @param consumeMoney   消费金额,表示此次消费的金额数量
+     */
+    public void restCardLimitedInfo(Map<String, Boolean> mapCardLimited, XfCardLimitedVo cardLimitedVo, BigDecimal consumeMoney) {
+        if (mapCardLimited.get("hasDiscount")) {
+            cardLimitedVo.setDayDiscountCount(cardLimitedVo.getDayDiscountCount() + 1);
+            cardLimitedVo.setMealDiscountCount(cardLimitedVo.getMealDiscountCount() + 1);
+        }
+        if (mapCardLimited.get("hasQuota")) {
+            cardLimitedVo.setDayMoney(cardLimitedVo.getDayMoney().add(consumeMoney));
+            cardLimitedVo.setMealMoney(cardLimitedVo.getMealMoney().add(consumeMoney));
+        }
+        if (mapCardLimited.get("hasLimited")) {
+            cardLimitedVo.setMealCount(cardLimitedVo.getMealCount() + 1);
+            cardLimitedVo.setDayCount(cardLimitedVo.getDayCount() + 1);
+        }
+
+        RedisUtils.setCacheMapValue(CacheNames.T_XF_CARD_LIMITED, String.valueOf(cardLimitedVo.getCardNo()), cardLimitedVo);
+        cardLimitedService.updateByVo(cardLimitedVo);
+
+    }
+
+    //endregion
+
+    //endregion
+
+    //region 明细消费记录处理
+
     /**
      * 消费记录入库
      * 1.写入消费明细表
@@ -194,74 +296,6 @@ public class BaseBusiness {
         return R.ok();
     }
 
-    /**
-     * 创建或更新人员日统计表
-     *
-     * @param bo        消费业务对象
-     * @param accountVo 人员账户视图
-     * @param cardVo    账户卡片视图
-     * @return 更新后的人员日统计表
-     */
-    public boolean createOrUpdateUserTotal(ConsumptionBo bo, RemoteUserAccountVo accountVo, RemoteCardVo cardVo) {
-        XfUserTotalBo xfUserTotalBo = new XfUserTotalBo();
-        xfUserTotalBo.setUserId(accountVo.getUserId());
-        xfUserTotalBo.setUserNumb(accountVo.getUserNumb());
-        xfUserTotalBo.setRealName(accountVo.getRealName());
-        xfUserTotalBo.setDeptName(accountVo.getDeptName());
-        xfUserTotalBo.setCardNo(cardVo.getCardNo());
-        xfUserTotalBo.setDateDay(DateUtil.format(bo.getConsumeDate(), DefaultConstants.DATE_FORMAT));
-        xfUserTotalBo.setUseType(bo.getUseType());
-        xfUserTotalBo.setConsumeMoney(bo.getConsumeMoney());
-
-        XfUserTotalVo vo = userTotalService.createOrUpdateUserTotal(xfUserTotalBo);
-        return !ObjUtil.isEmpty(vo);
-    }
-
-    /**
-     * 创建或更新设备日统计
-     *
-     * @param bo         消费业务对象
-     * @param termVo     设备视图
-     * @param mealTypeVo 餐类视图
-     * @return 日统计视图
-     */
-    public boolean createOrUpdateTermTotal(ConsumptionBo bo, XfTermVo termVo, RemoteMealTypeVo mealTypeVo) {
-        XfTermTotalBo termTotalBo = new XfTermTotalBo();
-        BeanUtil.copyProperties(termVo, termTotalBo);
-        termTotalBo.setDateDay(DateUtil.format(bo.getConsumeDate(), "yyyy-MM-dd"));
-        termTotalBo.setMealType(Long.valueOf(mealTypeVo.getTypeId()));
-        termTotalBo.setUseType(bo.getUseType());
-        CreditTypeEnum creditType = CreditTypeEnum.fromCode(bo.getCreditType());
-        if (creditType != null) {
-            switch (creditType) {
-                case TERM_CONSUME, HAND_CONSUME:
-                    termTotalBo.setMealCount(1L);
-                    termTotalBo.setMealAmount(bo.getConsumeMoney());
-                    break;
-                case ERROR_FILL:
-                    termTotalBo.setErrFillCount(1L);
-                    termTotalBo.setErrFillMoney(bo.getOperatorMoney());
-                    break;
-            }
-        }
-        XfTermTotalVo vo = termTotalService.createOrUpdateTermTotal(termTotalBo);
-        return !ObjUtil.isEmpty(vo);
-    }
-
-    public void completeUploadRecord(ConsumptionBo bo, RemoteUserAccountVo accountVo) {
-        // 消费记录上传完成,还有一些后续工作,不需要知道处理结果,采用异步任务提交
-        taskExecutor.submit(() -> sendConsumeToKafka(bo, accountVo));
-        taskExecutor.submit(() -> sendCloudConsume(bo));
-    }
-
-    public List<RemoteSendMessageRecordBo> queryConsumeErrorList(RemoteSendMessageRecordBo remoteBo) {
-        return syncRemoteSendMessageRecordService.queryConsumeErrorList(remoteBo);
-    }
-
-    public Boolean updateConsumeStatusById(Long messageId) {
-        return syncRemoteSendMessageRecordService.updateConsumeStatusById(messageId);
-    }
-
     /**
      * 创建一条消费明细记录
      *
@@ -277,9 +311,9 @@ public class BaseBusiness {
                                                     RemoteCardVo cardVo, PtBagVo bagVo, XfTermVo termVo,
                                                     RemoteMealTypeVo mealTypeVo, String remark) {
         String recordId = RecordIdUtils.getRecordId(bo.getConsumeDate(), Short.parseShort(bo.getTermNo().toString()),
-                                                    bo.getTermRecordId().intValue(),
-                                                    userAccountVo.getUserNo().intValue(),
-                                                    Integer.parseInt(bagVo.getBagCode()));
+            bo.getTermRecordId().intValue(),
+            userAccountVo.getUserNo().intValue(),
+            Integer.parseInt(bagVo.getBagCode()));
         XfConsumeDetailBo consumeDetailBo = new XfConsumeDetailBo();
         consumeDetailBo.setConsumeId(recordId);
         consumeDetailBo.setTenantId(bo.getTenantId());
@@ -325,42 +359,73 @@ public class BaseBusiness {
     }
 
     /**
-     * 查询指定日期的消费对账记录
+     * 创建或更新人员日统计表
      *
-     * @param consumeDate 消费日期
-     * @return 消费记录
+     * @param bo        消费业务对象
+     * @param accountVo 人员账户视图
+     * @param cardVo    账户卡片视图
+     * @return 更新后的人员日统计表
      */
-    public List<ConsumptionBo> selectOriginalReconciliation(Date consumeDate) {
-        return TenantHelper.ignore(() -> originalService.selectReconciliationData(consumeDate));
+    public boolean createOrUpdateUserTotal(ConsumptionBo bo, RemoteUserAccountVo accountVo, RemoteCardVo cardVo) {
+        XfUserTotalBo xfUserTotalBo = new XfUserTotalBo();
+        xfUserTotalBo.setUserId(accountVo.getUserId());
+        xfUserTotalBo.setUserNumb(accountVo.getUserNumb());
+        xfUserTotalBo.setRealName(accountVo.getRealName());
+        xfUserTotalBo.setDeptName(accountVo.getDeptName());
+        xfUserTotalBo.setCardNo(cardVo.getCardNo());
+        xfUserTotalBo.setDateDay(DateUtil.format(bo.getConsumeDate(), DefaultConstants.DATE_FORMAT));
+        xfUserTotalBo.setUseType(bo.getUseType());
+        xfUserTotalBo.setConsumeMoney(bo.getConsumeMoney());
+
+        return userTotalService.createOrUpdateUserTotal(xfUserTotalBo);
     }
 
     /**
-     * 查询注册信息,用来检查客户的公钥和么钥
+     * 创建或更新设备日统计
+     *
+     * @param bo         消费业务对象
+     * @param termVo     设备视图
+     * @param mealTypeVo 餐类视图
+     * @return 日统计视图
      */
-    public void getRegisterInfo() {
-        if (ObjectUtil.isEmpty(RedisUtils.getCacheObject(CacheNames.CUSTOM_PUB_KEY)) || ObjectUtil.isEmpty(
-            RedisUtils.getCacheObject(CacheNames.CUSTOM_PRI_KEY))) {
-            remoteRegisterInfoService.queryRegisterInfo();
+    public boolean createOrUpdateTermTotal(ConsumptionBo bo, XfTermVo termVo, RemoteMealTypeVo mealTypeVo) {
+        XfTermTotalBo termTotalBo = new XfTermTotalBo();
+        BeanUtil.copyProperties(termVo, termTotalBo);
+        termTotalBo.setDateDay(DateUtil.format(bo.getConsumeDate(), "yyyy-MM-dd"));
+        termTotalBo.setMealType(Long.valueOf(mealTypeVo.getTypeId()));
+        termTotalBo.setUseType(bo.getUseType());
+        CreditTypeEnum creditType = CreditTypeEnum.fromCode(bo.getCreditType());
+        if (creditType != null) {
+            switch (creditType) {
+                case TERM_CONSUME, HAND_CONSUME:
+                    termTotalBo.setMealCount(1L);
+                    termTotalBo.setMealAmount(bo.getConsumeMoney());
+                    break;
+                case ERROR_FILL:
+                    termTotalBo.setErrFillCount(1L);
+                    termTotalBo.setErrFillMoney(bo.getOperatorMoney());
+                    break;
+            }
         }
+        return termTotalService.createOrUpdateTermTotal(termTotalBo);
     }
 
     /**
-     * 根据消费记录的状态标志获取
+     * 完成上传记录的后续业务处理逻辑。
+     * <p>
+     * 1.发送消费信息到教务系统
+     * 2.发送消费信息到云平台
      *
-     * @param statusFlag 状态标志
-     *                   1,4--消费机消费=25000
-     *                   3-消费补扣=25020
-     *                   0-错扣补款=25010
-     * @return 根据状态转换的消费类型
+     * @param bo        消费业务对象,包含与消费相关的数据
+     * @param accountVo 远程用户账户值对象,包含与用户账户相关的信息
      */
-    public Integer getCreditType(Integer statusFlag) {
-        return switch (statusFlag) {
-            case 0 -> CreditTypeEnum.ERROR_FILL.code();
-            case 3 -> CreditTypeEnum.HAND_CONSUME.code();
-            default -> CreditTypeEnum.TERM_CONSUME.code();
-        };
+    public void completeUploadRecord(ConsumptionBo bo, RemoteUserAccountVo accountVo) {
+        // 消费记录上传完成,还有一些后续工作,不需要知道处理结果,采用异步任务提交
+        taskExecutor.submit(() -> sendConsumeToKafka(bo, accountVo));
+        taskExecutor.submit(() -> sendCloudConsume(bo));
     }
 
+
     /**
      * 将消费信息发送到kafka,教务消费此消息实现就餐打卡
      *
@@ -396,7 +461,7 @@ public class BaseBusiness {
 
             log.info("[向教务系统发送就餐打卡]-[{}]", ycSendConsumeInfo);
             kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.OLD_SYNC_TOPIC, EventTypeConstants.CONSUME_RECORD, EventSenderEnum.OLD.code(),
-                                                 ycSendConsumeInfo);
+                ycSendConsumeInfo);
         }
     }
 
@@ -410,61 +475,56 @@ public class BaseBusiness {
         kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.TO_CLOUD_TOPIC, EventTypeConstants.CONSUME, EventSenderEnum.CONSUME.code(), bo);
     }
 
-    public void resetCardConsumeInfo(RemoteCardVo userCardVo, Long mealType, BigDecimal consumeMoney, Date consumeDate) {
-        log.info("[请求交易完成]-[更新卡片日消费数据]");
-        // 更新缓存
-        String date1 = DateUtil.format(consumeDate, DefaultConstants.DATE_FORMAT);
-        String date2 = DateUtil.format(userCardVo.getLastPay(), DefaultConstants.DATE_FORMAT);
+    //endregion
 
-        if (ObjUtil.equals(date1, date2)) {
-            userCardVo.setDayCount(userCardVo.getDayCount() + 1);
-            userCardVo.setDayTotal(userCardVo.getDayTotal().add(consumeMoney));
-            if (userCardVo.getLastMeal().equals(mealType)) {
-                userCardVo.setMealCount(userCardVo.getMealCount() + 1);
-                userCardVo.setMealTotal(userCardVo.getMealTotal().add(consumeMoney));
-            } else {
-                userCardVo.setMealCount(1L);
-                userCardVo.setMealTotal(consumeMoney);
-            }
-        } else {
-            userCardVo.setDayCount(1L);
-            userCardVo.setDayTotal(consumeMoney);
-            userCardVo.setMealCount(1L);
-            userCardVo.setMealTotal(consumeMoney);
-        }
+    //region 消费对账相关
 
-        userCardVo.setLastMeal(mealType);
-        userCardVo.setLastPay(consumeDate);
-        RedisUtils.setCacheMapValue(CacheNames.PT_USER_CARD_NO, userCardVo.getCardNo().toString(), userCardVo);
-        RedisUtils.setCacheMapValue(CacheNames.PT_USER_CARD_USER_ID, userCardVo.getUserId().toString(), userCardVo);
-
-        // 更新数据库
-        remoteCardService.updateCardDayData(userCardVo);
+    /**
+     * 查询消息发送失败的消费记录
+     *
+     * @param remoteBo kafka消息记录
+     * @return 发送失败消息列表
+     */
+    public List<RemoteSendMessageRecordBo> queryConsumeErrorList(RemoteSendMessageRecordBo remoteBo) {
+        return syncRemoteSendMessageRecordService.queryConsumeErrorList(remoteBo);
     }
 
-    public void resetUserBalance(Long userId, BigDecimal balance) {
-        log.info("[请求交易完成]-[更新人员余额数据]");
-        String strUserId = String.valueOf(userId);
-        RedisUtils.setCacheMapValue(CacheNames.USER_TOTAL_BALANCE, strUserId, balance);
+    /**
+     * 查询指定日期的消费对账记录
+     *
+     * @param consumeDate 消费日期
+     * @return 消费记录
+     */
+    public List<ConsumptionBo> selectOriginalReconciliation(Date consumeDate) {
+        return TenantHelper.ignore(() -> originalService.selectReconciliationData(consumeDate));
     }
+    //endregion
 
-    public void restCardLimitedInfo(Map<String, Boolean> mapCardLimited, XfCardLimitedVo cardLimitedVo, BigDecimal consumeMoney) {
-        if (mapCardLimited.get("hasDiscount")) {
-            cardLimitedVo.setDayDiscountCount(cardLimitedVo.getDayDiscountCount() + 1);
-            cardLimitedVo.setMealDiscountCount(cardLimitedVo.getMealDiscountCount() + 1);
-        }
-        if (mapCardLimited.get("hasQuota")) {
-            cardLimitedVo.setDayMoney(cardLimitedVo.getDayMoney().add(consumeMoney));
-            cardLimitedVo.setMealMoney(cardLimitedVo.getMealMoney().add(consumeMoney));
-        }
-        if (mapCardLimited.get("hasLimited")) {
-            cardLimitedVo.setMealCount(cardLimitedVo.getMealCount() + 1);
-            cardLimitedVo.setDayCount(cardLimitedVo.getDayCount() + 1);
+    /**
+     * 查询注册信息,用来检查客户的公钥和么钥
+     */
+    public void getRegisterInfo() {
+        if (ObjectUtil.isEmpty(RedisUtils.getCacheObject(CacheNames.CUSTOM_PUB_KEY)) || ObjectUtil.isEmpty(
+            RedisUtils.getCacheObject(CacheNames.CUSTOM_PRI_KEY))) {
+            remoteRegisterInfoService.queryRegisterInfo();
         }
+    }
 
-        RedisUtils.setCacheMapValue(CacheNames.T_XF_CARD_LIMITED, String.valueOf(cardLimitedVo.getCardNo()), cardLimitedVo);
-        cardLimitedService.updateByVo(cardLimitedVo);
-
+    /**
+     * 根据消费记录的状态标志获取
+     *
+     * @param statusFlag 状态标志
+     *                   1,4--消费机消费=25000
+     *                   3-消费补扣=25020
+     *                   0-错扣补款=25010
+     * @return 根据状态转换的消费类型
+     */
+    public Integer getCreditType(Integer statusFlag) {
+        return switch (statusFlag) {
+            case 0 -> CreditTypeEnum.ERROR_FILL.code();
+            case 3 -> CreditTypeEnum.HAND_CONSUME.code();
+            default -> CreditTypeEnum.TERM_CONSUME.code();
+        };
     }
 
     public void dealUserTotal(Date date) throws ParseException {

+ 221 - 101
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/ConsumeBusiness.java

@@ -1,21 +1,17 @@
 package org.dromara.server.consume.business;
 
-import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.date.DateUtil;
-import cn.hutool.core.thread.ThreadUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
+import lombok.Data;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.backstage.api.domain.bo.RemoteSendMessageRecordBo;
 import org.dromara.backstage.api.domain.vo.RemoteCardVo;
 import org.dromara.backstage.api.domain.vo.RemoteMealTypeVo;
-import org.dromara.backstage.api.domain.vo.RemoteOperatorVo;
 import org.dromara.backstage.api.domain.vo.RemoteUserAccountVo;
-import org.dromara.common.core.config.DefaultConfig;
 import org.dromara.common.core.constant.ApiErrorTypeConstants;
-import org.dromara.common.core.constant.DefaultConstants;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.domain.model.ErrorInfo;
 import org.dromara.common.core.enums.ConsumeRecordTypeEnum;
@@ -37,10 +33,7 @@ import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -61,8 +54,6 @@ public class ConsumeBusiness {
     private final ConsumeRequestCheck requestCheck;
     private final ConsumeUploadCheck uploadCheck;
     private final BaseBusiness baseBusiness;
-    private final DefaultConfig defaultConfig;
-
 
     /**
      * 请求消费
@@ -74,11 +65,10 @@ public class ConsumeBusiness {
      */
     public R<ErrorInfo> createOrder(ConsumptionBo bo, String mac, String xfPwd) {
         long startTime = System.currentTimeMillis();
-        log.warn("[请求交易]-[开始记录有效性验证]-[{}]", JSONUtil.toJsonStr(bo));
         AllowConsumeValidationContext ctx = AllowConsumeValidationContext.create(bo);
         R<ErrorInfo> result = commonCheck.consumeValidation(ctx);
         if (R.isError(result)) {
-            log.error("[请求交易]-[记录有效性验证失败]-[耗时: {} 毫秒]-[错误:{}]", System.currentTimeMillis() - startTime, result.getData());
+            log.error("[请求交易]-[记录有效性验证失败]-[错误: {}毫秒]-[数据:{}]", result.getData(), JSONUtil.toJsonStr(bo));
             return result;
         }
         log.info("[请求交易]-[记录有效性验证完成]-[耗时: {} 毫秒]-[记录:{}]", System.currentTimeMillis() - startTime, JSONUtil.toJsonStr(bo));
@@ -87,22 +77,20 @@ public class ConsumeBusiness {
         RemoteCardVo userCardVo = ctx.getUserCardVo();
         XfTermVo termVo = ctx.getUseTermVo();
 
-        log.warn("[请求交易]-[交易流程验证]-[{}]", JSONUtil.toJsonStr(bo));
         Map<String, Boolean> mapCardLimited = new HashMap<>(4);
         XfCardLimitedVo cardLimitedVo = new XfCardLimitedVo();
         startTime = System.currentTimeMillis();
         result = requestCheck.checkConsume(bo, userAccountVo, userCardVo, termVo, mapCardLimited, cardLimitedVo);
         if (R.isError(result)) {
-            log.error("[请求交易]-[交易验证失败]-[{}]", JSONUtil.toJsonStr(result.getData()));
+            log.error("[请求交易]-[交易流程验证失败]-[错误: {}]-[数据:{}]", result.getData(), JSONUtil.toJsonStr(bo));
             return result;
         }
-        log.warn("[请求交易]-[交易流程验证]-[耗时: {} 毫秒]", System.currentTimeMillis() - startTime);
+        log.warn("[请求交易]-[交易流程验证完成]-[耗时: {} 毫秒]", System.currentTimeMillis() - startTime);
 
-        log.warn("[请求交易]-[生成原始消费记录]-[{}]", JSONUtil.toJsonStr(bo));
         startTime = System.currentTimeMillis();
         result = requestCheck.completeConsumeRequest(bo, userAccountVo, userCardVo, termVo, mapCardLimited, cardLimitedVo);
         if (R.isError(result)) {
-            log.error("[请求交易]-[消费原始记录表入库失败]-[{}]", JSONUtil.toJsonStr(result.getData()));
+            log.error("[请求交易]-[消费原始记录失败]-[错误: {}]-[数据:{}]", result.getData(), JSONUtil.toJsonStr(bo));
             return result;
         }
         log.info("[请求交易]-[生成原始消费记录完成]-[耗时: {} 毫秒]", System.currentTimeMillis() - startTime);
@@ -120,34 +108,35 @@ public class ConsumeBusiness {
      */
     public R<ErrorInfo> postOrder(ConsumptionBo bo, String mac, String xfPwd) {
         long startTime = System.currentTimeMillis();
-        // log.warn("[上传交易]-[开始记录有效性验证]-[{}]", JSONUtil.toJsonStr(bo));
         AllowConsumeValidationContext ctx = AllowConsumeValidationContext.create(bo);
         R<ErrorInfo> result = commonCheck.consumeValidation(ctx);
-        log.info("[上传交易]-[记录有效性验证完成]-[耗时: {} 毫秒]-[记录:{}]", System.currentTimeMillis() - startTime, JSONUtil.toJsonStr(bo));
         if (R.isError(result)) {
-            log.error("[上传交易]-[记录有效性验证失败]-[{}]", result.getData());
+            log.error("[上传交易]-[记录有效性验证失败]-[错误: {}]-[数据:{}]", result.getData(), JSONUtil.toJsonStr(bo));
             return result;
         }
+        log.info("[上传交易]-[记录有效性验证完成]-[耗时: {} 毫秒]-[记录:{}]", System.currentTimeMillis() - startTime, JSONUtil.toJsonStr(bo));
 
-        log.warn("[上传交易]-[交易账单处理]-[{}]", JSONUtil.toJsonStr(bo));
+        startTime = System.currentTimeMillis();
         List<PtBagVo> bagVos = new ArrayList<>();
         RemoteUserAccountVo userAccountVo = ctx.getUserAccountVo();
         RemoteCardVo userCardVo = ctx.getUserCardVo();
         XfTermVo termVo = ctx.getUseTermVo();
         RemoteMealTypeVo mealTypeVo = new RemoteMealTypeVo();
-        RemoteOperatorVo operatorVo = new RemoteOperatorVo();
         result = uploadCheck.checkBill(bo, userAccountVo, termVo, bagVos, mealTypeVo);
         if (R.isError(result)) {
-            log.error("[上传交易]-[交易账单处理失败]-[{}]", result.getData());
+            log.error("[上传交易]-[交易账单处理失败]-[错误: {}]-[数据:{}]", result.getData(), JSONUtil.toJsonStr(bo));
             return result;
         }
-        log.warn("[上传交易]-[交易入库]-[{}]", JSONUtil.toJsonStr(bo));
+        log.info("[上传交易]-[交易账单处理完成]-[耗时: {} 毫秒]-[记录:{}]", System.currentTimeMillis() - startTime, JSONUtil.toJsonStr(bo));
+
+        startTime = System.currentTimeMillis();
         try {
             result = baseBusiness.postConsumeRecord(bo, userAccountVo, userCardVo, bagVos, termVo, mealTypeVo, "");
             if (R.isError(result)) {
-                log.error("[上传交易]-[交易入库失败]-[{}]", result.getData());
+                log.error("[上传交易]-[交易入库失败]-[错误:{}]-[数据:{}]", result.getData(), JsonUtils.toJsonString(bo));
                 return result;
             }
+            log.info("[上传交易]-[交易入库处理完成]-[耗时: {} 毫秒]-[记录:{}]", System.currentTimeMillis() - startTime, JSONUtil.toJsonStr(bo));
             return R.ok();
         } catch (ConsumeException e) {
             log.error("[[上传交易]-[交易入库失败]-[{}]", e.getMessage(), e);
@@ -175,62 +164,31 @@ public class ConsumeBusiness {
         }
 
         R<ErrorInfo> result = this.createOrder(bo, mac, xfPwd);
-        if (!R.isSuccess(result)) {
-            log.error("[请求交易]-[请求交易处理失败]-[{}]", JSONUtil.toJsonStr(result.getData()));
-            return result;
-        }
-        result = this.postOrder(bo, mac, xfPwd);
-        if (!R.isSuccess(result)) {
-            log.error("[交易上传]-[交易上传处理失败]-[{}]", JSONUtil.toJsonStr(result.getData()));
+        if (R.isError(result)) {
             return result;
         }
-        return R.ok();
+        return this.postOrder(bo, mac, xfPwd);
     }
 
     /**
-     * 原始消费对账,将有原始消费记录但没有消费明细的消费记录写入消费明细
+     * 异步处理消费机上传的消费订单。
+     * 该方法用于在后台线程中提交消费订单,避免阻塞主线程,适用于需要高性能处理的场景。
      *
-     * @return 对账结果
+     * @param bo    消费记录对象 (ConsumptionBo)。
+     *              包含当前消费订单的详细信息,例如用户ID、消费金额、时间戳等。
+     * @param mac   设备的 MAC 地址 (String)。
+     *              表示发起消费请求的设备的唯一标识符,通常用于验证设备合法性。
+     * @param xfPwd 消费密码 (String)。
+     *              用户提供的消费密码,用于验证用户的操作权限。
      */
-    public R<ErrorInfo> originalReconciliation(String consumeDate) {
-        // 先查询没有写入消费明细的原始消费记录
-        List<ConsumptionBo> list = baseBusiness.selectOriginalReconciliation(DateUtil.parseDate(consumeDate));
-        if (CollectionUtil.isEmpty(list)) {
-            return R.ok(new ErrorInfo(ResultCodeEnum.DATA_NOT_FOUND.code(), ApiErrorTypeConstants.NOT_FOUND, "没有待入账的原始消费记录"));
-        }
-        List<String> doMessage = new ArrayList<>();
-        // 循环写入原始消费记录
-        int total = list.size();
-        AtomicInteger success = new AtomicInteger();
-        AtomicInteger fail = new AtomicInteger();
-        list.forEach(p -> {
-            p.setUseType(SystemUseTypeEnum.CONSUME.code());
-            p.setCreditType(baseBusiness.getCreditType(p.getStatusFlag()));
-            p.setRecordStatus(364L);
-            try {
-                R<ErrorInfo> result = fullOrder(p, "", "");
-                if (R.isSuccess(result)) {
-                    doMessage.add(MessageFormat.format("[入账成功]-[{0}]", JsonUtils.toJsonString(p)));
-                    success.getAndIncrement();
-                    // 如果是本地消费服务成功后需要发消息到云端同步处理
-                    if (ObjectUtil.equals(defaultConfig.getLocationFlag(), DefaultConstants.LOCAL_FLAG)) {
-                        ConsumptionBo bo = BeanUtil.copyProperties(p, ConsumptionBo.class);
-                        ThreadUtil.execAsync(() -> baseBusiness.sendCloudConsume(bo));
-                    }
-                } else {
-                    doMessage.add(MessageFormat.format("[入账失败]-[{0}]-[{1}]", JsonUtils.toJsonString(p), JSONUtil.toJsonStr(result.getData())));
-                    fail.getAndIncrement();
-                }
-            } catch (Exception e) {
-                doMessage.add(MessageFormat.format("[入账失败]-[{0}]-[{1}]", JsonUtils.toJsonString(p), e.getStackTrace()));
-                fail.getAndIncrement();
-                log.error("[对账失败]-[{}]", JSONUtil.toJsonStr(p), e);
-            }
-        });
-        doMessage.forEach(System.out::println);
-        return R.ok(MessageFormat.format("[对账处理完成]-[待处理:{0}条,成功:{1}条,失败:{2}条]", total, success.get(), fail.get()));
+    @Async
+    public void postOrderAsync(ConsumptionBo bo, String mac, String xfPwd) {
+        R<ErrorInfo> result = SpringUtils.getAopProxy(this).postOrder(bo, mac, xfPwd);
+        log.info("[上传交异步处理完成]");
     }
 
+    //region 云端消费对账
+
     /**
      * 云端同步对账,将接收到kafka消息但没有处理成功的消费记录重新处理.处理成功后将记录的处理状态设置成已处理
      *
@@ -238,46 +196,208 @@ public class ConsumeBusiness {
      * @return 处理结果.
      */
     public R<ErrorInfo> syncReconciliation(String consumeDate) {
+        // 1. 查询未处理的原始消费记录
         RemoteSendMessageRecordBo remoteBo = new RemoteSendMessageRecordBo();
         remoteBo.setEventType("12000001");
         remoteBo.setUpdateTime(DateUtil.parseDateTime(consumeDate));
         remoteBo.setConsumeStatus("N");
-        List<RemoteSendMessageRecordBo> messageRecordBoList = baseBusiness.queryConsumeErrorList(remoteBo);
-        if (CollectionUtil.isEmpty(messageRecordBoList)) {
-            return R.ok();
+        List<RemoteSendMessageRecordBo> noSendBos = baseBusiness.queryConsumeErrorList(remoteBo);
+
+        if (CollectionUtil.isEmpty(noSendBos)) {
+            log.warn("[云端对账完成]-[没有向云端请求消费失败记录消费记录]");
+            return R.ok(new ErrorInfo(ResultCodeEnum.DATA_NOT_FOUND.code(),
+                ApiErrorTypeConstants.NOT_FOUND,
+                "没有向云端请求消费失败记录消费记录"));
         }
-        int total = messageRecordBoList.size();
-        AtomicInteger success = new AtomicInteger();
-        AtomicInteger fail = new AtomicInteger();
-        List<String> doMessage = new ArrayList<>();
-        messageRecordBoList.forEach(p -> {
-            ConsumptionBo bo = JSONUtil.toBean(p.getMessage(), ConsumptionBo.class);
-            bo.setUseType(SystemUseTypeEnum.CONSUME.code());
-            bo.setRecordId(0L);
+
+        // 2.将取出的未发送成功的数据转换成消费对象列表
+        List<ConsumptionBo> records = new ArrayList<>();
+        noSendBos.forEach(p -> {
+            records.add(JSONUtil.toBean(p.getMessage(), ConsumptionBo.class));
+        });
+
+        // 3. 并行处理记录
+        ReconciliationResult result = processCloudRecordsInParallel(records);
+
+        // 4. 记录处理结果
+        result.getMessages().forEach(log::info);
+        return R.ok(MessageFormat.format("[云端对账完成]-[待处理:{0}条,成功:{1}条,失败:{2}条]",
+            records.size(), result.getSuccessCount(), result.getFailCount()));
+    }
+
+    /**
+     * 并行处理消费记录列表,并返回对账结果。
+     * 该方法通过并行化处理提高性能,适用于需要处理大量消费记录的场景。
+     *
+     * @param records 消费记录列表 (List<ConsumptionBo>)。
+     *                表示需要处理的所有消费记录。每条记录包含与消费相关的详细信息,
+     *                如用户ID、消费金额、时间戳等。
+     * @return 对账结果对象 (ReconciliationResult)。
+     * 包含处理后的结果信息,包括成功处理的记录、失败的记录以及异常详情。
+     */
+    private ReconciliationResult processCloudRecordsInParallel(List<ConsumptionBo> records) {
+        ReconciliationResult result = new ReconciliationResult();
+        // 使用并行流处理
+        records.parallelStream().forEach(record -> {
             try {
-                R<ErrorInfo> result = fullOrder(bo, "", "");
-                if (R.isSuccess(result)) {
-                    doMessage.add(MessageFormat.format("[同步消费成功]-[{0}]", JsonUtils.toJsonString(p)));
-                    success.getAndIncrement();
-                    baseBusiness.updateConsumeStatusById(p.getRecordId());
+                R<ErrorInfo> response = fullOrder(record, "", "");
+
+                if (R.isSuccess(response)) {
+                    handleSuccessfulRecord(record, result);
                 } else {
-                    doMessage.add(
-                        MessageFormat.format("[同步消费失败]-[{0}]-[{1}]", JsonUtils.toJsonString(p), JSONUtil.toJsonStr(result.getData())));
-                    fail.getAndIncrement();
+                    handleFailedRecord(record, response.getData(), result);
                 }
             } catch (Exception e) {
-                doMessage.add(MessageFormat.format("[同步消费失败]-[{0}]-[{1}]", JsonUtils.toJsonString(p), e.getStackTrace()));
-                fail.getAndIncrement();
-                log.error("[对账失败]-[{}]", JSONUtil.toJsonStr(p), e);
+                handleException(record, e, result);
             }
         });
-        doMessage.forEach(System.out::println);
-        return R.ok(MessageFormat.format("[同步消费完成]-[待处理:{0}条,成功:{1}条,失败:{2}条]", total, success.get(), fail.get()));
+
+        return result;
     }
 
-    @Async
-    public void postOrderAsync(ConsumptionBo bo, String mac, String xfPwd) {
-        R<ErrorInfo> result = SpringUtils.getAopProxy(this).postOrder(bo, mac, xfPwd);
-        log.info("[上传交易结果完成]");
+    //endregion
+
+    //region 原始消费记录对账
+
+    /**
+     * 原始消费对账,将有原始消费记录但没有消费明细的消费记录写入消费明细
+     *
+     * @return 对账结果
+     */
+    public R<ErrorInfo> originalReconciliation(String consumeDate) {
+        // 1. 查询未处理的原始消费记录
+        List<ConsumptionBo> records = baseBusiness.selectOriginalReconciliation(DateUtil.parseDate(consumeDate));
+        if (CollectionUtil.isEmpty(records)) {
+            log.warn("[对账处理完成]-[没有待入账的原始消费记录]");
+            return R.ok(new ErrorInfo(ResultCodeEnum.DATA_NOT_FOUND.code(),
+                ApiErrorTypeConstants.NOT_FOUND,
+                "没有待入账的原始消费记录"));
+        }
+
+        // 2. 并行处理记录
+        ReconciliationResult result = processRecordsInParallel(records);
+
+        // 3. 记录处理结果
+        result.getMessages().forEach(log::info);
+        return R.ok(MessageFormat.format("[对账处理完成]-[待处理:{0}条,成功:{1}条,失败:{2}条]",
+            records.size(), result.getSuccessCount(), result.getFailCount()));
+    }
+
+    /**
+     * 并行处理消费记录列表,并返回对账结果。
+     * 该方法通过并行化处理提高性能,适用于需要处理大量消费记录的场景。
+     *
+     * @param records 消费记录列表 (List<ConsumptionBo>)。
+     *                表示需要处理的所有消费记录。每条记录包含与消费相关的详细信息,
+     *                如用户ID、消费金额、时间戳等。
+     * @return 对账结果对象 (ReconciliationResult)。
+     * 包含处理后的结果信息,包括成功处理的记录、失败的记录以及异常详情。
+     */
+
+    private ReconciliationResult processRecordsInParallel(List<ConsumptionBo> records) {
+        ReconciliationResult result = new ReconciliationResult();
+
+        // 使用并行流处理
+        records.parallelStream().forEach(record -> {
+            try {
+                R<ErrorInfo> response = postOrder(record, "", "");
+
+                if (R.isSuccess(response)) {
+                    handleSuccessfulRecord(record, result);
+                } else {
+                    handleFailedRecord(record, response.getData(), result);
+                }
+            } catch (Exception e) {
+                handleException(record, e, result);
+            }
+        });
+
+        return result;
+    }
+    //endregion
+
+    //region 对账通用处理方法
+
+    /**
+     * 对账对账成功情况的处理。
+     * 该方法通常用于记录对账的成功信息并将信息记录到结果对象中,以便后续分析或展示。
+     *
+     * @param record 异常相关的消费记录对象 (ConsumptionBo)。
+     *               包含当前处理的消费记录的详细信息(如用户ID、消费金额等)。
+     * @param result 对账结果对象 (ReconciliationResult)。
+     *               用于存储对账过程的结果信息,包括成功记录、失败记录以及异常详情。
+     */
+    private void handleSuccessfulRecord(ConsumptionBo record, ReconciliationResult result) {
+        String successMsg = MessageFormat.format("[入账成功]-[{0}]", JsonUtils.toJsonString(record));
+        result.addMessage(successMsg);
+        result.incrementSuccess();
+    }
+
+    /**
+     * 对账对账失败情况的处理。
+     * 该方法通常用于记录对账的失败信息并将错误信息记录到结果对象中,以便后续分析或展示。
+     *
+     * @param record 异常相关的消费记录对象 (ConsumptionBo)。
+     *               包含当前处理的消费记录的详细信息(如用户ID、消费金额等)。
+     * @param result 对账结果对象 (ReconciliationResult)。
+     *               用于存储对账过程的结果信息,包括成功记录、失败记录以及异常详情。
+     */
+    private void handleFailedRecord(ConsumptionBo record, ErrorInfo errorInfo, ReconciliationResult result) {
+        String errorMsg = MessageFormat.format("[入账失败]-[{0}]-[{1}]",
+            JsonUtils.toJsonString(record),
+            JSONUtil.toJsonStr(errorInfo));
+        result.addMessage(errorMsg);
+        result.incrementFail();
+    }
+
+    /**
+     * 对账异常处理异常情况的方法。
+     * 该方法通常用于在对账或消费记录处理过程中捕获并处理异常,
+     * 同时将异常信息记录到结果对象中,以便后续分析或展示。
+     *
+     * @param record 异常相关的消费记录对象 (ConsumptionBo)。
+     *               包含当前处理的消费记录的详细信息(如用户ID、消费金额等)。
+     * @param e      捕获到的异常对象 (Exception)。
+     *               表示在处理消费记录时发生的错误或异常情况。
+     * @param result 对账结果对象 (ReconciliationResult)。
+     *               用于存储对账过程的结果信息,包括成功记录、失败记录以及异常详情。
+     */
+    private void handleException(ConsumptionBo record, Exception e, ReconciliationResult result) {
+        String stackTrace = Arrays.stream(e.getStackTrace())
+            .findFirst()
+            .map(StackTraceElement::toString)
+            .orElse("No stack trace available");
+
+        String errorMsg = MessageFormat.format("[入账失败]-[{0}]-[{1}]",
+            JsonUtils.toJsonString(record),
+            stackTrace);
+
+        result.addMessage(errorMsg);
+        result.incrementFail();
+        log.error("[对账失败]-[{}]-{}", JSONUtil.toJsonStr(record), e.getMessage(), e);
+    }
+
+    /**
+     * 对账结果封装
+     */
+    @Data
+    private static class ReconciliationResult {
+        private final List<String> messages = Collections.synchronizedList(new ArrayList<>());
+        private final AtomicInteger successCount = new AtomicInteger();
+        private final AtomicInteger failCount = new AtomicInteger();
+
+        public void addMessage(String message) {
+            messages.add(message);
+        }
+
+        public void incrementSuccess() {
+            successCount.incrementAndGet();
+        }
+
+        public void incrementFail() {
+            failCount.incrementAndGet();
+        }
     }
+    //endregion
+
 }

+ 15 - 3
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/InitBusiness.java

@@ -153,6 +153,8 @@ public class InitBusiness {
             RedisUtils.setCacheMapValue(CacheNames.USER_TOTAL_BALANCE, userId, balance);
         });
         RedisUtils.expire(CacheNames.USER_TOTAL_BALANCE, Duration.ofHours(5));
+
+        log.info("人员余额参数完成");
     }
 
     /**
@@ -164,7 +166,7 @@ public class InitBusiness {
         String userId = String.valueOf(id);
         BigDecimal balance = bagService.getUserBalance(id);
         RedisUtils.setCacheMapValue(CacheNames.USER_TOTAL_BALANCE, userId, balance);
-        log.info("初始化指定人员余额完成,Id:{}", id);
+        log.info("初始化指定人员余额完成,人员Id:{}", id);
     }
 
     /**
@@ -182,6 +184,8 @@ public class InitBusiness {
         });
         RedisUtils.expire(CacheNames.PT_USER_CARD_NO, Duration.ofHours(4));
         RedisUtils.expire(CacheNames.PT_USER_CARD_USER_ID, Duration.ofHours(4));
+
+        log.info("初始化人员卡片参数完成");
     }
 
     /**
@@ -198,6 +202,7 @@ public class InitBusiness {
             RedisUtils.setCacheMapValue(CacheNames.PT_USER_CARD_NO, cardNo, cardVo);
             RedisUtils.setCacheMapValue(CacheNames.PT_USER_CARD_USER_ID, factoryId, cardVo);
         }
+        log.info("初始指定人员卡片参数完成,人员Id:{}", id);
     }
 
     /**
@@ -228,6 +233,7 @@ public class InitBusiness {
             RedisUtils.expire(CacheNames.PT_USER_ACCOUNT_NO, Duration.ofHours(5));
             RedisUtils.expire(CacheNames.USER_TOTAL_BALANCE, Duration.ofHours(5));
         }
+        log.info("初始化人员账户参数完成");
     }
 
     /**
@@ -246,6 +252,7 @@ public class InitBusiness {
             BigDecimal balance = bagService.getUserBalance(id);
             RedisUtils.setCacheMapValue(CacheNames.USER_TOTAL_BALANCE, strUserId, balance);
         }
+        log.info("初始化指定人员账户参数完成,人员Id:{}", id);
     }
 
     /**
@@ -260,25 +267,30 @@ public class InitBusiness {
             });
             RedisUtils.expire(CacheNames.T_XF_CARD_LIMITED, Duration.ofHours(5));
         }
+        log.info("卡片消费限制参数完成");
     }
 
     /**
-     * 初始化消费清单
+     * 初始化消费设备清单
      */
     public void initTermInfo() {
         List<XfTermVo> list = xfTermService.queryList();
         RedisUtils.deleteKeys(CacheNames.PT_TERM_LIST);
         RedisUtils.setCacheList(CacheNames.PT_TERM_LIST, list);
         RedisUtils.expire(CacheNames.PT_TERM_LIST, Duration.ofDays(1));
+
+        log.info("初始化消费设备参数完成");
     }
 
     /**
-     * 初始化消费类
+     * 初始化消费
      */
     public void initMealTypeInfo() {
         List<RemoteMealTypeVo> list = remoteMealTypeService.selectMealTypeList();
         RedisUtils.deleteKeys(CacheNames.PT_MEAL_TYPE_LIST);
         RedisUtils.setCacheList(CacheNames.PT_MEAL_TYPE_LIST, list);
         RedisUtils.expire(CacheNames.PT_MEAL_TYPE_LIST, Duration.ofDays(1));
+
+        log.info("初始化消费餐类参数完成");
     }
 }

+ 0 - 1
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/CardConsumeValidation.java

@@ -188,7 +188,6 @@ public class CardConsumeValidation {
         int rateIndex = Math.min(rateCount, discountRates.length - 1);
         BigDecimal selectedRate = discountRates[rateIndex];
 
-        log.info("匹配到的折扣率:[{}]", selectedRate);
         // 4. 计算折扣金额(统一处理精度)
         BigDecimal discountFactor = selectedRate.divide(PERCENT_DIVISOR, 2, RoundingMode.HALF_UP);
         return consumeValue

+ 1 - 1
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/CommonCheck.java

@@ -505,7 +505,7 @@ public class CommonCheck {
         // 将当笔原始消费记录标识放入缓存,1天过期
         Set<String> originalId = Collections.singleton(bo.getOriginalId());
         RedisUtils.setCacheSet(CacheNames.XF_ORIGINAL_ID, originalId);
-        RedisUtils.expire(CacheNames.XF_ORIGINAL_ID, Duration.ofHours(6));
+        RedisUtils.expire(CacheNames.XF_ORIGINAL_ID, Duration.ofHours(20));
 
         if (ObjectUtil.equals(currentDateStr, consumeDateStr)) {
             // 重置卡天当日消费数据

+ 26 - 15
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/ConsumeUploadCheck.java

@@ -8,10 +8,12 @@ import lombok.extern.slf4j.Slf4j;
 import org.dromara.backstage.api.domain.vo.RemoteMealTypeVo;
 import org.dromara.backstage.api.domain.vo.RemoteUserAccountVo;
 import org.dromara.common.core.constant.ApiErrorTypeConstants;
+import org.dromara.common.core.constant.CacheNames;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.domain.model.ErrorInfo;
 import org.dromara.common.core.enums.BagNameEnum;
 import org.dromara.common.core.utils.RecordIdUtils;
+import org.dromara.common.redis.utils.RedisUtils;
 import org.dromara.server.common.domain.consume.bo.ConsumptionBo;
 import org.dromara.server.consume.business.BaseBusiness;
 import org.dromara.server.consume.domain.convert.RemoteVoConvert;
@@ -26,10 +28,8 @@ import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
 import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
+import java.time.Duration;
+import java.util.*;
 
 /**
  * 消费记录上传业务处理类
@@ -62,6 +62,17 @@ public class ConsumeUploadCheck {
      */
     public R<ErrorInfo> checkBill(ConsumptionBo bo, RemoteUserAccountVo userAccountVo,
                                   XfTermVo useTermVo, List<PtBagVo> bagVoList, RemoteMealTypeVo mealTypeVo) {
+        // 检验之前先检查这条记录是否上传过,如果上传过则返回已上传的错误,如未上传,将记录Id存入缓存
+        String detailId = RecordIdUtils.getRecordId(bo.getConsumeDate(), bo.getTermNo().shortValue(),
+            bo.getTermRecordId().shortValue(), bo.getUserNo().intValue(), 0);
+        Set<String> detailIdSet = RedisUtils.getCacheSet(CacheNames.XF_DETAIL_ID);
+        if (detailIdSet.contains(detailId)) {
+            return commonCheck.createErrorResponse(400, ApiErrorTypeConstants.CONSUME_CHECK_FAIL, "消费记录已上传",
+                MessageFormat.format("消费记录已上传:{0}", detailId));
+        }
+        RedisUtils.setCacheSet(CacheNames.XF_DETAIL_ID, Collections.singleton(detailId));
+        RedisUtils.expire(CacheNames.XF_DETAIL_ID, Duration.ofHours(20));
+
         R<ErrorInfo> result = checkOriginalRecord(bo, userAccountVo);
         if (R.isError(result)) {
             return result;
@@ -90,7 +101,7 @@ public class ConsumeUploadCheck {
 
     private R<ErrorInfo> checkOriginalRecord(ConsumptionBo bo, RemoteUserAccountVo userAccountVo) {
         String originalId = RecordIdUtils.getRecordId(bo.getConsumeDate(), bo.getTermNo().shortValue(),
-                                                      bo.getTermRecordId().shortValue(), bo.getUserNo().intValue(), 0);
+            bo.getTermRecordId().shortValue(), bo.getUserNo().intValue(), 0);
         //  补偿性措施:防止消费时间错乱问题(在线模式)
         int recordType = bo.getRecordStatus().intValue();
         long uniqueRecordId = bo.getRecordId();
@@ -114,7 +125,7 @@ public class ConsumeUploadCheck {
                 if (!checkFlag) {
                     // 根据卡流水号、机号、机器流水号和消费金额查询
                     originalVo = consumeDetailOriginalService.queryByConsumeMoney(bo.getCardNo(), bo.getTermNo(), bo.getTermRecordId(),
-                                                                                  bo.getConsumeMoney());
+                        bo.getConsumeMoney());
                     if (ObjectUtil.isEmpty(originalVo)) {
                         bo.setRecordStatus(108L);
                         // 判断脱机消费
@@ -141,7 +152,7 @@ public class ConsumeUploadCheck {
         if (ObjectUtil.isNotEmpty(consumeDetailVo)) {
             // 认为是重复上传,不再入账
             return R.fail(new ErrorInfo(400, ApiErrorTypeConstants.RECORD_IS_EXISTS, "原始消费记录已处理",
-                                        MessageFormat.format("标识为[{0}]的原始消费记录已处理", bo.getRecordId())));
+                MessageFormat.format("标识为[{0}]的原始消费记录已处理", bo.getRecordId())));
         }
         return R.ok();
     }
@@ -152,8 +163,8 @@ public class ConsumeUploadCheck {
         String consumeType = termVo.getConsumeType();
         // 分解扣费钱包,设置为可能有空值 ""或" ",要过滤掉
         List<String> bagCodes = StrUtil.split(consumeType, ",").stream()
-                                        .filter(s -> s != null && !s.trim().isEmpty())
-                                        .toList();
+            .filter(s -> s != null && !s.trim().isEmpty())
+            .toList();
         Long userId = userAccountVo.getUserId();
         // 可能会在处理过程中更改实际的消费金额,因此先取出来
         BigDecimal consumeMoney = bo.getConsumeMoney();
@@ -182,7 +193,7 @@ public class ConsumeUploadCheck {
                 sb.append(BagNameEnum.getMessage(Integer.valueOf(bagCode)));
                 doBagVos.add(bagVo);
                 log.warn("[上传交易]-[扣费钱包]-[钱包代码:{},钱包余额:{},消费金额:{},扣款金额:{},姓名:{}]", bagCode, balance, consumeMoney, doMoney,
-                         userAccountVo.getRealName());
+                    userAccountVo.getRealName());
                 break;
             } else {
                 // 如果消费金额>0,则可能会需要多钱包扣费
@@ -195,7 +206,7 @@ public class ConsumeUploadCheck {
                         sb.append(BagNameEnum.getMessage(Integer.parseInt(bagCode)));
                         doBagVos.add(bagVo);
                         log.warn("[上传交易]-[扣费钱包]-[钱包代码:{},钱包余额:{},消费金额:{},扣款金额:{},姓名:{}]", bagCode, balance, consumeMoney,
-                                 doMoney, userAccountVo.getRealName());
+                            doMoney, userAccountVo.getRealName());
                         break;
                     } else {
                         // 将钱包扣费为0,剩余待扣金额=消费金额-原钱包余额
@@ -205,7 +216,7 @@ public class ConsumeUploadCheck {
                         sb.append(BagNameEnum.getMessage(Integer.parseInt(bagCode)));
                         doBagVos.add(bagVo);
                         log.warn("[上传交易]-[扣费钱包]-[钱包代码:{},钱包余额:{},消费金额:{},扣款金额:{},姓名:{}]", bagCode, balance, balance,
-                                 doMoney, userAccountVo.getRealName());
+                            doMoney, userAccountVo.getRealName());
                     }
                 }
             }
@@ -235,7 +246,7 @@ public class ConsumeUploadCheck {
             if (RecordId == 0 && (recordStatus == 106 || recordStatus == 108 || recordStatus == 110 || recordStatus == 111)) {
                 if (recordStatus == 110 || recordStatus == 111) {
                     vo = consumeDetailOriginalService.queryByConsumeMoney(bo.getCardNo(), bo.getTermNo(), bo.getTermRecordId(),
-                                                                          bo.getOperatorMoney());
+                        bo.getOperatorMoney());
                     if (ObjectUtil.isNotEmpty(vo)) {
                         resetBoByOfflineResult(bo, vo);
                     }
@@ -252,7 +263,7 @@ public class ConsumeUploadCheck {
                     if (bo.getConsumeDate().getTime() > currentDate.getTime()) {
                         bo.setConsumeDate(currentDate);
                         originalId = RecordIdUtils.getRecordId(bo.getConsumeDate(), bo.getTermNo().shortValue(),
-                                                               bo.getTermRecordId().shortValue(), bo.getUserNo().intValue(), 0);
+                            bo.getTermRecordId().shortValue(), bo.getUserNo().intValue(), 0);
                         bo.setOriginalId(originalId);
                     }
                     vo = new XfConsumeDetailOriginalVo();
@@ -270,7 +281,7 @@ public class ConsumeUploadCheck {
         } catch (Exception e) {
             log.error("[处理脱机记录错误]-[{}]-[{}]", e.getMessage(), Arrays.toString(e.getStackTrace()));
             return R.fail(new ErrorInfo(400, ApiErrorTypeConstants.RECORD_IS_EXISTS, "处理脱机记录错误",
-                                        MessageFormat.format("错误消息:{0}", e.getMessage())));
+                MessageFormat.format("错误消息:{0}", e.getMessage())));
         }
     }
 

+ 0 - 19
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/controller/v1/ConsumeController.java

@@ -1,27 +1,21 @@
 package org.dromara.server.consume.controller.v1;
 
-import cn.hutool.core.thread.ThreadUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.dromara.common.core.config.DefaultConfig;
 import org.dromara.common.core.constant.CacheNames;
-import org.dromara.common.core.constant.DefaultConstants;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.domain.model.ErrorInfo;
 import org.dromara.common.core.domain.model.ErrorResult;
 import org.dromara.common.core.enums.SystemUseTypeEnum;
 import org.dromara.common.redis.utils.RedisUtils;
 import org.dromara.server.common.domain.consume.bo.ConsumptionBo;
-import org.dromara.server.consume.business.BaseBusiness;
 import org.dromara.server.consume.business.ConsumeBusiness;
-import org.dromara.server.consume.cache.ValidationParam;
 import org.dromara.server.consume.convert.strategy.RecordConvertStrategyContent;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
-import org.springframework.scheduling.annotation.Async;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
@@ -47,10 +41,7 @@ import java.util.Objects;
 public class ConsumeController {
     private final RecordConvertStrategyContent recordConvertStrategy;
     private final ConsumeBusiness consumeBusiness;
-    private final BaseBusiness baseBusiness;
-    private final DefaultConfig defaultConfig;
     private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
-    private final ValidationParam validationParam;
 
     /**
      * 请求消费(校园码)
@@ -192,14 +183,4 @@ public class ConsumeController {
         return recordConvertStrategy.reConvert(bo, "YC");
     }
 
-    @Async
-    public void sendConsumeToCloud(JSONObject jsonObject, Object record) {
-        if (ObjectUtil.isEmpty(jsonObject.get("body"))) {
-            // 发送消息
-            if (ObjectUtil.equals(defaultConfig.getLocationFlag(), DefaultConstants.LOCAL_FLAG)) {
-                ConsumptionBo bo = recordConvertStrategy.convert(record, "YC");
-                ThreadUtil.execAsync(() -> baseBusiness.sendCloudConsume(bo));
-            }
-        }
-    }
 }

+ 4 - 3
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/controller/v1/TermsController.java

@@ -31,7 +31,6 @@ public class TermsController {
     @GetMapping("/{termNo}")
     public Object getTermByTermNo(@PathVariable("termNo") Long termNo) {
         final String logPrefix = String.format("[获取设备信息]-[termNo:%s]", termNo);
-        log.info("{}-[开始]", logPrefix);
         long startTime = System.currentTimeMillis();
 
         try {
@@ -45,9 +44,10 @@ public class TermsController {
 
             // 4. 处理业务结果
             if (R.isError(result)) {
-                log.error("{}-[业务失败: {}]", logPrefix, result.getMsg());
+                log.error("{}-[失败: {}]", logPrefix, result.getMsg());
                 return ErrorResult.badRequestResponse("获取设备信息失败: " + result.getMsg());
             }
+            log.info("{}-[成功: {}]", logPrefix, result.getData());
             return ResponseEntity.ok(result.getData());
 
         } catch (Exception e) {
@@ -83,9 +83,10 @@ public class TermsController {
 
             // 4. 处理业务结果
             if (R.isError(result)) {
-                log.error("{}-[业务失败: {}]", logPrefix, result.getMsg());
+                log.error("{}-[失败: {}]", logPrefix, result.getMsg());
                 return ErrorResult.badRequestResponse("设备校时失败: " + result.getMsg());
             }
+            log.info("{}-[成功: {}]", logPrefix, result.getData());
             return ResponseEntity.ok(result.getData());
 
         } catch (Exception e) {

+ 2 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/domain/convert/RemoteConsumeBoConvert.java

@@ -24,4 +24,6 @@ public interface RemoteConsumeBoConvert {
 
     @InheritInverseConfiguration
     void updateRemote(@MappingTarget RemoteConsumeBo target, ConsumptionBo source);
+
+    ConsumptionBo copyBo(ConsumptionBo source);
 }

+ 4 - 4
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/IXfTermTotalService.java

@@ -37,16 +37,16 @@ public interface IXfTermTotalService {
      */
     Boolean updateByBo(XfTermTotalBo bo);
     /**
-     *
      * @param bo 设备日消费对象
      * @return 设备日消费视图
      */
-    XfTermTotalVo createOrUpdateTermTotal(XfTermTotalBo bo);
+    Boolean createOrUpdateTermTotal(XfTermTotalBo bo);
     /**
      * 更新日统计表数据
-     * @param bo 日统计表对象
+     *
+     * @param bo     日统计表对象
      * @param entity 日统计表
      * @return 日统计表视图
      */
-    XfTermTotalVo updateTotalByBo(XfTermTotalBo bo, XfTermTotal entity);
+    Boolean updateTotalByBo(XfTermTotalBo bo, XfTermTotal entity);
 }

+ 2 - 2
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/IXfUserTotalService.java

@@ -42,10 +42,10 @@ public interface IXfUserTotalService {
     /**
      * 更新/新增人员日统计表
      *
-     * @param bo      日统计表实体
+     * @param bo 日统计表实体
      * @return 更新后的日统计表
      */
-    XfUserTotalVo createOrUpdateUserTotal(XfUserTotalBo bo);
+    Boolean createOrUpdateUserTotal(XfUserTotalBo bo);
 
     /**
      * 更新日统计表中的充值/退款数据

+ 65 - 30
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/XfTermTotalServiceImpl.java

@@ -1,6 +1,6 @@
 package org.dromara.server.consume.service.impl;
 
-import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.collection.CollectionUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -14,6 +14,7 @@ import org.dromara.server.consume.mapper.XfTermTotalMapper;
 import org.dromara.server.consume.service.IXfTermTotalService;
 import org.springframework.stereotype.Service;
 
+import java.math.BigDecimal;
 import java.util.List;
 import java.util.Map;
 
@@ -93,65 +94,99 @@ public class XfTermTotalServiceImpl implements IXfTermTotalService {
     /**
      * 保存前的数据校验
      */
-    private void validEntityBeforeSave(XfTermTotal entity){
+    private void validEntityBeforeSave(XfTermTotal entity) {
         //TODO 做一些数据校验,如唯一约束
     }
 
     /**
-     *
      * @param bo 设备日消费对象
      * @return 设备日消费视图
      */
     @Override
-    public XfTermTotalVo createOrUpdateTermTotal(XfTermTotalBo bo) {
-        XfTermTotal entity = baseMapper.selectOne(new LambdaQueryWrapper<XfTermTotal>()
+    public Boolean createOrUpdateTermTotal(XfTermTotalBo bo) {
+        XfTermTotal entity = MapstructUtils.convert(bo, XfTermTotal.class);
+        List<XfTermTotal> list = baseMapper.selectList(new LambdaQueryWrapper<XfTermTotal>()
             .eq(XfTermTotal::getTermNo, bo.getTermNo())
             .eq(XfTermTotal::getMealType, bo.getMealType())
             .eq(XfTermTotal::getUseType, bo.getUseType())
             .eq(XfTermTotal::getDateDay, bo.getDateDay()));
 
-        if (ObjUtil.isEmpty(entity)) {
-            entity = MapstructUtils.convert(bo, XfTermTotal.class);
-            boolean flag = baseMapper.insert(entity) > 0;
-            if (flag) {
-                if (entity != null) {
-                    bo.setTotalId(entity.getTotalId());
-                    return baseMapper.selectVoById(entity.getTotalId());
-                }
-            }
+        if (CollectionUtil.isEmpty(list)) {
+            // 还没有日统计数据,插入
+            return baseMapper.insert(entity) > 0;
+        } else if (list.size() == 1) {
+            // 有一条,更新
+            return updateTotalByBo(bo, list.get(0));
         } else {
-            bo.setTotalId(entity.getTotalId());
+            entity = dealRepeatData(list);
             return updateTotalByBo(bo, entity);
         }
-        return null;
     }
 
     /**
      * 更新日统计表数据
-     * @param bo 日统计表对象
+     *
+     * @param bo     日统计表对象
      * @param entity 日统计表
-     * @return 日统计表视图
+     * @return 更新成功与否
      */
     @Override
-    public XfTermTotalVo updateTotalByBo(XfTermTotalBo bo, XfTermTotal entity) {
+    public Boolean updateTotalByBo(XfTermTotalBo bo, XfTermTotal entity) {
         LambdaUpdateWrapper<XfTermTotal> lqw = Wrappers.lambdaUpdate();
-        if(bo.getErrFillCount()!=null){
-            lqw.set(XfTermTotal::getErrFillCount, entity.getErrFillCount()+1);
+        if (bo.getErrFillCount() != null) {
+            lqw.set(XfTermTotal::getErrFillCount, entity.getErrFillCount() + 1);
         }
-        if(bo.getErrFillMoney()!=null){
-            lqw.set(XfTermTotal::getErrFillMoney,entity.getErrFillMoney().add(bo.getErrFillMoney()));
+        if (bo.getErrFillMoney() != null) {
+            lqw.set(XfTermTotal::getErrFillMoney, entity.getErrFillMoney().add(bo.getErrFillMoney()));
         }
-        if(bo.getMealCount()!=null){
-            lqw.set(XfTermTotal::getMealCount, entity.getMealCount()+1);
+        if (bo.getMealCount() != null) {
+            lqw.set(XfTermTotal::getMealCount, entity.getMealCount() + 1);
         }
-        if(bo.getMealAmount()!=null){
-            lqw.set(XfTermTotal::getMealAmount,entity.getMealAmount().add(bo.getMealAmount()));
+        if (bo.getMealAmount() != null) {
+            lqw.set(XfTermTotal::getMealAmount, entity.getMealAmount().add(bo.getMealAmount()));
         }
         lqw.eq(XfTermTotal::getTotalId, entity.getTotalId());
 
-        if(baseMapper.update(null,lqw)>0){
-            return baseMapper.selectVoById(entity.getTotalId());
+        return baseMapper.update(null, lqw) > 0;
+    }
+
+    /**
+     * 处理重复数据的方法。
+     * <p>
+     * 将所有数据的金额和次数求和合并成一条
+     * 保留第一条记录并将数据赋值给这条记录
+     * 删除其它的记录后返回第一条
+     *
+     * @param list 包含 XfTermTotal 对象的列表,用于检查和处理重复数据
+     * @return 返回处理后的 XfTermTotal 对象
+     */
+
+    private XfTermTotal dealRepeatData(List<XfTermTotal> list) {
+        BigDecimal mealAmount = BigDecimal.ZERO;
+        BigDecimal errFillMoney = BigDecimal.ZERO;
+        int mealCount = 0;
+        int errFillCount = 0;
+        // 合并计算金额与次数
+        for (XfTermTotal termTotal : list) {
+            mealAmount = mealAmount.add(termTotal.getMealAmount());
+            errFillMoney = errFillMoney.add(termTotal.getErrFillMoney());
+
+            mealCount += termTotal.getMealCount();
+            errFillCount += termTotal.getErrFillCount();
         }
-        return null;
+        // 将合并计算的金额与次数赋值给第一条记录
+        XfTermTotal entity = list.get(0);
+        entity.setMealAmount(mealAmount);
+        entity.setErrFillMoney(errFillMoney);
+        entity.setMealCount((long) mealCount);
+        entity.setErrFillCount((long) errFillCount);
+
+        // 从列表中删除第一条
+        list.remove(0);
+        // 删除列表中已有记录
+        List<String> ids = list.stream().map(XfTermTotal::getTotalId).toList();
+        baseMapper.deleteByIds(ids);
+
+        return entity;
     }
 }

+ 65 - 16
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/XfUserTotalServiceImpl.java

@@ -1,6 +1,6 @@
 package org.dromara.server.consume.service.impl;
 
-import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.collection.CollectionUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -14,6 +14,7 @@ import org.dromara.server.consume.service.IXfUserTotalService;
 import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
+import java.util.List;
 
 /**
  * 个人与消费与充值统计Service业务层处理
@@ -72,27 +73,23 @@ public class XfUserTotalServiceImpl implements IXfUserTotalService {
      * @return 更新后的日统计表
      */
     @Override
-    public XfUserTotalVo createOrUpdateUserTotal(XfUserTotalBo bo) {
-        XfUserTotal entity = baseMapper.selectOne(new LambdaQueryWrapper<XfUserTotal>()
+    public Boolean createOrUpdateUserTotal(XfUserTotalBo bo) {
+        XfUserTotal entity = MapstructUtils.convert(bo, XfUserTotal.class);
+        List<XfUserTotal> list = baseMapper.selectList(new LambdaQueryWrapper<XfUserTotal>()
             .eq(XfUserTotal::getUserId, bo.getUserId())
             .eq(XfUserTotal::getCardNo, bo.getCardNo())
             .eq(XfUserTotal::getUseType, bo.getUseType())
             .eq(XfUserTotal::getDateDay, bo.getDateDay()));
-
-        if (ObjUtil.isEmpty(entity)) {
-            entity = MapstructUtils.convert(bo, XfUserTotal.class);
-            boolean flag = baseMapper.insert(entity) > 0;
-            if (flag) {
-                if (entity != null) {
-                    bo.setTotalId(entity.getTotalId());
-                    return baseMapper.selectVoById(entity.getTotalId());
-                }
-            }
+        if (CollectionUtil.isEmpty(list)) {
+            // 还没有日统计数据,插入
+            return baseMapper.insert(entity) > 0;
+        } else if (list.size() == 1) {
+            // 有一条,更新
+            return updateTotalByBo(bo, list.get(0));
         } else {
-            bo.setTotalId(entity.getTotalId());
-            return updateTotalCreditByBo(bo, entity);
+            entity = dealRepeatData(list);
+            return updateTotalByBo(bo, entity);
         }
-        return null;
     }
 
     /**
@@ -135,4 +132,56 @@ public class XfUserTotalServiceImpl implements IXfUserTotalService {
        }
        return null;
     }
+
+    /**
+     * 更新日统计表数据
+     *
+     * @param bo     日统计表对象
+     * @param entity 日统计表
+     * @return 更新成功与否
+     */
+    private Boolean updateTotalByBo(XfUserTotalBo bo, XfUserTotal entity) {
+        LambdaUpdateWrapper<XfUserTotal> lqw = Wrappers.lambdaUpdate();
+        if (bo.getConsumeMoney() != null) {
+            lqw.set(XfUserTotal::getConsumeMoney, entity.getConsumeMoney().add(bo.getConsumeMoney()));
+        }
+        if (bo.getCreditMoney() != null) {
+            lqw.set(XfUserTotal::getCreditMoney, entity.getCreditMoney().add(bo.getCreditMoney()));
+        }
+        lqw.eq(XfUserTotal::getTotalId, entity.getTotalId());
+
+        return baseMapper.update(null, lqw) > 0;
+    }
+
+    /**
+     * 处理重复数据的方法。
+     * <p>
+     * 将所有数据的金额和次数求和合并成一条
+     * 保留第一条记录并将数据赋值给这条记录
+     * 删除其它的记录后返回第一条
+     *
+     * @param list 包含 XfUserTotal 对象的列表,用于检查和处理重复数据
+     * @return 返回处理后的 XfUserTotal 对象
+     */
+    private XfUserTotal dealRepeatData(List<XfUserTotal> list) {
+        BigDecimal consumeMoney = BigDecimal.ZERO;
+        BigDecimal creditMoney = BigDecimal.ZERO;
+        // 合并计算金额与次数
+        for (XfUserTotal total : list) {
+            consumeMoney = consumeMoney.add(total.getConsumeMoney());
+            creditMoney = creditMoney.add(total.getCreditMoney());
+        }
+        // 将合并计算的金额与次数赋值给第一条记录
+        XfUserTotal entity = list.get(0);
+        entity.setConsumeMoney(consumeMoney);
+        entity.setCreditMoney(creditMoney);
+
+        // 从列表中删除第一条
+        list.remove(0);
+        // 删除列表中已有记录
+        List<String> ids = list.stream().map(XfUserTotal::getTotalId).toList();
+        baseMapper.deleteByIds(ids);
+
+        return entity;
+    }
 }

+ 11 - 7
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/task/InitTasks.java

@@ -31,12 +31,16 @@ public class InitTasks implements ApplicationRunner {
     @Override
     public void run(ApplicationArguments args) throws Exception {
         log.info("初始化消费验证基础数据");
-        // initBusiness.initGlobalData();
-        // initBusiness.initDiscountAndOther();
-        // initBusiness.initUserCard();
-        // initBusiness.initUserAccount();
-        // initBusiness.initXfCardLimited();
-        // initBusiness.initTermInfo();
-         validationParam.refresh();
+        long startTime = System.currentTimeMillis();
+        initBusiness.initGlobalData();
+        initBusiness.initTermInfo();
+        initBusiness.initMealTypeInfo();
+        initBusiness.initDiscountAndOther();
+        initBusiness.initUserCard();
+        initBusiness.initUserAccount();
+        initBusiness.initXfCardLimited();
+        validationParam.refresh();
+
+        log.info("初始化消费验证基础数据完成。耗时:{} ms", System.currentTimeMillis() - startTime);
     }
 }

+ 1 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/task/ScheduledTasks.java

@@ -70,6 +70,7 @@ public class ScheduledTasks {
 
     @Scheduled(cron = "0 0 4 * * ?")
     public void initDiscountAndOther() {
+        ThreadUtil.execAsync(baseBusiness::initGlobalData);
         ThreadUtil.execAsync(baseBusiness::initDiscountAndOther);
         ThreadUtil.execAsync(baseBusiness::initTermInfo);
         ThreadUtil.execAsync(baseBusiness::initMealTypeInfo);

+ 1 - 1
ruoyi-server/ruoyi-server-consume/src/main/resources/mapper/consume/ConsumeDetailOriginalMapper.xml

@@ -44,7 +44,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectReconciliationData" resultType="org.dromara.server.common.domain.consume.bo.ConsumptionBo">
         SELECT term_no as termNo,user_numb as userNumb,card_no as cardNo,factory_id as factoryId
         ,consume_date as consumeDate,consume_money as consumeMoney,term_record_id as termRecordId,status_flag as
-        statusFlag,record_id as recordId
+        statusFlag,record_id as recordId,data_flag as recordStatus,100 as useType,25000 as creditType
         FROM t_xf_consumedetailoriginal txc
         <where>
             and original_id NOT IN (SELECT original_id FROM t_xf_consumedetail where consume_date>#{consumeDate})