Prechádzať zdrojové kódy

perf(消费服务): 增加海康消费机请求消费接口及消费逻辑优化

- 在消费服务增加了海康消费机请求消费接口,此接口仅验证是否可以消费,不对已有缓存及数据库中数据进行变更
- 增加了消费检查中消费日期校正:原来只有因消费机时钟问题造成请求的消费日期为2000年的消费记录的消费日期校正为上传的时间;现在加了如果时间大于今年的也校正为上传的时间
- 上传交易增加了钱包余额的验证,如果余额不足则不能入消费明细表
- 增加了将人员现金与补助钱包写入缓存
- 请求消费对余额的查验调整成根据设备的扣费钱包计算总余额并校验是否余额不足;同时按钱包模拟扣费并在原始记录入库后更新对应钱包的缓存
- 定时任务不再重新加载钱包余额缓存,避免在原始记录已存在(缓存已扣费)但没有入库到明细表(数据库未扣费)的场景下加载的缓存为正式扣费前的数据
autumnal_wind 8 mesiacov pred
rodič
commit
9e6f79c917
13 zmenil súbory, kde vykonal 270 pridanie a 415 odobranie
  1. 3 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
  2. 36 5
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/BaseBusiness.java
  3. 38 75
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/ConsumeBusiness.java
  4. 32 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/InitBusiness.java
  5. 18 2
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/CommonCheck.java
  6. 54 290
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/ConsumeRequestCheck.java
  7. 10 3
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/ConsumeUploadCheck.java
  8. 3 2
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/controller/v1/ConsumeController.java
  9. 33 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/controller/v1/InitDataController.java
  10. 1 11
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/dubbo/RemoteConsumeServiceImpl.java
  11. 19 1
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/IPtBagService.java
  12. 22 1
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/PtBagServiceImpl.java
  13. 1 25
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/task/ScheduledTasks.java

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

@@ -210,4 +210,7 @@ public interface CacheNames {
 
     // 最后心跳时间 to hik
     String LAST_TIME_HEARTBEAT_LIST = "last_time_heartbeat_list";
+
+    // 人员指定钱包余额
+    String USER_BAG_BALANCE = "user_bag_balance";
 }

+ 36 - 5
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/BaseBusiness.java

@@ -23,7 +23,6 @@ import org.dromara.common.core.enums.CreditTypeEnum;
 import org.dromara.common.core.exception.consume.ConsumeException;
 import org.dromara.common.core.utils.DateUtils;
 import org.dromara.common.core.utils.RecordIdUtils;
-import org.dromara.common.json.utils.JsonUtils;
 import org.dromara.common.message.kafka.constant.EventTypeConstants;
 import org.dromara.common.message.kafka.constant.KafkaTopicConstants;
 import org.dromara.common.message.kafka.enums.EventSenderEnum;
@@ -87,9 +86,7 @@ public class BaseBusiness {
     @DubboReference
     private final RemoteCardService remoteCardService;
 
-
     //region 原始消费记录处理
-
     /**
      * 生成原始消费记录并入库
      *
@@ -126,7 +123,6 @@ public class BaseBusiness {
      * @param originalId 原始记录的ID,用于标识特定的消费明细记录
      * @return 返回一个XfConsumeDetailOriginalBo对象,表示消费明细的原始业务数据
      */
-
     @NotNull
     private XfConsumeDetailOriginalBo getXfConsumeDetailOriginalBo(ConsumptionBo consumeBo, RemoteUserAccountVo accountVo, String originalId) {
         XfConsumeDetailOriginalBo originalBo = new XfConsumeDetailOriginalBo();
@@ -160,6 +156,41 @@ public class BaseBusiness {
         return originalBo;
     }
 
+    /**
+     * 模拟钱包扣费,支持多钱包扣费
+     * @param consumeMoney  消费金额
+     * @param bagVoList 扣费钱包
+     */
+    public void simulateDeduction(BigDecimal consumeMoney, List<PtBagVo> bagVoList){
+        // 扣费的过程金额
+        BigDecimal doMoney = consumeMoney;
+        for (PtBagVo bagVo : bagVoList) {
+            // 1.比较扣费金额
+            BigDecimal balance = bagVo.getBalance();
+            if (consumeMoney.compareTo(BigDecimal.ZERO) == 0) {
+                // 如果是消费0元,设置为第一个钱包扣费
+                bagVo.setReceiptMoney(BigDecimal.ZERO);
+                bagVo.setBalance(balance);
+                break;
+            } else {
+                // 如果消费金额>0,则可能会需要多钱包扣费
+                if (balance.compareTo(BigDecimal.ZERO) > 0) {
+                    // 钱包有余额则扣费
+                    if (balance.compareTo(doMoney) >= 0) {
+                        // 如果钱包金额>=扣费金额,设置扣费结果并中断循环
+                        bagVo.setReceiptMoney(doMoney);
+                        bagVo.setBalance(balance.subtract(doMoney));
+                        break;
+                    } else {
+                        // 将钱包扣费为0,剩余待扣金额=消费金额-原钱包余额
+                        bagVo.setReceiptMoney(balance);
+                        bagVo.setBalance(BigDecimal.ZERO);
+                        doMoney = doMoney.subtract(balance);
+                    }
+                }
+            }
+        }
+    }
     //region 请求消费成功后重置相关的验证数据
 
     /**
@@ -337,7 +368,7 @@ public class BaseBusiness {
         // 设置消费信息
         consumeDetailBo.setConsumeDate(bo.getConsumeDate());
         consumeDetailBo.setConsumeMoney(bagVo.getReceiptMoney());
-        consumeDetailBo.setConsumeBalance(bo.getBalance());
+        consumeDetailBo.setConsumeBalance(bagVo.getBalance());
         // 设置卡片信息
         consumeDetailBo.setCardNo(cardVo.getCardNo());
         consumeDetailBo.setFactoryId(cardVo.getFactoryId());

+ 38 - 75
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/ConsumeBusiness.java

@@ -7,7 +7,6 @@ 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.RemoteUserAccountVo;
@@ -82,8 +81,9 @@ public class ConsumeBusiness {
 
         Map<String, Boolean> mapCardLimited = new HashMap<>(4);
         XfCardLimitedVo cardLimitedVo = new XfCardLimitedVo();
+        List<PtBagVo> deductionBags = new ArrayList<>();
         startTime = System.currentTimeMillis();
-        result = requestCheck.checkConsume(bo, userAccountVo, userCardVo, termVo, mapCardLimited, cardLimitedVo);
+        result = requestCheck.checkConsume(bo, userAccountVo, userCardVo, termVo, mapCardLimited, cardLimitedVo,deductionBags );
         if (R.isError(result)) {
             log.error("[请求交易]-[交易流程验证失败]-[数据:{}]-[耗时:{} ms]-[错误:{}]", JSONUtil.toJsonStr(bo),
                 System.currentTimeMillis() - startTime, JSONUtil.toJsonStr(result.getData()));
@@ -92,7 +92,7 @@ public class ConsumeBusiness {
         log.info("[请求交易]-[交易流程验证完成]-[数据:{}]-[耗时: {} ms]", JSONUtil.toJsonStr(bo), System.currentTimeMillis() - startTime);
 
         startTime = System.currentTimeMillis();
-        result = requestCheck.completeConsumeRequest(bo, userAccountVo, userCardVo, termVo, mapCardLimited, cardLimitedVo);
+        result = requestCheck.completeConsumeRequest(bo, userAccountVo, userCardVo, termVo, mapCardLimited, cardLimitedVo, deductionBags);
         if (R.isError(result)) {
             log.error("[请求交易]-[生成原始消费记录失败]-[数据:{}]-[耗时:{} ms]-[错误:{}]", JSONUtil.toJsonStr(bo),
                 System.currentTimeMillis() - startTime, JSONUtil.toJsonStr(result.getData()));
@@ -103,6 +103,34 @@ public class ConsumeBusiness {
         return R.ok();
     }
 
+    /**
+     * 海康设备请求消费,此时只做是否可以消费的校验,不对缓存与数据库做任何修改
+     *
+     * @param bo    消费信息
+     * @param mac   设备MAC地址
+     * @param xfPwd 消费密码
+     * @return 返回错误信息或者成功信息
+     */
+    public R<ErrorInfo> createHikOrder(ConsumptionBo bo, String mac, String xfPwd) {
+        AllowConsumeValidationContext ctx = AllowConsumeValidationContext.create(bo);
+        R<ErrorInfo> result = commonCheck.consumeValidation(ctx);
+        if (R.isError(result)) {
+            return result;
+        }
+        RemoteUserAccountVo userAccountVo = ctx.getUserAccountVo();
+        RemoteCardVo userCardVo = ctx.getUserCardVo();
+        XfTermVo termVo = ctx.getUseTermVo();
+
+        List<PtBagVo> deductionBags = new ArrayList<>();
+        Map<String, Boolean> mapCardLimited = new HashMap<>(4);
+        XfCardLimitedVo cardLimitedVo = new XfCardLimitedVo();
+        result = requestCheck.checkConsume(bo, userAccountVo, userCardVo, termVo, mapCardLimited, cardLimitedVo, deductionBags);
+        if (R.isError(result)) {
+            return result;
+        }
+        return R.ok();
+    }
+
     /**
      * 上传消费记录
      *
@@ -202,76 +230,6 @@ public class ConsumeBusiness {
         log.info("[上传交易异步处理完成]");
     }
 
-    //region 云端消费对账
-
-    /**
-     * 云端同步对账,将接收到kafka消息但没有处理成功的消费记录重新处理.处理成功后将记录的处理状态设置成已处理
-     *
-     * @param consumeDate 待处理日期
-     * @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> noSendBos = baseBusiness.queryConsumeErrorList(remoteBo);
-
-        if (CollectionUtil.isEmpty(noSendBos)) {
-            log.warn("[云端对账完成]-[没有向云端请求消费失败记录消费记录]");
-            return R.ok(new ErrorInfo(ResultCodeEnum.DATA_NOT_FOUND.code(),
-                ApiErrorTypeConstants.NOT_FOUND,
-                "没有向云端请求消费失败记录消费记录"));
-        }
-
-        // 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> response = fullOrder(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 原始消费记录对账
 
     /**
@@ -296,8 +254,13 @@ public class ConsumeBusiness {
                 needList.add(record);
             }
         });
-         // 2. 并行处理记录
-        //ReconciliationResult result = processRecordsInParallel(records);
+        if (CollectionUtil.isEmpty(needList)) {
+            log.warn("[对账处理完成]-[没有待入账的原始消费记录]");
+            return R.ok(new ErrorInfo(ResultCodeEnum.DATA_NOT_FOUND.code(),
+                ApiErrorTypeConstants.NOT_FOUND,
+                "没有待入账的原始消费记录"));
+        }
+        // 2. 对账处理
         ReconciliationResult result = processRecordsInParallel(needList);
 
         // 3. 记录处理结果

+ 32 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/InitBusiness.java

@@ -10,6 +10,7 @@ import org.dromara.backstage.api.*;
 import org.dromara.backstage.api.domain.vo.*;
 import org.dromara.common.core.constant.CacheNames;
 import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.redis.utils.CacheUtils;
 import org.dromara.common.redis.utils.RedisUtils;
 import org.dromara.server.common.util.CardDateUtils;
 import org.dromara.server.consume.cache.CardCacheManager;
@@ -311,4 +312,35 @@ public class InitBusiness {
             RedisUtils.setCacheMapValue(CacheNames.PT_USER_CARD_FACTORYID, factoryId, strCard);
         }
     }
+
+
+    //region 初始化人员钱包余额
+    /**
+     * 初始化用户的钱包(现金钱包与补助钱包)余额到缓存
+     */
+    public void initUserBagBalance() {
+        List<PtBagVo> list = bagService.selectBalanceBag();
+        setBagBalanceToCache(list);
+    }
+
+    /**
+     * 初始化指定用户的钱包余额
+     * @param userId 用户
+     */
+    public void initUserBagBalance(Long userId) {
+        List<PtBagVo> list = bagService.selectUserBalanceBag(userId);
+        setBagBalanceToCache(list);
+    }
+
+    private void setBagBalanceToCache(List<PtBagVo> list) {
+        for (PtBagVo vo : list) {
+            String bagCode = vo.getBagCode();
+            Long userId = vo.getUserId();
+            BigDecimal amount = vo.getBalance();
+            String key = String.format("%s_%s", userId, bagCode);
+            CacheUtils.put(CacheNames.USER_BAG_BALANCE, key, amount);
+        }
+        log.info("初始化人员钱包余额参数完成");
+    }
+    //endregion
 }

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

@@ -16,11 +16,16 @@ import org.dromara.common.core.domain.R;
 import org.dromara.common.core.domain.model.ErrorInfo;
 import org.dromara.common.core.enums.CardStatusEnum;
 import org.dromara.common.core.enums.UserAccountStatusEnum;
+import org.dromara.common.redis.utils.CacheUtils;
 import org.dromara.common.redis.utils.RedisUtils;
 import org.dromara.server.common.constant.ConsumeConstants;
 import org.dromara.server.common.domain.consume.bo.ConsumptionBo;
 import org.dromara.server.common.util.CardDateUtils;
-import org.dromara.server.consume.business.*;
+import org.dromara.server.consume.business.BaseBusiness;
+import org.dromara.server.consume.business.CardBusiness;
+import org.dromara.server.consume.business.EmployeeBusiness;
+import org.dromara.server.consume.business.TermBusiness;
+import org.dromara.server.consume.domain.vo.PtBagVo;
 import org.dromara.server.consume.domain.vo.XfCardLimitedVo;
 import org.dromara.server.consume.domain.vo.XfTermVo;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@@ -28,6 +33,7 @@ import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
 import java.text.MessageFormat;
+import java.time.Duration;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.*;
@@ -613,14 +619,24 @@ public class CommonCheck {
      *                       该映射可能以键值对的形式存储限制条件的状态(例如是否超出限制)。
      * @param cardLimitedVo  卡片限制信息对象,包含详细的卡片限制规则(如每日最大消费次数、每餐最大消费金额等)。
      *                       该对象可能被用于检查或更新卡片的限制状态。
+     * @param deductionBags 扣费钱包列表
      */
     public void updateValidationData(ConsumptionBo bo, RemoteCardVo userCardVo,
                                      Map<String, Boolean> mapCardLimited,
-                                     XfCardLimitedVo cardLimitedVo) {
+                                     XfCardLimitedVo cardLimitedVo, List<PtBagVo> deductionBags) {
         Long mealType = bo.getMealType();
         Date consumeDate = bo.getConsumeDate();
         BigDecimal consumeMoney = bo.getConsumeMoney();
 
+        // 将原始消费记录写入缓存,防止重复请求
+        String keyName = String.format("%s:%s", CacheNames.XF_ORIGINAL_ID, bo.getOriginalId());
+        RedisUtils.setCacheObject(keyName, "", Duration.ofHours(1));
+
+        // 重置缓存中的钱包余额
+        for (PtBagVo bagVo : deductionBags) {
+            String key = String.format("%s_%s", bagVo.getUserId(), bagVo.getBagCode());
+            CacheUtils.put(CacheNames.USER_BAG_BALANCE, key, bagVo.getBalance());
+        }
         // 重置卡天当日消费数据
         threadPoolTaskExecutor.execute(() -> baseBusiness.resetCardConsumeInfo(userCardVo, mealType, consumeMoney, consumeDate));
         // 重置卡片当日限制数据

+ 54 - 290
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/ConsumeRequestCheck.java

@@ -1,9 +1,8 @@
 package org.dromara.server.consume.check;
-import java.util.Date;
 
-import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.backstage.api.domain.vo.RemoteCardVo;
@@ -16,12 +15,12 @@ import org.dromara.common.core.domain.model.ErrorInfo;
 import org.dromara.common.core.enums.ConsumeRecordTypeEnum;
 import org.dromara.common.core.enums.TradeStatusEnum;
 import org.dromara.common.core.utils.RecordIdUtils;
-import org.dromara.common.json.utils.JsonUtils;
 import org.dromara.common.redis.utils.RedisUtils;
 import org.dromara.server.common.domain.consume.bo.ConsumptionBo;
 import org.dromara.server.common.util.CardDateUtils;
 import org.dromara.server.consume.business.BaseBusiness;
 import org.dromara.server.consume.domain.convert.RemoteVoConvert;
+import org.dromara.server.consume.domain.vo.PtBagVo;
 import org.dromara.server.consume.domain.vo.XfCardLimitedVo;
 import org.dromara.server.consume.domain.vo.XfConsumeDetailOriginalVo;
 import org.dromara.server.consume.domain.vo.XfTermVo;
@@ -31,14 +30,14 @@ import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
 import java.text.MessageFormat;
-import java.time.Duration;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
-import java.util.*;
-import java.util.concurrent.*;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Supplier;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
 
 /**
  * 请求消费业务处理类
@@ -62,12 +61,13 @@ public class ConsumeRequestCheck {
 
     public R<ErrorInfo> checkConsume(ConsumptionBo bo, RemoteUserAccountVo userAccountVo,
                                      RemoteCardVo userCardVo, XfTermVo useTermVo,
-                                     Map<String, Boolean> mapCardLimited, XfCardLimitedVo cardLimitedVo) {
-        // 如果消费时间为2000年,认为消费机时钟错误,更改消费时间为当前时间
+                                     Map<String, Boolean> mapCardLimited, XfCardLimitedVo cardLimitedVo, List<PtBagVo> deductionBags) {
+        // 如果消费时间为2000年或大于当前年份,认为消费机时钟错误,更改消费时间为当前时间
         Date consumeDate = bo.getConsumeDate();
         LocalDate localDate = consumeDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+        int justYear = DateUtil.year(new Date());
 
-        if (localDate.getYear() == 2000) {
+        if (localDate.getYear() == 2000 || localDate.getYear() > justYear) {
             consumeDate = new Date();
             bo.setConsumeDate(consumeDate);
         }
@@ -79,7 +79,7 @@ public class ConsumeRequestCheck {
         // 获取餐类
         long startTime = System.currentTimeMillis();
         RemoteMealTypeVo remoteMealTypeVo = commonCheck.getMealType(consumeDate);
-        log.info("[请求交易]-[获取餐类当前餐类]-[耗时:{} ms] ", System.currentTimeMillis()-startTime);
+        log.info("[请求交易]-[获取餐类当前餐类]-[耗时:{} ms] ", System.currentTimeMillis() - startTime);
         if (ObjectUtil.isEmpty(remoteMealTypeVo)) {
             return CheckError.createErrorResponse(400, ApiErrorTypeConstants.NOT_FOUND, "不在交易时段",
                 MessageFormat.format("非营业时段,不允许交易。消费时间[{0}]",
@@ -93,7 +93,6 @@ public class ConsumeRequestCheck {
         int statusFlag = bo.getStatusFlag();
         if (statusFlag == Integer.parseInt(ConsumeRecordTypeEnum.XFJXF_1.code())
             || statusFlag == Integer.parseInt(ConsumeRecordTypeEnum.XFJXF_4.code())) {
-
             // 设备是否可以消费验证
             result = checkTermLimitedAndOther(bo, useTermVo, userCardVo, remoteMealTypeVo);
             if (R.isError(result)) {
@@ -105,24 +104,14 @@ public class ConsumeRequestCheck {
                 return obtainResult(result);
             }
         }
+
         // 余额校验,余额不足不能交易,如果有折扣,则消费金额是以折扣金额为准的,所以余额验证放在最后
-        result = checkWalletBalance(bo);
+        // List<PtBagVo> deductionBags = new ArrayList<>();
+        result = checkWalletBalance(bo, useTermVo, deductionBags);
         if (R.isError(result)) {
             return result;
         }
         RemoteVoConvert.INSTANCE.copyXfCardLimitedVo(cardLimitedVo, xfCardLimitedVo);
-        // cardLimitedVo
-        // cardLimitedVo.setLimitedId(xfCardLimitedVo.getLimitedId());
-        // cardLimitedVo.setCardNo(xfCardLimitedVo.getCardNo());
-        // cardLimitedVo.setDayCount(xfCardLimitedVo.getDayCount());
-        // cardLimitedVo.setDayMoney(xfCardLimitedVo.getDayMoney());
-        // cardLimitedVo.setMealCount(xfCardLimitedVo.getMealCount());
-        // cardLimitedVo.setMealMoney(xfCardLimitedVo.getMealMoney());
-        // cardLimitedVo.setDayDiscountCount(xfCardLimitedVo.getDayDiscountCount());
-        // cardLimitedVo.setMealDiscountCount(xfCardLimitedVo.getMealDiscountCount());
-        // cardLimitedVo.setLastPay(xfCardLimitedVo.getLastPay());
-        // cardLimitedVo.setLastMeal(xfCardLimitedVo.getLastMeal());
-        // BeanUtil.copyProperties(xfCardLimitedVo, cardLimitedVo);
         return R.ok();
     }
 
@@ -136,7 +125,7 @@ public class ConsumeRequestCheck {
     public R<ErrorInfo> completeConsumeRequest(ConsumptionBo bo, RemoteUserAccountVo userAccountVo,
                                                RemoteCardVo userCardVo, XfTermVo useTermVo,
                                                Map<String, Boolean> mapCardLimited,
-                                               XfCardLimitedVo cardLimitedVo) {
+                                               XfCardLimitedVo cardLimitedVo, List<PtBagVo> deductionBags) {
         // 生成原始消费记录
         XfConsumeDetailOriginalVo originalVo = new XfConsumeDetailOriginalVo();
         R<ErrorInfo> result = baseBusiness.createOriginalOrder(bo, userAccountVo, originalVo);
@@ -146,7 +135,7 @@ public class ConsumeRequestCheck {
         bo.setRecordId(originalVo.getRecordId());
         bo.setOriginalId(originalVo.getOriginalId());
 
-        commonCheck.updateValidationData(bo, userCardVo, mapCardLimited, cardLimitedVo);
+        commonCheck.updateValidationData(bo, userCardVo, mapCardLimited, cardLimitedVo, deductionBags);
 
         return R.ok();
     }
@@ -159,26 +148,46 @@ public class ConsumeRequestCheck {
      * 该方法用于检查消费业务对象中的消费金额是否小于或等于用户钱包的可用余额。
      * 如果余额不足,则返回错误信息;否则返回成功结果。
      *
-     * @param bo 消费业务对象,包含消费相关的基础信息(如消费金额、用户信息等)
+     * @param bo            消费业务对象,包含消费相关的基础信息(如消费金额、用户信息等)
+     * @param useTermVo     消费机对象
+     * @param deductionBags 扣款钱包对象
      * @return 如果校验失败,则返回包含错误信息的 R 对象;如果校验成功,则返回表示成功的 R 对象
      */
-    private R<ErrorInfo> checkWalletBalance(ConsumptionBo bo) {
+    private R<ErrorInfo> checkWalletBalance(ConsumptionBo bo, XfTermVo useTermVo, List<PtBagVo> deductionBags) {
+        long userId = bo.getUserId();
         long startTime = System.currentTimeMillis();
         BigDecimal consumeMoney = bo.getConsumeMoney();
-        BigDecimal totalBalance = bagService.getUserTotalBalance(bo.getUserId());
-        if (ObjectUtil.isEmpty(totalBalance)) {
-            totalBalance = BigDecimal.ZERO;
+        StringBuilder sb = new StringBuilder();
+        // 设备的扣费钱包字符串
+        String consumeType = useTermVo.getConsumeType();
+        // 分解扣费钱包,设置为可能有空值 ""或" ",要过滤掉
+        List<String> bagCodes = StrUtil.split(consumeType, ",").stream()
+            .filter(s -> s != null && !s.trim().isEmpty())
+            .toList();
+
+        BigDecimal totalBalance = BigDecimal.ZERO;
+        PtBagVo bagVo;
+        // 计算扣费钱包的总金额
+        for (String bagCode : bagCodes) {
+            BigDecimal balance = bagService.getUserBagBalanceFromCache(String.valueOf(userId), bagCode);
+            totalBalance = totalBalance.add(balance);
+            bagVo = new PtBagVo();
+            bagVo.setUserId(userId);
+            bagVo.setBagCode(bagCode);
+            bagVo.setBalance(balance);
+            deductionBags.add(bagVo);
         }
         if (consumeMoney.compareTo(totalBalance) > 0) {
-            log.info("[请求交易]-[钱包余额验证完成]-[耗时:{} ms]",System.currentTimeMillis()-startTime);
+            log.info("[请求交易]-[钱包余额验证完成]-[耗时:{} ms]", System.currentTimeMillis() - startTime);
             return CheckError.createErrorResponse(400, ApiErrorTypeConstants.CONSUME_CHECK_FAIL, "账户余额不足",
                 MessageFormat.format("总余额[{0}],消费金额[{1}]", totalBalance, consumeMoney));
         }
+        // 模拟扣费并更新对应扣费钱包余额,主要用于后面入原始表成功后更新钱包的余额缓存
+        baseBusiness.simulateDeduction(consumeMoney, deductionBags);
         // 计算扣费后的余额
         BigDecimal balance = totalBalance.subtract(consumeMoney);
         bo.setBalance(balance);
-        baseBusiness.resetUserBalance(bo.getUserId(), balance);
-        log.info("[请求交易]-[钱包余额验证完成]-[耗时:{} ms]",System.currentTimeMillis()-startTime);
+        log.info("[请求交易]-[钱包余额验证完成]-[耗时:{} ms]", System.currentTimeMillis() - startTime);
         return R.ok();
     }
 
@@ -202,15 +211,12 @@ public class ConsumeRequestCheck {
 
         String keyName = String.format("%s:%s", CacheNames.XF_ORIGINAL_ID, originalId);
         if (RedisUtils.hasKey(keyName)) {
-            log.info("[请求交易]-[检查是否重复请求]-[耗时:{} ms] ", System.currentTimeMillis()-startTime);
+            log.info("[请求交易]-[检查是否重复请求]-[耗时:{} ms] ", System.currentTimeMillis() - startTime);
             return CheckError.createErrorResponse(400, ApiErrorTypeConstants.CONSUME_CHECK_FAIL, "原始消费记录存在",
                 MessageFormat.format("原始消费记录已存在:{0}", originalId));
         }
         bo.setOriginalId(originalId);
-
-        // 将当笔原始消费记录标识放入缓存,1小时过期
-        RedisUtils.setCacheObject(keyName, "",Duration.ofHours(1));
-        log.info("[请求交易]-[检查是否重复请求]-[耗时:{} ms] ", System.currentTimeMillis()-startTime);
+        log.info("[请求交易]-[检查是否重复请求]-[耗时:{} ms] ", System.currentTimeMillis() - startTime);
         return R.ok();
     }
 
@@ -233,8 +239,8 @@ public class ConsumeRequestCheck {
             new ValidationTaskDes<>("设备每餐限制验证", () -> validateMealLimit(validationContext)),
             new ValidationTaskDes<>("设备每日限制验证", () -> validateDailyLimit(validationContext))
         );
-        R<ErrorInfo>result =  taskExecutor.executeParallelTasks(tasks, 5, TimeUnit.SECONDS);
-        log.info("[请求交易]-[设备消费验证完成]-[耗时:{} ms]",System.currentTimeMillis()-startTime);
+        R<ErrorInfo> result = taskExecutor.executeParallelTasks(tasks, 5, TimeUnit.SECONDS);
+        log.info("[请求交易]-[设备消费验证完成]-[耗时:{} ms]", System.currentTimeMillis() - startTime);
 
         return result;
     }
@@ -282,253 +288,6 @@ public class ConsumeRequestCheck {
         }
     }
 
-    /**
-     * 执行消费终端校验链。
-     * <p>
-     * 该方法用于执行一系列与消费终端相关的校验逻辑,确保消费请求符合所有业务规则。
-     * 校验链包括但不限于以下内容:
-     * 1. 消费间隔校验。
-     * 2. 单次消费限额校验。
-     * 3. 卡片有效期校验。
-     * 4. 卡类限制校验。
-     * 5. 餐次限次校验。
-     * 6. 日限次和日限额校验。
-     * <p>
-     * 如果任意一个校验失败,则立即返回错误信息并停止后续校验。
-     *
-     * @param context 校验上下文对象,包含消费相关的所有必要信息(如消费金额、终端设备信息、用户卡片信息等)
-     * @return 如果校验成功,返回表示成功的 R 对象;如果校验失败,返回包含错误信息的 R 对象
-     */
-    private R<ErrorInfo> executeTermValidationChain(TermConsumeValidationContext context) {
-        long startTime = System.currentTimeMillis();
-        // 创建CountDownLatch 有1个任务返回错误时就唤醒主线程 // 使用CountDownLatch跟踪任务完成
-        CountDownLatch latch = new CountDownLatch(1);
-        // 用于存储第一个错误结果
-        AtomicReference<R<ErrorInfo>> firstError = new AtomicReference<>(null);
-        // 创建验证任务列表
-        List<Callable<R<ErrorInfo>>> validationTasks = getTermValidationCallables(context, firstError, latch);
-
-        // 所有任务的结果
-        List<Future<R<ErrorInfo>>> futures = new ArrayList<>();
-        // 提交所有任务
-        for (Callable<R<ErrorInfo>> task : validationTasks) {
-            futures.add(threadPoolTaskExecutor.submit(task));
-        }
-
-        // 最终执行的结果,true 为正常执行结束及latch减至了0;false 为超时
-        boolean finallyResult;
-        try {
-            // 阻塞主线程,等待执行结果; latch 为0时,会唤醒主线程,最多阻塞10s
-            finallyResult = latch.await(10, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            log.error("executeTermValidationChain- main 被中断 :{}", e.getMessage());
-            Thread.currentThread().interrupt();
-            // 取消所有任务
-            futures.forEach(f -> f.cancel(true));
-            return CheckError.createError(TradeStatusEnum.SysError);
-        }
-
-        // 循环取消所有任务
-        futures.forEach(f -> f.cancel(true));
-        if (!finallyResult) {
-            log.error("executeTermValidationChain- 执行消费终端校验链10s超时");
-            return CheckError.createError(TradeStatusEnum.SysError);
-        }
-
-        R<ErrorInfo> errorInfoR = firstError.get();
-        log.info("executeTermValidationChain-  执行消费终端校验链耗时:{}ms", System.currentTimeMillis() - startTime);
-        if (errorInfoR != null && R.isError(errorInfoR)) {
-            return errorInfoR;
-        }else {
-            return R.ok();
-        }
-
-
-//        int taskIndex = 0;
-//        try {
-//            for (Future<R<ErrorInfo>> future : futures) {
-//                long starTime = System.currentTimeMillis();
-//                if (firstError.get() != null) {
-//
-//                    future.cancel(true); // 取消剩余任务
-//                    continue;
-//                }
-//
-//                try {
-//                    // R<ErrorInfo> result = future.get(VALIDATION_TIMEOUT, TimeUnit.MILLISECONDS);
-//                    R<ErrorInfo> result = future.get();
-//                    // 发现错误,立即取消其他任务
-//                    if (result != null && R.isError(result)) {
-//
-//                        if (firstError.compareAndSet(null, result)) {
-//                            futures.forEach(f -> f.cancel(true));
-//                        }
-//                    }
-//                } catch (ExecutionException e) {
-//                    log.error("{}验证执行异常", getTaskName(taskIndex), e);
-//                    if (firstError.compareAndSet(null, createError(TradeStatusEnum.SysError))) {
-//                        futures.forEach(f -> f.cancel(true));
-//                    }
-//                } finally {
-//                    log.info("{}结束,耗时:{} ms", getTaskName(taskIndex), System.currentTimeMillis() - starTime);
-//                    taskIndex++;
-//                }
-//            }
-//            return firstError.get() != null ? firstError.get() : R.ok();
-//
-//        } catch (InterruptedException e) {
-//            Thread.currentThread().interrupt(); // 保留中断状态
-//            log.error("验证过程被中断", e);
-//            return createError(TradeStatusEnum.SysError);
-//        }
-
-
-        //// 创建验证任务列表
-        //List<Supplier<R<ErrorInfo>>> validationTasks = new ArrayList<>();
-        //
-        //validationTasks.add(() -> validateSwipeInterval(context));
-        //validationTasks.add(() -> validateSingleLimit(context));
-        //validationTasks.add(() -> validateCardValidity(context));
-        //validationTasks.add(() -> validateCardType(context));
-        //validationTasks.add(() -> validateMealLimit(context));
-        //validationTasks.add(() -> validateDailyLimit(context));
-        //
-        //// 用于存储第一个错误结果
-        //AtomicReference<R<ErrorInfo>> firstError = new AtomicReference<>(null);
-        //
-        //// 使用CountDownLatch跟踪任务完成
-        //CountDownLatch latch = new CountDownLatch(validationTasks.size());
-        //
-        //// 提交所有验证任务
-        //for (Supplier<R<ErrorInfo>> task : validationTasks) {
-        //    threadPoolTaskExecutor.execute(() -> {
-        //        try {
-        //            R<ErrorInfo> result = task.get();
-        //            // 如果发现错误且尚未设置错误结果
-        //            if (result != null && R.isError(result) &&
-        //                firstError.compareAndSet(null, result)) {
-        //                // 取消其他任务(通过中断)
-        //                threadPoolTaskExecutor.getThreadPoolExecutor().getQueue().clear();
-        //            }
-        //        } catch (Exception e) {
-        //            if (firstError.compareAndSet(null, commonCheck.createError(TradeStatusEnum.SysError))) {
-        //                threadPoolTaskExecutor.getThreadPoolExecutor().getQueue().clear();
-        //            }
-        //        } finally {
-        //            latch.countDown();
-        //        }
-        //    });
-        //}
-        //
-        //try {
-        //    // 等待所有任务完成或超时
-        //    //if (!latch.await(300, TimeUnit.MILLISECONDS)) {
-        //    //    return commonCheck.createError(TradeStatusEnum.VALIDATION_TIMEOUT);
-        //    //}
-        //    latch.await();
-        //
-        //    // 返回第一个发现的错误,如果没有错误则返回成功
-        //    log.info("校验设备完成,耗时:{} ms",System.currentTimeMillis()-startTime);
-        //    return firstError.get() != null ? firstError.get() : R.ok();
-        //} catch (InterruptedException e) {
-        //    Thread.currentThread().interrupt();
-        //    return commonCheck.createError(TradeStatusEnum.SysError);
-        //}
-    }
-
-    /**
-     *  获取任务列表
-     * @param context
-     * @param firstError
-     * @param latch
-     * @return
-     */
-    private List<Callable<R<ErrorInfo>>> getTermValidationCallables(TermConsumeValidationContext context,
-                                                                    AtomicReference<R<ErrorInfo>> firstError, CountDownLatch latch) {
-        List<Callable<R<ErrorInfo>>> validationTasks = new ArrayList<>();
-
-        // 消费时间间隔验证
-        validationTasks.add(() -> {
-            R<ErrorInfo> errorInfoR = firstError.get();
-            if (errorInfoR != null && R.isError(errorInfoR)) {
-                return null;
-            }
-            R<ErrorInfo> infoR = validateSwipeInterval(context);
-            if (infoR != null && R.isError(infoR)) {
-                // 设置错误结果
-                firstError.set(infoR);
-                latch.countDown();
-            }
-            return infoR;
-        });
-        // 单次消费限额判断
-        validationTasks.add(() -> {
-            R<ErrorInfo> errorInfoR = firstError.get();
-            if (errorInfoR != null && R.isError(errorInfoR)) {
-                return null;
-            }
-            R<ErrorInfo> infoR = validateSingleLimit(context);
-            if (infoR != null && R.isError(infoR)) {
-                firstError.set(infoR);
-                latch.countDown();
-            }
-            return infoR;
-        });
-        // 卡有效期验证
-        validationTasks.add(() -> {
-            R<ErrorInfo> errorInfoR = firstError.get();
-            if (errorInfoR != null && R.isError(errorInfoR)) {
-                return null;
-            }
-            R<ErrorInfo> infoR = validateCardValidity(context);
-            if (infoR != null && R.isError(infoR)) {
-                firstError.set(infoR);
-                latch.countDown();
-            }
-            return infoR;
-        });
-        // 卡类型验证
-        validationTasks.add(() -> {
-            R<ErrorInfo> errorInfoR = firstError.get();
-            if (errorInfoR != null && R.isError(errorInfoR)) {
-                return null;
-            }
-            R<ErrorInfo> infoR = validateCardType(context);
-            if (infoR != null && R.isError(infoR)) {
-                firstError.set(infoR);
-                latch.countDown();
-            }
-            return infoR;
-        });
-        // 餐次验证
-        validationTasks.add(() -> {
-            R<ErrorInfo> errorInfoR = firstError.get();
-            if (errorInfoR != null && R.isError(errorInfoR)) {
-                return null;
-            }
-            R<ErrorInfo> infoR = validateMealLimit(context);
-            if (infoR != null && R.isError(infoR)) {
-                firstError.set(infoR);
-                latch.countDown();
-            }
-            return infoR;
-        });
-        // 日限额验证
-        validationTasks.add(() -> {
-            R<ErrorInfo> errorInfoR = firstError.get();
-            if (errorInfoR != null && R.isError(errorInfoR)) {
-                return null;
-            }
-            R<ErrorInfo> infoR = validateDailyLimit(context);
-            if (infoR != null && R.isError(infoR)) {
-                firstError.set(infoR);
-                latch.countDown();
-            }
-            return infoR;
-        });
-        return validationTasks;
-    }
-
     private R<ErrorInfo> validateSwipeInterval(TermConsumeValidationContext ctx) {
         if (ctx.getTermSwipeInterval() <= 0) return null;
 
@@ -539,25 +298,30 @@ public class ConsumeRequestCheck {
         return intervalMin < ctx.getTermSwipeInterval() ?
             CheckError.createError(TradeStatusEnum.TimeInterval) : null;
     }
+
     private R<ErrorInfo> validateSingleLimit(TermConsumeValidationContext ctx) {
         if (ctx.getTermSingleMoney().compareTo(BigDecimal.ZERO) <= 0) return null;
         return ctx.getConsumeValue().compareTo(ctx.getTermSingleMoney()) > 0 ?
             CheckError.createError(TradeStatusEnum.OnceBigMoney) : null;
     }
+
     private R<ErrorInfo> validateCardValidity(TermConsumeValidationContext ctx) {
         if (ctx.getFactoryId() == 0 || !ctx.isTermUseValidity()) return null;
         return ctx.getCurrentTime().isAfter(ctx.getExpiryTime()) ?
             CheckError.createError(TradeStatusEnum.CardValidDate) : null;
     }
+
     private R<ErrorInfo> validateCardType(TermConsumeValidationContext ctx) {
         int mask = 1 << (ctx.getCardType() - 1);
         return (mask & ctx.getTermCardType()) == 0 ?
             CheckError.createError(TradeStatusEnum.CardTypeLimit) : null;
     }
+
     private R<ErrorInfo> validateMealLimit(TermConsumeValidationContext ctx) {
         return ctx.getTermMealCount() > 0 && ctx.getMealCount() >= ctx.getTermMealCount() ?
             CheckError.createError(TradeStatusEnum.MealLimitTimes) : null;
     }
+
     private R<ErrorInfo> validateDailyLimit(TermConsumeValidationContext ctx) {
         // 日限次验证
         if (ctx.getTermDayCount() > 0 && ctx.getDayCount() >= ctx.getTermDayCount()) {

+ 10 - 3
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/ConsumeUploadCheck.java

@@ -29,7 +29,10 @@ import org.springframework.stereotype.Service;
 import java.math.BigDecimal;
 import java.text.MessageFormat;
 import java.time.Duration;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
 
 /**
  * 消费记录上传业务处理类
@@ -65,7 +68,6 @@ public class ConsumeUploadCheck {
         // 检验之前先检查这条记录是否上传过,如果上传过则返回已上传的错误,如未上传,将记录Id存入缓存
         String detailId = RecordIdUtils.getRecordId(bo.getConsumeDate(), bo.getTermNo().intValue(),
             bo.getTermRecordId().intValue(), bo.getUserNo().intValue(), 0);
-        //Set<String> detailIdSet = RedisUtils.getCacheSet(CacheNames.XF_DETAIL_ID);
         String detailIdSet = RedisUtils.getCacheMapValue(CacheNames.XF_DETAIL_ID,detailId);
 
         if (detailIdSet!=null && detailIdSet.equals(detailId)) {
@@ -75,7 +77,7 @@ public class ConsumeUploadCheck {
 
 
         RedisUtils.setCacheMapValue(CacheNames.XF_DETAIL_ID, detailId,detailId);
-        RedisUtils.expire(CacheNames.XF_DETAIL_ID, Duration.ofHours(2));
+        RedisUtils.expire(CacheNames.XF_DETAIL_ID, Duration.ofHours(1));
 
         R<ErrorInfo> result = checkOriginalRecord(bo, userAccountVo);
         if (R.isError(result)) {
@@ -186,6 +188,11 @@ public class ConsumeUploadCheck {
                 preDeductions.add(bagVo);
             }
         }
+        // 消费明细入库时也进行余额验证
+        if (consumeMoney.compareTo(totalBalance) > 0) {
+            return CheckError.createErrorResponse(400, ApiErrorTypeConstants.CONSUME_CHECK_FAIL, "账户余额不足",
+                MessageFormat.format("总余额[{0}],消费金额[{1}]", totalBalance, consumeMoney));
+        }
         for (PtBagVo bagVo : preDeductions) {
             // 1.比较扣费金额
             String bagCode = bagVo.getBagCode();

+ 3 - 2
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/controller/v1/ConsumeController.java

@@ -22,7 +22,6 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
-import java.util.Date;
 import java.util.Objects;
 
 /**
@@ -150,7 +149,9 @@ public class ConsumeController {
      */
     @PostMapping("/consume/sync/reconciliation/{consumeDate}")
     public R<ErrorInfo> syncReconciliation(@PathVariable("consumeDate") String consumeDate) {
-        return consumeBusiness.syncReconciliation(consumeDate);
+        // return consumeBusiness.syncReconciliation(consumeDate);
+
+        return R.ok();
     }
 
     /**

+ 33 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/controller/v1/InitDataController.java

@@ -38,6 +38,7 @@ public class InitDataController {
 
         return R.ok();
     }
+
     /**
      * 初始化折扣、限额和限次的卡类与设备
      *
@@ -49,6 +50,7 @@ public class InitDataController {
 
         return R.ok();
     }
+
     /**
      * 初始化用户卡片
      *
@@ -60,6 +62,7 @@ public class InitDataController {
 
         return R.ok();
     }
+
     /**
      * 初始化指定用户的卡片.
      *
@@ -71,6 +74,7 @@ public class InitDataController {
 
         return R.ok();
     }
+
     /**
      * 初始化人员余额信息.
      *
@@ -82,6 +86,7 @@ public class InitDataController {
 
         return R.ok();
     }
+
     /**
      * 初始化人员余额信息.
      *
@@ -93,6 +98,7 @@ public class InitDataController {
 
         return R.ok();
     }
+
     /**
      * 初始化用户账户信息.包括基本信息、余额信息
      *
@@ -104,6 +110,7 @@ public class InitDataController {
 
         return R.ok();
     }
+
     /**
      * 初始化用户账户信息.包括基本信息、余额信息
      *
@@ -115,6 +122,7 @@ public class InitDataController {
 
         return R.ok();
     }
+
     /**
      * 初始化卡片限制信息.
      *
@@ -126,6 +134,7 @@ public class InitDataController {
 
         return R.ok();
     }
+
     /**
      * 初始化餐类信息.
      *
@@ -137,6 +146,7 @@ public class InitDataController {
 
         return R.ok();
     }
+
     /**
      * 刷新卡片是否可消费的初始数据.
      *
@@ -160,4 +170,27 @@ public class InitDataController {
 
         return R.ok();
     }
+
+    /**
+     * 初始化用户的钱包(现金钱包与补助钱包)余额到缓存
+     *
+     * @return 结果
+     */
+    @GetMapping("/user-bag-balance")
+    public R<Void> initUserBagBalance() {
+        initBusiness.initUserBagBalance();
+
+        return R.ok();
+    }
+
+    /**
+     * 初始化指定用户的钱包余额
+     * @param userId 用户Id
+     * @return 初始化结果
+     */
+    @GetMapping("/user-bag-balance/{userId}")
+    public R<Void> uintBagBalanceByUserId(@PathVariable("userId") Long userId) {
+        initBusiness.initUserBagBalance(userId);
+        return R.ok();
+    }
 }

+ 1 - 11
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/dubbo/RemoteConsumeServiceImpl.java

@@ -70,7 +70,7 @@ public class RemoteConsumeServiceImpl implements RemoteConsumeService {
     @Override
     public RemoteResultDto dealHikRequestConsume(RemoteConsumeBo remoteBo) {
         ConsumptionBo bo = RemoteConsumeBoConvert.INSTANCE.fromRemote(remoteBo);
-        R<ErrorInfo> result =  consumeBusiness.createOrder(bo, "", "");
+        R<ErrorInfo> result =  consumeBusiness.createHikOrder(bo, "", "");
 
         RemoteConsumeBo updatedRemote = RemoteConsumeBoConvert.INSTANCE.toRemote(bo);
 
@@ -82,11 +82,6 @@ public class RemoteConsumeServiceImpl implements RemoteConsumeService {
         ConsumptionBo bo = RemoteConsumeBoConvert.INSTANCE.fromRemote(remoteBo);
         bo.setUseType(SystemUseTypeEnum.CONSUME.code());
         R<ErrorInfo> result =   consumeBusiness.postOrder(bo, "", "");
-        //if(R.isSuccess(result)){
-        //    if (ObjectUtil.equals(defaultConfig.getLocationFlag(), DefaultConstants.LOCAL_FLAG)) {
-        //        ThreadUtil.execAsync(() -> baseBusiness.sendCloudConsume(bo));
-        //    }
-        //}
         RemoteConsumeBo updatedRemote = RemoteConsumeBoConvert.INSTANCE.toRemote(bo);
 
         return new RemoteResultDto(result, updatedRemote);
@@ -97,11 +92,6 @@ public class RemoteConsumeServiceImpl implements RemoteConsumeService {
         ConsumptionBo bo = RemoteConsumeBoConvert.INSTANCE.fromRemote(remoteBo);
         bo.setUseType(SystemUseTypeEnum.CONSUME.code());
         R<ErrorInfo> result =   consumeBusiness.fullOrder(bo, "", "");
-        //if(R.isSuccess(result)){
-        //    if (ObjectUtil.equals(defaultConfig.getLocationFlag(), DefaultConstants.LOCAL_FLAG)) {
-        //        ThreadUtil.execAsync(() -> baseBusiness.sendCloudConsume(bo));
-        //    }
-        //}
         RemoteConsumeBo updatedRemote = RemoteConsumeBoConvert.INSTANCE.toRemote(bo);
 
         return new RemoteResultDto(result, updatedRemote);

+ 19 - 1
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/IPtBagService.java

@@ -98,6 +98,7 @@ public interface IPtBagService {
      * @return 验证通过后的账户钱包
      */
     PtBag verification(PtBagBo bo);
+
     /**
      * 查询账户钱包
      *
@@ -116,11 +117,28 @@ public interface IPtBagService {
 
     void updateConsumeBalance(List<PtBagVo> bagVos);
 
+    /**
+     * 查询现金与补助钱包余额
+     * @return 钱包列表
+     */
     List<PtBagVo> selectBalanceBag();
 
     BigDecimal cacheUserTotalBalance(Long userId, BigDecimal balance);
 
     String getUserSecretKeyByUserId(Long userId);
 
-    // boolean updateBalanceByByConsume(PtBagBo bo);
+    /**
+     * 查询指定用户的现金与补助钱包余额
+     * @param userId 用户Id
+     * @return 钱包列表
+     */
+    List<PtBagVo> selectUserBalanceBag(Long userId);
+
+    /**
+     * 查询指定用户的指定钱包余额,如缓存没有则查数据库
+     * @param userId 用户Id
+     * @param bagCode 钱包代码
+     * @return 钱包余额
+     */
+    BigDecimal getUserBagBalanceFromCache(String userId,String bagCode);
 }

+ 22 - 1
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/PtBagServiceImpl.java

@@ -339,7 +339,7 @@ public class PtBagServiceImpl implements IPtBagService {
     public List<PtBagVo> selectBalanceBag() {
         LambdaQueryWrapper<PtBag> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.in(PtBag::getBagCode, Arrays.asList("1", "3"));
-        queryWrapper.select(PtBag::getBalance,PtBag::getUserId);
+        queryWrapper.select(PtBag::getBalance,PtBag::getBagCode,PtBag::getUserId);
 
         return baseMapper.selectVoList(queryWrapper);
     }
@@ -355,6 +355,27 @@ public class PtBagServiceImpl implements IPtBagService {
         return YcEncryptUtil.getBalanceSecretKey(RedisUtils.getCacheObject(CacheNames.CUSTOM_PUB_KEY).toString(), userId.toString());
     }
 
+    @Override
+    public List<PtBagVo> selectUserBalanceBag(Long userId) {
+        LambdaQueryWrapper<PtBag> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(PtBag::getUserId, userId);
+        queryWrapper.in(PtBag::getBagCode, Arrays.asList("1", "3"));
+        queryWrapper.select(PtBag::getBalance,PtBag::getBagCode,PtBag::getUserId);
+
+        return baseMapper.selectVoList(queryWrapper);
+    }
+
+    @Override
+    @Cacheable(cacheNames = CacheNames.USER_BAG_BALANCE, key = "#userId+'_'+#bagCode", unless = "#result == null")
+    public BigDecimal getUserBagBalanceFromCache(String userId, String bagCode) {
+        LambdaQueryWrapper<PtBag> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(PtBag::getBagCode,bagCode);
+        queryWrapper.eq(PtBag::getUserId,Long.valueOf(userId));
+        queryWrapper.select(PtBag::getBalance,PtBag::getBagCode,PtBag::getUserId);
+
+        return baseMapper.selectVoOne(queryWrapper).getBalance();
+    }
+
     /**
      * 组装充值钱包数据
      * 1.设置充值后余额=账户原余额+充值金额

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

@@ -1,14 +1,8 @@
 package org.dromara.server.consume.task;
 
-import cn.hutool.core.date.DateUtil;
-import cn.hutool.core.thread.ThreadUtil;
-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.DefaultConstants;
-import org.dromara.common.core.domain.R;
-import org.dromara.common.core.domain.model.ErrorInfo;
 import org.dromara.server.consume.business.ConsumeBusiness;
 import org.dromara.server.consume.business.InitBusiness;
 import org.dromara.server.consume.cache.ValidationParam;
@@ -50,24 +44,6 @@ public class ScheduledTasks {
     //    }
     //}
 
-    /**
-     * 执行云同步对账任务。
-     * 该方法在每天的9:45, 14:45, 20:45和23:45自动调用,用于处理云端部署环境下的消费记录对账。
-     * 如果对账过程中出现错误,会记录错误信息;如果成功,则记录警告消息。
-     */
-    //@Scheduled(cron = "0 45 9,10,14,16,20,23 * * *")
-    //public void CloudSyncReconciliation() {
-    //    if (defaultConfig.getLocationFlag().equals(DefaultConstants.CLOUD_FLAG)) {
-    //        String consumeDate = DateUtil.format(DateUtil.date(), "yyyy-MM-dd 00:00:00");
-    //        R<ErrorInfo> result = consumeBusiness.syncReconciliation(consumeDate);
-    //        if (R.isError(result)) {
-    //            log.error(JSONUtil.toJsonStr(result.getData()));
-    //        } else {
-    //            log.warn(result.getMsg());
-    //        }
-    //    }
-    //}
-
     @Scheduled(cron = "0 0 4 * * ?")
     public void initDiscountAndOther() {
         baseBusiness.initGlobalData();
@@ -82,6 +58,6 @@ public class ScheduledTasks {
         baseBusiness.initXfCardLimited();
         baseBusiness.initUserCard();
         baseBusiness.initUserAccount();
-        baseBusiness.initUserBalance();
+        // baseBusiness.initUserBagBalance();
     }
 }