Browse Source

Merge remote-tracking branch 'origin/master'

baiyun 1 year ago
parent
commit
587a6ab74b
100 changed files with 3891 additions and 771 deletions
  1. 12 12
      pom.xml
  2. 1 1
      ruoyi-auth/src/main/resources/application.yml
  3. 0 1
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ThreadPoolConfig.java
  4. 2 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
  5. 2 1
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/TradeStatusEnum.java
  6. 4 3
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/RecordIdUtils.java
  7. 3 1
      ruoyi-common/ruoyi-common-dubbo/src/main/resources/common-dubbo.yml
  8. 3 7
      ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/YcEncryptUtil.java
  9. 3 6
      ruoyi-common/ruoyi-common-message/src/main/java/org/dromara/common/message/kafka/aop/aspect/SyncDataToLocalAspect.java
  10. 2 0
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/RuoYiBackstageApplication.java
  11. 0 12
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/basics/controller/PtAccountController.java
  12. 0 6
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/basics/controller/PtAreaController.java
  13. 0 6
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/basics/controller/PtMealtypeController.java
  14. 0 6
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/basics/controller/PtParameterController.java
  15. 0 7
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/basics/controller/PtRoomController.java
  16. 243 0
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/business/InitServiceBusiness.java
  17. 0 25
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/business/accouunt/UserAccountBusiness.java
  18. 4 7
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/business/card/CardBusiness.java
  19. 2 2
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/business/consume/ConsumeBusiness.java
  20. 1 1
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/business/errfill/ErrFillBusiness.java
  21. 1 1
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/business/lock/LockBusiness.java
  22. 1 1
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/business/payments/PayBaseBusiness.java
  23. 2 6
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/business/payments/ThirdPayBusiness.java
  24. 0 27
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/controller/CardApiController.java
  25. 0 5
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/controller/PtCardController.java
  26. 0 7
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/controller/PtSubsidyController.java
  27. 0 5
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/controller/PtSubsidyitemController.java
  28. 4 0
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/service/IPtCardService.java
  29. 41 8
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/service/impl/PtCardServiceImpl.java
  30. 4 48
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/config/ArcFaceConfig.java
  31. 3 3
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/config/FaceEngineFactory.java
  32. 0 6
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/consumption/controller/XfDiscountController.java
  33. 0 5
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/consumption/controller/XfDiscounttermController.java
  34. 0 6
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/consumption/controller/XfLimitedController.java
  35. 0 5
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/consumption/controller/XfLimitedtermController.java
  36. 0 6
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/consumption/controller/XfQuotaController.java
  37. 0 5
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/consumption/controller/XfQuotatermController.java
  38. 0 7
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/consumption/controller/XfTermController.java
  39. 35 0
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/consumption/domain/convert/RemoteConvert.java
  40. 87 41
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/consumption/service/impl/XfConsumeDetailServiceImpl.java
  41. 0 2
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/controller/lock/LockController.java
  42. 1 1
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/domain/vo/card/PtCardVo.java
  43. 1 10
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/controller/PtBagController.java
  44. 0 8
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/controller/PtUserAccountController.java
  45. 4 4
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/domain/vo/PtUserAccountVo.java
  46. 5 0
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/service/IPtBagService.java
  47. 30 0
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/service/impl/PtBagServiceImpl.java
  48. 37 26
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/service/impl/PtUserAccountServiceImpl.java
  49. 12 3
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/task/InitRunner.java
  50. 0 1
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/wx/contorller/WxController.java
  51. 3 0
      ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/wx/service/impl/WxServiceImpl.java
  52. 0 1
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java
  53. 3 3
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java
  54. 6 6
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java
  55. 1 0
      ruoyi-server/pom.xml
  56. 2 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/RuoYiServerConsumeApplication.java
  57. 14 15
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/BaseBusiness.java
  58. 5 2
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/CardBusiness.java
  59. 12 8
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/ConsumeBusiness.java
  60. 21 4
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/EmployeeBusiness.java
  61. 1 9
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/InitBusiness.java
  62. 2 1
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/TermBusiness.java
  63. 8 8
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/cache/UserAccountCacheManager.java
  64. 162 61
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/CardConsumeValidation.java
  65. 70 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/CheckError.java
  66. 185 195
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/CommonCheck.java
  67. 286 75
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/ConsumeRequestCheck.java
  68. 16 12
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/ConsumeUploadCheck.java
  69. 15 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/IValidationTask.java
  70. 162 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/ParallelTaskExecutor.java
  71. 21 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/ValidationTaskDes.java
  72. 1 1
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/controller/v1/ConsumeController.java
  73. 3 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/IPtBagService.java
  74. 8 0
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/PtBagServiceImpl.java
  75. 0 1
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/XfCardLimitedServiceImpl.java
  76. 1 1
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/task/InitTasks.java
  77. 18 17
      ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/task/ScheduledTasks.java
  78. 176 0
      ruoyi-server/ruoyi-server-face/pom.xml
  79. 26 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/RuoYiServerFaceApplication.java
  80. 53 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/constant/ErrCodeConstants.java
  81. 40 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/constant/HikApiConstants.java
  82. 16 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/constant/HikDefaultConstants.java
  83. 12 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/constant/HikEventTypeConstant.java
  84. 482 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/controller/TestController.java
  85. 106 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/controller/XfFailedRecordController.java
  86. 205 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/XfConsumeDetail.java
  87. 171 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/XfConsumeDetailOriginal.java
  88. 88 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/XfFailedRecord.java
  89. 367 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/XfTerm.java
  90. 89 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/bo/XfFailedRecordBo.java
  91. 74 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/DeviceDto.java
  92. 44 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/QueryDto.java
  93. 54 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/UploadEmpDto.java
  94. 33 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/UserInfoDto.java
  95. 41 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/base/CardDto.java
  96. 37 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/base/CardListDto.java
  97. 70 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/base/EmpInfoDto.java
  98. 54 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/base/FaceDto.java
  99. 36 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/base/FaceListDto.java
  100. 41 0
      ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/base/ValidDto.java

+ 12 - 12
pom.xml

@@ -88,31 +88,31 @@
             </properties>
             <activation>
                 <!-- 默认环境 -->
-                <activeByDefault>true</activeByDefault>
+                <activeByDefault>false</activeByDefault>
             </activation>
         </profile>
         <profile>
             <id>prod</id>
             <properties>
-                <profiles.active>prod</profiles.active>
-                <nacos.server>10.32.23.155:8845,10.32.23.156:8847,10.32.16.215:8849</nacos.server>
-                <nacos.discovery.group>DEFAULT_GROUP</nacos.discovery.group>
-                <nacos.config.group>DEFAULT_GROUP</nacos.config.group>
-                <nacos.username>nacos</nacos.username>
-                <nacos.password>nacos</nacos.password>
-                <logstash.address>10.32.23.155:4560</logstash.address>
-                <!-- 党校本地环境 -->
 <!--                <profiles.active>prod</profiles.active>-->
-<!--                <nacos.server>172.16.137.72:8845,172.16.137.73:8847,172.16.137.74:8849</nacos.server>-->
+<!--                <nacos.server>10.32.23.155:8845,10.32.23.156:8847,10.32.16.215:8849</nacos.server>-->
 <!--                <nacos.discovery.group>DEFAULT_GROUP</nacos.discovery.group>-->
 <!--                <nacos.config.group>DEFAULT_GROUP</nacos.config.group>-->
 <!--                <nacos.username>nacos</nacos.username>-->
 <!--                <nacos.password>nacos</nacos.password>-->
-<!--                <logstash.address>172.16.137.72:4560</logstash.address>-->
+<!--                <logstash.address>10.32.23.155:4560</logstash.address>-->
+                <!-- 党校本地环境 -->
+                <profiles.active>prod</profiles.active>
+                <nacos.server>172.16.137.175:8848</nacos.server>
+                <nacos.discovery.group>DEFAULT_GROUP</nacos.discovery.group>
+                <nacos.config.group>DEFAULT_GROUP</nacos.config.group>
+                <nacos.username>nacos</nacos.username>
+                <nacos.password>123456</nacos.password>
+                <logstash.address>172.16.137.175:4560</logstash.address>
             </properties>
             <activation>
                 <!-- 默认环境 -->
-                <activeByDefault>false</activeByDefault>
+                <activeByDefault>true</activeByDefault>
             </activation>
         </profile>
     </profiles>

+ 1 - 1
ruoyi-auth/src/main/resources/application.yml

@@ -1,6 +1,6 @@
 # Tomcat
 server:
-  port: 9212
+  port: 9210
 
 # Spring
 spring:

+ 0 - 1
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ThreadPoolConfig.java

@@ -58,7 +58,6 @@ public class ThreadPoolConfig {
                 }
             };
         });
-
         executor.initialize(); // 主动初始化线程池
         return executor;
     }

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

@@ -204,4 +204,6 @@ public interface CacheNames {
     String XF_DETAIL_ID = "xf_detail_id";
 
     String XF_TERM_IP = "term_ip:";
+
+    String USER_HAS_CARD = "user_has_card";
 }

+ 2 - 1
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/TradeStatusEnum.java

@@ -32,7 +32,8 @@ public enum TradeStatusEnum {
     SysError("系统错误", (byte)0x63),
     MidasApiError("米大师接口调用失败", (byte)0x64),
     NoDish("无订餐数据", (byte)0x70),
-    VALIDATION_TIMEOUT("交易超时",(byte)0x80);
+    VALIDATION_TIMEOUT("交易超时",(byte)0x80),
+    TimeoutError("业务处理超时",(byte)0x81);
 
     private String name; // 名称
     private Byte value; // 值

+ 4 - 3
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/RecordIdUtils.java

@@ -32,7 +32,7 @@ public class RecordIdUtils {
      * @param key4 钱包代码
      * @return java.lang.String
      */
-    public static String getRecordId(Date date, short key1, int key2, int key3, int key4){
+    public static String getRecordId(Date date, int key1, int key2, int key3, int key4){
         StringBuilder recordId = new StringBuilder();
 
         // 10位时间
@@ -41,8 +41,8 @@ public class RecordIdUtils {
         recordId.append(Long.toHexString(time));
 
         // 4位 消费:设备机号   资金收支:收支类型
-        int i = DigitUtils.shortToUnsigned(key1);
-        StringBuilder str1 = new StringBuilder(Integer.toHexString(i));
+        long i = DigitUtils.intToUnsigned(key1);
+        StringBuilder str1 = new StringBuilder(Long.toHexString(i));
         while (str1.length() < 4) {
             str1.insert(0, "0");
         }
@@ -55,6 +55,7 @@ public class RecordIdUtils {
         }
         recordId.append(str2);
         //8位 消费:卡片流水号  资金收支:充值金额*100去掉小数点
+
         long l2 = DigitUtils.intToUnsigned(key3);
         StringBuilder str3 = new StringBuilder(Long.toHexString(l2));
         while (str3.length() < 8) {

+ 3 - 1
ruoyi-common/ruoyi-common-dubbo/src/main/resources/common-dubbo.yml

@@ -20,14 +20,16 @@ dubbo:
     parameters:
       namespace: ${spring.profiles.active}
   metadata-report:
+    # 集群模式 address 注释掉
     address: redis://${spring.data.redis.host}:${spring.data.redis.port}
     group: DUBBO_GROUP
     username: dubbo
     password: ${spring.data.redis.password}
-    # redis 集群模式
+    # redis 集群模式 cluster改成true
     cluster: false
     parameters:
       namespace: ${spring.profiles.active}
+      # redis集群模式这里database注释掉
       database: ${spring.data.redis.database}
 #      backup: ${spring.data.redis.cluster.nodes}
   # 消费者相关配置

+ 3 - 7
ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/YcEncryptUtil.java

@@ -122,16 +122,12 @@ public class YcEncryptUtil extends EncryptUtils {
      * @return 生成的密钥
      */
     public static String getBalanceSecretKey(String publicKey, String data) {
-        String secretKey;
-        String cacheKey = CacheNames.USER_SECRET_KEY + ":" + data;
-        if (ObjectUtil.isNotNull(RedisUtils.getCacheObject(cacheKey))) {
-            secretKey = RedisUtils.getCacheObject(cacheKey).toString();
-        } else {
+        String secretKey = RedisUtils.getCacheMapValue(CacheNames.USER_SECRET_KEY,data);
+        if (ObjectUtil.isEmpty(secretKey)) {
             String md5DigestData = getMd5DigestData(publicKey);
             secretKey = encryptByMd5WidthAes(data, md5DigestData, false);
-            RedisUtils.setCacheObject(cacheKey, secretKey);
+            RedisUtils.setCacheMapValue(CacheNames.USER_SECRET_KEY,data, secretKey);
         }
-
         return secretKey;
     }
 

+ 3 - 6
ruoyi-common/ruoyi-common-message/src/main/java/org/dromara/common/message/kafka/aop/aspect/SyncDataToLocalAspect.java

@@ -7,7 +7,6 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.dubbo.config.annotation.DubboReference;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.annotation.AfterReturning;
-import org.aspectj.lang.annotation.Aspect;
 import org.aspectj.lang.reflect.CodeSignature;
 import org.dromara.backstage.api.RemoteSyncToLocalByKafkaService;
 import org.dromara.backstage.api.domain.bo.MessageBo;
@@ -17,8 +16,6 @@ import org.dromara.common.message.kafka.aop.annotation.SyncDataToLocal;
 import org.dromara.common.message.kafka.constant.KafkaTopicConstants;
 import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.system.api.model.LoginUser;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
-import org.springframework.stereotype.Component;
 
 import java.lang.reflect.Field;
 import java.util.HashMap;
@@ -31,9 +28,9 @@ import java.util.Map;
  */
 @RequiredArgsConstructor
 @Slf4j
-@Aspect
-@Component
-@ConditionalOnExpression("'cloud'.equals('${locationFlag}')") // 只在云端时装载bean
+//@Aspect
+//@Component
+//@ConditionalOnExpression("'cloud'.equals('${locationFlag}')")
 public class SyncDataToLocalAspect {
 
 //    private final KafkaNormalProducer kafkaProducer;

+ 2 - 0
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/RuoYiBackstageApplication.java

@@ -6,6 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
 
 /**
  * 系统模块
@@ -16,6 +17,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
 @SpringBootApplication
 @EnableScheduling
 @EnableAsync
+@EnableTransactionManagement
 public class RuoYiBackstageApplication {
     public static void main(String[] args) {
         SpringApplication application = new SpringApplication(RuoYiBackstageApplication.class);

+ 0 - 12
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/basics/controller/PtAccountController.java

@@ -15,7 +15,6 @@ import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
-import org.dromara.common.message.kafka.aop.annotation.SyncDataToLocal;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
@@ -24,8 +23,6 @@ import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
-import static org.dromara.common.message.kafka.constant.MessageEventTypeConstants.*;
-
 /**
  * 结算账户
  * 前端访问路由地址为:/backstage/merchant/ptAccount
@@ -79,7 +76,6 @@ public class PtAccountController extends BaseController {
     @SaCheckPermission("merchant:ptAccount:add")
     @Log(title = "结算账户", businessType = BusinessType.INSERT)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = ptAccount_ADD, sender = ACCOUNT_SENDER)
     @PostMapping()
     public R<Void> add(@Validated(AddGroup.class) @RequestBody PtAccountBo bo) {
         return toAjax(ptAccountService.insertByBo(bo));
@@ -91,7 +87,6 @@ public class PtAccountController extends BaseController {
     @SaCheckPermission("merchant:ptAccount:edit")
     @Log(title = "结算账户", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = ptAccount_EDIT, sender = ACCOUNT_SENDER)
     @PutMapping()
     public R<Void> edit(@Validated(EditGroup.class) @RequestBody PtAccountBo bo) {
         return toAjax(ptAccountService.updateByBo(bo));
@@ -104,17 +99,10 @@ public class PtAccountController extends BaseController {
      */
     @SaCheckPermission("merchant:ptAccount:remove")
     @Log(title = "结算账户", businessType = BusinessType.DELETE)
-    @SyncDataToLocal(eventType = ptAccount_REMOVE, sender = ACCOUNT_SENDER)
     @DeleteMapping("/{accountIds}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable Long[] accountIds) {
         return toAjax(ptAccountService.deleteWithValidByIds(List.of(accountIds), true));
     }
 
-    /*public static void main(String[] args) throws FileNotFoundException {
-        Map<String,String> hashMap = new HashMap<>();
-        hashMap.put("currentDate", "11111");
-        PdfUtil.renderPdf("bussinessTemplate1.html",hashMap, new FileOutputStream("D:/temp1.pdf"), PageSize.A3);
-        System.err.println("ok");
-    }*/
 }

+ 0 - 6
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/basics/controller/PtAreaController.java

@@ -19,15 +19,12 @@ import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
-import org.dromara.common.message.kafka.aop.annotation.SyncDataToLocal;
 import org.dromara.common.web.core.BaseController;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
-import static org.dromara.common.message.kafka.constant.MessageEventTypeConstants.*;
-
 /**
  * 建筑物区域
  * 前端访问路由地址为:/room/ptArea
@@ -107,7 +104,6 @@ public class PtAreaController extends BaseController {
     @SaCheckPermission("room:ptArea:add")
     @Log(title = "建筑物区域", businessType = BusinessType.INSERT)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = ptArea_ADD, sender = AREA_SENDER)
     @PostMapping()
     public R<Void> add(@Validated(AddGroup.class) @RequestBody PtAreaBo bo) {
         return toAjax(ptAreaService.insertByBo(bo));
@@ -119,7 +115,6 @@ public class PtAreaController extends BaseController {
     @SaCheckPermission("room:ptArea:edit")
     @Log(title = "建筑物区域", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = ptArea_EDIT, sender = AREA_SENDER)
     @PutMapping()
     public R<Void> edit(@Validated(EditGroup.class) @RequestBody PtAreaBo bo) {
         return toAjax(ptAreaService.updateByBo(bo));
@@ -132,7 +127,6 @@ public class PtAreaController extends BaseController {
      */
     @SaCheckPermission("room:ptArea:remove")
     @Log(title = "建筑物区域", businessType = BusinessType.DELETE)
-    @SyncDataToLocal(eventType = ptArea_REMOVE,sender = AREA_SENDER)
     @DeleteMapping("/{areaIds}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable Long[] areaIds) {

+ 0 - 6
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/basics/controller/PtMealtypeController.java

@@ -15,7 +15,6 @@ import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
-import org.dromara.common.message.kafka.aop.annotation.SyncDataToLocal;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
@@ -24,8 +23,6 @@ import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
-import static org.dromara.common.message.kafka.constant.MessageEventTypeConstants.*;
-
 /**
  * 营业时段
  * 前端访问路由地址为:/basicParameter/ptMealtype
@@ -85,7 +82,6 @@ public class PtMealtypeController extends BaseController {
     @SaCheckPermission("basicParameter:ptMealtype:add")
     @Log(title = "营业时段", businessType = BusinessType.INSERT)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = ptMealtype_ADD,sender = MEAL_TYPE_SENDER)
     @PostMapping()
     public R<Void> add(@Validated(AddGroup.class) @RequestBody PtMealTypeBo bo) {
         return toAjax(ptMealtypeService.insertByBo(bo));
@@ -97,7 +93,6 @@ public class PtMealtypeController extends BaseController {
     @SaCheckPermission("basicParameter:ptMealtype:edit")
     @Log(title = "营业时段", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = ptMealtype_EDIT, sender = MEAL_TYPE_SENDER)
     @PutMapping()
     public R<Void> edit(@Validated(EditGroup.class) @RequestBody PtMealTypeBo bo) {
         return toAjax(ptMealtypeService.updateByBo(bo));
@@ -111,7 +106,6 @@ public class PtMealtypeController extends BaseController {
     @SaCheckPermission("basicParameter:ptMealtype:remove")
     @Log(title = "营业时段", businessType = BusinessType.DELETE)
     @DeleteMapping("/{mealIds}")
-    @SyncDataToLocal(eventType = ptMealtype_REMOVE,sender = MEAL_TYPE_SENDER)
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable Long[] mealIds) {
         return toAjax(ptMealtypeService.deleteWithValidByIds(List.of(mealIds), true));

+ 0 - 6
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/basics/controller/PtParameterController.java

@@ -15,7 +15,6 @@ import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
-import org.dromara.common.message.kafka.aop.annotation.SyncDataToLocal;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
@@ -24,8 +23,6 @@ import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
-import static org.dromara.common.message.kafka.constant.MessageEventTypeConstants.*;
-
 /**
  * 系统参数
  * 前端访问路由地址为:/basicParameter/ptParameter
@@ -79,7 +76,6 @@ public class PtParameterController extends BaseController {
     @SaCheckPermission("basicParameter:ptParameter:add")
     @Log(title = "系统参数", businessType = BusinessType.INSERT)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = ptParameter_ADD, sender = PARAMETER_SENDER)
     @PostMapping()
     public R<Void> add(@Validated(AddGroup.class) @RequestBody PtParameterBo bo) {
         return toAjax(ptParameterService.insertByBo(bo));
@@ -93,7 +89,6 @@ public class PtParameterController extends BaseController {
     @SaCheckPermission("basicParameter:ptParameter:remove")
     @Log(title = "系统参数", businessType = BusinessType.DELETE)
     @DeleteMapping("/{paramIds}")
-    @SyncDataToLocal(eventType = ptParameter_REMOVE,sender = PARAMETER_SENDER)
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable Long[] paramIds) {
         return toAjax(ptParameterService.deleteWithValidByIds(List.of(paramIds), true));
@@ -105,7 +100,6 @@ public class PtParameterController extends BaseController {
     @SaCheckPermission("basicParameter:ptParameter:edit")
     @Log(title = "系统参数", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = ptParameter_EDIT, sender = PARAMETER_SENDER)
     @PutMapping()
     public R<Void> edit(@Validated(EditGroup.class) @RequestBody PtParameterBo bo) {
         return toAjax(ptParameterService.updateByBo(bo));

+ 0 - 7
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/basics/controller/PtRoomController.java

@@ -23,7 +23,6 @@ import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
-import org.dromara.common.message.kafka.aop.annotation.SyncDataToLocal;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
@@ -36,8 +35,6 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
-import static org.dromara.common.message.kafka.constant.MessageEventTypeConstants.*;
-
 /**
  * 房间定义
  * 前端访问路由地址为:/room/ptRoom
@@ -132,7 +129,6 @@ public class PtRoomController extends BaseController {
      */
     @SaCheckPermission("room:ptRoom:add")
     @Log(title = "房间定义", businessType = BusinessType.INSERT)
-    @SyncDataToLocal(eventType = ptRoom_ADD, sender = ROOM_SENDER)
     @RepeatSubmit()
     @PostMapping()
     public R<Void> add(@Validated(AddGroup.class) @RequestBody PtRoomBo bo) {
@@ -144,7 +140,6 @@ public class PtRoomController extends BaseController {
      */
     @SaCheckPermission("room:ptRoom:set")
     @Log(title = "批量设置房间类型", businessType = BusinessType.OTHER)
-    @SyncDataToLocal(eventType = ptRoom_SET,sender = ROOM_SENDER)
     @RepeatSubmit()
     @PostMapping("/batchSet")
     public R<Void> batchSet(@Validated @RequestBody PtRoomBatchSetBo bo) {
@@ -159,7 +154,6 @@ public class PtRoomController extends BaseController {
      */
     @SaCheckPermission("room:ptRoom:edit")
     @Log(title = "房间定义", businessType = BusinessType.UPDATE)
-    @SyncDataToLocal(eventType = ptRoom_EDIT,sender = ROOM_SENDER)
     @RepeatSubmit()
     @PutMapping()
     public R<Void> edit(@Validated(EditGroup.class) @RequestBody PtRoomBo bo) {
@@ -173,7 +167,6 @@ public class PtRoomController extends BaseController {
      */
     @SaCheckPermission("room:ptRoom:remove")
     @Log(title = "房间定义", businessType = BusinessType.DELETE)
-    @SyncDataToLocal(eventType = ptRoom_REMOVE, sender = ROOM_SENDER)
     @DeleteMapping("/{roomIds}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable Long[] roomIds) {

+ 243 - 0
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/business/InitServiceBusiness.java

@@ -0,0 +1,243 @@
+package org.dromara.backstage.business;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.backstage.api.domain.vo.*;
+import org.dromara.backstage.basics.domain.bo.PtMealTypeBo;
+import org.dromara.backstage.basics.domain.vo.PtMealTypeVo;
+import org.dromara.backstage.basics.service.IPtMealtypeService;
+import org.dromara.backstage.basics.service.IPtParameterService;
+import org.dromara.backstage.cardCenter.service.IPtCardService;
+import org.dromara.backstage.consumption.domain.bo.*;
+import org.dromara.backstage.consumption.domain.vo.*;
+import org.dromara.backstage.consumption.service.*;
+import org.dromara.backstage.domain.vo.card.PtCardVo;
+import org.dromara.backstage.payment.domain.bo.PtUserAccountBo;
+import org.dromara.backstage.payment.domain.vo.PtUserAccountVo;
+import org.dromara.backstage.payment.service.IPtUserAccountService;
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.springframework.stereotype.Service;
+
+import java.time.Duration;
+import java.util.List;
+
+/**
+ * 服务启动后一些初始化业务处理
+ * <p>
+ * [功能说明]
+ * ${1:在此简要描述文件核心功能}
+ *
+ * @author luoyibo
+ * @version 2.2.0
+ * @date 2025-07-10
+ * @since JDK17
+ */
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class InitServiceBusiness {
+    private final IPtParameterService parameterService;
+    private final IPtMealtypeService mealTypeService;
+    private final IXfLimitedTermService limitedTermService;
+    private final IXfLimitedService limitedService;
+    private final IXfDiscountService discountService;
+    private final IXfDiscountTermService discountTermService;
+    private final IXfQuotaService xfQuotaService;
+    private final IXfQuotaTermService xfQuotaTermService;
+    private final IPtCardService cardService;
+    private final IPtUserAccountService userAccountService;
+
+    /**
+     * 初始化全局消费参数.
+     * 这部分数据系统初始化时设置后基本不会改动,只需要启动后初始化即可
+     * <p>
+     * 1.是否全局启用了消费限次
+     * 2.是否全局启用了消费限额
+     * 3.是否全局启用了消费折扣
+     * >
+     */
+    public void initGlobalData() {
+        String disCount = parameterService.selectParamByCode("RATE_CONSUME");
+        String quota = parameterService.selectParamByCode("XE_CONSUME");
+        String limited = parameterService.selectParamByCode("XC_CONSUME");
+
+        log.info("初始化全局限次、限额和折扣参数完成");
+    }
+
+    /**
+     * 初始化消费餐类清单参数.
+     * <p>
+     * 该方法用于在系统启动时初始化消费餐类的相关信息,并将其存储在 Redis 缓存中以便快速访问。
+     */
+    public void initMealTypeInfo() {
+        PtMealTypeBo bo = new PtMealTypeBo();
+        List<PtMealTypeVo> list = mealTypeService.queryList(bo);
+
+        RedisUtils.deleteKeys(CacheNames.PT_MEAL_TYPE_LIST);
+        RedisUtils.setCacheList(CacheNames.PT_MEAL_TYPE_LIST, MapstructUtils.convert(list, RemoteMealTypeVo.class));
+        RedisUtils.expire(CacheNames.PT_MEAL_TYPE_LIST, Duration.ofDays(1));
+
+        log.info("初始化消费餐类清单参数完成");
+    }
+
+    /**
+     * 初始化折扣、限次、限额信息
+     * 需要折扣、限额和限次的设备与卡类在系统使用前就会初始化,后续除了增加新设备外,一般不再改动
+     * 在服务启动时加载一次,再根据是否有调整数据手工加载或第天定时加载一次
+     */
+    public void initDiscountAndOther() {
+        XfLimitedTermBo bo = new XfLimitedTermBo();
+        List<XfLimitedTermVo> list = limitedTermService.queryList(bo);
+        if (CollUtil.isNotEmpty(list)) {
+            List<Long> limitedTermIds = list.stream().map(XfLimitedTermVo::getTermId).toList();
+            RedisUtils.deleteKeys(CacheNames.T_XF_LIMITEDTERM);
+            limitedTermIds.forEach(p -> {
+                String key = String.valueOf(p);
+                RedisUtils.setCacheMapValue(CacheNames.T_XF_LIMITEDTERM, key, p);
+            });
+        }
+        log.info("初始化限次设备参数完成");
+
+        XfLimitedBo limitedBo = new XfLimitedBo();
+        limitedBo.setStatus("1");
+        List<XfLimitedVo> limitedVos = limitedService.queryList(limitedBo);
+        if (CollUtil.isNotEmpty(limitedVos)) {
+            RedisUtils.deleteKeys(CacheNames.T_XF_LIMITED);
+            limitedVos.forEach(p -> {
+                RemoteLimitedVo remoteLimitedVo = new RemoteLimitedVo();
+                remoteLimitedVo.setLimitedId(p.getLimitedId());
+                remoteLimitedVo.setCardType(p.getCardType());
+                remoteLimitedVo.setCardTypeName(p.getCardTypeName());
+                remoteLimitedVo.setDailyCount(p.getDailyCount());
+                remoteLimitedVo.setOneCount(p.getOneCount());
+                remoteLimitedVo.setTwoCount(p.getTwoCount());
+                remoteLimitedVo.setThreeCount(p.getThreeCount());
+                remoteLimitedVo.setFourCount(p.getFourCount());
+                remoteLimitedVo.setRemark(p.getRemark());
+                remoteLimitedVo.setStatus(p.getStatus());
+
+                String key = String.valueOf(p.getCardType());
+                RedisUtils.setCacheMapValue(CacheNames.T_XF_LIMITED, key, remoteLimitedVo);
+            });
+        }
+        log.info("初始化限次卡类参数完成");
+
+
+        XfDiscountTermBo xfDiscountTermBo = new XfDiscountTermBo();
+        List<XfDiscountTermVo> xfDiscountTermVos = discountTermService.queryList(xfDiscountTermBo);
+        if (CollUtil.isNotEmpty(xfDiscountTermVos)) {
+            List<Long> discountTermIds = xfDiscountTermVos.stream().map(XfDiscountTermVo::getTermId).toList();
+            RedisUtils.deleteKeys(CacheNames.T_XF_DISCOUNTTERM);
+            discountTermIds.forEach(p -> {
+                String key = String.valueOf(p);
+                RedisUtils.setCacheMapValue(CacheNames.T_XF_DISCOUNTTERM, key, p);
+            });
+        }
+        log.info("初始化折扣设备参数完成");
+
+        XfDiscountBo xfDiscountBo = new XfDiscountBo();
+        xfDiscountBo.setStatus("1");
+        List<XfDiscountVo> xfDiscountVos = discountService.queryList(xfDiscountBo);
+        if (CollUtil.isNotEmpty(xfDiscountVos)) {
+            // List<RemoteDiscountVo> discountCards = MapstructUtils.convert(list, RemoteDiscountVo.class);
+            RedisUtils.deleteKeys(CacheNames.T_XF_DISCOUNT);
+            xfDiscountVos.forEach(p -> {
+                String key = String.format("%s-%s", p.getCardType(), p.getMealType());
+                RemoteDiscountVo remoteDiscountVo = new RemoteDiscountVo();
+                remoteDiscountVo.setDiscountId(p.getDiscountId());
+                remoteDiscountVo.setCardType(p.getCardType());
+                remoteDiscountVo.setCardTypeName(p.getCardTypeName());
+                remoteDiscountVo.setMealType(p.getMealType());
+                remoteDiscountVo.setMealTypeName(p.getCardTypeName());
+                remoteDiscountVo.setRateType(p.getRateType());
+                remoteDiscountVo.setOneRate(p.getOneRate());
+                remoteDiscountVo.setTwoRate(p.getTwoRate());
+                remoteDiscountVo.setThreeRate(p.getThreeRate());
+                remoteDiscountVo.setFourRate(p.getFourRate());
+                remoteDiscountVo.setRemark(p.getRemark());
+                remoteDiscountVo.setStatus(p.getStatus());
+
+                RedisUtils.setCacheMapValue(CacheNames.T_XF_DISCOUNT, key, remoteDiscountVo);
+            });
+        }
+        log.info("初始化折扣卡类参数完成");
+
+        XfQuotaTermBo xfQuotaTermBo = new XfQuotaTermBo();
+        List<XfQuotatermVo> xfQuotatermVoList = xfQuotaTermService.queryList(xfQuotaTermBo);
+        if (CollUtil.isNotEmpty(xfQuotatermVoList)) {
+            List<Long> quotaTermIds = xfQuotatermVoList.stream().map(XfQuotatermVo::getTermId).toList();
+            if (CollUtil.isNotEmpty(quotaTermIds)) {
+                RedisUtils.deleteKeys(CacheNames.T_XF_QUOTATERM);
+                quotaTermIds.forEach(p -> {
+                    String key = String.valueOf(p);
+                    RedisUtils.setCacheMapValue(CacheNames.T_XF_QUOTATERM, key, p);
+                });
+            }
+        }
+        log.info("初始化限额设备参数完成");
+
+        XfQuotaBo xfQuotaBo = new XfQuotaBo();
+        xfQuotaBo.setStatus("1");
+        List<XfQuotaVo> xfQuotaVos = xfQuotaService.queryList(xfQuotaBo);
+        if (CollUtil.isNotEmpty(xfQuotaVos)) {
+            RedisUtils.deleteKeys(CacheNames.T_XF_QUOTA);
+            xfQuotaVos.forEach(p -> {
+                String key = String.valueOf(p.getCardType());
+                RemoteQuotaVo remoteQuotaVo = new RemoteQuotaVo();
+                remoteQuotaVo.setQuotaId(p.getQuotaId());
+                remoteQuotaVo.setCardType(p.getCardType());
+                remoteQuotaVo.setCardTypeName(p.getCardTypeName());
+                remoteQuotaVo.setDailyMoney(p.getDailyMoney());
+                remoteQuotaVo.setOneMoney(p.getOneMoney());
+                remoteQuotaVo.setTwoMoney(p.getTwoMoney());
+                remoteQuotaVo.setThreeMoney(p.getThreeMoney());
+                remoteQuotaVo.setFourMoney(p.getFourMoney());
+                remoteQuotaVo.setRemark(p.getRemark());
+                remoteQuotaVo.setStatus(p.getStatus());
+
+                RedisUtils.setCacheMapValue(CacheNames.T_XF_QUOTA, key, remoteQuotaVo);
+            });
+        }
+        log.info("初始化限额卡类参数完成");
+    }
+
+    /**
+     * 初始化人员卡片
+     */
+    public void initUserCard() {
+        List<PtCardVo> list = cardService.selectNormalCards();
+        RedisUtils.deleteKeys(CacheNames.PT_USER_CARD_NO);
+        RedisUtils.deleteKeys(CacheNames.PT_USER_CARD_USER_ID);
+        RedisUtils.deleteKeys(CacheNames.PT_USER_CARD_FACTORYID);
+        list.forEach(p -> {
+            RedisUtils.setCacheMapValue(CacheNames.PT_USER_CARD_NO, p.getCardNo().toString(), JsonUtils.toJsonString(p));
+            RedisUtils.setCacheMapValue(CacheNames.PT_USER_CARD_USER_ID, p.getUserId().toString(), JsonUtils.toJsonString(p));
+            RedisUtils.setCacheMapValue(CacheNames.PT_USER_CARD_FACTORYID, p.getFactoryId().toString(), JsonUtils.toJsonString(p));
+        });
+        log.info("初始化人员卡片参数完成");
+    }
+
+    public void initUserAccount() {
+        PtUserAccountBo bo = new PtUserAccountBo();
+        List<PtUserAccountVo> voList = userAccountService.queryList(bo);
+        List<PtUserAccountVo> filterList = voList.stream().filter(
+            p -> p.getDelFlag().equals("0")
+                && p.getLifespan().getTime() > System.currentTimeMillis()).toList();
+        RedisUtils.deleteKeys(CacheNames.PT_USER_ACCOUNT_NO);
+        RedisUtils.deleteKeys(CacheNames.PT_USER_ACCOUNT_NUMB);
+        RedisUtils.deleteKeys(CacheNames.PT_USER_ACCOUNT_ID);
+        if (CollUtil.isNotEmpty(filterList)) {
+            filterList.forEach(p -> {
+                RedisUtils.setCacheMapValue(CacheNames.PT_USER_ACCOUNT_NO, p.getUserNo().toString(), JsonUtils.toJsonString(p));
+                RedisUtils.setCacheMapValue(CacheNames.PT_USER_ACCOUNT_NUMB, p.getUserNumb(), JsonUtils.toJsonString(p));
+                RedisUtils.setCacheMapValue(CacheNames.PT_USER_ACCOUNT_ID, p.getUserId().toString(), JsonUtils.toJsonString(p));
+            });
+        }
+        log.info("初始化人员账户参数完成");
+    }
+}

+ 0 - 25
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/business/accouunt/UserAccountBusiness.java

@@ -16,14 +16,11 @@ import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.ObjectUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.dubbo.config.annotation.DubboReference;
-import org.dromara.backstage.api.RemotePtParameterService;
 import org.dromara.backstage.business.card.CardBusiness;
 import org.dromara.backstage.cardCenter.domain.bo.PtCardBo;
 import org.dromara.backstage.cardCenter.service.IPtCardService;
 import org.dromara.backstage.domain.vo.account.AccountInfoVo;
 import org.dromara.backstage.domain.vo.card.PtCardVo;
-import org.dromara.backstage.mq.KafkaNormalProducer;
 import org.dromara.backstage.payment.domain.bo.PtBagBo;
 import org.dromara.backstage.payment.domain.bo.PtUserAccountBo;
 import org.dromara.backstage.payment.domain.vo.PtBagVo;
@@ -44,10 +41,6 @@ public class UserAccountBusiness {
     private final IPtBagService bagService;
     private final IPtCardService cardService;
     private final CardBusiness cardBusiness;
-    private final KafkaNormalProducer kafkaNormalProducer;
-
-    @DubboReference
-    private final RemotePtParameterService remotePtParameterService;
 
     /**
      * 开通一卡通账户
@@ -67,7 +60,6 @@ public class UserAccountBusiness {
             //--------- 设置 流水号和主键id set 到 cardBo
             bo.setCardId(cardBo.getCardId());
             bo.setCardNo(cardBo.getCardNo());
-            //sendCloudConsume(ptUserAccountService.queryById(bo.getUserId()));
             return rs;
         }
 
@@ -100,21 +92,4 @@ public class UserAccountBusiness {
         accountInfoVo.setAccountBase(ptUserAccountVo);
         return R.ok(accountInfoVo);
     }
-
-    //private void sendCloudConsume(PtUserAccountVo vo) {
-    //    String pushData = remotePtParameterService.getPtParameterByKey("PUSH_MQ_DATA");
-    //    if (ObjectUtil.isNotEmpty(pushData) && ObjectUtil.equals(pushData, "1")) {
-    //        KafkaMessage<PtUserAccountVo> message = new KafkaMessage<>();
-    //        KafkaHeader header = message.getHeader();
-    //        header.setTimestamp(System.currentTimeMillis());
-    //        header.setEventId(CloudMqEventConstants.ACCOUNT);
-    //        header.setEventType(CloudMqEventConstants.ACCOUNT);
-    //        header.setSender(CloudMqEventConstants.SENDER);
-    //
-    //        message.setHeader(header);
-    //        message.setBody(vo);
-    //
-    //        kafkaNormalProducer.sendKafkaMessage(CloudMqEventConstants.TOPIC, message);
-    //    }
-    //}
 }

+ 4 - 7
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/business/card/CardBusiness.java

@@ -14,7 +14,6 @@ import org.dromara.backstage.cardCenter.domain.bo.PtCardBo;
 import org.dromara.backstage.cardCenter.service.IPtCardService;
 import org.dromara.backstage.domain.vo.card.InitCardVo;
 import org.dromara.backstage.domain.vo.card.PtCardVo;
-import org.dromara.backstage.mq.PushKafkaData;
 import org.dromara.backstage.payment.domain.bo.PtBagBo;
 import org.dromara.backstage.payment.domain.vo.PtBagVo;
 import org.dromara.backstage.payment.domain.vo.PtUserAccountVo;
@@ -24,9 +23,6 @@ import org.dromara.common.core.config.DefaultConfig;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.domain.model.ResultInfo;
 import org.dromara.common.core.enums.*;
-import org.dromara.common.message.kafka.constant.EventTypeConstants;
-import org.dromara.common.message.kafka.constant.KafkaTopicConstants;
-import org.dromara.common.message.kafka.enums.EventSenderEnum;
 import org.springframework.stereotype.Service;
 
 import java.text.MessageFormat;
@@ -53,7 +49,7 @@ public class CardBusiness {
     private final IPtWorkstationService workstationService;
     private final IPtParameterService ptParameterService;
     private final DefaultConfig defaultConfig;
-    private final PushKafkaData kafkaNormalProducer;
+    //private final PushKafkaData kafkaNormalProducer;
 
     public R<String> openVirtualCard(PtCardBo cardBo) {
         String resultMsg;
@@ -206,9 +202,10 @@ public class CardBusiness {
             if (result) {
                 // 回收成功,将回收后的卡片信息发kafka
                 PtCardVo recycleVo = ptCardService.selectOneByBo(cardBo);
-                kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.CARD, EventSenderEnum.BACKSTAGE.code(),
-                                                     recycleVo);
+                //kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.CARD, EventSenderEnum.BACKSTAGE.code(),
+                //                                     recycleVo);
             }
+
             return result ? R.ok() : R.fail();
         } else {
             return R.fail(MessageFormat.format("[卡片回收]-[没有对应的卡片]-[{0}]", JSONUtil.toJsonStr(cardBo)));

+ 2 - 2
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/business/consume/ConsumeBusiness.java

@@ -108,8 +108,8 @@ public class ConsumeBusiness {
      * @return 生成的记录Id
      */
     @NotNull
-    private java.lang.String getRecordKeyId(Integer termNo, Long termRecordId, Long userNo, Integer bagCode) {
-        return RecordIdUtils.getRecordId(new Date(), termNo.shortValue(), termRecordId.shortValue(), userNo.intValue(), bagCode);
+    private String getRecordKeyId(Integer termNo, Long termRecordId, Long userNo, Integer bagCode) {
+        return RecordIdUtils.getRecordId(new Date(), termNo, termRecordId.shortValue(), userNo.intValue(), bagCode);
     }
 
     public PtUserAccountVo getPtUserAccountVo(ConsumptionBo recordBo) {

+ 1 - 1
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/business/errfill/ErrFillBusiness.java

@@ -306,7 +306,7 @@ public class ErrFillBusiness {
      */
     @NotNull
     private String getRecordKeyId(ConsumptionBo bo, PtUserAccountVo accountVo, PtBagVo bagVo) {
-        return RecordIdUtils.getRecordId(new Date(), Short.parseShort(bo.getTermNo().toString()), 0, accountVo.getUserNo().intValue(), Integer.parseInt(bagVo.getBagCode()));
+        return RecordIdUtils.getRecordId(new Date(), bo.getTermNo().intValue(), 0, accountVo.getUserNo().intValue(), Integer.parseInt(bagVo.getBagCode()));
     }
 
     private void updateCacheBalanceByUserId(Long id, BigDecimal dealMoney) {

+ 1 - 1
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/business/lock/LockBusiness.java

@@ -104,7 +104,7 @@ public class LockBusiness {
         if (ObjectUtil.equals(flag, "0")) {
             return R.ok("远程开门成功", "远程开门成功");
         }
-        return R.fail("远程开门失败", "远程开门失败");
+        return R.fail("远程开门失败", openResult);
     }
 
     /**

+ 1 - 1
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/business/payments/PayBaseBusiness.java

@@ -445,6 +445,6 @@ public class PayBaseBusiness {
             key3 = bo.getReceiptMoney().negate().multiply(a).intValue();
         }
         return RecordIdUtils.getRecordId(new Date(), Short.parseShort(bo.getCreditType()),
-                                         Integer.parseInt(accountVo.getUserNo().toString()), key3, Integer.parseInt(bagVo.getBagCode()));
+                                         accountVo.getUserNo().intValue(), key3, Integer.parseInt(bagVo.getBagCode()));
     }
 }

+ 2 - 6
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/business/payments/ThirdPayBusiness.java

@@ -11,7 +11,6 @@ import cn.hutool.json.JSONUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.backstage.config.ThirdPayConfig;
-import org.dromara.backstage.mq.PushKafkaData;
 import org.dromara.backstage.payment.domain.bo.PayOrderBo;
 import org.dromara.backstage.payment.domain.bo.PurseInOutBo;
 import org.dromara.backstage.payment.domain.vo.PayOrderVo;
@@ -20,9 +19,6 @@ import org.dromara.backstage.payment.service.IPayOrderService;
 import org.dromara.backstage.payment.service.IPtUserAccountService;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.enums.*;
-import org.dromara.common.message.kafka.constant.EventTypeConstants;
-import org.dromara.common.message.kafka.constant.KafkaTopicConstants;
-import org.dromara.common.message.kafka.enums.EventSenderEnum;
 import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
@@ -48,7 +44,7 @@ public class ThirdPayBusiness {
     private final PayOrderBusiness payOrderBusiness;
     private final ThirdPayConfig thirdPayConfig;
     private final IPtUserAccountService userAccountService;
-    private final PushKafkaData kafkaNormalProducer;
+    //private final PushKafkaData kafkaNormalProducer;
 
     /**
      * 创建二维码支付的收款码
@@ -182,7 +178,7 @@ public class ThirdPayBusiness {
         updateBo.setOriginalId(postResult.getData().getOriginalId());
         updateBo.setCreditStatus(CreditStatusEnum.SUCCESS.code().toString());
         //充值信息发送kafka
-        kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.RECHARGE, EventSenderEnum.BACKSTAGE.code(), postResult.getData());
+        //kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.RECHARGE, EventSenderEnum.BACKSTAGE.code(), postResult.getData());
 
         return setOrderPostResult(updateBo);
     }

+ 0 - 27
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/controller/CardApiController.java

@@ -21,7 +21,6 @@ import org.dromara.common.core.enums.CardOperateEnum;
 import org.dromara.common.core.enums.CardStatusEnum;
 import org.dromara.common.core.enums.ResultCodeEnum;
 import org.dromara.common.core.exception.ApiException;
-import org.dromara.common.message.kafka.aop.annotation.SyncDataToLocal;
 import org.dromara.common.web.core.BaseController;
 import org.jetbrains.annotations.NotNull;
 import org.springframework.validation.annotation.Validated;
@@ -34,8 +33,6 @@ import java.math.BigDecimal;
 import java.text.MessageFormat;
 import java.util.Map;
 
-import static org.dromara.common.message.kafka.constant.MessageEventTypeConstants.*;
-
 /**
  * @ClassName CardApiController
  * @Description 提供给自助客户端对卡片操作的接口
@@ -72,16 +69,6 @@ public class CardApiController extends BaseController {
         }
         throw new ApiException(result.getData().getDetail(), result.getData().getCode());
     }
-    //@PostMapping("/api/v1/change")
-    //@SyncDataToLocal(eventType = CARD_OPERATION, sender = CARD_CENTER_SENDER)
-    //public ReturnResult initChangeCard(@RequestBody Map<String, Object> initParam) {
-    //    return getReturnResult(initParam);
-    //}
-    //@PostMapping("/api/v1/reissue")
-    //@SyncDataToLocal(eventType = CARD_OPERATION, sender = CARD_CENTER_SENDER)
-    //public ReturnResult initReissueCard(@RequestBody Map<String, Object> initParam) {
-    //    return getReturnResult(initParam);
-    //}
 
     @NotNull
     private ReturnResult getReturnResult(@RequestBody Map<String, Object> initParam) {
@@ -95,7 +82,6 @@ public class CardApiController extends BaseController {
     }
 
     @PostMapping("/api/v1/write/success")
-    @SyncDataToLocal(eventType = CARD_OPERATION, sender = CARD_CENTER_SENDER)
     public ReturnResult writeNewCardOk(@RequestBody Map<String, Object> initParam) throws InterruptedException {
         PtCardBo bo = getInitInfo(initParam);
         R<PtCardVo> vo = cardBusiness.writeCardSuccess(bo);
@@ -115,7 +101,6 @@ public class CardApiController extends BaseController {
      * @return 返回结果
      */
     @PostMapping("/api/v1/lock")
-    @SyncDataToLocal(eventType = CARD_LOCK, sender = CARD_CENTER_SENDER)
     public ReturnResult lockCard(@RequestBody Map<String, String> mapParams){
         String defaultTenantId = defaultConfig.getTenantId();
         Long cardNo = Long.parseLong(mapParams.get("cardNo"));
@@ -137,7 +122,6 @@ public class CardApiController extends BaseController {
      * @return 回收结果
      */
     @PostMapping("/api/v1/recycle")
-    @SyncDataToLocal(eventType = CARD_RECOVERY, sender = CARD_CENTER_SENDER)
     public ReturnResult recycleCard(@RequestBody Map<String, String> mapParams){
         String defaultTenantId = defaultConfig.getTenantId();
         Long cardNo = Long.parseLong(mapParams.get("cardNo"));
@@ -209,15 +193,4 @@ public class CardApiController extends BaseController {
         return bo;
     }
 
-    //@PostMapping("/api/v1/init")
-    //public ReturnResult initSefNewCard(@RequestBody Map<String, Object> initParam) {
-    //    PtCardBo bo = getInitInfo(initParam);
-    //    InitCardVo initCardVo = new InitCardVo();
-    //    R<ResultInfo> result = cardBusiness.initEntityCard(bo, initCardVo);
-    //    if (R.isSuccess(result)) {
-    //        PtCardVo cardVo = initCardVo.getCard();
-    //        return ReturnResult.success(YcVoConvert.cardVoConvert(cardVo));
-    //    }
-    //    throw new ApiException(result.getData().getDetail(), result.getData().getCode());
-    //}
 }

+ 0 - 5
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/controller/PtCardController.java

@@ -17,7 +17,6 @@ import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
-import org.dromara.common.message.kafka.aop.annotation.SyncDataToLocal;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
@@ -26,8 +25,6 @@ import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
-import static org.dromara.common.message.kafka.constant.MessageEventTypeConstants.*;
-
 /**
  * 账户卡片
  * 前端访问路由地址为:/cardOperation/ptCard
@@ -116,7 +113,6 @@ public class PtCardController extends BaseController {
     @SaCheckPermission("cardOperation:ptCard:edit")
     @Log(title = "账户卡片", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = ptCard_CARD_LOCK, sender = CARD_CENTER_SENDER)
     @PutMapping("/lockCard/{cardId}")
     public R<Void> lockPtCard(@PathVariable Long cardId) {
         return toAjax(ptCardService.lockCard(cardId));
@@ -128,7 +124,6 @@ public class PtCardController extends BaseController {
     @SaCheckPermission("cardOperation:ptCard:edit")
     @Log(title = "账户卡片", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = ptCard_CARD_UNLOCK, sender = CARD_CENTER_SENDER)
     @PutMapping("/unlockCard/{cardId}")
     public R<Void> unlockPtCard(@PathVariable Long cardId) {
         return toAjax(ptCardService.unlockCard(cardId));

+ 0 - 7
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/controller/PtSubsidyController.java

@@ -16,7 +16,6 @@ import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
-import org.dromara.common.message.kafka.aop.annotation.SyncDataToLocal;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
@@ -25,8 +24,6 @@ import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
-import static org.dromara.common.message.kafka.constant.MessageEventTypeConstants.*;
-
 /**
  * 补助设置
  * 前端访问路由地址为:/subsidy/ptSubsidy
@@ -81,7 +78,6 @@ public class PtSubsidyController extends BaseController {
     @SaCheckPermission("subsidy:ptSubsidy:add")
     @Log(title = "补助设置", businessType = BusinessType.INSERT)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = SUBSIDY_ADD, sender = SUBSIDY_SENDER)
     @PostMapping()
     public R<Void> add(@Validated(AddGroup.class) @RequestBody PtSubsidyBo bo) {
         return toAjax(ptSubsidyService.insertByBo(bo));
@@ -93,7 +89,6 @@ public class PtSubsidyController extends BaseController {
     @SaCheckPermission("subsidy:ptSubsidy:edit")
     @Log(title = "补助设置", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = SUBSIDY_EDIT, sender = SUBSIDY_SENDER)
     @PutMapping()
     public R<Void> edit(@Validated(EditGroup.class) @RequestBody PtSubsidyBo bo) {
         return toAjax(ptSubsidyService.updateByBo(bo));
@@ -105,7 +100,6 @@ public class PtSubsidyController extends BaseController {
     @SaCheckPermission("subsidy:ptSubsidy:audit")
     @Log(title = "补助设置审核", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = SUBSIDY_EDIT, sender = SUBSIDY_SENDER)
     @PutMapping("/audit")
     public R<Void> audit(@Validated(EditGroup.class) @RequestBody PtSubsidyBo bo) {
         return toAjax(ptSubsidyService.updateByBo(bo));
@@ -119,7 +113,6 @@ public class PtSubsidyController extends BaseController {
      */
     @SaCheckPermission("subsidy:ptSubsidy:remove")
     @Log(title = "补助设置", businessType = BusinessType.DELETE)
-    @SyncDataToLocal(eventType = SUBSIDY_REMOVE, sender = SUBSIDY_SENDER)
     @DeleteMapping("/{mainIds}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable Long[] mainIds) {

+ 0 - 5
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/controller/PtSubsidyitemController.java

@@ -21,7 +21,6 @@ import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
-import org.dromara.common.message.kafka.aop.annotation.SyncDataToLocal;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.satoken.utils.LoginHelper;
@@ -39,9 +38,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
-import static org.dromara.common.message.kafka.constant.MessageEventTypeConstants.SUBSIDY_ITEM_REMOVE;
-import static org.dromara.common.message.kafka.constant.MessageEventTypeConstants.SUBSIDY_SENDER;
-
 /**
  * 补助明细
  * 前端访问路由地址为:/subsidy/ptSubsidyitem
@@ -120,7 +116,6 @@ public class PtSubsidyitemController extends BaseController {
      */
     @SaCheckPermission("subsidy:ptSubsidyitem:remove")
     @Log(title = "补助明细", businessType = BusinessType.DELETE)
-    @SyncDataToLocal(eventType = SUBSIDY_ITEM_REMOVE, sender = SUBSIDY_SENDER)
     @DeleteMapping("/{itemIds}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable Long[] itemIds) {

+ 4 - 0
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/service/IPtCardService.java

@@ -1,5 +1,6 @@
 package org.dromara.backstage.cardCenter.service;
 
+import org.dromara.backstage.api.domain.vo.RemoteCardVo;
 import org.dromara.backstage.cardCenter.domain.bo.PtCardBo;
 import org.dromara.backstage.domain.vo.card.PtCardVo;
 import org.dromara.backstage.payment.domain.bo.PtBagBo;
@@ -10,6 +11,7 @@ import java.math.BigDecimal;
 import java.util.Collection;
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 账户卡片Service接口
@@ -230,4 +232,6 @@ public interface IPtCardService {
     List<PtCardVo> selectNormalCards();
 
     Boolean updateCardDayData(PtCardBo bo);
+
+    Map<Long, String> getUserHasCard();
 }

+ 41 - 8
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/cardCenter/service/impl/PtCardServiceImpl.java

@@ -12,6 +12,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.RequiredArgsConstructor;
 import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.backstage.api.domain.vo.RemoteCardVo;
 import org.dromara.backstage.basics.domain.vo.PtCardtypeVo;
 import org.dromara.backstage.basics.service.IPtCardtypeService;
 import org.dromara.backstage.basics.service.IPtParameterService;
@@ -20,26 +21,26 @@ import org.dromara.backstage.cardCenter.domain.bo.PtCardBo;
 import org.dromara.backstage.cardCenter.mapper.PtCardMapper;
 import org.dromara.backstage.cardCenter.service.IPtCardService;
 import org.dromara.backstage.domain.vo.card.PtCardVo;
-import org.dromara.backstage.mq.PushKafkaData;
 import org.dromara.backstage.payment.domain.bo.PtBagBo;
+import org.dromara.common.core.constant.CacheNames;
 import org.dromara.common.core.constant.Constants;
 import org.dromara.common.core.constant.DefaultConstants;
 import org.dromara.common.core.enums.CardOpenEnum;
 import org.dromara.common.core.enums.CardStatusEnum;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.StringUtils;
-import org.dromara.common.message.kafka.constant.EventTypeConstants;
-import org.dromara.common.message.kafka.constant.KafkaTopicConstants;
-import org.dromara.common.message.kafka.enums.EventSenderEnum;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.tenant.helper.TenantHelper;
 import org.dromara.hotel.api.service.RemoteCardDataService;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Service;
 
+import javax.smartcardio.Card;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * 账户卡片Service业务层处理
@@ -53,7 +54,8 @@ public class PtCardServiceImpl implements IPtCardService {
     private final PtCardMapper baseMapper;
     private final IPtParameterService parameterService;
     private final IPtCardtypeService cardTypeService;
-    private final PushKafkaData kafkaNormalProducer;
+    //private final PushKafkaData kafkaNormalProducer;
+
     @DubboReference
     private final RemoteCardDataService remoteCardDataService;
 
@@ -174,7 +176,7 @@ public class PtCardServiceImpl implements IPtCardService {
             }
         }
         PtCardVo vo = baseMapper.selectVoById(bo.getCardId());
-        kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.CARD, EventSenderEnum.BACKSTAGE.code(), vo);
+        //kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.CARD, EventSenderEnum.BACKSTAGE.code(), vo);
         return flag;
     }
 
@@ -190,7 +192,7 @@ public class PtCardServiceImpl implements IPtCardService {
         validEntityBeforeSave(update);
         int count = baseMapper.updateById(update);
         PtCardVo vo = baseMapper.selectVoById(bo.getCardId());
-        kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.CARD, EventSenderEnum.BACKSTAGE.code(), vo);
+        //kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.CARD, EventSenderEnum.BACKSTAGE.code(), vo);
         return count > 0;
     }
 
@@ -607,7 +609,13 @@ public class PtCardServiceImpl implements IPtCardService {
             .eq(PtCard::getStatus, CardStatusEnum.NORMAL.code().toString())
             .ge(PtCard::getLifespan, DateUtil.date())
             .orderByDesc(PtCard::getChangeTime);
-        return baseMapper.selectVoList(lqw);
+
+        List<PtCardVo> cardVos = baseMapper.selectVoList(lqw);
+        cardVos.forEach(p -> {
+            PtCardtypeVo cardTypeVo = cardTypeService.queryByCode(p.getCardType());
+            p.setCardTypeName(cardTypeVo == null ? "未知卡类" : cardTypeVo.getTypeName());
+        });
+        return cardVos;
     }
 
     @Override
@@ -615,4 +623,29 @@ public class PtCardServiceImpl implements IPtCardService {
         PtCard update = MapstructUtils.convert(bo, PtCard.class);
         return baseMapper.updateById(update) > 0;
     }
+
+    @Override
+    public Map<Long, String> getUserHasCard() {
+        LambdaQueryWrapper<PtCard> lqw = new LambdaQueryWrapper<>();
+        lqw.in(PtCard::getStatus, "1", "2", "7");
+        lqw.select(PtCard::getUserId, PtCard::getFactoryId);
+
+        List<PtCard> listVo = baseMapper.selectList(lqw);
+        // 按userId分组
+        return listVo.stream()
+            .collect(Collectors.groupingBy(
+                PtCard::getUserId,  // 按userId分组
+                Collectors.collectingAndThen(
+                    Collectors.toList(),
+                    cards -> {
+                        if (cards.size() > 1) {
+                            return "实体卡";
+                        } else {
+                            PtCard singleCard = cards.get(0);
+                            return (singleCard.getFactoryId() == 0L) ? "虚拟卡" : "实体卡";
+                        }
+                    }
+                )
+            ));
+    }
 }

+ 4 - 48
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/config/ArcFaceConfig.java

@@ -1,6 +1,9 @@
 package org.dromara.backstage.config;
 
 
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.context.annotation.Configuration;
 
@@ -9,9 +12,9 @@ import org.springframework.context.annotation.Configuration;
  * @date 2020/7/31 0031
  * @time 16:31
  */
+@Data
 @Configuration
 @ConfigurationProperties(prefix = "arcconfig.arcface-sdk")
-//@ConditionalOnProperty(value = { "arcConfig.arcface-sdk.enable" },matchIfMissing = false)
 public class ArcFaceConfig {
     //获取配置参数
     public String sdkLibPath="";
@@ -27,51 +30,4 @@ public class ArcFaceConfig {
     // 离线激活文件
     public String activeFile = "";
 
-    public String getSdkLibPath() {
-        return sdkLibPath;
-    }
-
-    public void setSdkLibPath(String sdkLibPath) {
-        this.sdkLibPath = sdkLibPath;
-    }
-
-    public String getAppId() {
-        return appId;
-    }
-
-    public void setAppId(String appId) {
-        this.appId = appId;
-    }
-
-    public String getSdkKey() {
-        return sdkKey;
-    }
-
-    public void setSdkKey(String sdkKey) {
-        this.sdkKey = sdkKey;
-    }
-
-    public Integer getDetectPooSize() {
-        return detectPooSize;
-    }
-
-    public void setDetectPooSize(Integer detectPooSize) {
-        this.detectPooSize = detectPooSize;
-    }
-
-    public Integer getComparePooSize() {
-        return comparePooSize;
-    }
-
-    public void setComparePooSize(Integer comparePooSize) {
-        this.comparePooSize = comparePooSize;
-    }
-
-    public String getActiveFile() {
-        return activeFile;
-    }
-
-    public void setActiveFile(String activeFile) {
-        this.activeFile = activeFile;
-    }
 }

+ 3 - 3
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/config/FaceEngineFactory.java

@@ -17,13 +17,13 @@ import org.dromara.common.core.exception.ServiceException;
 @Slf4j
 //@ConditionalOnProperty(value = { "arcConfig.arcface-sdk.enable" }, matchIfMissing = false)
 public class FaceEngineFactory extends BasePooledObjectFactory<FaceEngine> {
-	private String libPath;
+	private final String libPath;
 	private String appId;
 	private String sdkKey;
 	private String activeKey;
-	private EngineConfiguration engineConfiguration;
+	private final EngineConfiguration engineConfiguration;
 
-    private String activeFile;
+    private final String activeFile;
 
 	public FaceEngineFactory(String libPath, String appId, String sdkKey, String activeKey,
                              EngineConfiguration engineConfiguration, String activeFile) {

+ 0 - 6
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/consumption/controller/XfDiscountController.java

@@ -15,7 +15,6 @@ import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
-import org.dromara.common.message.kafka.aop.annotation.SyncDataToLocal;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
@@ -24,8 +23,6 @@ import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
-import static org.dromara.common.message.kafka.constant.MessageEventTypeConstants.*;
-
 /**
  * 折扣管理
  * 前端访问路由地址为:/consumption/xfDiscount
@@ -79,7 +76,6 @@ public class XfDiscountController extends BaseController {
     @SaCheckPermission("consumption:xfDiscount:add")
     @Log(title = "折扣管理", businessType = BusinessType.INSERT)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = xfDiscount_ADD, sender = XF_DISCOUNT_SENDER)
     @PostMapping()
     public R<Void> add(@Validated(AddGroup.class) @RequestBody XfDiscountBo bo) {
         return toAjax(xfDiscountService.insertByBo(bo));
@@ -91,7 +87,6 @@ public class XfDiscountController extends BaseController {
     @SaCheckPermission("consumption:xfDiscount:edit")
     @Log(title = "折扣管理", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = xfDiscount_EDIT, sender = XF_DISCOUNT_SENDER)
     @PutMapping()
     public R<Void> edit(@Validated(EditGroup.class) @RequestBody XfDiscountBo bo) {
         return toAjax(xfDiscountService.updateByBo(bo));
@@ -104,7 +99,6 @@ public class XfDiscountController extends BaseController {
      */
     @SaCheckPermission("consumption:xfDiscount:remove")
     @Log(title = "折扣管理", businessType = BusinessType.DELETE)
-    @SyncDataToLocal(eventType = xfDiscount_REMOVE, sender = XF_DISCOUNT_SENDER)
     @DeleteMapping("/{discountIds}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable Long[] discountIds) {

+ 0 - 5
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/consumption/controller/XfDiscounttermController.java

@@ -15,7 +15,6 @@ import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
-import org.dromara.common.message.kafka.aop.annotation.SyncDataToLocal;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
@@ -24,8 +23,6 @@ import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
-import static org.dromara.common.message.kafka.constant.MessageEventTypeConstants.*;
-
 /**
  * 折扣设备
  * 前端访问路由地址为:/consumption/xfDiscountterm
@@ -91,7 +88,6 @@ public class XfDiscounttermController extends BaseController {
     @SaCheckPermission("consumption:xfDiscountterm:add")
     @Log(title = "批量添加折扣设备", businessType = BusinessType.INSERT)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = DISCOUNT_TERM_ADD, sender = XF_DISCOUNT_SENDER)
     @PostMapping("/batchAdd")
     public R<Void> batchAdd(@RequestBody List<Long> ids) {
         return toAjax(xfDiscounttermService.insertByTermIds(ids));
@@ -115,7 +111,6 @@ public class XfDiscounttermController extends BaseController {
      */
     @SaCheckPermission("consumption:xfDiscountterm:remove")
     @Log(title = "折扣设备", businessType = BusinessType.DELETE)
-    @SyncDataToLocal(eventType = DISCOUNT_TERM_REMOVE, sender = XF_DISCOUNT_SENDER)
     @DeleteMapping("/{discountTermIds}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable Long[] discountTermIds) {

+ 0 - 6
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/consumption/controller/XfLimitedController.java

@@ -15,7 +15,6 @@ import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
-import org.dromara.common.message.kafka.aop.annotation.SyncDataToLocal;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
@@ -24,8 +23,6 @@ import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
-import static org.dromara.common.message.kafka.constant.MessageEventTypeConstants.*;
-
 /**
  * 限次管理
  * 前端访问路由地址为:/consumption/xfLimited
@@ -79,7 +76,6 @@ public class XfLimitedController extends BaseController {
     @SaCheckPermission("consumption:xfLimited:add")
     @Log(title = "限次管理", businessType = BusinessType.INSERT)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = xfLimited_ADD, sender = XF_LIMITED_SENDER)
     @PostMapping()
     public R<Void> add(@Validated(AddGroup.class) @RequestBody XfLimitedBo bo) {
         return toAjax(xfLimitedService.insertByBo(bo));
@@ -91,7 +87,6 @@ public class XfLimitedController extends BaseController {
     @SaCheckPermission("consumption:xfLimited:edit")
     @Log(title = "限次管理", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = xfLimited_EDIT, sender = XF_LIMITED_SENDER)
     @PutMapping()
     public R<Void> edit(@Validated(EditGroup.class) @RequestBody XfLimitedBo bo) {
         return toAjax(xfLimitedService.updateByBo(bo));
@@ -104,7 +99,6 @@ public class XfLimitedController extends BaseController {
      */
     @SaCheckPermission("consumption:xfLimited:remove")
     @Log(title = "限次管理", businessType = BusinessType.DELETE)
-    @SyncDataToLocal(eventType = xfLimited_REMOVE, sender = XF_LIMITED_SENDER)
     @DeleteMapping("/{limitedIds}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable Long[] limitedIds) {

+ 0 - 5
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/consumption/controller/XfLimitedtermController.java

@@ -15,7 +15,6 @@ import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
-import org.dromara.common.message.kafka.aop.annotation.SyncDataToLocal;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
@@ -24,8 +23,6 @@ import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
-import static org.dromara.common.message.kafka.constant.MessageEventTypeConstants.*;
-
 /**
  * 限次设备绑定
  * 前端访问路由地址为:/consumption/xfLimitedterm
@@ -91,7 +88,6 @@ public class XfLimitedtermController extends BaseController {
     @SaCheckPermission("consumption:xfDiscountterm:add")
     @Log(title = "批量添加限次设备", businessType = BusinessType.INSERT)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = LIMITED_TERM_ADD, sender = XF_LIMITED_SENDER)
     @PostMapping("/batchAdd")
     public R<Void> batchAdd(@RequestBody List<Long> ids) {
         return toAjax(xfLimitedtermService.insertByTermIds(ids));
@@ -115,7 +111,6 @@ public class XfLimitedtermController extends BaseController {
      */
     @SaCheckPermission("consumption:xfLimitedterm:remove")
     @Log(title = "限次设备绑定", businessType = BusinessType.DELETE)
-    @SyncDataToLocal(eventType = LIMITED_TERM_REMOVE, sender = XF_LIMITED_SENDER)
     @DeleteMapping("/{limitedTermIds}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable Long[] limitedTermIds) {

+ 0 - 6
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/consumption/controller/XfQuotaController.java

@@ -15,7 +15,6 @@ import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
-import org.dromara.common.message.kafka.aop.annotation.SyncDataToLocal;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
@@ -24,8 +23,6 @@ import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
-import static org.dromara.common.message.kafka.constant.MessageEventTypeConstants.*;
-
 /**
  * 限额管理
  * 前端访问路由地址为:/consumption/xfQuota
@@ -79,7 +76,6 @@ public class XfQuotaController extends BaseController {
     @SaCheckPermission("consumption:xfQuota:add")
     @Log(title = "限额管理", businessType = BusinessType.INSERT)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = xfQuota_ADD, sender = XF_QUOTA_SENDER)
     @PostMapping()
     public R<Void> add(@Validated(AddGroup.class) @RequestBody XfQuotaBo bo) {
         return toAjax(xfQuotaService.insertByBo(bo));
@@ -91,7 +87,6 @@ public class XfQuotaController extends BaseController {
     @SaCheckPermission("consumption:xfQuota:edit")
     @Log(title = "限额管理", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = xfQuota_EDIT, sender = XF_QUOTA_SENDER)
     @PutMapping()
     public R<Void> edit(@Validated(EditGroup.class) @RequestBody XfQuotaBo bo) {
         return toAjax(xfQuotaService.updateByBo(bo));
@@ -104,7 +99,6 @@ public class XfQuotaController extends BaseController {
      */
     @SaCheckPermission("consumption:xfQuota:remove")
     @Log(title = "限额管理", businessType = BusinessType.DELETE)
-    @SyncDataToLocal(eventType = xfQuota_REMOVE, sender = XF_QUOTA_SENDER)
     @DeleteMapping("/{quotaIds}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable Long[] quotaIds) {

+ 0 - 5
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/consumption/controller/XfQuotatermController.java

@@ -15,7 +15,6 @@ import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
-import org.dromara.common.message.kafka.aop.annotation.SyncDataToLocal;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
@@ -24,8 +23,6 @@ import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
-import static org.dromara.common.message.kafka.constant.MessageEventTypeConstants.*;
-
 /**
  * 限额设备绑定
  * 前端访问路由地址为:/consumption/xfQuotaterm
@@ -91,7 +88,6 @@ public class XfQuotatermController extends BaseController {
     @SaCheckPermission("consumption:xfDiscountterm:add")
     @Log(title = "批量添加限额设备", businessType = BusinessType.INSERT)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = QUOTA_TERM_ADD, sender = XF_QUOTA_SENDER)
     @PostMapping("/batchAdd")
     public R<Void> batchAdd(@RequestBody List<Long> ids) {
         return toAjax(xfQuotatermService.insertByTermIds(ids));
@@ -115,7 +111,6 @@ public class XfQuotatermController extends BaseController {
      */
     @SaCheckPermission("consumption:xfQuotaterm:remove")
     @Log(title = "限额设备绑定", businessType = BusinessType.DELETE)
-    @SyncDataToLocal(eventType = QUOTA_TERM_REMOVE, sender = XF_QUOTA_SENDER)
     @DeleteMapping("/{quotaTermIds}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable Long[] quotaTermIds) {

+ 0 - 7
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/consumption/controller/XfTermController.java

@@ -20,7 +20,6 @@ import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
-import org.dromara.common.message.kafka.aop.annotation.SyncDataToLocal;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
@@ -32,8 +31,6 @@ import org.springframework.web.multipart.MultipartFile;
 import java.util.ArrayList;
 import java.util.List;
 
-import static org.dromara.common.message.kafka.constant.MessageEventTypeConstants.*;
-
 /**
  * 消费设备
  * 前端访问路由地址为:/consumption/xfTerm
@@ -119,7 +116,6 @@ public class XfTermController extends BaseController {
     @SaCheckPermission("consumption:xfTerm:add")
     @Log(title = "消费设备", businessType = BusinessType.INSERT)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = xfTerm_ADD, sender = XF_TERM_SENDER)
     @PostMapping()
     public R<Void> add(@Validated(AddGroup.class) @RequestBody XfTermBo bo) {
         return toAjax(xfTermService.insertByBo(bo));
@@ -131,7 +127,6 @@ public class XfTermController extends BaseController {
     @SaCheckPermission("consumption:xfTerm:edit")
     @Log(title = "消费设备", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = xfTerm_EDIT, sender = XF_TERM_SENDER)
     @PutMapping()
     public R<Void> edit(@Validated(EditGroup.class) @RequestBody XfTermBo bo) {
         return toAjax(xfTermService.updateByBo(bo));
@@ -143,7 +138,6 @@ public class XfTermController extends BaseController {
     @SaCheckPermission("consumption:xfTerm:edit")
     @Log(title = "消费设备参数编辑", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = xfTerm_param_EDIT, sender = XF_TERM_SENDER)
     @PostMapping("/updateParam")
     public R<Void> editParam(@Validated @RequestBody XfTermParamBo bo) {
         return toAjax(xfTermService.updateParamByBo(bo));
@@ -156,7 +150,6 @@ public class XfTermController extends BaseController {
      */
     @SaCheckPermission("consumption:xfTerm:remove")
     @Log(title = "消费设备", businessType = BusinessType.DELETE)
-    @SyncDataToLocal(eventType = xfTerm_REMOVE, sender = XF_TERM_SENDER)
     @DeleteMapping("/{termIds}")
     public R<Void> remove(@NotEmpty(message = "主键不能为空")
                           @PathVariable Long[] termIds) {

+ 35 - 0
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/consumption/domain/convert/RemoteConvert.java

@@ -0,0 +1,35 @@
+package org.dromara.backstage.consumption.domain.convert;
+
+import org.dromara.backstage.api.domain.vo.RemoteLimitedVo;
+import org.dromara.backstage.consumption.domain.vo.XfLimitedVo;
+
+/**
+ * 类转换
+ * <p>
+ * [功能说明]
+ * ${1:在此简要描述文件核心功能}
+ *
+ * @author luoyibo
+ * @date 2025-07-10
+ * @since JDK17
+ */
+public class RemoteConvert {
+    public static RemoteLimitedVo convertLimited(XfLimitedVo arg0, RemoteLimitedVo arg1) {
+        if ( arg0 == null ) {
+            return arg1;
+        }
+
+        arg1.setLimitedId( arg0.getLimitedId() );
+        arg1.setCardType( arg0.getCardType() );
+        arg1.setCardTypeName( arg0.getCardTypeName() );
+        arg1.setDailyCount( arg0.getDailyCount() );
+        arg1.setOneCount( arg0.getOneCount() );
+        arg1.setTwoCount( arg0.getTwoCount() );
+        arg1.setThreeCount( arg0.getThreeCount() );
+        arg1.setFourCount( arg0.getFourCount() );
+        arg1.setRemark( arg0.getRemark() );
+        arg1.setStatus( arg0.getStatus() );
+
+        return arg1;
+    }
+}

+ 87 - 41
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/consumption/service/impl/XfConsumeDetailServiceImpl.java

@@ -1,4 +1,6 @@
 package org.dromara.backstage.consumption.service.impl;
+import java.math.BigDecimal;
+import java.util.Date;
 
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.http.HttpRequest;
@@ -10,8 +12,10 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import io.seata.common.util.CollectionUtils;
+import kotlin.reflect.jvm.internal.impl.load.java.JavaClassesTracker;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.annotation.DubboReference;
 import org.dromara.backstage.consumption.domain.XfConsumeDetail;
 import org.dromara.backstage.consumption.domain.bo.HandImportConsumeBo;
 import org.dromara.backstage.consumption.domain.bo.HandImportExcelConsumeBo;
@@ -21,13 +25,18 @@ import org.dromara.backstage.consumption.domain.vo.XfConsumeDetailCKBKVo;
 import org.dromara.backstage.consumption.domain.vo.XfConsumeDetailVo;
 import org.dromara.backstage.consumption.mapper.XfConsumeDetailMapper;
 import org.dromara.backstage.consumption.service.IXfConsumeDetailService;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.domain.model.ErrorInfo;
 import org.dromara.common.core.enums.ConsumeRecordTypeEnum;
 import org.dromara.common.core.enums.CreditTypeEnum;
+import org.dromara.common.core.enums.SystemUseTypeEnum;
 import org.dromara.common.core.utils.DateUtils;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.consume.api.RemoteConsumeService;
+import org.dromara.consume.api.domain.bo.RemoteConsumeBo;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
@@ -48,6 +57,9 @@ public class XfConsumeDetailServiceImpl implements IXfConsumeDetailService {
 
     private final XfConsumeDetailMapper baseMapper;
 
+    @DubboReference
+    private final RemoteConsumeService remoteConsumeService;
+
     // http://172.16.137.72/prod-api/consume
     @Value("${XF_CONSUME_URL}")
     private String XF_CONSUME_URL;
@@ -331,48 +343,82 @@ public class XfConsumeDetailServiceImpl implements IXfConsumeDetailService {
             bo.setPosRecordState("364");
             bo.setTermRecordID(String.valueOf(i));
 
-            // 调用消费接口
-            // http://172.16.137.72/prod-api/consume/v1/Consumes/ConsumeOriginal
-            // 使用hutool工具包的http请求工具类 发送post请求
-            HttpRequest httpRequest = HttpRequest.post(XF_CONSUME_URL+"/v1/Consumes/ConsumeOriginal")
-                .body(JSONUtil.toJsonStr(bo));
-            log.info("请求ConsumeOriginal开始:{}",httpRequest.toString());
-            HttpResponse result = httpRequest.execute();
-            log.info("请求ConsumeOriginal返回:{}", result.toString());
-            String resBody = result.body();
-            if(StringUtils.isBlank(resBody)){
-                bo.setResult("失败");
-                bo.setReason("ConsumeOriginal请求失败,返回为空");
-                continue;
-            }
-            JSONObject jsonObject = JSONUtil.parseObj(resBody);
-            String employeeStrID = jsonObject.getStr("employeeStrID");
-            if(StringUtils.isBlank(employeeStrID)){
-                bo.setResult("失败");
-                bo.setReason(resBody);
-                continue;
-            }
-            bo.setEmployeeName(jsonObject.getStr("employeeName"));
-
-            // http://172.16.137.72/prod-api/consume/v1/Consumes/Consume
-            HttpRequest httpRequestAgain = HttpRequest.post(XF_CONSUME_URL+"/v1/Consumes/Consume")
-                .body(resBody);
-            log.info("请求Consume开始:{}",httpRequestAgain.toString());
-            HttpResponse resultAgain = httpRequestAgain.execute();
-            log.info("请求Consume返回:{}", resultAgain.toString());
-            String resBodyAgain = resultAgain.body();
-            if(StringUtils.isBlank(resBodyAgain)){
-                bo.setResult("失败");
-                bo.setReason("Consume请求失败,返回为空");
-                continue;
-            }
-            JSONObject jsonObjectAgain = JSONUtil.parseObj(resBodyAgain);
-            String employeeStrIDAgain = jsonObjectAgain.getStr("employeeStrID");
-            if(StringUtils.isBlank(employeeStrIDAgain)){
-                bo.setResult("失败");
-                bo.setReason(resBody);
-                continue;
+            RemoteConsumeBo remoteConsumeBo = new RemoteConsumeBo();
+            remoteConsumeBo.setUserId(0L);
+            remoteConsumeBo.setUserNo(0L);
+            remoteConsumeBo.setUserNumb(handImportExcelConsumeBo.getEmployeeStrID());
+            remoteConsumeBo.setRealName("");
+            remoteConsumeBo.setCardNo(0L);
+            remoteConsumeBo.setFactoryId(0L);
+            remoteConsumeBo.setBagType("");
+            remoteConsumeBo.setConsumeMoney(new BigDecimal(handImportExcelConsumeBo.getConsumeValue()));
+            remoteConsumeBo.setBalance(new BigDecimal("0"));
+            remoteConsumeBo.setConsumeDate(DateUtils.dateTime("yyyy-MM-dd HH:mm:ss",handImportExcelConsumeBo.getConsumeDate()));
+            remoteConsumeBo.setTermNo(Long.valueOf(handImportExcelConsumeBo.getTermID()));
+            remoteConsumeBo.setTermMac("");
+            remoteConsumeBo.setTermRecordId((long) i);
+            remoteConsumeBo.setMealType(0L);
+            // remoteConsumeBo.setConsumeId("");
+            // remoteConsumeBo.setRecordId(0L);
+            remoteConsumeBo.setRecordStatus(364L);
+            remoteConsumeBo.setStatusFlag(3);
+            remoteConsumeBo.setUseType(SystemUseTypeEnum.CONSUME.code());
+            // remoteConsumeBo.setOperatorId(0L);
+            // remoteConsumeBo.setOperatorName("");
+            remoteConsumeBo.setCreditType(CreditTypeEnum.HAND_CONSUME.code());
+            remoteConsumeBo.setTenantId("25");
+
+
+            R<ErrorInfo> result = remoteConsumeService.dealKafkaConsumeData(remoteConsumeBo);
+            if (R.isError(result)) {
+                    bo.setResult("失败");
+                    bo.setReason(JSONUtil.toJsonStr(result.getData()));
+                    continue;
             }
+            // 调用消费接口
+            // // http://172.16.137.72/prod-api/consume/v1/Consumes/ConsumeOriginal
+            // // 使用hutool工具包的http请求工具类 发送post请求
+            // HttpRequest httpRequest = HttpRequest.post(XF_CONSUME_URL+"/v1/Consumes/ConsumeOriginal")
+            //     .body(JSONUtil.toJsonStr(bo));
+            // log.info("请求ConsumeOriginal开始:{}",httpRequest.toString());
+            // HttpResponse result = httpRequest.execute();
+            // log.info("请求ConsumeOriginal返回:{}", result.toString());
+            // String resBody = result.body();
+            //
+            //
+            // if(StringUtils.isBlank(resBody)){
+            //     bo.setResult("失败");
+            //     bo.setReason("ConsumeOriginal请求失败,返回为空");
+            //     continue;
+            // }
+            // JSONObject jsonObject = JSONUtil.parseObj(resBody);
+            // String employeeStrID = jsonObject.getStr("employeeStrID");
+            // if(StringUtils.isBlank(employeeStrID)){
+            //     bo.setResult("失败");
+            //     bo.setReason(resBody);
+            //     continue;
+            // }
+            // bo.setEmployeeName(jsonObject.getStr("employeeName"));
+            //
+            // // http://172.16.137.72/prod-api/consume/v1/Consumes/Consume
+            // HttpRequest httpRequestAgain = HttpRequest.post(XF_CONSUME_URL+"/v1/Consumes/Consume")
+            //     .body(resBody);
+            // log.info("请求Consume开始:{}",httpRequestAgain.toString());
+            // HttpResponse resultAgain = httpRequestAgain.execute();
+            // log.info("请求Consume返回:{}", resultAgain.toString());
+            // String resBodyAgain = resultAgain.body();
+            // if(StringUtils.isBlank(resBodyAgain)){
+            //     bo.setResult("失败");
+            //     bo.setReason("Consume请求失败,返回为空");
+            //     continue;
+            // }
+            // JSONObject jsonObjectAgain = JSONUtil.parseObj(resBodyAgain);
+            // String employeeStrIDAgain = jsonObjectAgain.getStr("employeeStrID");
+            // if(StringUtils.isBlank(employeeStrIDAgain)){
+            //     bo.setResult("失败");
+            //     bo.setReason(resBody);
+            //     continue;
+            // }
             bo.setResult("成功");
 
         }

+ 0 - 2
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/controller/lock/LockController.java

@@ -7,8 +7,6 @@ import org.dromara.backstage.domain.bo.lock.RoomCardBo;
 import org.dromara.backstage.task.ScheduledTasks;
 import org.dromara.common.core.api.ResponseResult;
 import org.dromara.common.core.api.ReturnResult;
-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.enums.ResultCodeEnum;
 import org.springframework.web.bind.annotation.*;

+ 1 - 1
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/domain/vo/card/PtCardVo.java

@@ -152,7 +152,7 @@ public class PtCardVo implements Serializable {
     /**
      * 卡片类型名称
      */
-    @Translation(type = TransConstant.CARD_TYPE_ID_TO_NAME, mapper = "cardType")
+    // @Translation(type = TransConstant.CARD_TYPE_ID_TO_NAME, mapper = "cardType")
     private String cardTypeName;
 
     /**

+ 1 - 10
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/controller/PtBagController.java

@@ -22,7 +22,6 @@ import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
-import org.dromara.common.message.kafka.aop.annotation.SyncDataToLocal;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.satoken.utils.LoginHelper;
@@ -36,8 +35,6 @@ import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.List;
 
-import static org.dromara.common.message.kafka.constant.MessageEventTypeConstants.*;
-
 /**
  * 账户钱包
  * 前端访问路由地址为:/payment/ptBag
@@ -59,7 +56,7 @@ public class PtBagController extends BaseController {
     //根据userId查询账户余额
 //    @SaCheckPermission("payment:ptBag:query")
     @GetMapping("/selectSelfAccountBalance")
-    public R<java.lang.String> selectAccountBalanceByIds() {
+    public R<String> selectAccountBalanceByIds() {
         java.lang.String userIds = java.lang.String.valueOf(LoginHelper.getUserId());
         return R.ok("", ptBagService.selectAccountBalanceByIds(userIds));
     }
@@ -137,7 +134,6 @@ public class PtBagController extends BaseController {
     @SaCheckPermission("payment:ptBag:edit")
     @Log(title = "账户钱包", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = ptCard_CHARGE_EDIT, sender = CARD_CENTER_SENDER)
     @PostMapping("/recharge")
     public R<PurseInOutBo> recharge(@Validated(EditGroup.class) @RequestBody PurseInOutBo bo) {
         return payOrderBusiness.recharge(bo);
@@ -149,7 +145,6 @@ public class PtBagController extends BaseController {
     @SaCheckPermission("payment:ptBag:edit")
     @Log(title = "账户钱包", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = ptCard_refund_EDIT, sender = CARD_CENTER_SENDER)
     @PostMapping("/refund")
     public R<PurseInOutBo> refund(@Validated(EditGroup.class) @RequestBody PurseInOutBo bo) {
         return payOrderBusiness.refund(bo);
@@ -161,7 +156,6 @@ public class PtBagController extends BaseController {
     @SaCheckPermission("cardOperation:ptCard:edit")
     @Log(title = "批量账户充值", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = ptCard_multicharge_EDIT, sender = CARD_CENTER_SENDER)
     @PostMapping("/recharge/{userIds}")
     public R<java.lang.String> batchRecharge(@Validated(EditGroup.class) @RequestBody PurseInOutBo bo, @PathVariable Long[] userIds) {
         // TODO 2024-10-30 09:49:36 luoyibo 这里最好是提前处理下工作站信息
@@ -196,7 +190,6 @@ public class PtBagController extends BaseController {
     @SaCheckPermission("cardOperation:ptCard:edit")
     @Log(title = "账户批量退款", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = ptCard_multirefund_EDIT, sender = CARD_CENTER_SENDER)
     @PostMapping("/refund/{userIds}")
     public R<Void> batchRefund(@Validated(EditGroup.class) @RequestBody PurseInOutBo bo, @PathVariable Long[] userIds) {
         List<java.lang.String> resultList = new ArrayList<>();
@@ -230,7 +223,6 @@ public class PtBagController extends BaseController {
     @SaCheckPermission("cardOperation:ptCard:edit")
     @Log(title = "账户批量设置余额", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = ptCard_multiSet_EDIT, sender = CARD_CENTER_SENDER)
     @PostMapping("/recover/{userIds}")
     public R<Void> batchRecover(@Validated(EditGroup.class) @RequestBody PtBagBo bo, @PathVariable Long[] userIds) {
         bo.setUseType(SystemUseTypeEnum.CONSUME.code().toString());
@@ -249,7 +241,6 @@ public class PtBagController extends BaseController {
     @SaCheckPermission("payment:ptBag:edit")
     @Log(title = "账户钱包", businessType = BusinessType.UPDATE)
     @RepeatSubmit()
-    @SyncDataToLocal(eventType = ptCard_supply_EDIT, sender = CARD_CENTER_SENDER)
     @PostMapping("/err-fill")
     public R<PtBagVo> errFill(@Validated(EditGroup.class) @RequestBody ConsumptionBo bo) {
         bo.setUseType(SystemUseTypeEnum.CONSUME.code().toString());

+ 0 - 8
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/controller/PtUserAccountController.java

@@ -33,7 +33,6 @@ import org.dromara.common.excel.utils.ExcelUtil;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
 import org.dromara.common.log.annotation.Log;
 import org.dromara.common.log.enums.BusinessType;
-import org.dromara.common.message.kafka.aop.annotation.SyncDataToLocal;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
@@ -49,8 +48,6 @@ import java.text.SimpleDateFormat;
 import java.util.Arrays;
 import java.util.List;
 
-import static org.dromara.common.message.kafka.constant.MessageEventTypeConstants.*;
-
 /**
  * 一卡通账户
  * 前端访问路由地址为:/payment/ptUserAccount
@@ -272,7 +269,6 @@ public class PtUserAccountController extends BaseController {
      */
     @SaCheckPermission("payment:ptUserAccount:edit")
     @Log(title = "支付账户管理", businessType = BusinessType.UPDATE)
-    @SyncDataToLocal(eventType = ptUserAccount_FREEZE_EDIT, sender = USER_ACCOUNT_SENDER)
     @PutMapping("/{freezeStatus}/{userIds}")
     public R<Void> batchChangeFreezeStatus(@PathVariable String freezeStatus, @PathVariable Long[] userIds) {
         return toAjax(ptUserAccountService.updateFreezeStatus(userIds, freezeStatus));
@@ -286,7 +282,6 @@ public class PtUserAccountController extends BaseController {
      */
     @SaCheckPermission("payment:ptUserAccount:edit")
     @Log(title = "支付账户管理", businessType = BusinessType.UPDATE)
-    @SyncDataToLocal(eventType = ptUserAccount_RESETTIME_EDIT, sender = USER_ACCOUNT_SENDER)
     @PutMapping("/resetLifespan/{lifespan}/{userIds}")
     public R<Void> batchResetLifespan(@PathVariable String lifespan, @PathVariable Long[] userIds) {
         return toAjax(ptUserAccountService.resetLifespan(userIds, lifespan));
@@ -300,7 +295,6 @@ public class PtUserAccountController extends BaseController {
      */
     @SaCheckPermission("payment:ptUserAccount:edit")
     @Log(title = "支付账户管理", businessType = BusinessType.UPDATE)
-    @SyncDataToLocal(eventType = ptUserAccount_RESETTYPE_EDIT, sender = USER_ACCOUNT_SENDER)
     @PutMapping("/resetCardType/{cardType}/{userIds}")
     public R<Void> batchResetCardType(@PathVariable String cardType, @PathVariable Long[] userIds) {
         return toAjax(ptUserAccountService.resetCardType(userIds, cardType));
@@ -326,7 +320,6 @@ public class PtUserAccountController extends BaseController {
      */
     @SaCheckPermission("payment:ptUserAccount:edit")
     @Log(title = "支付账户管理", businessType = BusinessType.UPDATE)
-    @SyncDataToLocal(eventType = ptUserAccount_OPEN_EDIT, sender = USER_ACCOUNT_SENDER)
     @PutMapping("/open/{userIds}")
     public R<Void> batchOpenAccount(@PathVariable Long[] userIds) {
         return toAjax(ptUserAccountService.openAccount(userIds));
@@ -339,7 +332,6 @@ public class PtUserAccountController extends BaseController {
      */
     @SaCheckPermission("payment:ptUserAccount:edit")
     @Log(title = "支付账户管理", businessType = BusinessType.UPDATE)
-    @SyncDataToLocal(eventType = ptUserAccount_CLOSE_EDIT, sender = USER_ACCOUNT_SENDER)
     @PutMapping("/close/{userIds}")
     public R<Void> batchCloseAccount(@PathVariable Long[] userIds) {
         if (ptUserAccountService.closeAccount(userIds)) {

+ 4 - 4
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/domain/vo/PtUserAccountVo.java

@@ -216,25 +216,25 @@ public class PtUserAccountVo implements Serializable {
     /**
      * 部门名
      */
-    @Translation(type = TransConstant.DEPT_ID_TO_NAME, mapper = "deptId")
+    // @Translation(type = TransConstant.DEPT_ID_TO_NAME, mapper = "deptId")
     private String deptName;
 
     /**
      * 卡类名
      */
-    @Translation(type = TransConstant.CARD_TYPE_ID_TO_NAME, mapper = "cardType")
+    // @Translation(type = TransConstant.CARD_TYPE_ID_TO_NAME, mapper = "cardType")
     private String cardTypeName;
 
     /**
      * 账户余额
      */
-    @Translation(type = TransConstant.USER_ID_TO_BALANCE, mapper = "userId")
+    // @Translation(type = TransConstant.USER_ID_TO_BALANCE, mapper = "userId")
     private String accountBalance;
 
     /**
      * 账户发卡信息
      */
-    @Translation(type = TransConstant.USER_ID_TO_CARD_INFO, mapper = "userId")
+    // @Translation(type = TransConstant.USER_ID_TO_CARD_INFO, mapper = "userId")
     private String accountCard;
 
     /**

+ 5 - 0
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/service/IPtBagService.java

@@ -9,6 +9,7 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
 import java.math.BigDecimal;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 账户钱包Service接口
@@ -106,4 +107,8 @@ public interface IPtBagService {
      * @return 账户钱包
      */
     PtBagVo queryByUserBagCode(Long userId,String bagCode);
+
+    Map<Long, BigDecimal> queryUserBalance();
+
+    BigDecimal queryBalanceByUserId(Long userId);
 }

+ 30 - 0
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/service/impl/PtBagServiceImpl.java

@@ -281,6 +281,36 @@ public class PtBagServiceImpl implements IPtBagService {
         return entity;
     }
 
+    @Override
+    public Map<Long, BigDecimal> queryUserBalance() {
+        LambdaQueryWrapper<PtBag> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(PtBag::getBagCode, Arrays.asList("1", "3"));
+        queryWrapper.select(PtBag::getBalance,PtBag::getUserId);
+        List<PtBagVo> listVo = baseMapper.selectVoList(queryWrapper);
+
+        Map<Long, BigDecimal> sumMap = new HashMap<>();
+        for (PtBagVo vo : listVo) {
+            Long userId = vo.getUserId();
+            BigDecimal amount = vo.getBalance();
+            sumMap.merge(userId, amount, BigDecimal::add);
+        }
+        return sumMap;
+    }
+
+    @Override
+    public BigDecimal queryBalanceByUserId(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::getUserId);
+        List<PtBagVo> listVo = baseMapper.selectVoList(queryWrapper);
+        BigDecimal amount = BigDecimal.ZERO;
+        for (PtBagVo ptBagVo : listVo) {
+            amount = amount.add(ptBagVo.getBalance());
+        }
+        return amount;
+    }
+
     /**
      * 组装充值钱包数据
      * 1.设置充值后余额=账户原余额+充值金额

+ 37 - 26
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/payment/service/impl/PtUserAccountServiceImpl.java

@@ -1,10 +1,7 @@
 package org.dromara.backstage.payment.service.impl;
 
 import cn.hutool.core.bean.BeanUtil;
-import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollectionUtil;
-import cn.hutool.core.date.DateTime;
-import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.lang.UUID;
 import cn.hutool.core.util.ObjUtil;
@@ -24,7 +21,6 @@ import org.dromara.backstage.cardCenter.domain.bo.PtCardBo;
 import org.dromara.backstage.cardCenter.service.IPtCardService;
 import org.dromara.backstage.domain.vo.card.PtCardVo;
 import org.dromara.backstage.domain.vo.yc.YcTraineeVo;
-import org.dromara.backstage.mq.PushKafkaData;
 import org.dromara.backstage.payment.domain.PtUserAccount;
 import org.dromara.backstage.payment.domain.PtUserAccount4SelectVo;
 import org.dromara.backstage.payment.domain.bo.PtUserAccountBo;
@@ -43,9 +39,6 @@ import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.core.utils.file.FileUtils;
 import org.dromara.common.core.utils.file.MimeTypeUtils;
 import org.dromara.common.encrypt.interceptor.MybatisEncryptInterceptor;
-import org.dromara.common.message.kafka.constant.EventTypeConstants;
-import org.dromara.common.message.kafka.constant.KafkaTopicConstants;
-import org.dromara.common.message.kafka.enums.EventSenderEnum;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.redis.utils.RedisUtils;
@@ -80,7 +73,7 @@ public class PtUserAccountServiceImpl implements IPtUserAccountService {
     private final IPtCardService ptCardService;
     private final IPtBagService bagService;
     private final IPtCardtypeService cardTypeService;
-    private final PushKafkaData kafkaNormalProducer;
+    //private final PushKafkaData kafkaNormalProducer;
     private final DictService dictService;
 
     @DubboReference
@@ -125,6 +118,9 @@ public class PtUserAccountServiceImpl implements IPtUserAccountService {
             .orderByAsc(PtUserAccount::getRealName);
         //Page<PtUserAccountVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
         Page<PtUserAccountVo> result = baseMapper.customSelectVoPage(pageQuery.build(), lqw);
+        List<PtUserAccountVo> list = result.getRecords();
+        addOtherInfoForList(list);
+        result.setRecords(list);
         return TableDataInfo.build(result);
     }
 
@@ -140,6 +136,7 @@ public class PtUserAccountServiceImpl implements IPtUserAccountService {
 //        }
 //        wrapper.orderByAsc("u.dept_id").orderByAsc("u.create_time");
         Page<PtUserAccount4SelectVo> result = baseMapper.customPageList(pageQuery.build(), bo);
+
         return TableDataInfo.build(result);
     }
 
@@ -153,20 +150,9 @@ public class PtUserAccountServiceImpl implements IPtUserAccountService {
     public List<PtUserAccountVo> queryList(PtUserAccountBo bo) {
         LambdaQueryWrapper<PtUserAccount> lqw = buildQueryWrapper(bo);
         List<PtUserAccountVo> accountVoList = baseMapper.selectVoList(lqw);
-        List<RemoteDeptVo> deptVoList = remoteDeptService.selectDeptList();
-        PtCardtypeBo cardTypeBo = new PtCardtypeBo();
-        cardTypeBo.setStatus("0");
-
-        List<PtCardtypeVo> cardTypeVos = cardTypeService.queryList(cardTypeBo);
-        accountVoList.forEach(p -> {
-            String deptName = deptVoList.parallelStream().filter(k -> k.getDeptId().equals(p.getDeptId()))
-                .findFirst().map(RemoteDeptVo::getDeptName).orElse("未知部门");
-            p.setDeptName(deptName);
-
-            String cardTypeNme = cardTypeVos.parallelStream().filter(k -> k.getCode().equals(p.getCardType()))
-                .findFirst().map(PtCardtypeVo::getTypeName).orElse("未知卡类");
-            p.setCardTypeName(cardTypeNme);
-        });
+        if (CollectionUtil.isNotEmpty(accountVoList)) {
+            addOtherInfoForList(accountVoList);
+        }
         return accountVoList;
     }
 
@@ -214,6 +200,7 @@ public class PtUserAccountServiceImpl implements IPtUserAccountService {
         lqw.eq(StringUtils.isNotBlank(bo.getUserState()), PtUserAccount::getUserState, bo.getUserState());
         lqw.eq(StringUtils.isNotBlank(bo.getStatus()), PtUserAccount::getStatus, bo.getStatus());
         // lqw.eq(StringUtils.isNotBlank(bo.getUserType()), PtUserAccount::getUserType, bo.getUserType());
+        lqw.isNotNull(PtUserAccount::getLifespan);
         return lqw;
     }
 
@@ -257,7 +244,7 @@ public class PtUserAccountServiceImpl implements IPtUserAccountService {
             bagService.initAccountBag(bo.getUserId());
         }
         PtUserAccountVo vo = baseMapper.selectVoById(bo.getUserId());
-        kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.ACCOUNT, EventSenderEnum.BACKSTAGE.code(), vo);
+        //kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.ACCOUNT, EventSenderEnum.BACKSTAGE.code(), vo);
         return flag;
     }
 
@@ -273,7 +260,7 @@ public class PtUserAccountServiceImpl implements IPtUserAccountService {
         validEntityBeforeSave(update);
         int count = baseMapper.updateById(update);
         PtUserAccountVo vo = baseMapper.selectVoById(bo.getUserId());
-        kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.ACCOUNT, EventSenderEnum.BACKSTAGE.code(), vo);
+        //kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.ACCOUNT, EventSenderEnum.BACKSTAGE.code(), vo);
         return count > 0;
     }
 
@@ -690,7 +677,7 @@ public class PtUserAccountServiceImpl implements IPtUserAccountService {
             .between(PtUserAccount::getUpdateTime, startDate, date);
         List<PtUserAccountVo> userAccountVos = baseMapper.selectVoList(between);
         List<PtUserAccountVo> updatePhotos = userAccountVos.stream().filter(e -> StringUtils.isNotBlank(e.getPhoto())).toList();
-        updatePhotos.forEach(e -> e.setFacePicUrl(photoPrefix+e.getPhoto()));
+        updatePhotos.forEach(e -> e.setFacePicUrl(photoPrefix + e.getPhoto()));
 
         List<PtUserAccountVo> rs = new ArrayList<>(updatePhotos);
 
@@ -698,7 +685,7 @@ public class PtUserAccountServiceImpl implements IPtUserAccountService {
         List<PtCardVo> cardVos = ptCardService.getList(startDate, date);
         List<Long> userIds = cardVos.stream().map(PtCardVo::getUserId).toList();
         Map<Long, PtCardVo> collect = cardVos.stream().collect(Collectors.toMap(PtCardVo::getUserId, e -> e));
-        if(CollectionUtil.isNotEmpty(userIds)){
+        if (CollectionUtil.isNotEmpty(userIds)) {
             LambdaQueryWrapper<PtUserAccount> in = Wrappers.lambdaQuery(PtUserAccount.class).in(PtUserAccount::getUserId, userIds);
             List<PtUserAccountVo> userAccountVos1 = baseMapper.selectVoList(in);
             userAccountVos1.forEach(e -> e.setCard(collect.get(e.getUserId())));
@@ -710,5 +697,29 @@ public class PtUserAccountServiceImpl implements IPtUserAccountService {
         return rs;
     }
 
+    private void addOtherInfoForList(List<PtUserAccountVo> list){
+        // 附加部门信息
+        List<RemoteDeptVo> deptVoList = remoteDeptService.selectDeptList();
+        // 附加余额信息
+        Map<Long, BigDecimal> accountBalance = bagService.queryUserBalance();
+        // 附加发卡信息
+        Map<Long, String> accountCards = ptCardService.getUserHasCard();
+
+        list.forEach(p -> {
+            addOtherInfo(p, deptVoList, accountBalance, accountCards);
+        });
+    }
+   private void addOtherInfo(PtUserAccountVo vo,List<RemoteDeptVo> deptVoList,Map<Long, BigDecimal> accountBalance,Map<Long, String> accountCards){
+       String deptName = deptVoList.parallelStream().filter(k -> k.getDeptId().equals(vo.getDeptId()))
+           .findFirst().map(RemoteDeptVo::getDeptName).orElse("未知部门");
+       vo.setDeptName(deptName);
+
+       PtCardtypeVo cardTypeVo = cardTypeService.queryByCode(vo.getCardType());
+       vo.setCardTypeName(cardTypeVo == null ? "未知卡类" : cardTypeVo.getTypeName());
+
+       BigDecimal balance = accountBalance.getOrDefault(vo.getUserId(),BigDecimal.ZERO);
+       vo.setAccountBalance(balance.toString());
 
+       vo.setAccountCard(accountCards.getOrDefault(vo.getUserId(), "未发卡"));
+   }
 }

+ 12 - 3
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/task/InitRunner.java

@@ -2,6 +2,9 @@ package org.dromara.backstage.task;
 
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.dromara.backstage.business.InitServiceBusiness;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
 import org.springframework.boot.CommandLineRunner;
 import org.springframework.stereotype.Component;
 
@@ -18,9 +21,15 @@ import org.springframework.stereotype.Component;
 @RequiredArgsConstructor
 @Component
 @Slf4j
-public class InitRunner implements CommandLineRunner {
-    @Override
-    public void run(String... args) throws Exception {
+public class InitRunner implements ApplicationRunner {
+    private final InitServiceBusiness initServiceBusiness;
 
+    @Override
+    public void run(ApplicationArguments args) throws Exception {
+        initServiceBusiness.initGlobalData();
+        initServiceBusiness.initMealTypeInfo();
+        initServiceBusiness.initDiscountAndOther();
+        initServiceBusiness.initUserCard();
+        initServiceBusiness.initUserAccount();
     }
 }

+ 0 - 1
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/wx/contorller/WxController.java

@@ -26,7 +26,6 @@ import java.util.Map;
 @Validated
 @RequiredArgsConstructor
 @RestController
-//@ResponseResult
 @RequestMapping("/wx")
 public class WxController extends BaseController {
     private final IWxService wxService;

+ 3 - 0
ruoyi-modules/ruoyi-backstage/src/main/java/org/dromara/backstage/wx/service/impl/WxServiceImpl.java

@@ -18,6 +18,7 @@ import org.dromara.backstage.cardCenter.mapper.PtCardMapper;
 import org.dromara.backstage.consumption.mapper.XfCreditAccountMapper;
 import org.dromara.backstage.payment.domain.vo.PtUserAccountVo;
 import org.dromara.backstage.payment.mapper.PtUserAccountMapper;
+import org.dromara.backstage.payment.service.IPtBagService;
 import org.dromara.backstage.wx.domain.vo.WxCreditAccountVo;
 import org.dromara.backstage.wx.service.IWxService;
 import org.dromara.common.core.config.DefaultConfig;
@@ -50,6 +51,7 @@ public class WxServiceImpl implements IWxService {
     private final RemoteDictService dictService;
     private final DefaultConfig defaultConfig;
     private final UserFaceBusiness userFaceBusiness;
+    private final IPtBagService bagService;
 
     @Value("${dzbp.sync-img.url}/")     // 电子班牌照片推送接口
     private String syncImgToDzbpUrl;
@@ -119,6 +121,7 @@ public class WxServiceImpl implements IWxService {
                                               Collectors.toMap(RemoteDictDataVo::getDictValue, RemoteDictDataVo::getDictLabel));
         PtUserAccountVo vo = accountMapper.selectVoById(userId);
         vo.setAccountStatus(dictMap.getOrDefault(vo.getAccountStatus(), ""));
+        vo.setAccountBalance(bagService.queryBalanceByUserId(vo.getUserId()).toString());
         // 根据userId查询卡片信息
         PtCard card = cardMapper.selectOne(new LambdaUpdateWrapper<PtCard>()
                                                .eq(PtCard::getUserId, userId)

+ 0 - 1
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java

@@ -153,7 +153,6 @@ public class SysUserController extends BaseController {
      */
     @SaCheckPermission("system:user:add")
     @Log(title = "用户管理", businessType = BusinessType.INSERT)
-    @SyncDataToLocal(eventType = MessageEventTypeConstants.user_ADD,sender = MessageEventTypeConstants.USER_ACCOUNT_SENDER)
     @PostMapping
     public R<Void> add(@Validated @RequestBody SysUserBo user) {
         deptService.checkDeptDataScope(user.getDeptId());

+ 3 - 3
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDeptServiceImpl.java

@@ -296,7 +296,7 @@ public class SysDeptServiceImpl implements ISysDeptService {
         int count = this.insertDept(bo);
         if (count > 0) {
             SysDeptVo vo = baseMapper.selectVoById(bo.getDeptId());
-            kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.DEPT, EventSenderEnum.SYSTEM.code(), vo);
+            // kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.DEPT, EventSenderEnum.SYSTEM.code(), vo);
             return vo;
         }
         return null;
@@ -331,8 +331,8 @@ public class SysDeptServiceImpl implements ISysDeptService {
             updateParentDeptStatusNormal(dept);
         }
         // sendCloudConsume(baseMapper.selectVoById(dept.getDeptId()));
-        SysDeptVo vo = baseMapper.selectVoById(bo.getDeptId());
-        kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.DEPT, EventSenderEnum.SYSTEM.code(), vo);
+        // SysDeptVo vo = baseMapper.selectVoById(bo.getDeptId());
+        // kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.DEPT, EventSenderEnum.SYSTEM.code(), vo);
         return result;
     }
 

+ 6 - 6
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java

@@ -395,10 +395,10 @@ public class SysUserServiceImpl implements ISysUserService {
                     user.setUserNo(remoteUserAccountBo.getUserNo());
                 }
             }
-            SysUserVo vo = baseMapper.selectVoById(user.getUserId());
-            vo.setUserNo(user.getUserNo());
-            vo.setCardNo(user.getCardNo());
-            kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.USER, EventSenderEnum.SYSTEM.code(), vo);
+            // SysUserVo vo = baseMapper.selectVoById(user.getUserId());
+            // vo.setUserNo(user.getUserNo());
+            // vo.setCardNo(user.getCardNo());
+            // kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.USER, EventSenderEnum.SYSTEM.code(), vo);
             return rows;
         } catch (Exception e) {
             log.error(e.getMessage(), e);
@@ -441,8 +441,8 @@ public class SysUserServiceImpl implements ISysUserService {
             int flag = baseMapper.updateById(sysUser);
             // 更新对应的一卡通账户信息
             remoteUserAccountService.updateByBo(BeanUtil.copyProperties(user, RemoteUserAccountBo.class));
-            SysUserVo vo = baseMapper.selectVoById(user.getUserId());
-            kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.USER, EventSenderEnum.SYSTEM.code(), vo);
+            // SysUserVo vo = baseMapper.selectVoById(user.getUserId());
+            // kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.SYNC_DATA_TOPIC, EventTypeConstants.USER, EventSenderEnum.SYSTEM.code(), vo);
             return flag;
         } catch (Exception e) {
             log.error(e.getMessage(), e);

+ 1 - 0
ruoyi-server/pom.xml

@@ -12,6 +12,7 @@
         <module>ruoyi-server-mqdata</module>
         <module>ruoyi-server-consume</module>
         <module>ruoyi-server-hik</module>
+        <module>ruoyi-server-face</module>
     </modules>
     <modelVersion>4.0.0</modelVersion>
 

+ 2 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/RuoYiServerConsumeApplication.java

@@ -6,6 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
 
 /**
  * 系统模块
@@ -15,6 +16,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
 @EnableDubbo
 @EnableAsync
 @EnableScheduling
+@EnableTransactionManagement
 @SpringBootApplication
 public class RuoYiServerConsumeApplication {
     public static void main(String[] args) {

+ 14 - 15
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/BaseBusiness.java

@@ -79,7 +79,7 @@ public class BaseBusiness {
     private final DefaultConfig defaultConfig;
     private final SyncRemoteSendMessageRecordService syncRemoteSendMessageRecordService;
     private final IXfCardLimitedService cardLimitedService;
-    private final ThreadPoolTaskExecutor taskExecutor;
+    private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
     private final CardCacheManager cardCacheManager;
 
     @DubboReference
@@ -102,8 +102,8 @@ public class BaseBusiness {
                                             XfConsumeDetailOriginalVo originalVo) {
         String originalId = consumeBo.getOriginalId();
         if (ObjectUtil.isEmpty(originalId)) {
-            originalId = RecordIdUtils.getRecordId(consumeBo.getConsumeDate(), consumeBo.getTermNo().shortValue(),
-                consumeBo.getTermRecordId().shortValue(),
+            originalId = RecordIdUtils.getRecordId(consumeBo.getConsumeDate(), consumeBo.getTermNo().intValue(),
+                consumeBo.getTermRecordId().intValue(),
                 accountVo.getUserNo().intValue(), 0);
         }
 
@@ -271,11 +271,11 @@ public class BaseBusiness {
     @Transactional(rollbackFor = ConsumeException.class)
     public R<ErrorInfo> postConsumeRecord(ConsumptionBo bo, RemoteUserAccountVo userAccountVo, RemoteCardVo cardVo,
                                           List<PtBagVo> bagVos, XfTermVo termVo, RemoteMealTypeVo mealTypeVo, String remark) {
-        XfConsumeDetailVo consumeDetailVo = consumeDetailService.queryVoByOriginalId(bo.getOriginalId());
-        if (ObjectUtil.isNotEmpty(consumeDetailVo)) {
-            // 认为是重复上传,不再写入明细
-            return R.ok();
-        }
+        // XfConsumeDetailVo consumeDetailVo = consumeDetailService.queryVoByOriginalId(bo.getOriginalId());
+        // if (ObjectUtil.isNotEmpty(consumeDetailVo)) {
+        //     // 认为是重复上传,不再写入明细
+        //     return R.ok();
+        // }
         List<XfConsumeDetailBo> detailBos = new ArrayList<>();
         for (PtBagVo bagVo : bagVos) {
             XfConsumeDetailBo detailBo = this.createConsumeDetailBo(bo, userAccountVo, cardVo, bagVo, termVo, mealTypeVo, remark);
@@ -296,7 +296,6 @@ public class BaseBusiness {
             throw new ConsumeException("更新设备日统计表失败");
         }
         // 4.更新钱包余额
-
         log.warn("[上传交易]-[更新钱包余额]-[{}]", JSONUtil.toJsonStr(JSONUtil.toJsonStr(bagVos)));
         bagService.updateConsumeBalance(bagVos);
 
@@ -320,7 +319,7 @@ public class BaseBusiness {
     private XfConsumeDetailBo createConsumeDetailBo(ConsumptionBo bo, RemoteUserAccountVo userAccountVo,
                                                     RemoteCardVo cardVo, PtBagVo bagVo, XfTermVo termVo,
                                                     RemoteMealTypeVo mealTypeVo, String remark) {
-        String recordId = RecordIdUtils.getRecordId(bo.getConsumeDate(), Short.parseShort(bo.getTermNo().toString()),
+        String recordId = RecordIdUtils.getRecordId(bo.getConsumeDate(), bo.getTermNo().intValue(),
             bo.getTermRecordId().intValue(),
             userAccountVo.getUserNo().intValue(),
             Integer.parseInt(bagVo.getBagCode()));
@@ -431,8 +430,8 @@ public class BaseBusiness {
      */
     public void completeUploadRecord(ConsumptionBo bo, RemoteUserAccountVo accountVo) {
         // 消费记录上传完成,还有一些后续工作,不需要知道处理结果,采用异步任务提交
-        taskExecutor.submit(() -> sendConsumeToKafka(bo, accountVo));
-        taskExecutor.submit(() -> sendCloudConsume(bo));
+        // threadPoolTaskExecutor.execute(() -> sendConsumeToKafka(bo, accountVo));
+        //taskExecutor.submit(() -> sendCloudConsume(bo));
     }
 
 
@@ -444,7 +443,7 @@ public class BaseBusiness {
      */
     private void sendConsumeToKafka(ConsumptionBo bo, RemoteUserAccountVo accountVo) {
         // 只有本地消费完成后才会向教务发kafka消息
-        if (ObjectUtil.equals(defaultConfig.getLocationFlag(), DefaultConstants.LOCAL_FLAG)) {
+        //if (ObjectUtil.equals(defaultConfig.getLocationFlag(), DefaultConstants.LOCAL_FLAG)) {
             YcPushConsumeInfoVo ycSendConsumeInfo = new YcPushConsumeInfoVo();
             ycSendConsumeInfo.setRecordId(bo.getRecordId().toString());
             ycSendConsumeInfo.setUserId(bo.getUserId().toString());
@@ -472,7 +471,7 @@ public class BaseBusiness {
             log.info("[向教务系统发送就餐打卡]-[{}]", ycSendConsumeInfo);
             kafkaNormalProducer.sendKafkaMessage(KafkaTopicConstants.OLD_SYNC_TOPIC, EventTypeConstants.CONSUME_RECORD, EventSenderEnum.OLD.code(),
                 ycSendConsumeInfo);
-        }
+        //}
     }
 
     /**
@@ -554,7 +553,7 @@ public class BaseBusiness {
                 RemoteUserAccountVo accountVo = accountVoList.parallelStream()
                     .filter(k -> k.getUserId().equals(p.getUserId())).findFirst().orElse(null);
                 //向教务推送刷卡数据
-                taskExecutor.submit(() -> sendConsumeToKafka(p, accountVo));
+                threadPoolTaskExecutor.execute(() -> sendConsumeToKafka(p, accountVo));
             });
         }
         return R.ok();

+ 5 - 2
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/CardBusiness.java

@@ -96,8 +96,11 @@ public class CardBusiness {
 
     private Object setCardBagInfo(RemoteCardVo remoteCardVo) {
         CardBagInfo cardBagInfo = new CardBagInfo();
-        // String StrUserId = String.valueOf(remoteCardVo.getUserId());
-        RemoteUserAccountVo userAccountVo = userAccountCacheManager.getByUserId(remoteCardVo.getUserId());
+        String stringAccount = userAccountCacheManager.getByUserId(remoteCardVo.getUserId());
+        if (ObjectUtil.isEmpty(stringAccount)) {
+            return null;
+        }
+        RemoteUserAccountVo userAccountVo = JsonUtils.parseObject(stringAccount, RemoteUserAccountVo.class);
         if (userAccountVo == null) {
             return null;
         }

+ 12 - 8
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/ConsumeBusiness.java

@@ -68,10 +68,11 @@ public class ConsumeBusiness {
         AllowConsumeValidationContext ctx = AllowConsumeValidationContext.create(bo);
         R<ErrorInfo> result = commonCheck.consumeValidation(ctx);
         if (R.isError(result)) {
-            log.error("[请求交易]-[记录有效性验证失败]-[{}]-[{}]", JSONUtil.toJsonStr(result.getData()), JSONUtil.toJsonStr(bo));
+            log.error("[请求交易]-[记录有效性验证失败]-[数据:{}]-[耗时:{} ms]-[错误:{}]", JSONUtil.toJsonStr(bo),
+                System.currentTimeMillis() - startTime, JSONUtil.toJsonStr(result.getData()));
             return result;
         }
-        log.info("[请求交易]-[记录有效性验证完成]-[耗时: {} ms]-[{}]", System.currentTimeMillis() - startTime, JSONUtil.toJsonStr(bo));
+        log.info("[请求交易]-[记录有效性验证完成]-[数据:{}]-[耗时: {} ms]", JSONUtil.toJsonStr(bo), System.currentTimeMillis() - startTime);
 
         RemoteUserAccountVo userAccountVo = ctx.getUserAccountVo();
         RemoteCardVo userCardVo = ctx.getUserCardVo();
@@ -82,15 +83,17 @@ public class ConsumeBusiness {
         startTime = System.currentTimeMillis();
         result = requestCheck.checkConsume(bo, userAccountVo, userCardVo, termVo, mapCardLimited, cardLimitedVo);
         if (R.isError(result)) {
-            log.error("[请求交易]-[交易流程验证失败]-[{}]-[{}]", JSONUtil.toJsonStr(result.getData()), JSONUtil.toJsonStr(bo));
+            log.error("[请求交易]-[交易流程验证失败]-[数据:{}]-[耗时:{} ms]-[错误:{}]", JSONUtil.toJsonStr(bo),
+                System.currentTimeMillis() - startTime, JSONUtil.toJsonStr(result.getData()));
             return result;
         }
-        log.warn("[请求交易]-[交易流程验证完成]-[耗时: {} ms]-[{}]", System.currentTimeMillis() - startTime, JSONUtil.toJsonStr(bo));
+        log.info("[请求交易]-[交易流程验证完成]-[数据:{}]-[耗时: {} ms]", JSONUtil.toJsonStr(bo), System.currentTimeMillis() - startTime);
 
         startTime = System.currentTimeMillis();
         result = requestCheck.completeConsumeRequest(bo, userAccountVo, userCardVo, termVo, mapCardLimited, cardLimitedVo);
         if (R.isError(result)) {
-            log.error("[请求交易]-[消费原始记录失败]-[{}]-[{}]", JSONUtil.toJsonStr(result.getData()), JSONUtil.toJsonStr(bo));
+            log.error("[请求交易]-[生成原始消费记录失败]-[数据:{}]-[耗时:{} ms]-[错误:{}]", JSONUtil.toJsonStr(bo),
+                System.currentTimeMillis() - startTime, JSONUtil.toJsonStr(result.getData()));
             return result;
         }
         log.info("[请求交易]-[生成原始消费记录完成]-[耗时: {} ms]-[{}]", System.currentTimeMillis() - startTime, JSONUtil.toJsonStr(bo));
@@ -111,7 +114,8 @@ public class ConsumeBusiness {
         AllowConsumeValidationContext ctx = AllowConsumeValidationContext.create(bo);
         R<ErrorInfo> result = commonCheck.consumeValidation(ctx);
         if (R.isError(result)) {
-            log.error("[上传交易]-[记录有效性验证失败]-[{}]-[{}]", JSONUtil.toJsonStr(result.getData()), JSONUtil.toJsonStr(bo));
+            log.error("[上传交易]-[记录有效性验证失败]-[数据:{}]-[耗时:{} ms]-[错误:{}]", JSONUtil.toJsonStr(bo),
+                System.currentTimeMillis() - startTime, JSONUtil.toJsonStr(result.getData()));
             return result;
         }
         log.info("[上传交易]-[记录有效性验证完成]-[耗时: {} ms]-[{}]", System.currentTimeMillis() - startTime, JSONUtil.toJsonStr(bo));
@@ -183,7 +187,7 @@ public class ConsumeBusiness {
      */
     @Async
     public void postOrderAsync(ConsumptionBo bo, String mac, String xfPwd) {
-        R<ErrorInfo> result=null;
+        R<ErrorInfo> result = null;
         for (int i = 0; i < 3; i++) {
             result = SpringUtils.getAopProxy(this).postOrder(bo, mac, xfPwd);
             if (R.isSuccess(result)) {
@@ -191,7 +195,7 @@ public class ConsumeBusiness {
             }
         }
         if (R.isError(result)) {
-            log.info("[上传交易异步处理尝试3次失败,需要手工处理]-[{}]", JSONUtil.toJsonStr(bo));
+            log.info("[上传交易异步处理尝试3次失败,需要手工处理]-[{}]-[{}]", JSONUtil.toJsonStr(bo), JSONUtil.toJsonStr(result.getData()));
         }
         log.info("[上传交易异步处理完成]");
     }

+ 21 - 4
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/EmployeeBusiness.java

@@ -1,5 +1,6 @@
 package org.dromara.server.consume.business;
 
+import cn.hutool.core.util.ObjectUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.backstage.api.domain.vo.RemoteCardVo;
@@ -33,7 +34,11 @@ public class EmployeeBusiness {
     private final CardCacheManager cardCacheManager;
 
     public ReturnResult getEmployeeVoByNumb(String userNumb) {
-        RemoteUserAccountVo userAccountVo = userAccountCacheManager.getByUserNumb(userNumb);
+        String stringAccount = userAccountCacheManager.getByUserNumb(userNumb);
+        if (ObjectUtil.isEmpty(stringAccount)) {
+            return null;
+        }
+        RemoteUserAccountVo userAccountVo = JsonUtils.parseObject(stringAccount, RemoteUserAccountVo.class);
         if (userAccountVo == null) {
             ErrorResult result = new ErrorResult();
             result.setStatusCode(HttpStatus.NOT_FOUND.value());
@@ -55,13 +60,25 @@ public class EmployeeBusiness {
     public RemoteUserAccountVo getAccountFromCache(String checkParam, Integer checkMode) {
         switch (checkMode) {
             case ConsumeConstants.USER_ID -> {
-                return userAccountCacheManager.getByUserId(Long.valueOf(checkParam));
+                String stringAccount = userAccountCacheManager.getByUserId(Long.valueOf(checkParam));
+                if (ObjectUtil.isEmpty(stringAccount)) {
+                    return null;
+                }
+                return JsonUtils.parseObject(stringAccount, RemoteUserAccountVo.class);
             }
             case ConsumeConstants.USER_NO -> {
-                return userAccountCacheManager.getByUserNo(Long.valueOf(checkParam));
+                String stringAccount = userAccountCacheManager.getByUserNo(Long.valueOf(checkParam));
+                if (ObjectUtil.isEmpty(stringAccount)) {
+                    return null;
+                }
+                return JsonUtils.parseObject(stringAccount, RemoteUserAccountVo.class);
             }
             case ConsumeConstants.USER_NUMB -> {
-                return userAccountCacheManager.getByUserNumb(checkParam);
+                String stringAccount = userAccountCacheManager.getByUserNumb(checkParam);
+                if (ObjectUtil.isEmpty(stringAccount)) {
+                    return null;
+                }
+                return JsonUtils.parseObject(stringAccount, RemoteUserAccountVo.class);
             }
         }
         return null;

+ 1 - 9
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/InitBusiness.java

@@ -79,17 +79,8 @@ public class InitBusiness {
      */
     public void initGlobalData() {
         String disCount = remotePtParameterService.getPtParameterByKey("RATE_CONSUME");
-        if (ObjectUtil.isNotEmpty(disCount)) {
-            RedisUtils.setCacheMapValue(CacheNames.PT_PARAMETER, "RATE_CONSUME", disCount);
-        }
         String quota = remotePtParameterService.getPtParameterByKey("XE_CONSUME");
-        if (ObjectUtil.isNotEmpty(quota)) {
-            RedisUtils.setCacheMapValue(CacheNames.PT_PARAMETER, "XE_CONSUME", quota);
-        }
         String limited = remotePtParameterService.getPtParameterByKey("XC_CONSUME");
-        if (ObjectUtil.isNotEmpty(limited)) {
-            RedisUtils.setCacheMapValue(CacheNames.PT_PARAMETER, "XC_CONSUME", limited);
-        }
 
         log.info("初始化全局限次、限额和折扣参数完成");
     }
@@ -177,6 +168,7 @@ public class InitBusiness {
         }
         for (Map.Entry<Long, BigDecimal> entry : sumMap.entrySet()) {
             bagService.cacheUserTotalBalance(entry.getKey(), entry.getValue());
+            bagService.getUserSecretKeyByUserId(entry.getKey());
         }
         log.info("初始化人员余额参数完成");
     }

+ 2 - 1
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/business/TermBusiness.java

@@ -79,7 +79,8 @@ public class TermBusiness {
         // TermInfo termInfo = result.getData();
         final String strTermNo = String.valueOf(termNo);
         // final String roomName = termInfo == null ? "未知房间" : termInfo.getRoomName();
-        String roomName = termService.queryRoomNameByNo(termNo);
+        // String roomName = termService.queryRoomNameByNo(termNo);
+        String roomName = "未知房间";
         // 使用termNo字符串作为锁对象,减小锁粒度
         // synchronized (strTermNo.intern()) {
         //     Map<String, TermToken> tokenMap = tokenManager.getTermToken();

+ 8 - 8
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/cache/UserAccountCacheManager.java

@@ -32,17 +32,17 @@ public class UserAccountCacheManager {
     private final RemoteUserAccountService remoteUserAccountService;
 
     @Cacheable(cacheNames = CacheNames.PT_USER_ACCOUNT_ID, key = "#userId", unless = "#result == null")
-    public RemoteUserAccountVo getByUserId(Long userId) {
-        return remoteUserAccountService.getUserAccountVoById(userId);
+    public String getByUserId(Long userId) {
+        return JsonUtils.toJsonString(remoteUserAccountService.getUserAccountVoById(userId));
     }
     @Cacheable(cacheNames = CacheNames.PT_USER_ACCOUNT_NO, key = "#userNo", unless = "#result == null")
-    public RemoteUserAccountVo getByUserNo(Long userNo) {
-        return  remoteUserAccountService.getUserAccountVoByUserNo(userNo);
+    public String getByUserNo(Long userNo) {
+        return  JsonUtils.toJsonString(remoteUserAccountService.getUserAccountVoByUserNo(userNo));
     }
 
     @Cacheable(cacheNames = CacheNames.PT_USER_ACCOUNT_NUMB, key = "#userNumb", unless = "#result == null")
-    public RemoteUserAccountVo getByUserNumb(String userNumb) {
-        return remoteUserAccountService.getUserAccountVoByUserNumb(userNumb);
+    public String getByUserNumb(String userNumb) {
+        return JsonUtils.toJsonString(remoteUserAccountService.getUserAccountVoByUserNumb(userNumb));
     }
 
     @Caching(
@@ -52,7 +52,7 @@ public class UserAccountCacheManager {
             @CachePut(cacheNames = CacheNames.PT_USER_ACCOUNT_NUMB, key = "#accountVo.userNumb")
         }
     )
-    public RemoteUserAccountVo cacheUserAccountVo(RemoteUserAccountVo accountVo) {
-        return accountVo;
+    public String cacheUserAccountVo(RemoteUserAccountVo accountVo) {
+        return JsonUtils.toJsonString(accountVo);
     }
 }

+ 162 - 61
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/CardConsumeValidation.java

@@ -44,20 +44,33 @@ public class CardConsumeValidation {
     private final CommonCheck commonCheck;
     private final ValidationParam validationParam;
     private final ThreadPoolTaskExecutor taskExecutor;
+    private final ParallelTaskExecutor taskExecutor1;
 
     public R<ErrorInfo> cardValidation(ConsumptionBo bo, XfTermVo termVo, RemoteCardVo userCardVo,
                                        RemoteMealTypeVo mealTypeVo,
                                        XfCardLimitedVo cardLimitedVo, Map<String, Boolean> mapCardLimited) {
 
         // public R<ErrorInfo> cardValidation(CardConsumeValidationContext validationContext) {
+        long startTime = System.currentTimeMillis();
         // 1.初始化验证上下文
         CardConsumeValidationContext validationContext = CardConsumeValidationContext.create(bo, termVo, userCardVo, mealTypeVo.getTypeId());
 
         // 2. 执行异步验证链
-        R<ErrorInfo> result = executeCardValidationChain(validationContext);
+        // R<ErrorInfo> result = executeCardValidationChain(validationContext);
         // if (R.isSuccess(result)) {
         //     dealCardQuota(validationContext);
         // }
+        List<ValidationTaskDes<R<ErrorInfo>>> tasks = List.of(
+            new ValidationTaskDes<>("折扣验证", () -> dealCardDiscount(validationContext)),
+            new ValidationTaskDes<>("限次验证", () -> dealCardLimited(validationContext))
+        );
+        //
+        R<ErrorInfo> result = taskExecutor1.executeParallelTasks(tasks, 5, TimeUnit.SECONDS);
+        // R<ErrorInfo> result = executeTermValidationChain(ctx);
+        if (R.isSuccess(result)) {
+            // 限次验证和折扣计算完成,进行限额验证
+            result = dealCardQuota(validationContext);
+        }
         // 3.不管验证是否成功,获取验证后的折扣金额及卡验证限制数据
         bo.setConsumeMoney(validationContext.discountMoney);
         RemoteVoConvert.INSTANCE.copyXfCardLimitedVo(cardLimitedVo, validationContext.getCardLimitedVo());
@@ -65,74 +78,116 @@ public class CardConsumeValidation {
         mapCardLimited.put("hasQuota", validationContext.getHasQuota());
         mapCardLimited.put("hasLimited", validationContext.getHasLimited());
 
+        log.info("[请求交易]-[卡片消费验证完成]-[耗时:{} ms]",System.currentTimeMillis()-startTime);
+
         return result;
     }
 
     private R<ErrorInfo> executeCardValidationChain(CardConsumeValidationContext ctx) {
-        // 创建验证任务列表
-        List<Callable<R<ErrorInfo>>> validationTasks = new ArrayList<>();
-        validationTasks.add(() -> dealCardDiscount(ctx));
-        validationTasks.add(() -> dealCardLimited(ctx));
-
+        // 创建CountDownLatch 有1个任务返回错误时就唤醒主线程
+        CountDownLatch latch = new CountDownLatch(1);
         // 存储第一个错误结果
         AtomicReference<R<ErrorInfo>> firstError = new AtomicReference<>(null);
+        // 创建验证任务列表
+        List<Callable<R<ErrorInfo>>> validationTasks = getCardValidCallables(ctx, firstError, latch);
+        // 所有任务的结果集合
         List<Future<R<ErrorInfo>>> futures = new ArrayList<>();
-
         // 提交所有验证任务
         for (Callable<R<ErrorInfo>> task : validationTasks) {
             futures.add(taskExecutor.submit(task));
         }
-        int taskIndex = 0;
+
+        long starTime = System.currentTimeMillis();
+        // 最终执行的结果,true 为正常执行结束及latch减至了0;false 为超时
+        boolean finallyResult;
         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();
-                    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, commonCheck.createError(TradeStatusEnum.SysError))) {
-                        futures.forEach(f -> f.cancel(true));
-                    }
-                } finally {
-                    log.info("{}结束,耗时:{} ms", getTaskName(taskIndex), System.currentTimeMillis() - starTime);
-                    taskIndex++;
-                }
-            }
+            // 阻塞主线程,等待执行结果; latch 为0时,会唤醒主线程,最多阻塞10s
+            finallyResult = latch.await(10, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            log.error("executeCardValidationChain- main 被中断 :{}", e.getMessage());
+            Thread.currentThread().interrupt();
+            // 取消所有任务
+            futures.forEach(f -> f.cancel(true));
+            return CheckError.createError(TradeStatusEnum.SysError);
+        }
 
-            // 如果已有错误,直接返回
-            if (firstError.get() != null) {
-                return firstError.get();
-            }
+        // 循环取消所有任务
+        futures.forEach(f -> f.cancel(true));
+        if (!finallyResult) {
+            log.error("executeCardValidationChain- 折扣和限次校验10s超时");
+            return CheckError.createError(TradeStatusEnum.SysError);
+        }
 
-            // 所有前置任务通过,执行最后一个任务
-            long starTime = System.currentTimeMillis();
-            try {
-                return dealCardQuota(ctx);
-            } catch (Exception e) {
-                log.error("{}执行异常", getTaskName(2), e);
-                return commonCheck.createError(TradeStatusEnum.DayLimitMoney, "日限额验证失败");
-            } finally {
-                log.info("{}结束,耗时:{} ms", getTaskName(2), System.currentTimeMillis() - starTime);
-            }
+        R<ErrorInfo> errorInfoR = firstError.get();
+        log.info("executeCardValidationChain- 折扣和限次校验耗时:{}ms", System.currentTimeMillis() - starTime);
+        if (errorInfoR != null && R.isError(errorInfoR)) {
+            return errorInfoR;
+        }
 
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt(); // 保留中断状态
-            log.error("验证过程被中断", e);
-            return commonCheck.createError(TradeStatusEnum.SysError);
+        // 所有前置任务通过,执行最后一个任务
+        starTime = System.currentTimeMillis();
+        try {
+            // 限额逻辑
+            return dealCardQuota(ctx);
+        } catch (Exception e) {
+            log.error("{}执行异常", getTaskName(2), e);
+            return CheckError.createError(TradeStatusEnum.DayLimitMoney, "日限额验证失败");
+        }finally {
+            log.info("{}结束,耗时:{} ms", getTaskName(2), System.currentTimeMillis() - starTime);
         }
 
+
+//        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();
+//                    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, CheckError.createError(TradeStatusEnum.SysError))) {
+//                        futures.forEach(f -> f.cancel(true));
+//                    }
+//                } finally {
+//                    log.info("{}结束,耗时:{} ms", getTaskName(taskIndex), System.currentTimeMillis() - starTime);
+//                    taskIndex++;
+//                }
+//            }
+//
+//            // 如果已有错误,直接返回
+//            if (firstError.get() != null) {
+//                return firstError.get();
+//            }
+//
+//            // 所有前置任务通过,执行最后一个任务
+//            long starTime = System.currentTimeMillis();
+//            try {
+//                return dealCardQuota(ctx);
+//            } catch (Exception e) {
+//                log.error("{}执行异常", getTaskName(2), e);
+//                return CheckError.createError(TradeStatusEnum.DayLimitMoney, "日限额验证失败");
+//            } finally {
+//                log.info("{}结束,耗时:{} ms", getTaskName(2), System.currentTimeMillis() - starTime);
+//            }
+//
+//        } catch (InterruptedException e) {
+//            Thread.currentThread().interrupt(); // 保留中断状态
+//            log.error("验证过程被中断", e);
+//            return CheckError.createError(TradeStatusEnum.SysError);
+//        }
+
         // 使用CountDownLatch跟踪任务完成
         // CountDownLatch latch = new CountDownLatch(validationTasks.size());
         //
@@ -149,7 +204,7 @@ public class CardConsumeValidation {
         //             }
         //         } catch (Exception e) {
         //             log.error("系统错误1", e);
-        //             if (firstError.compareAndSet(null, commonCheck.createError(TradeStatusEnum.SysError))) {
+        //             if (firstError.compareAndSet(null, CheckError.createError(TradeStatusEnum.SysError))) {
         //                 taskExecutor.getThreadPoolExecutor().getQueue().clear();
         //             }
         //         } finally {
@@ -161,7 +216,7 @@ public class CardConsumeValidation {
         // try {
         //     // 等待所有任务完成或超时
         //     //if (!latch.await(VALIDATION_TIMEOUT, TimeUnit.MILLISECONDS)) {
-        //     //    return commonCheck.createError(TradeStatusEnum.VALIDATION_TIMEOUT);
+        //     //    return CheckError.createError(TradeStatusEnum.VALIDATION_TIMEOUT);
         //     //}
         //     latch.await();
         //     // 返回第一个发现的错误,如果没有错误则返回成功
@@ -174,10 +229,54 @@ public class CardConsumeValidation {
         // } catch (InterruptedException e) {
         //     Thread.currentThread().interrupt();
         //     log.error("系统错误", e);
-        //     return commonCheck.createError(TradeStatusEnum.SysError);
+        //     return CheckError.createError(TradeStatusEnum.SysError);
         // }
     }
 
+    /**
+     * 获取校验的任务列表
+     * @param ctx
+     * @param firstError
+     * @param latch
+     * @return
+     */
+    private List<Callable<R<ErrorInfo>>> getCardValidCallables(CardConsumeValidationContext ctx,
+                                                               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 = dealCardDiscount(ctx);
+            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 = dealCardLimited(ctx);
+            if (infoR != null && R.isError(infoR)) {
+                // 存储错误结果
+                firstError.set(infoR);
+                latch.countDown();
+            }
+            return infoR;
+        });
+        return validationTasks;
+    }
+
     private String getTaskName(int taskIndex) {
         return switch (taskIndex) {
             case 0 -> "卡片折扣计算";
@@ -188,7 +287,8 @@ public class CardConsumeValidation {
     }
 
     // region 卡片折扣处理
-    private R<ErrorInfo> dealCardDiscount(CardConsumeValidationContext ctx) {
+    private R<ErrorInfo> dealCardDiscount(CardConsumeValidationContext ctx) throws InterruptedException {
+        Thread.sleep(1);
         if (!validationParam.getRATE_CONSUME()) {
             log.warn("全局折扣功能未启用,跳过折扣验证");
             return R.ok();
@@ -261,7 +361,8 @@ public class CardConsumeValidation {
     // endregion
 
     //region 卡片限次处理
-    private R<ErrorInfo> dealCardLimited(CardConsumeValidationContext ctx) {
+    private R<ErrorInfo> dealCardLimited(CardConsumeValidationContext ctx) throws InterruptedException {
+        Thread.sleep(1);
         if (!validationParam.getXC_CONSUME()) {
             log.warn("全局限次功能未启用,跳过限次验证");
             return R.ok();
@@ -312,7 +413,7 @@ public class CardConsumeValidation {
     private R<ErrorInfo> checkDailyLimit(RemoteLimitedVo limitedVo, Long currentDayCount) {
         Long dayLimit = limitedVo.getDailyCount();
         if (dayLimit > 0 && currentDayCount >= dayLimit) {
-            return commonCheck.createError(TradeStatusEnum.DayLimitTimes, "卡类日次数限制");
+            return CheckError.createError(TradeStatusEnum.DayLimitTimes, "卡类日次数限制");
         }
         return null;
     }
@@ -345,7 +446,7 @@ public class CardConsumeValidation {
 
         if (currentMealCount >= mealLimit) {
             String mealName = MEAL_TYPE_NAMES.getOrDefault(mealType, "未知餐次");
-            return commonCheck.createError(TradeStatusEnum.MealLimitTimes, String.format("卡类%s次数限制", mealName));
+            return CheckError.createError(TradeStatusEnum.MealLimitTimes, String.format("卡类%s次数限制", mealName));
         }
         return R.ok();
     }
@@ -405,7 +506,7 @@ public class CardConsumeValidation {
     private R<ErrorInfo> checkDailyQuota(RemoteQuotaVo quotaVo, BigDecimal dayMoney, BigDecimal consumeMoney) {
         BigDecimal dayQuotaMoney = quotaVo.getDailyMoney();
         if (dayQuotaMoney.compareTo(BigDecimal.ZERO) > 0 && dayQuotaMoney.compareTo(dayMoney.add(consumeMoney)) < 0) {
-            return commonCheck.createError(TradeStatusEnum.DayLimitMoney, "卡类日限制额度");
+            return CheckError.createError(TradeStatusEnum.DayLimitMoney, "卡类日限制额度");
         }
         return null;
     }
@@ -439,7 +540,7 @@ public class CardConsumeValidation {
         if (mealQuota.compareTo(BigDecimal.ZERO) > 0) {
             if (mealQuota.compareTo(mealMoney.add(consumeMoney)) < 0) {
                 String mealName = MEAL_TYPE_NAMES.getOrDefault(mealType, "未知餐次");
-                return commonCheck.createError(TradeStatusEnum.MealLimitMoney, String.format("卡类%s额度限制", mealName));
+                return CheckError.createError(TradeStatusEnum.MealLimitMoney, String.format("卡类%s额度限制", mealName));
             }
         }
         return R.ok();

+ 70 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/CheckError.java

@@ -0,0 +1,70 @@
+package org.dromara.server.consume.check;
+
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.domain.model.ErrorInfo;
+import org.dromara.common.core.enums.TradeStatusEnum;
+
+/**
+ * 所有的验证错误方法封装
+ * <p>
+ * [功能说明]
+ * ${1:在此简要描述文件核心功能}
+ *
+ * @author luoyibo
+ * @date 2025-07-13
+ * @since JDK17
+ */
+@Slf4j
+public class CheckError {
+
+    /**
+     * 创建错误响应对象
+     *
+     * @param status  交易状态
+     * @param message 错误消息
+     * @return 封装好的错误响应
+     */
+    public static R<ErrorInfo> createError(TradeStatusEnum status, String message) {
+        return R.fail(new ErrorInfo(
+            400,
+            status.toString(),
+            message,
+            status.getName()));
+    }
+    public static R<ErrorInfo> createError(TradeStatusEnum status) {
+        return R.fail(new ErrorInfo(
+            400,
+            status.toString(),
+            status.getName(),
+            status.getName()));
+    }
+    public static R<ErrorInfo> createTimeoutError() {
+        log.error("验证任务整体超时");
+        return createError(TradeStatusEnum.TimeoutError);
+    }
+
+    public static R<ErrorInfo> createExecutionError(String taskName, Throwable cause) {
+        log.error("{}执行异常: {}", taskName, cause.getMessage(), cause);
+        return createError(TradeStatusEnum.SysError);
+    }
+
+    public static R<ErrorInfo> createInterruptionError() {
+        log.error("验证过程被中断");
+        return createError(TradeStatusEnum.SysError);
+    }
+
+    /**
+     * 创建错误响应对象
+     *
+     * @param code   错误码
+     * @param type   错误类型
+     * @param msg    错误消息
+     * @param detail 错误详情
+     * @return 封装好的错误响应
+     */
+    public static R<ErrorInfo> createErrorResponse(int code, String type, String msg, String detail) {
+        ErrorInfo errorInfo = new ErrorInfo(code, type, msg, detail);
+        return R.fail(errorInfo);
+    }
+}

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

@@ -6,7 +6,6 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.time.DateFormatUtils;
 import org.dromara.backstage.api.domain.vo.RemoteCardVo;
 import org.dromara.backstage.api.domain.vo.RemoteMealTypeVo;
 import org.dromara.backstage.api.domain.vo.RemoteUserAccountVo;
@@ -16,15 +15,12 @@ import org.dromara.common.core.constant.Constants;
 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.TradeStatusEnum;
 import org.dromara.common.core.enums.UserAccountStatusEnum;
-import org.dromara.common.json.utils.JsonUtils;
 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.cache.CardCacheManager;
 import org.dromara.server.consume.domain.vo.XfCardLimitedVo;
 import org.dromara.server.consume.domain.vo.XfTermVo;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@@ -32,14 +28,11 @@ 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.*;
 import java.util.concurrent.*;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Supplier;
 
 /**
  * 消费业务通用验证类
@@ -54,26 +47,39 @@ import java.util.function.Supplier;
 @Service
 @RequiredArgsConstructor
 public class CommonCheck {
-    // 异步线程执行超时时间,以毫秒为单位
-    private static final long VALIDATION_TIMEOUT = 500;
+    // 多任务执行总体超时时间,以毫秒为单位
+    private static final long TIMEOUT_TOTAL = 3000;
 
     private final BaseBusiness baseBusiness;
     private final EmployeeBusiness employeeBusiness;
     private final CardBusiness cardBusiness;
-    private final ThreadPoolTaskExecutor taskExecutor;
+    private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
     private final TermBusiness termBusiness;
+    private final ParallelTaskExecutor taskExecutor;
 
-    //region 账户、卡片与设备验证
+    //region 账户、卡片与设备的有效性验证
 
     /**
      * 账户、设备有效性验证
+     * <p>
+     * 1.验证传入的参数是否满足要求:机号大于0,卡流水号、物理卡号、人员流水号、人员编号必有一个
+     * 2.根据机号验证是否是系统中的设备
+     * 3.根据卡流水号或物理卡号验证是否是系统中的卡片、卡片的状态是否正常,卡正常后还要根据卡查询账户,验证账户是否有效
+     * 4.根据人员流水号或人员编号验证是否是系统中的账户、账户的状态是否正常,如果账户正常则根据账户获取正常的主卡
      *
      * @param ctx 验证上下文
      * @return 校验结果,包含错误信息或成功标识
      */
     public R<ErrorInfo> consumeValidation(AllowConsumeValidationContext ctx) {
-        // 1. 执行异步验证链
-        R<ErrorInfo> result = executeTermValidationChain(ctx);
+        // 使用线程池并行验证
+        List<ValidationTaskDes<R<ErrorInfo>>> tasks = List.of(
+            new ValidationTaskDes<>("参数有效性验证", () -> checkParam(ctx)),
+            new ValidationTaskDes<>("设备有效性验证", () -> checkTerm(ctx)),
+            new ValidationTaskDes<>("账户有效性验证", () -> checkUserAccount(ctx))
+        );
+        //
+        R<ErrorInfo> result = taskExecutor.executeParallelTasks(tasks, TIMEOUT_TOTAL, TimeUnit.MILLISECONDS);
+        // R<ErrorInfo> result = executeTermValidationChain(ctx);
         if (R.isError(result)) {
             return result;
         }
@@ -87,116 +93,142 @@ public class CommonCheck {
      * @return 校验结果,包含错误信息或成功标识
      */
     private R<ErrorInfo> executeTermValidationChain(AllowConsumeValidationContext ctx) {
-        // 创建验证任务列表
-        // List<Supplier<R<ErrorInfo>>> validationTasks = new ArrayList<>();
-        List<Callable<R<ErrorInfo>>> validationTasks = new ArrayList<>();
-
-        validationTasks.add(() -> checkParam(ctx));
-        validationTasks.add(() -> checkTerm(ctx));
-        validationTasks.add(() -> checkUserAccount(ctx));
-
-        // 用于存储第一个错误结果
-        AtomicReference<R<ErrorInfo>> firstError = new AtomicReference<>(null);
-
-        // 使用CountDownLatch跟踪任务完成
-        // CountDownLatch latch = new CountDownLatch(validationTasks.size());
-        // AtomicBoolean cancelled = new AtomicBoolean(false);
-        List<Future<R<ErrorInfo>>> futures = new ArrayList<>();
-        // 提交所有任务
-        for (Callable<R<ErrorInfo>> task : validationTasks) {
-            futures.add(taskExecutor.submit(task));
-        }
-        int taskIndex = 0;
+        // 1. 使用LinkedHashMap保持任务顺序(便于调试)
+        final Map<String, Callable<R<ErrorInfo>>> validationTasks  = new LinkedHashMap<>();
+        validationTasks.put("参数有效性验证", () -> checkParam(ctx));
+        validationTasks.put("设备有效性验证", () -> checkTerm(ctx));
+        validationTasks.put("账户有效性验证", () -> checkUserAccount(ctx));
+
+        // 2. 快速失败标志和获取任务完成结果
+        final AtomicBoolean hasError = new AtomicBoolean(false);
+        final ExecutorCompletionService<R<ErrorInfo>> completionService = new ExecutorCompletionService<>(threadPoolTaskExecutor);
+
+        // 3. 使用IdentityHashMap优化Future查找
+        final Map<Future<R<ErrorInfo>>, String> futureToName = new IdentityHashMap<>();
+        final List<Future<R<ErrorInfo>>> allFutures = new ArrayList<>();
+        // 4. 提取常量
+        final int totalTasks = validationTasks.size();
+        final long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(TIMEOUT_TOTAL);
+        // 5. 提交任务(优化日志封装)
+        validationTasks.forEach((taskName, task) -> {
+            Callable<R<ErrorInfo>> loggedTask = createLoggedTask(taskName, task, hasError);
+            Future<R<ErrorInfo>> future = completionService.submit(loggedTask);
+            futureToName.put(future, taskName);
+            allFutures.add(future);
+        });
+        R<ErrorInfo> firstError = null;
+        int completedTasks = 0;
         try {
-            for (Future<R<ErrorInfo>> future : futures) {
-                long starTime = System.currentTimeMillis();
-                if (firstError.get() != null) {
-                    future.cancel(true); // 取消剩余任务
-                    continue;
+            while (completedTasks < totalTasks && !hasError.get()) {
+                // 5. 使用纳秒级精确时间计算
+                long remainingNanos = deadline - System.nanoTime();
+                if (remainingNanos <= 0) {
+                    // 设置错误标志
+                    hasError.set(true);
+                    break;
                 }
 
+                Future<R<ErrorInfo>> future = completionService.poll(remainingNanos, TimeUnit.NANOSECONDS);
+                if (future == null) {
+                    firstError = CheckError.createTimeoutError();
+                    // 设置错误标志
+                    hasError.set(true);
+                    break;
+                }
+
+                completedTasks++;
+                String taskName = futureToName.get(future);
+
                 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));
-                        }
+                    if (R.isError(result)) {
+                        firstError = result;
+                        log.info("{}验证失败: {}", taskName, result);
+                        // 设置错误标志并立即取消其他任务
+                        hasError.set(true);
+                        cancelPendingFutures(allFutures);
                     }
                 } 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++;
+                    firstError = CheckError.createExecutionError(taskName, e.getCause());
+                    hasError.set(true);
+                    cancelPendingFutures(allFutures);
+                } catch (CancellationException e) {
+                    log.info("{}已提前终止", taskName);
+                    completedTasks--; // 调整计数器
                 }
             }
-            return firstError.get() != null ? firstError.get() : R.ok();
-
         } catch (InterruptedException e) {
-            Thread.currentThread().interrupt(); // 保留中断状态
-            log.error("验证过程被中断", e);
-            return createError(TradeStatusEnum.SysError);
+            Thread.currentThread().interrupt();
+            firstError = CheckError.createInterruptionError();
+            hasError.set(true);
+            cancelPendingFutures(allFutures);
+        } finally {
+            // 6. 确保所有任务最终被清理
+            if (firstError == null && completedTasks < totalTasks) {
+                cancelPendingFutures(allFutures);
+            }
         }
-        // // 提交所有验证任务
-        // for (int i = 0, j = validationTasks.size(); i < j; i++) {
-        //     long starTime = System.currentTimeMillis();
-        //     final int taskIndex = i;
-        //     final Supplier<R<ErrorInfo>> task = validationTasks.get(taskIndex);
-        //     taskExecutor.execute(() -> {
-        //         if (cancelled.get()) {
-        //             latch.countDown();
-        //             return;
-        //         }
-        //
-        //         try {
-        //             R<ErrorInfo> result = task.get();
-        //             // 如果发现错误且尚未设置错误结果
-        //             if (result != null && R.isError(result) &&
-        //                 firstError.compareAndSet(null, result)) {
-        //                 // 取消其他任务(通过中断)
-        //                 taskExecutor.getThreadPoolExecutor().getQueue().clear();
-        //             }
-        //         } catch (Exception e) {
-        //             if (firstError.compareAndSet(null, createError(TradeStatusEnum.SysError))) {
-        //                 taskExecutor.getThreadPoolExecutor().getQueue().clear();
-        //             }
-        //         } finally {
-        //             log.info("{}完成,耗时:{} ms",getTaskName(taskIndex),System.currentTimeMillis()-starTime);
-        //             latch.countDown();
-        //         }
-        //     });
-        // }
-        //
-        // try {
-        //     // 等待所有任务完成或超时
-        //     if (!latch.await(VALIDATION_TIMEOUT, TimeUnit.MILLISECONDS)) {
-        //         return createError(TradeStatusEnum.VALIDATION_TIMEOUT);
-        //     }
-        //
-        //     // 返回第一个发现的错误,如果没有错误则返回成功
-        //     return firstError.get() != null ? firstError.get() : R.ok();
-        // } catch (InterruptedException e) {
-        //     Thread.currentThread().interrupt();
-        //     log.error("error:{}", e.getMessage(), e);
-        //     return createError(TradeStatusEnum.SysError);
-        // }
+
+        return firstError != null ? firstError : R.ok();
     }
 
-    private String getTaskName(int taskIndex) {
-        return switch (taskIndex) {
-            case 0 -> "参数验证";
-            case 1 -> "终端验证";
-            case 2 -> "账户验证";
-            default -> "未知任务";
+    /**
+     * 取消所有未完成的任务。
+     *
+     * @param futures 任务的 Future 对象列表
+     */
+    private void cancelPendingFutures(List<Future<R<ErrorInfo>>> futures) {
+        for (Future<R<ErrorInfo>> f : futures) {
+            if (!f.isDone()) {
+                f.cancel(true);
+            }
+        }
+    }
+
+    /**
+     * 创建一个带有日志记录的任务。
+     *
+     * @param taskName 任务名称
+     * @param task 需要执行的任务
+     * @param hasError 用于检查是否有其他任务已经失败的标志
+     * @return 带有日志记录的 Callable 任务
+     */
+    private Callable<R<ErrorInfo>> createLoggedTask(String taskName, Callable<R<ErrorInfo>> task, AtomicBoolean hasError) {
+        return () -> {
+            // 快速失败检查
+            if (hasError.get()) {
+                log.warn("{}-跳过执行(已有失败)", taskName);
+                throw new CancellationException("任务已取消");
+            }
+            // 主动检查中断状态
+            if (Thread.currentThread().isInterrupted()) {
+                log.info("{}-被中断", taskName);
+                throw new CancellationException("线程已中断");
+            }
+            final long startTime = System.currentTimeMillis();
+            log.info("{}-开始执行", taskName);
+
+            try {
+                R<ErrorInfo> result = task.call();
+
+                if (Thread.currentThread().isInterrupted()) {
+                    throw new InterruptedException("任务执行中被取消");
+                }
+                return result;
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                log.info("{}-执行中被取消", taskName);
+                throw new CancellationException("线程已中断");
+            } finally {
+                final long duration = System.currentTimeMillis() - startTime;
+                log.info("{}-结束-耗时:{}ms", taskName, duration);
+            }
         };
     }
 
-    // region 消费参数校验
+    // endregion
 
+    // region 消费参数校验
     /**
      * 消费参数校验
      * <p>1.必须有设备机号或mac地址,如果是机号则机号>0</p>
@@ -208,15 +240,15 @@ public class CommonCheck {
     public R<ErrorInfo> checkParam(AllowConsumeValidationContext ctx) {
         // 1. 校验设备标识参数
         if (isTerminalInvalid(ctx)) {
-            return createErrorResponse(1, ApiErrorTypeConstants.PARAM_ERROR,
+            return CheckError.createErrorResponse(1, ApiErrorTypeConstants.PARAM_ERROR,
                 "设备机号不正确", "设备机号必须大于零或MAC地址不能为空!");
         }
 
         // 2. 校验用户标识参数
         if (isUserIdentifierInvalid(ctx)) {
-            return createErrorResponse(1, ApiErrorTypeConstants.PARAM_ERROR,
+            return CheckError.createErrorResponse(1, ApiErrorTypeConstants.PARAM_ERROR,
                 "交易人员标识不满足",
-                "必须提供 [CardNo | FactoryId | userNo | userNumb]  中至少1项来标识交易用户");
+                "必须提供 [cardID | factoryFixID | employeeID | employeeStrID]  中至少1项来标识交易用户");
         }
 
         // 3. 所有参数校验通过
@@ -258,14 +290,16 @@ public class CommonCheck {
      * @param ctx 消费有效性校验上下文
      * @return 检查结果
      */
-    public R<ErrorInfo> checkTerm(AllowConsumeValidationContext ctx) {
+    public R<ErrorInfo> checkTerm(AllowConsumeValidationContext ctx) throws InterruptedException {
         String msgInfo = ObjectUtil.isEmpty(ctx.getTermMac()) ? ctx.getTermNo().toString() : ctx.getTermMac();
         String checkParam = ObjectUtil.isNotEmpty(ctx.getTermMac()) ? ctx.getTermMac() : ctx.getTermNo().toString();
         Integer checkMode = ObjectUtil.isNotEmpty(ctx.getTermMac()) ? ConsumeConstants.TERM_MAC : ConsumeConstants.TERM_NO;
+        // 延时1毫秒以便捕获中断响应
+        Thread.sleep(1);
         XfTermVo termVo = termBusiness.getTermFromCache(checkParam, checkMode);
 
         if (ObjectUtil.isEmpty(termVo)) {
-            return createErrorResponse(400, ApiErrorTypeConstants.OBJECT_NOT_EXISTS,
+            return CheckError.createErrorResponse(400, ApiErrorTypeConstants.OBJECT_NOT_EXISTS,
                 "设备不存在",
                 MessageFormat.format("机号或MAC为[{0}]的设备不存在,不允许交易", msgInfo));
         }
@@ -288,12 +322,14 @@ public class CommonCheck {
      * @param ctx 消费有效性校验上下文,包含消费相关的基础信息(如用户流水号、消费金额等)
      * @return 如果校验失败,则返回包含错误信息的 R 对象;如果校验成功,则返回表示成功的 R 对象
      */
-    public R<ErrorInfo> checkUserAccount(AllowConsumeValidationContext ctx) {
+    public R<ErrorInfo> checkUserAccount(AllowConsumeValidationContext ctx) throws InterruptedException {
         long cardNo = ObjectUtil.isEmpty(ctx.getCardNo()) ? 0 : ctx.getCardNo();
         long userNo = ObjectUtil.isEmpty(ctx.getUserNo()) ? 0 : ctx.getUserNo();
         long factoryId = ObjectUtil.isEmpty(ctx.getFactoryId()) ? 0 : ctx.getFactoryId();
         String userNumb = ObjectUtil.isEmpty(ctx.getUserNumb()) ? null : ctx.getUserNumb();
 
+        // 延时1毫秒以便捕获中断响应
+        Thread.sleep(1);
         // 如果卡流水号>0验证卡信息
         if (cardNo > 0) {
             return checkCardNo(ctx);
@@ -327,7 +363,7 @@ public class CommonCheck {
         Long factoryId = ctx.getFactoryId();
         Long cardFactoryId = ctx.getUserCardVo().getFactoryId();
         if (factoryId.compareTo(0L) > 0 && !cardFactoryId.equals(factoryId)) {
-            return createErrorResponse(400, ApiErrorTypeConstants.PARAM_ERROR,
+            return CheckError.createErrorResponse(400, ApiErrorTypeConstants.PARAM_ERROR,
                 "卡片不正确",
                 MessageFormat.format("物理卡号不一致,不允许交易。传入值[{0}],系统值[{1}]", factoryId,
                     cardFactoryId));
@@ -370,7 +406,6 @@ public class CommonCheck {
         return R.ok();
     }
 
-
     /**
      * 根据用户流水号校验账户是否可以消费。
      *
@@ -389,7 +424,7 @@ public class CommonCheck {
     }
 
     /**
-     * 根据用户编号校验账户是否可以消费。
+     * 根据用户编号校验账户是否可以消费。
      *
      * @param ctx 消费有效性校验上下文,包含消费相关的基础信息(如用户流水号、消费金额等)
      * @return 如果校验失败,则返回包含错误信息的 R 对象;如果校验成功,则返回表示成功的 R 对象
@@ -403,9 +438,14 @@ public class CommonCheck {
         }
         // 校验卡片信息
         return checkCardAfterUser(ctx);
-
     }
 
+    /**
+     * 在用户检查之后验证卡片。
+     *
+     * @param ctx 验证上下文
+     * @return 验证结果
+     */
     private R<ErrorInfo> checkCardAfterUser(AllowConsumeValidationContext ctx) {
         R<ErrorInfo> result;
         Long userId = ctx.getUserAccountVo().getUserId();
@@ -434,31 +474,31 @@ public class CommonCheck {
         RemoteUserAccountVo accountVo = employeeBusiness.getAccountFromCache(checkParam, checkMode);
         // 账户不存在,不允许交易
         if (accountVo == null) {
-            return createErrorResponse(400, ApiErrorTypeConstants.NOT_FOUND,
+            return CheckError.createErrorResponse(400, ApiErrorTypeConstants.NOT_FOUND,
                 "账户不存在",
                 MessageFormat.format("流水号或Id为[{0}]的账户不存在,不允许交易", checkParam));
         }
         // 账户被冻结,不允许交易
         if (ObjectUtil.equals(accountVo.getFreezeStatus(), Constants.SYS_YES)) {
-            return createErrorResponse(400, ApiErrorTypeConstants.BAD_REQUEST,
+            return CheckError.createErrorResponse(400, ApiErrorTypeConstants.BAD_REQUEST,
                 "账户被冻结",
                 MessageFormat.format("流水号或Id为[{0}]的账户被冻结,不允许交易", checkParam));
         }
         // 账户状态不正常,不允许交易
         if (ObjectUtil.equals(accountVo.getStatus(), Constants.SYS_NO)) {
-            return createErrorResponse(400, ApiErrorTypeConstants.NOT_FOUND,
+            return CheckError.createErrorResponse(400, ApiErrorTypeConstants.NOT_FOUND,
                 "账户状态不正常",
                 MessageFormat.format("流水号或Id为[{0}]的账户状态不正常,不允许交易", checkParam));
         }
         // 未开户,不允许交易
         if (!ObjectUtil.equals(accountVo.getAccountStatus(), UserAccountStatusEnum.IS_OPEN.code().toString())) {
-            return createErrorResponse(400, ApiErrorTypeConstants.NOT_FOUND,
+            return CheckError.createErrorResponse(400, ApiErrorTypeConstants.NOT_FOUND,
                 "账户尚未开户",
                 MessageFormat.format("流水号或Id为[{0}]的账户尚未开户,不允许交易", checkParam));
         }
         // 账户已过有效期,不允许消费
         if (accountVo.getLifespan().getTime() < System.currentTimeMillis()) {
-            return createErrorResponse(400, ApiErrorTypeConstants.BAD_REQUEST,
+            return CheckError.createErrorResponse(400, ApiErrorTypeConstants.BAD_REQUEST,
                 "账户过期",
                 MessageFormat.format("流水号或Id为[{0}]的账户已过期,不允许交易", checkParam));
         }
@@ -482,13 +522,13 @@ public class CommonCheck {
         RemoteCardVo cardVo = cardBusiness.getCardFromCache(checkParam, checkMode);
         // 卡片不存在,不允许消费
         if (cardVo == null) {
-            return createErrorResponse(400, ApiErrorTypeConstants.CARD_NOT_EXISTS,
+            return CheckError.createErrorResponse(400, ApiErrorTypeConstants.CARD_NOT_EXISTS,
                 "卡片不存在",
                 MessageFormat.format("流水号或用户Id或物理卡号为[{0}]的卡片不存在,不允许交易", checkParam));
         }
         // 卡片状态不正常,不允许消费
         if (!String.valueOf(CardStatusEnum.NORMAL.code()).equals(cardVo.getStatus())) {
-            return createErrorResponse(400, ApiErrorTypeConstants.CARD_STATUS_NOT_NORMAL,
+            return CheckError.createErrorResponse(400, ApiErrorTypeConstants.CARD_STATUS_NOT_NORMAL,
                 "卡片状态不正确",
                 MessageFormat.format("流水号或用户Id或物理卡号为[{0}]的卡片状态不正确,不允许交易", checkParam));
 
@@ -507,43 +547,31 @@ public class CommonCheck {
     }
 
     /**
-     * 创建错误响应对象
+     * 设置校验后的账户与卡片数据。
+     * <p>
+     * 该方法用于在校验完成后,将相关的账户信息和卡片信息设置到业务对象中,以便后续的业务逻辑使用。
      *
-     * @param code   错误码
-     * @param type   错误类型
-     * @param msg    错误消息
-     * @param detail 错误详情
-     * @return 封装好的错误响应
+     * @param ctx 消费有效性校验上下文,包含消费相关的基础信息
      */
-    public R<ErrorInfo> createErrorResponse(int code, String type, String msg, String detail) {
-        ErrorInfo errorInfo = new ErrorInfo(code, type, msg, detail);
-        return R.fail(errorInfo);
-    }
+    private void setCheckAfterAccountData(AllowConsumeValidationContext ctx) {
+        RemoteUserAccountVo accountVo = ctx.getUserAccountVo();
+        RemoteCardVo cardVo = ctx.getUserCardVo();
+        // 重置部部分卡片信息
+        ctx.getBo().setCardNo(cardVo.getCardNo());
+        ctx.getBo().setFactoryId(cardVo.getFactoryId());
+        ctx.getBo().setCardTypeName(cardVo.getCardTypeName());
 
-    /**
-     * 创建错误响应对象
-     *
-     * @param status 交易状态
-     * @return 封装好的错误响应
-     */
-    public R<ErrorInfo> createError(TradeStatusEnum status) {
-        return createError(status, status.getName());
+        // 重置部分账户信息
+        ctx.getBo().setUserId(accountVo.getUserId());
+        ctx.getBo().setRealName(StrUtil.isEmpty(accountVo.getRealName()) ? "----" : accountVo.getRealName());
+        ctx.getBo().setUserNo(accountVo.getUserNo());
+        ctx.getBo().setUserNumb(accountVo.getUserNumb());
+        ctx.getBo().setTenantId(accountVo.getTenantId());
+        ctx.getBo().setExpireDate(accountVo.getLifespan());
+        ctx.getBo().setDeptName(accountVo.getDeptName());
     }
 
-    /**
-     * 创建错误响应对象
-     *
-     * @param status  交易状态
-     * @param message 错误消息
-     * @return 封装好的错误响应
-     */
-    public R<ErrorInfo> createError(TradeStatusEnum status, String message) {
-        return R.fail(new ErrorInfo(
-            400,
-            status.toString(),
-            message,
-            status.getName()));
-    }
+    // endregion
 
     /**
      * 根据消费日期获取对应的餐次类型。
@@ -587,48 +615,10 @@ public class CommonCheck {
         Long mealType = bo.getMealType();
         Date consumeDate = bo.getConsumeDate();
         BigDecimal consumeMoney = bo.getConsumeMoney();
-        String currentDateStr = DateFormatUtils.format(new Date(), "yyyy-MM-dd");
-        String consumeDateStr = DateFormatUtils.format(consumeDate, "yyyy-MM-dd");
-        Long userId = bo.getUserId();
-        BigDecimal balance = bo.getBalance();
-
-        // 将当笔原始消费记录标识放入缓存,1天过期
-        Set<String> originalId = Collections.singleton(bo.getOriginalId());
-        RedisUtils.setCacheSet(CacheNames.XF_ORIGINAL_ID, originalId);
-        RedisUtils.expire(CacheNames.XF_ORIGINAL_ID, Duration.ofHours(5));
 
-        // if (ObjectUtil.equals(currentDateStr, consumeDateStr)) {
         // 重置卡天当日消费数据
-        taskExecutor.submit(() -> baseBusiness.resetCardConsumeInfo(userCardVo, mealType, consumeMoney, consumeDate));
+        threadPoolTaskExecutor.execute(() -> baseBusiness.resetCardConsumeInfo(userCardVo, mealType, consumeMoney, consumeDate));
         // 重置卡片当日限制数据
-        taskExecutor.submit(() -> baseBusiness.restCardLimitedInfo(mapCardLimited, cardLimitedVo, consumeMoney));
-        // 重置人员当日总卡余
-        // taskExecutor.submit(() -> baseBusiness.resetUserBalance(userId, balance));
-        // }
-    }
-
-    /**
-     * 设置校验后的账户与卡片数据。
-     * <p>
-     * 该方法用于在校验完成后,将相关的账户信息和卡片信息设置到业务对象中,以便后续的业务逻辑使用。
-     *
-     * @param ctx 消费有效性校验上下文,包含消费相关的基础信息
-     */
-    private void setCheckAfterAccountData(AllowConsumeValidationContext ctx) {
-        RemoteUserAccountVo accountVo = ctx.getUserAccountVo();
-        RemoteCardVo cardVo = ctx.getUserCardVo();
-        // 重置部部分卡片信息
-        ctx.getBo().setCardNo(cardVo.getCardNo());
-        ctx.getBo().setFactoryId(cardVo.getFactoryId());
-        ctx.getBo().setCardTypeName(cardVo.getCardTypeName());
-
-        // 重置部分账户信息
-        ctx.getBo().setUserId(accountVo.getUserId());
-        ctx.getBo().setRealName(StrUtil.isEmpty(accountVo.getRealName()) ? "----" : accountVo.getRealName());
-        ctx.getBo().setUserNo(accountVo.getUserNo());
-        ctx.getBo().setUserNumb(accountVo.getUserNumb());
-        ctx.getBo().setTenantId(accountVo.getTenantId());
-        ctx.getBo().setExpireDate(accountVo.getLifespan());
-        ctx.getBo().setDeptName(accountVo.getDeptName());
+        threadPoolTaskExecutor.execute(() -> baseBusiness.restCardLimitedInfo(mapCardLimited, cardLimitedVo, consumeMoney));
     }
 }

+ 286 - 75
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/ConsumeRequestCheck.java

@@ -1,5 +1,7 @@
 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 lombok.RequiredArgsConstructor;
@@ -14,6 +16,7 @@ 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;
@@ -28,11 +31,12 @@ 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.CountDownLatch;
+import java.util.concurrent.*;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Supplier;
 
@@ -49,11 +53,12 @@ import java.util.function.Supplier;
 @Service
 @RequiredArgsConstructor
 public class ConsumeRequestCheck {
-    private final ThreadPoolTaskExecutor taskExecutor;
+    private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
     private final CommonCheck commonCheck;
     private final CardConsumeValidation cardConsumevalidation;
     private final BaseBusiness baseBusiness;
     private final IPtBagService bagService;
+    private final ParallelTaskExecutor taskExecutor;
 
     public R<ErrorInfo> checkConsume(ConsumptionBo bo, RemoteUserAccountVo userAccountVo,
                                      RemoteCardVo userCardVo, XfTermVo useTermVo,
@@ -72,19 +77,22 @@ public class ConsumeRequestCheck {
             return result;
         }
         // 获取餐类
+        long startTime = System.currentTimeMillis();
         RemoteMealTypeVo remoteMealTypeVo = commonCheck.getMealType(consumeDate);
+        log.info("[请求交易]-[获取餐类当前餐类]-[耗时:{} ms] ", System.currentTimeMillis()-startTime);
         if (ObjectUtil.isEmpty(remoteMealTypeVo)) {
-            return commonCheck.createErrorResponse(400, ApiErrorTypeConstants.NOT_FOUND, "不在交易时段",
-                                                   MessageFormat.format("非营业时段,不允许交易。消费时间[{0}]",
-                                                                        DateUtil.format(consumeDate, "HH:mm:ss")));
+            return CheckError.createErrorResponse(400, ApiErrorTypeConstants.NOT_FOUND, "不在交易时段",
+                MessageFormat.format("非营业时段,不允许交易。消费时间[{0}]",
+                    DateUtil.format(consumeDate, "HH:mm:ss")));
         }
         // 设置消费记录的当前餐类
         bo.setMealType(Long.valueOf(remoteMealTypeVo.getTypeId()));
+
         // 如果消费记录标识为消费机消费则需要进行限次、限额和折扣验证
         XfCardLimitedVo xfCardLimitedVo = new XfCardLimitedVo();
         int statusFlag = bo.getStatusFlag();
         if (statusFlag == Integer.parseInt(ConsumeRecordTypeEnum.XFJXF_1.code())
-                || statusFlag == Integer.parseInt(ConsumeRecordTypeEnum.XFJXF_4.code())) {
+            || statusFlag == Integer.parseInt(ConsumeRecordTypeEnum.XFJXF_4.code())) {
 
             // 设备是否可以消费验证
             result = checkTermLimitedAndOther(bo, useTermVo, userCardVo, remoteMealTypeVo);
@@ -97,17 +105,28 @@ public class ConsumeRequestCheck {
                 return obtainResult(result);
             }
         }
-
         // 余额校验,余额不足不能交易,如果有折扣,则消费金额是以折扣金额为准的,所以余额验证放在最后
         result = checkWalletBalance(bo);
         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();
     }
 
-    private  R<ErrorInfo> obtainResult(R<ErrorInfo> result){
+    private R<ErrorInfo> obtainResult(R<ErrorInfo> result) {
         ErrorInfo data = result.getData();
         String details = Optional.ofNullable(data).map(ErrorInfo::getDetils).orElse("超过消费限制");
         String msg = Optional.ofNullable(data).map(ErrorInfo::getMessage).orElse("消费限制判断存在问题");
@@ -144,20 +163,22 @@ public class ConsumeRequestCheck {
      * @return 如果校验失败,则返回包含错误信息的 R 对象;如果校验成功,则返回表示成功的 R 对象
      */
     private R<ErrorInfo> checkWalletBalance(ConsumptionBo bo) {
-        String userIdStr = bo.getUserId().toString();
+        long startTime = System.currentTimeMillis();
         BigDecimal consumeMoney = bo.getConsumeMoney();
         BigDecimal totalBalance = bagService.getUserTotalBalance(bo.getUserId());
         if (ObjectUtil.isEmpty(totalBalance)) {
             totalBalance = BigDecimal.ZERO;
         }
         if (consumeMoney.compareTo(totalBalance) > 0) {
-            return commonCheck.createErrorResponse(400, ApiErrorTypeConstants.CONSUME_CHECK_FAIL, "账户余额不足",
-                                                   MessageFormat.format("总余额[{0}],消费金额[{1}]", totalBalance, consumeMoney));
+            log.info("[请求交易]-[钱包余额验证完成]-[耗时:{} ms]",System.currentTimeMillis()-startTime);
+            return CheckError.createErrorResponse(400, ApiErrorTypeConstants.CONSUME_CHECK_FAIL, "账户余额不足",
+                MessageFormat.format("总余额[{0}],消费金额[{1}]", totalBalance, consumeMoney));
         }
         // 计算扣费后的余额
         BigDecimal balance = totalBalance.subtract(consumeMoney);
         bo.setBalance(balance);
-        baseBusiness.resetUserBalance(bo.getUserId(),balance);
+        baseBusiness.resetUserBalance(bo.getUserId(), balance);
+        log.info("[请求交易]-[钱包余额验证完成]-[耗时:{} ms]",System.currentTimeMillis()-startTime);
         return R.ok();
     }
 
@@ -172,32 +193,50 @@ public class ConsumeRequestCheck {
      * @return 如果原始消费记录ID重复,返回包含错误信息的响应对象;否则返回成功的响应对象
      */
     private R<ErrorInfo> checkRepeatOriginalId(ConsumptionBo bo) {
-        Set<String> originalIdSet = RedisUtils.getCacheSet(CacheNames.XF_ORIGINAL_ID);
-
+        long startTime = System.currentTimeMillis();
         String originalId = bo.getOriginalId();
         if (ObjectUtil.isEmpty(originalId)) {
-            originalId = RecordIdUtils.getRecordId(bo.getConsumeDate(), bo.getTermNo().shortValue(),
-                                                   bo.getTermRecordId().shortValue(), bo.getUserNo().shortValue(), 0);
+            originalId = RecordIdUtils.getRecordId(bo.getConsumeDate(), bo.getTermNo().intValue(),
+                bo.getTermRecordId().intValue(), bo.getUserNo().intValue(), 0);
         }
-        if (originalIdSet.contains(originalId)) {
-            return commonCheck.createErrorResponse(400, ApiErrorTypeConstants.CONSUME_CHECK_FAIL, "原始消费记录存在",
-                                                   MessageFormat.format("原始消费记录已存在:{0}", originalId));
+
+        String keyName = String.format("%s:%s", CacheNames.XF_ORIGINAL_ID, originalId);
+        if (RedisUtils.hasKey(keyName)) {
+            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);
         return R.ok();
     }
 
     private R<ErrorInfo> checkTermLimitedAndOther(ConsumptionBo bo, XfTermVo termVo, RemoteCardVo userCardVo, RemoteMealTypeVo mealTypeVo) {
-        // 1. 前置检查与数据初始化
+        // 1. 如果机号为0并且餐类为0,直接返回成功
         if (isSpecialScenario(termVo, mealTypeVo)) return R.ok();
-        initializeCardData(userCardVo, mealTypeVo);
 
+        // 2.初始化卡片的消费信息
+        long startTime = System.currentTimeMillis();
+        initializeCardData(userCardVo, mealTypeVo);
         // 2. 准备验证上下文
         TermConsumeValidationContext validationContext = TermConsumeValidationContext.create(bo, termVo, userCardVo, mealTypeVo);
 
         // 3. 执行异步验证链
-        return executeTermValidationChain(validationContext);
+        List<ValidationTaskDes<R<ErrorInfo>>> tasks = List.of(
+            new ValidationTaskDes<>("设备消费间隔验证", () -> validateSwipeInterval(validationContext)),
+            new ValidationTaskDes<>("设备单次金额验证", () -> validateSingleLimit(validationContext)),
+            new ValidationTaskDes<>("设备卡片有效验证", () -> validateCardValidity(validationContext)),
+            new ValidationTaskDes<>("设备消费卡类验证", () -> validateCardType(validationContext)),
+            new ValidationTaskDes<>("设备每餐限制验证", () -> validateMealLimit(validationContext)),
+            new ValidationTaskDes<>("设备每日限制验证", () -> validateDailyLimit(validationContext))
+        );
+        R<ErrorInfo>result =  taskExecutor.executeParallelTasks(tasks, 5, TimeUnit.SECONDS);
+        log.info("[请求交易]-[设备消费验证完成]-[耗时:{} ms]",System.currentTimeMillis()-startTime);
+
+        return result;
     }
 
     /**
@@ -261,56 +300,233 @@ public class ConsumeRequestCheck {
      * @return 如果校验成功,返回表示成功的 R 对象;如果校验失败,返回包含错误信息的 R 对象
      */
     private R<ErrorInfo> executeTermValidationChain(TermConsumeValidationContext context) {
-        // 创建验证任务列表
-        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));
-
+        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);
 
-        // 使用CountDownLatch跟踪任务完成
-        CountDownLatch latch = new CountDownLatch(validationTasks.size());
-
-        // 提交所有验证任务
-        for (Supplier<R<ErrorInfo>> task : validationTasks) {
-            taskExecutor.execute(() -> {
-                try {
-                    R<ErrorInfo> result = task.get();
-                    // 如果发现错误且尚未设置错误结果
-                    if (result != null && R.isError(result) &&
-                            firstError.compareAndSet(null, result)) {
-                        // 取消其他任务(通过中断)
-                        taskExecutor.getThreadPoolExecutor().getQueue().clear();
-                    }
-                } catch (Exception e) {
-                    if (firstError.compareAndSet(null, commonCheck.createError(TradeStatusEnum.SysError))) {
-                        taskExecutor.getThreadPoolExecutor().getQueue().clear();
-                    }
-                } finally {
-                    latch.countDown();
-                }
-            });
+        // 所有任务的结果
+        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 {
-            // 等待所有任务完成或超时
-            //if (!latch.await(300, TimeUnit.MILLISECONDS)) {
-            //    return commonCheck.createError(TradeStatusEnum.VALIDATION_TIMEOUT);
-            //}
-            latch.await();
-
-            // 返回第一个发现的错误,如果没有错误则返回成功
-            return firstError.get() != null ? firstError.get() : R.ok();
+            // 阻塞主线程,等待执行结果; latch 为0时,会唤醒主线程,最多阻塞10s
+            finallyResult = latch.await(10, TimeUnit.SECONDS);
         } catch (InterruptedException e) {
+            log.error("executeTermValidationChain- main 被中断 :{}", e.getMessage());
             Thread.currentThread().interrupt();
-            return commonCheck.createError(TradeStatusEnum.SysError);
+            // 取消所有任务
+            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) {
@@ -321,43 +537,38 @@ public class ConsumeRequestCheck {
         long intervalMin = (currentSec - lastPaySec) / 60;
 
         return intervalMin < ctx.getTermSwipeInterval() ?
-                   commonCheck.createError(TradeStatusEnum.TimeInterval) : null;
+            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 ?
-                   commonCheck.createError(TradeStatusEnum.OnceBigMoney) : null;
+            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()) ?
-                   commonCheck.createError(TradeStatusEnum.CardValidDate) : null;
+            CheckError.createError(TradeStatusEnum.CardValidDate) : null;
     }
-
     private R<ErrorInfo> validateCardType(TermConsumeValidationContext ctx) {
         int mask = 1 << (ctx.getCardType() - 1);
         return (mask & ctx.getTermCardType()) == 0 ?
-                   commonCheck.createError(TradeStatusEnum.CardTypeLimit) : null;
+            CheckError.createError(TradeStatusEnum.CardTypeLimit) : null;
     }
-
     private R<ErrorInfo> validateMealLimit(TermConsumeValidationContext ctx) {
         return ctx.getTermMealCount() > 0 && ctx.getMealCount() >= ctx.getTermMealCount() ?
-                   commonCheck.createError(TradeStatusEnum.MealLimitTimes) : null;
+            CheckError.createError(TradeStatusEnum.MealLimitTimes) : null;
     }
-
     private R<ErrorInfo> validateDailyLimit(TermConsumeValidationContext ctx) {
         // 日限次验证
         if (ctx.getTermDayCount() > 0 && ctx.getDayCount() >= ctx.getTermDayCount()) {
-            return commonCheck.createError(TradeStatusEnum.DayLimitTimes);
+            return CheckError.createError(TradeStatusEnum.DayLimitTimes);
         }
 
         // 日限额验证
         if (ctx.getTermDayMoney().compareTo(BigDecimal.ZERO) > 0) {
             BigDecimal total = ctx.getDayValue().add(ctx.getConsumeValue());
             if (total.compareTo(ctx.getTermDayMoney()) > 0) {
-                return commonCheck.createError(TradeStatusEnum.DayLimitMoney);
+                return CheckError.createError(TradeStatusEnum.DayLimitMoney);
             }
         }
         return null;

+ 16 - 12
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/ConsumeUploadCheck.java

@@ -63,15 +63,19 @@ 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, "消费记录已上传",
+        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)) {
+            return CheckError.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(5));
+
+
+        RedisUtils.setCacheMapValue(CacheNames.XF_DETAIL_ID, detailId,detailId);
+        RedisUtils.expire(CacheNames.XF_DETAIL_ID, Duration.ofHours(2));
 
         R<ErrorInfo> result = checkOriginalRecord(bo, userAccountVo);
         if (R.isError(result)) {
@@ -100,9 +104,9 @@ 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);
-        //  补偿性措施:防止消费时间错乱问题(在线模式)
+        String originalId = RecordIdUtils.getRecordId(bo.getConsumeDate(), bo.getTermNo().intValue(),
+            bo.getTermRecordId().intValue(), userAccountVo.getUserNo().intValue(), 0);
+           //  补偿性措施:防止消费时间错乱问题(在线模式)
         int recordType = bo.getRecordStatus().intValue();
         long uniqueRecordId = bo.getRecordId();
         bo.setOriginalId(originalId);
@@ -262,8 +266,8 @@ public class ConsumeUploadCheck {
                 if (bo.getRecordId() == 0) {
                     if (bo.getConsumeDate().getTime() > currentDate.getTime()) {
                         bo.setConsumeDate(currentDate);
-                        originalId = RecordIdUtils.getRecordId(bo.getConsumeDate(), bo.getTermNo().shortValue(),
-                            bo.getTermRecordId().shortValue(), bo.getUserNo().intValue(), 0);
+                        originalId = RecordIdUtils.getRecordId(bo.getConsumeDate(), bo.getTermNo().intValue(),
+                            bo.getTermRecordId().intValue(), bo.getUserNo().intValue(), 0);
                         bo.setOriginalId(originalId);
                     }
                     vo = new XfConsumeDetailOriginalVo();

+ 15 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/IValidationTask.java

@@ -0,0 +1,15 @@
+package org.dromara.server.consume.check;
+
+/**
+ * 通用消费校验任务接口
+ * <p>
+ * [功能说明]
+ * ${1:在此简要描述文件核心功能}
+ *
+ * @author luoyibo
+ * @date 2025-07-13
+ * @since JDK17
+ */
+public interface IValidationTask<T> {
+    T execute() throws Exception;
+}

+ 162 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/ParallelTaskExecutor.java

@@ -0,0 +1,162 @@
+package org.dromara.server.consume.check;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.domain.model.ErrorInfo;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * 并行任务执行器
+ * <p>
+ * [功能说明]
+ * ${1:在此简要描述文件核心功能}
+ *
+ * @author luoyibo
+ * @date 2025-07-13
+ * @since JDK17
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class ParallelTaskExecutor {
+    private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
+
+    public <T> R<ErrorInfo> executeParallelTasks(
+        List<ValidationTaskDes<T>> taskDescriptors,
+        long timeout,
+        TimeUnit unit
+    ) {
+        final int totalTasks = taskDescriptors.size();
+        final AtomicBoolean hasError = new AtomicBoolean(false);
+        final AtomicReference<R<ErrorInfo>> firstError = new AtomicReference<>();
+        final long deadline = System.nanoTime() + unit.toNanos(timeout);
+        final ExecutorCompletionService<T> completionService = new ExecutorCompletionService<>(threadPoolTaskExecutor);
+
+
+        // 用于跟踪任务名称
+        Map<Future<T>, String> futureToName = new IdentityHashMap<>();
+        List<Future<?>> allFutures = new ArrayList<>();
+
+        // 提交任务
+        for (ValidationTaskDes<T> descriptor : taskDescriptors) {
+            String taskName = descriptor.getName();
+            Callable<T> task = createLoggedTask(taskName, descriptor.getTask(), hasError);
+            Future<T> future = completionService.submit(task);
+            futureToName.put(future, taskName);
+            allFutures.add(future);
+        }
+
+        try {
+            int completedTasks = 0;
+            while (completedTasks < totalTasks && !hasError.get()) {
+                long remainingNanos = deadline - System.nanoTime();
+                if (remainingNanos <= 0) {
+                    if (firstError.compareAndSet(null, CheckError.createTimeoutError())) {
+                        cancelPendingFutures(allFutures);
+                    }
+                    break;
+                }
+
+                Future<T> future = completionService.poll(remainingNanos, TimeUnit.NANOSECONDS);
+                if (future == null) {
+                    if (firstError.compareAndSet(null, CheckError.createTimeoutError())) {
+                        cancelPendingFutures(allFutures);
+                    }
+                    break;
+                }
+
+                completedTasks++;
+                String taskName = futureToName.get(future);
+
+                try {
+                    // 避免长时间阻塞,添加超时时间
+                    T result = future.get(5L, TimeUnit.SECONDS);
+                    if (result instanceof R && ((R<?>) result).getCode()!=200) {
+                        @SuppressWarnings("unchecked")
+                        R<ErrorInfo> errorResult = (R<ErrorInfo>) result;
+                        if (firstError.compareAndSet(null, errorResult)) {
+                            cancelPendingFutures(allFutures);
+                        }
+                    }
+                } catch (ExecutionException e) {
+                    if (firstError.compareAndSet(null, CheckError.createExecutionError(taskName, e.getCause()))) {
+                        cancelPendingFutures(allFutures);
+                    }
+                } catch (CancellationException e) {
+                    // 忽略已取消的任务
+                    log.info("忽略已取消的任务");
+                } catch (TimeoutException e) {
+                    // 获取单个任务执行5s超时,立即结束
+                    firstError.compareAndSet(null, CheckError.createTimeoutError());
+                    cancelPendingFutures(allFutures);
+                    break;
+                }
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            if (firstError.compareAndSet(null, CheckError.createInterruptionError())) {
+                cancelPendingFutures(allFutures);
+            }
+        } finally {
+            if (firstError.get() == null && allFutures.stream().anyMatch(f -> !f.isDone())) {
+                cancelPendingFutures(allFutures);
+            }
+        }
+
+        return firstError.get() != null ? firstError.get() : R.ok();
+    }
+
+    /**
+     * 创建带日志和中断检查的任务
+     */
+    private <T> Callable<T> createLoggedTask(String taskName, IValidationTask<T> task, AtomicBoolean hasError) {
+        return () -> {
+            if (hasError.get()) {
+                throw new CancellationException("已有其他任务返回false,取消执行任务");
+            }
+
+            if (Thread.currentThread().isInterrupted()) {
+                throw new CancellationException("线程已中断");
+            }
+
+            final long startTime = System.currentTimeMillis();
+            log.info("{}开始执行", taskName);
+
+            try {
+                T result = task.execute();
+
+                // 检查执行过程中是否被中断
+                if (Thread.currentThread().isInterrupted()) {
+                    throw new InterruptedException("任务执行中被取消");
+                }
+
+                return result;
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                log.warn("{}执行中被取消", taskName);
+                throw new CancellationException("线程已中断");
+            } finally {
+                long duration = System.currentTimeMillis() - startTime;
+                log.info("{}结束,耗时:{}ms", taskName, duration);
+            }
+        };
+    }
+
+    private void cancelPendingFutures(List<Future<?>> futures) {
+        futures.forEach(f -> {
+            if (!f.isDone()) {
+                f.cancel(true);
+            }
+        });
+    }
+}

+ 21 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/check/ValidationTaskDes.java

@@ -0,0 +1,21 @@
+package org.dromara.server.consume.check;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * 任务描述器,封装任务名称和实际的任务逻辑
+ * <p>
+ * [功能说明]
+ * ${1:在此简要描述文件核心功能}
+ *
+ * @author luoyibo
+ * @date 2025-07-13
+ * @since JDK17
+ */
+@Data
+@AllArgsConstructor
+public class ValidationTaskDes <T>{
+    private final String name;
+    private final IValidationTask<T> task;
+}

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

@@ -176,7 +176,7 @@ public class ConsumeController {
             if (Objects.equals(type, "requestConsume")) {
                 errorInfo = consumeBusiness.createOrder(bo, mac, xfPwd);
             } else if (Objects.equals(type, "uploadRecord")) {
-                threadPoolTaskExecutor.submit(() -> consumeBusiness.postOrderAsync(bo, mac, xfPwd));
+                threadPoolTaskExecutor.execute(() -> consumeBusiness.postOrderAsync(bo, mac, xfPwd));
                 errorInfo = R.ok();
             } else {
                 errorInfo = consumeBusiness.fullOrder(bo, mac, xfPwd);

+ 3 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/IPtBagService.java

@@ -119,5 +119,8 @@ public interface IPtBagService {
     List<PtBagVo> selectBalanceBag();
 
     BigDecimal cacheUserTotalBalance(Long userId, BigDecimal balance);
+
+    String getUserSecretKeyByUserId(Long userId);
+
     // boolean updateBalanceByByConsume(PtBagBo bo);
 }

+ 8 - 0
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/PtBagServiceImpl.java

@@ -19,6 +19,7 @@ import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.encrypt.utils.YcEncryptUtil;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.redis.utils.RedisUtils;
 import org.dromara.common.tenant.helper.TenantHelper;
 import org.dromara.server.consume.domain.PtBag;
 import org.dromara.server.consume.domain.bo.PtBagBo;
@@ -347,6 +348,13 @@ public class PtBagServiceImpl implements IPtBagService {
     public BigDecimal cacheUserTotalBalance(Long userId, BigDecimal balance) {
         return balance;
     }
+
+    @Override
+    @CachePut(cacheNames = CacheNames.USER_SECRET_KEY, key = "#userId")
+    public String getUserSecretKeyByUserId(Long userId) {
+        return YcEncryptUtil.getBalanceSecretKey(RedisUtils.getCacheObject(CacheNames.CUSTOM_PUB_KEY).toString(), userId.toString());
+    }
+
     /**
      * 组装充值钱包数据
      * 1.设置充值后余额=账户原余额+充值金额

+ 0 - 1
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/service/impl/XfCardLimitedServiceImpl.java

@@ -334,7 +334,6 @@ public class XfCardLimitedServiceImpl implements IXfCardLimitedService {
         } else {
             baseMapper.insert(entity);
         }
-
     }
 
     @Override

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

@@ -38,7 +38,7 @@ public class InitTasks implements ApplicationRunner {
         // initBusiness.initDiscountAndOther();
         // initBusiness.initUserCard();
         // initBusiness.initUserAccount();
-        // initBusiness.initXfCardLimited();
+        initBusiness.initXfCardLimited();
         // initBusiness.initUserBalance();
         validationParam.refresh();
 

+ 18 - 17
ruoyi-server/ruoyi-server-consume/src/main/java/org/dromara/server/consume/task/ScheduledTasks.java

@@ -55,18 +55,18 @@ 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 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() {
@@ -76,11 +76,12 @@ public class ScheduledTasks {
         baseBusiness.initMealTypeInfo();
         validationParam.refresh();
     }
-    @Scheduled(cron = "0 0 5,9,13,16,20 * * ?")
+
+    @Scheduled(cron = "0 0 5,9,10,13,16,20 * * ?")
     public void initConsumeInfo() {
-        ThreadUtil.execAsync(baseBusiness::initXfCardLimited);
-        ThreadUtil.execAsync(baseBusiness::initUserCard);
-        ThreadUtil.execAsync(baseBusiness::initUserAccount);
-        ThreadUtil.execAsync(baseBusiness::initUserBalance);
+        baseBusiness.initXfCardLimited();
+        baseBusiness.initUserCard();
+        baseBusiness.initUserAccount();
+        baseBusiness.initUserBalance();
     }
 }

+ 176 - 0
ruoyi-server/ruoyi-server-face/pom.xml

@@ -0,0 +1,176 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-server</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-server-face</artifactId>
+
+    <description>
+        ruoyi-service-face 一卡通人脸服务
+    </description>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-nacos</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-sentinel</artifactId>
+        </dependency>
+
+        <!-- RuoYi Common Log -->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-log</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-dict</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-doc</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-mybatis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-dubbo</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-seata</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-idempotent</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-tenant</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-security</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-translation</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-sensitive</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-encrypt</artifactId>
+        </dependency>
+
+        <!-- RuoYi Api System -->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-api-system</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-api-backstage</artifactId>
+        </dependency>
+         <dependency>
+            <groupId>cn.com.kingbase</groupId>
+            <artifactId>kingbase8</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-server-base</artifactId>
+            <version>2.2.0</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-api-consume</artifactId>
+            <version>2.2.0</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.kafka</groupId>
+            <artifactId>kafka-clients</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.kafka</groupId>
+            <artifactId>spring-kafka</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.arcsoft.face</groupId>
+            <artifactId>arcsoft-sdk-face</artifactId>
+            <version>4.1.1.0</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.14</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.27</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-fileupload</groupId>
+            <artifactId>commons-fileupload</artifactId>
+            <version>1.4</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.6</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>${project.artifactId}</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>${spring-boot.version}</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 26 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/RuoYiServerFaceApplication.java

@@ -0,0 +1,26 @@
+package org.dromara.server.hik;
+
+import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+/**
+ * 系统模块
+ *
+ * @author ruoyi
+ */
+@EnableDubbo
+@EnableAsync
+@EnableScheduling
+@SpringBootApplication
+public class RuoYiServerFaceApplication {
+    public static void main(String[] args) {
+        SpringApplication application = new SpringApplication(RuoYiServerFaceApplication.class);
+        application.setApplicationStartup(new BufferingApplicationStartup(2048));
+        application.run(args);
+        System.out.println("(♥◠‿◠)ノ゙  人脸服务启动成功   ლ(´ڡ`ლ)゙  ");
+    }
+}

+ 53 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/constant/ErrCodeConstants.java

@@ -0,0 +1,53 @@
+package org.dromara.server.hik.constant;
+
+/**
+ * 错误码
+ * <p>
+ * 错误码定义
+ *
+ * @author luoyibo
+ * @version 2.2.0
+ * @date 2025-05-21
+ * @since JDK17
+ */
+public interface ErrCodeConstants {
+    /**
+     * ok
+     */
+    Integer OK = 0;
+
+    /**
+     * ok
+     */
+    Integer YES = 1;
+
+    /**
+     * 设备繁忙
+     */
+    Integer DEVICE_BUSY = 2;
+
+    /**
+     * 设备错误
+     */
+    Integer DEVICE_ERROR = 3;
+
+    /**
+     * 无效操作
+     */
+    Integer INVALID_OPERATION = 4;
+
+    /**
+     * 无效XML格式
+     */
+    Integer INVALID_XML_FORMAT = 5;
+
+    /**
+     * 无效XML内容
+     */
+    Integer INVALID_XML_CONTENT = 6;
+
+    /**
+     * 设备需要重启
+     */
+    Integer REBOOT_REQUIRED = 7;
+}

+ 40 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/constant/HikApiConstants.java

@@ -0,0 +1,40 @@
+package org.dromara.server.hik.constant;
+
+/**
+ * api接口
+ * <p>
+ * 访问设备的api接口定义
+ *
+ * @author luoyibo
+ * @version 2.2.0
+ * @date 2025-05-21
+ * @since JDK17
+ */
+public interface HikApiConstants {
+
+    /**
+     * POST 向设备下发人员、卡片、照片信息
+     */
+    String SEND_EMP_INFO = "/ISAPI/AccessControl/userInfoAndRight/setup?format=json";
+
+    /**
+     * POST 从设备查询所有 人员信息
+     */
+    String QUERY_EMP_ALL = "/ISAPI/AccessControl/userInfoAndRight/searchList?format=json";
+
+    /**
+     * GET 给设备配置http监听的IP和端口
+     */
+    String SET_HTTP_HOSTS = "/ISAPI/Event/notification/httpHosts";
+
+
+    /**
+     * PUT 确认交易记录; 应答 ConsumptionEvent
+     */
+   String Consumption_Event_confirm = "/ISAPI/Consume/consumptionEventConfirm?format=json";
+
+    /**
+     * PUT 确认交易记录; 应答TransactionRecordEvent
+     */
+    String TRANSACTION_RECORD_Event_confirm = "/ISAPI/Consume/transactionRecordEventConfirm?format=json";
+}

+ 16 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/constant/HikDefaultConstants.java

@@ -0,0 +1,16 @@
+package org.dromara.server.hik.constant;
+
+/**
+ * 海康默认配置
+ * <p>
+ * 海康默认配置
+ *
+ * @author luoyibo
+ * @version 2.2.0
+ * @date 2025-05-23
+ * @since JDK17
+ */
+public interface HikDefaultConstants {
+
+    String EMP_END_TIME = "2037-12-31 23:59:59";
+}

+ 12 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/constant/HikEventTypeConstant.java

@@ -0,0 +1,12 @@
+package org.dromara.server.hik.constant;
+
+public interface HikEventTypeConstant {
+
+    // 交易记录事件
+    String TransactionRecordEvent = "TransactionRecordEvent";
+
+    // 交易请求事件   minor:transactionConfirmingRequest 为 交易确认请求事件 minor:transactionPreprocessingRequest 为 交易预处理请求事件
+    String ConsumptionEvent = "ConsumptionEvent";
+
+    String HeartBeat = "heartBeat";
+}

+ 482 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/controller/TestController.java

@@ -0,0 +1,482 @@
+package org.dromara.server.hik.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.ObjectUtil;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.Part;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.IOUtils;
+import org.apache.dubbo.config.annotation.DubboReference;
+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.CreditTypeEnum;
+import org.dromara.consume.api.RemoteConsumeService;
+import org.dromara.consume.api.domain.bo.RemoteConsumeBo;
+import org.dromara.consume.api.domain.bo.RemoteResultDto;
+import org.dromara.server.hik.domain.dto.DeviceDto;
+import org.dromara.server.hik.domain.dto.QueryDto;
+import org.dromara.server.hik.domain.dto.UploadEmpDto;
+import org.dromara.server.hik.event.EventHandleRouter;
+import org.dromara.server.hik.event.domain.FileContent;
+import org.dromara.server.hik.event.timedtask.HandleTask;
+import org.dromara.server.hik.service.ISendDeviceService;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.StreamUtils;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 海康消费机
+ * <p>
+ * 海康消费机对接测试控制器
+ *
+ * @author luoyibo
+ * @version 2.2.0
+ * @date 2025-05-21
+ * @since JDK17
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/hik/test")
+@SaIgnore
+@Slf4j
+public class TestController {
+    private final ISendDeviceService sendDeviceService;
+
+    private final EventHandleRouter eventHandleRouter;
+
+    private final HandleTask handleTask;
+
+    @DubboReference
+    private final RemoteConsumeService remoteConsumeService;
+
+    //region 设备监听相关
+    /**
+     * 设置指定设备监听服务地址
+     * @param dto 设备信息
+     * @return 设置结果
+     */
+    @PostMapping("/set/hosts")
+    public R<Void> setHosts(@RequestBody DeviceDto dto) {
+        return sendDeviceService.setHttpHostByDto(dto);
+    }
+
+    /**
+     * 设置所有终端设备的监听服务地址
+     * @return 设置结果
+     */
+    @PostMapping("/set/hosts/all")
+    public R<Void> setHosts() {
+        return sendDeviceService.setHttpHostAll();
+    }
+
+    /**
+     * 设置指定终端设备的监听服务地址。
+     *
+     * @param termNo 终端编号,用于标识目标设备
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @PostMapping("/set/hosts/{termNo}")
+    public R<Void> setHosts(@PathVariable("termNo") Long termNo) {
+        return sendDeviceService.setHttpHostByTermNo(termNo);
+    }
+    //endregion
+
+    /**
+     * 查询设备上所有人员信息
+     * @param dto 设备信息
+     * @return 查询结果
+     */
+    @PostMapping("/query/emp/all")
+    public R<Void> queryEmpAll(@RequestBody QueryDto dto) {
+        return sendDeviceService.queryBatchEmpFormDevice(dto);
+    }
+
+    //region 人员删除相关
+    /**
+     * 删除指定终端设备上的指定员工信息。
+     * <p>
+     * 根据终端编号和用户编号,从对应的设备中删除指定员工的信息。
+     *
+     * @param termNo 终端编号,用于标识目标设备
+     * @param userId 用户编号,用于标识需要删除的员工信息
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @GetMapping("/emp/del/{termNo}/{userId}")
+    public R<Void> deleteEmpFromDevice(@PathVariable("termNo") Long termNo,@PathVariable("userId") Long userId) {
+        return sendDeviceService.deleteEmpFromDevice(termNo,userId);
+    }
+
+    /**
+     * 删除指定终端设备上的所有员工信息。
+     * <p>
+     * 根据终端编号,从对应的设备中删除
+        return sendDeviceService.deleteEmpFromDevice(termNo);
+    }
+
+    /**
+     * 删除指定终端设备上的员工信息。
+     * <p>
+     * 根据终端编号,从对应的设备中删除员工信息。该方法通过HTTP POST请求触发,
+     * 并将终端编号作为路径变量传递。
+     *
+     * @param termNo 终端编号,用于标识目标设备
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @GetMapping("/emp/del/{termNo}")
+    public R<Void> deleteEmpByFromDevice(@PathVariable("termNo") Long termNo) {
+        return sendDeviceService.deleteEmpFromDevice(termNo);
+    }
+
+    /**
+     * 从所有终端设备上删除指定员工信息
+     * 根据终端编号和用户编号,从所有的设备中删除指定员工的信息。
+     *
+      * @param userId 用户编号,用于标识需要删除的员工信息
+     *
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @GetMapping("/emp/del/all/{userId}")
+    public R<Void> deleteAllEmpByFromDevice(@PathVariable("userId") Long userId) {
+        return sendDeviceService.deleteOneEmpFromDevice(userId);
+    }
+
+    /**
+     * 删除所有设备上的所有员工
+     *
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @GetMapping("/emp/del/all")
+    public R<Void> deleteAllEmpByFromDevice() {
+        return sendDeviceService.deleteEmpFromDevice();
+    }
+    //endregion
+
+    //region 人员信息上传有关
+    /**
+     * 上传员工信息到指定设备。
+     * <p>
+     * 该方法接收包含员工信息和设备信息的数据传输对象,将员工信息上传至指定设备。
+     *
+     * @param uploadEmpDto 包含员工信息和设备信息的数据传输对象
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @PostMapping("/emp/upload")
+    public R<Void> uploadEmpToDevice(@RequestBody UploadEmpDto uploadEmpDto) {
+        return sendDeviceService.upLoadEmpToDevice(uploadEmpDto);
+    }
+
+    /**
+     * 上传指定员工信息到指定设备。
+     * <p>
+     * 根据终端编号和用户编号,将对应的员工信息上传至指定设备。
+     *
+     * @param termNo 终端编号,用于标识目标设备
+     * @param userId 用户编号,用于标识需要上传的员工信息
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @GetMapping("/emp/upload/{termNo}/{userId}")
+    public R<Void> upLoadEmpToDevice(@PathVariable("termNo") Long termNo,@PathVariable("userId") Long userId){
+        return sendDeviceService.upLoadEmpToDevice(termNo, userId);
+    }
+
+    /**
+     * 上传所有员工信息到指定设备。
+     * <p>
+     * 根据终端编号,将员工信息上传至指定设备。该方法通过HTTP GET请求触发,
+     * 并将终端编号作为路径变量传递。
+     *
+     * @param termNo 终端编号,用于标识目标设备
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @GetMapping("/emp/upload/all/{termNo}")
+    public R<Void> upLoadAllEmpToDevice(@PathVariable("termNo") Long termNo){
+        return sendDeviceService.upLoadEmpToDevice(termNo);
+    }
+
+    /**
+     * 上传指定员工信息到所有设备。
+     * <p>
+     * 根据用户编号,将对应的员工信息上传至设备。该方法通过HTTP GET请求触发,
+     * 并将用户编号作为路径变量传递。
+     *
+     * @param userId 用户编号,用于标识需要上传的员工信息
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @GetMapping("/emp/upload/{userId}")
+    public R<Void> upLoadEmpToDevice(@PathVariable("userId") Long userId){
+        return sendDeviceService.upLoadEmpToAllDevice(userId);
+    }
+
+    /**
+     *  通常不要调用此接口,不能同时给所有设备上传员工信息,只能给指定设备上传员工信息
+     * 上传所有员工信息到所有设备。
+     * <p>
+     * 该方法通过HTTP GET请求触发,将系统中的所有员工信息上传至所有关联的设备。
+     * 具体上传逻辑由服务层实现,返回操作结果的状态信息。
+     *
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @GetMapping("/emp/upload/all")
+    public R<Void> upLoadAllEmpToAllDevice(){
+        return sendDeviceService.upLoadEmpToDevice(true);
+    }
+    //endregion
+
+    /**
+     * 删除指定设备上某个用户的所有卡片信息。
+     * <p>
+     * 根据用户编号和设备信息,删除该用户在指定设备上的所有关联卡片数据。
+     *
+     * @param userNo 用户编号,用于标识需要删除卡片信息的用户
+     * @param device 设备信息,包含设备的详细配置数据
+     * @return 响应信息主体,包含操作结果的状态码、消息内容以及可能的附加数据
+     */
+    @PostMapping("/card/deleteAll/{userNo}")
+    public R<Void> deleteAllCardByUserNo(@PathVariable("userNo") String userNo, @RequestBody DeviceDto device) {
+        return sendDeviceService.deleteAllCardByUserNo(device,userNo);
+    }
+
+    /**
+     * 接收并处理发送到服务器的设备推送数据。
+     * 该方法处理不同类型的 content,包括 multipart/form-data 和 application/json。
+     * 对于 multipart/form-data,它分别处理 JSON 和 image/jpeg 部分。
+     * 对于 application/json,它直接处理 JSON 负载。
+     * 如果内容类型不受支持,则记录警告并返回 null。
+     *
+     * @param request 包含传入数据和元数据的 HttpServletRequest 对象。
+     *                它必须包含适合处理的内容类型和有效负载。
+     * @return 一个包含由 eventHandleRouter 路由的处理数据的 Map<String, Object>。
+     *         如果在处理过程中发生错误或内容类型不受支持,则返回 null。
+     */
+    @PostMapping(("/info"))
+    public Map<String, Object> receiveDevicePushData(HttpServletRequest request) {
+        try {
+            // 判断请求类型
+            List<String> jsonListString = new ArrayList<>();
+            List<FileContent> fileByteList = new ArrayList<>();
+            String s = request.getContentType().toLowerCase();
+            if (s.contains("multipart/form-data")) {
+                Collection<Part> parts = request.getParts();
+
+                for (Part part : parts) {
+                    String contentType = part.getContentType().toLowerCase();
+                    InputStream inputStream = part.getInputStream();
+                    switch (contentType) {
+                        case "application/json" -> {
+                            String jsonData = StreamUtils.copyToString(inputStream, Charset.defaultCharset());
+                            jsonListString.add(jsonData);
+                        }
+                        case "image/jpeg" -> {
+                            // 流转字节数组
+                            byte[] bytes = IOUtils.toByteArray(inputStream);
+                            fileByteList.add(new FileContent(".jpg", bytes));
+                            /*String image_dir_path = "C:\\image\\";
+                            File image_dir = new File(image_dir_path);
+                            if (!image_dir.exists()) {
+                                image_dir.mkdir();
+                            }
+                            String file_name = UUID.randomUUID().toString();
+                            File image_file = new File(image_dir_path + file_name + ".jpg");
+
+                            FileUtils.copyInputStreamToFile(inputStream, image_file);*/
+                        }
+                        default -> {
+                            log.warn("out contentType : multipart/form-data; unknown contentType " + contentType + " 跳过");
+                        }
+                    }
+
+                    /*MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
+
+                    multipartRequest.getParameterMap().forEach((fieldName, values) -> {
+                        System.out.println("字段 [" + fieldName + "] 值: " + String.join(", ", values));
+                    });
+
+                    multipartRequest.getFileMap().forEach((fieldName, file) -> {
+                        if (!file.isEmpty()) {
+                            String fileName = file.getOriginalFilename();
+                            try {
+                                file.transferTo(Path.of("f:/uploads", fileName));
+                            } catch (IOException e) {
+                                throw new RuntimeException("文件保存失败: " + fileName, e);
+                            }
+                        }
+                    });*/
+
+                }
+            }else if(s.contains("application/json")){
+                    // 处理 JSON 请求
+                    String jsonData = handleJsonRequest(request);
+                    jsonListString.add(jsonData);
+            }else{
+                log.warn("unknown out contentType " + s);
+                return null;
+            }
+            return eventHandleRouter.route(jsonListString,fileByteList);
+            } catch(Exception e){
+                e.printStackTrace();
+                log.error(e.getMessage(), e);
+            }
+        return null;
+    }
+
+    /**
+     * 根据提供的消费信息处理海康消费机的消费请求。
+     * 该方法处理传入的请求,验证消费详情,
+     * 并执行必要的操作以完成消费请求。
+     *
+     * @param mapConsumeInfo 包含消费详细信息的键值对的 Map
+     * @return 表示消费请求处理结果的 Object
+     */
+    @PostMapping("/requestConsume")
+    public Object requestConsume(@RequestBody Map<String, Object> mapConsumeInfo) {
+        RemoteConsumeBo remoteBo = new RemoteConsumeBo();
+        if(ObjectUtil.isNotEmpty(mapConsumeInfo.get("userNo"))){
+            remoteBo.setUserNo(Long.valueOf(mapConsumeInfo.get("userNo").toString()));
+        }
+        if(ObjectUtil.isNotEmpty(mapConsumeInfo.get("factoryId"))){
+            remoteBo.setFactoryId(Long.valueOf(mapConsumeInfo.get("factoryId").toString()));
+        } else {
+            remoteBo.setFactoryId(0L);
+        }
+        if(ObjectUtil.isNotEmpty(mapConsumeInfo.get("consumeDate"))){
+            remoteBo.setConsumeDate(DateUtil.parseDateTime(mapConsumeInfo.get("consumeDate").toString()));
+        }
+        if(ObjectUtil.isNotEmpty(mapConsumeInfo.get("termRecordId"))){
+            remoteBo.setTermRecordId(Long.valueOf(mapConsumeInfo.get("termRecordId").toString()));
+        }
+        if(ObjectUtil.isNotEmpty(mapConsumeInfo.get("consumeMoney"))){
+            remoteBo.setConsumeMoney(new BigDecimal(mapConsumeInfo.get("consumeMoney").toString()));
+        }
+        if(ObjectUtil.isNotEmpty(mapConsumeInfo.get("termMac"))){
+            remoteBo.setTermMac(mapConsumeInfo.get("termMac").toString());
+        }
+        remoteBo.setCardNo(0L);
+        remoteBo.setStatusFlag(4);
+        remoteBo.setCreditType(CreditTypeEnum.TERM_CONSUME.code());
+
+        RemoteResultDto result = remoteConsumeService.dealHikRequestConsume(remoteBo);
+        R<ErrorInfo> errorInfo = result.getErrorInfo();
+        if (R.isError(errorInfo)) {
+            ErrorResult errResult = new ErrorResult();
+            errResult.setStatusCode(HttpStatus.NOT_FOUND.value());
+            errResult.setMessage(errorInfo.getMsg());
+            errResult.getErrors().add(errorInfo.getData());
+            return new ResponseEntity<Object>(errResult, null, HttpStatus.NOT_FOUND);
+        }
+        // if(ObjectUtil.isNotEmpty(mapConsumeInfo.get("qrCode"))){
+        //     remoteBo.setUserNo(Long.valueOf(mapConsumeInfo.get("qrCode").toString()));
+        // }
+        return result.getUpdatedRemoteBo();
+    }
+
+    /**
+     * 将消费记录上传到服务器。
+     *
+     * @param mapConsumeInfo 包含要上传的消费信息的 Map。
+     *                       键表示字段名称,值表示相应的数据。
+     * @return 表示上传操作结果的 Object。
+     *         这可能是成功消息、错误详情或其他相关信息。
+     */
+    @PostMapping("/uploadRecord")
+    public Object uploadConsumeRecord(@RequestBody Map<String, Object> mapConsumeInfo) {
+        RemoteConsumeBo remoteBo = new RemoteConsumeBo();
+        if(ObjectUtil.isNotEmpty(mapConsumeInfo.get("userNo"))){
+            remoteBo.setUserNo(Long.valueOf(mapConsumeInfo.get("userNo").toString()));
+        }
+        if(ObjectUtil.isNotEmpty(mapConsumeInfo.get("factoryId"))){
+            remoteBo.setFactoryId(Long.valueOf(mapConsumeInfo.get("factoryId").toString()));
+        } else {
+            remoteBo.setFactoryId(0L);
+        }
+        if(ObjectUtil.isNotEmpty(mapConsumeInfo.get("consumeDate"))){
+            remoteBo.setConsumeDate(DateUtil.parseDateTime(mapConsumeInfo.get("consumeDate").toString()));
+        }
+        if(ObjectUtil.isNotEmpty(mapConsumeInfo.get("termRecordId"))){
+            remoteBo.setTermRecordId(Long.valueOf(mapConsumeInfo.get("termRecordId").toString()));
+        }
+        if(ObjectUtil.isNotEmpty(mapConsumeInfo.get("consumeMoney"))){
+            remoteBo.setConsumeMoney(new BigDecimal(mapConsumeInfo.get("consumeMoney").toString()));
+        }
+        if(ObjectUtil.isNotEmpty(mapConsumeInfo.get("termMac"))){
+            remoteBo.setTermMac(mapConsumeInfo.get("termMac").toString());
+        }
+        remoteBo.setCardNo(0L);
+        remoteBo.setStatusFlag(4);
+        remoteBo.setCreditType(CreditTypeEnum.TERM_CONSUME.code());
+        remoteBo.setRecordStatus(364L);
+        remoteBo.setRecordId(0L);
+
+        RemoteResultDto result = remoteConsumeService.dealHikUploadRecord(remoteBo);
+        R<ErrorInfo> errorInfo = result.getErrorInfo();
+        if (R.isError(errorInfo)) {
+            ErrorResult errResult = new ErrorResult();
+            errResult.setStatusCode(HttpStatus.NOT_FOUND.value());
+            errResult.setMessage(errorInfo.getMsg());
+            errResult.getErrors().add(errorInfo.getData());
+            return new ResponseEntity<Object>(errResult, null, HttpStatus.NOT_FOUND);
+        }
+         return result.getUpdatedRemoteBo();
+    }
+
+    /**
+     * 将离线记录上传到服务器。
+     * 该方法处理提供的消费信息并负责上传过程。
+     *
+     * @param mapConsumeInfo 包含要上传的消费信息的 Map
+     * @return 表示上传操作结果的 Object
+     */
+    @PostMapping("/offlineRecord")
+    public Object uploadOfflineRecord(@RequestBody Map<String, Object> mapConsumeInfo) {
+
+
+        return null;
+    }
+
+    // 处理 JSON 请求
+    private String handleJsonRequest(HttpServletRequest request) throws IOException {
+
+        if (request.getContentType() != null
+                && request.getContentType().contains("application/json")) {
+
+            try (BufferedReader reader = request.getReader()) {
+                StringBuilder requestData = new StringBuilder();
+                String line;
+                while ((line = reader.readLine()) != null) {
+                    requestData.append(line);
+                }
+
+                return requestData.toString();
+                // 使用任意 JSON 库解析
+                //                JSONObject jsonObject = JSONUtil.parseObj(requestData.toString());
+                //                System.out.println("收到 JSON 数据:");
+                //                System.out.println(jsonObject.toString());
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 手动执行定时任务
+     */
+    @PostMapping("/executeTask")
+    public void executeTask() {
+        handleTask.handleTransactionRecordEventTask();
+    }
+
+}

+ 106 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/controller/XfFailedRecordController.java

@@ -0,0 +1,106 @@
+package org.dromara.server.hik.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.server.hik.domain.vo.XfFailedRecordVo;
+import org.dromara.server.hik.domain.bo.XfFailedRecordBo;
+import org.dromara.server.hik.service.IXfFailedRecordService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 海康消费失败记录
+ * 前端访问路由地址为:/FailedRecord/xfFailedRecord
+ *
+ * @author LionLi
+ * @date 2025-06-09
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/FailedRecord/xfFailedRecord")
+public class XfFailedRecordController extends BaseController {
+
+    private final IXfFailedRecordService xfFailedRecordService;
+
+    /**
+     * 查询海康消费失败记录列表
+     */
+    @SaCheckPermission("FailedRecord:xfFailedRecord:list")
+    @GetMapping("/list")
+    public TableDataInfo<XfFailedRecordVo> list(XfFailedRecordBo bo, PageQuery pageQuery) {
+        return xfFailedRecordService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出海康消费失败记录列表
+     */
+    @SaCheckPermission("FailedRecord:xfFailedRecord:export")
+    @Log(title = "海康消费失败记录", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(XfFailedRecordBo bo, HttpServletResponse response) {
+        List<XfFailedRecordVo> list = xfFailedRecordService.queryList(bo);
+        ExcelUtil.exportExcel(list, "海康消费失败记录", XfFailedRecordVo.class, response);
+    }
+
+    /**
+     * 获取海康消费失败记录详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("FailedRecord:xfFailedRecord:query")
+    @GetMapping("/{id}")
+    public R<XfFailedRecordVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable String id) {
+        return R.ok(xfFailedRecordService.queryById(id));
+    }
+
+    /**
+     * 新增海康消费失败记录
+     */
+    @SaCheckPermission("FailedRecord:xfFailedRecord:add")
+    @Log(title = "海康消费失败记录", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody XfFailedRecordBo bo) {
+        return toAjax(xfFailedRecordService.insertByBo(bo));
+    }
+
+    /**
+     * 修改海康消费失败记录
+     */
+    @SaCheckPermission("FailedRecord:xfFailedRecord:edit")
+    @Log(title = "海康消费失败记录", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody XfFailedRecordBo bo) {
+        return toAjax(xfFailedRecordService.updateByBo(bo));
+    }
+
+    /**
+     * 删除海康消费失败记录
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("FailedRecord:xfFailedRecord:remove")
+    @Log(title = "海康消费失败记录", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable String[] ids) {
+        return toAjax(xfFailedRecordService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 205 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/XfConsumeDetail.java

@@ -0,0 +1,205 @@
+package org.dromara.server.hik.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.io.Serial;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 消费明细对象 t_xf_consumeDetail
+ *
+ * @author LionLi
+ * @date 2024-08-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("t_xf_consumeDetail")
+public class XfConsumeDetail extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 消费记录Id,主键
+     */
+    @TableId(value = "consume_id")
+    private String consumeId;
+
+    /**
+     * 原始记录Id,主键
+     */
+    private String originalId;
+
+    /**
+     * 记录Id
+     */
+    private Long recordId;
+
+    /**
+     * 人员Id
+     */
+    private Long userId;
+
+    /**
+     * 学/工号
+     */
+    private String userNumb;
+
+    /**
+     * 用户姓名
+     */
+    private String realName;
+
+    /**
+     * 部门Id
+     */
+    private Long deptId;
+
+    /**
+     * 部门名称
+     */
+    private String deptName;
+
+    /**
+     * 消费日期
+     */
+    private Date consumeDate;
+
+    /**
+     * 消费金额
+     */
+    private BigDecimal consumeMoney;
+
+    /**
+     * 卡流水号
+     */
+    private Long cardNo;
+
+    /**
+     * 物理卡号
+     */
+    private Long factoryId;
+
+    /**
+     * 卡上余额
+     */
+    private BigDecimal cardValue;
+
+    /**
+     * 卡使用次数
+     */
+    private Long cardCount;
+
+    /**
+     * 消费账户金额
+     */
+    private BigDecimal consumeBalance;
+
+    /**
+     * 设备机号
+     */
+    private Long termNo;
+
+    /**
+     * 设备名称
+     */
+    private String termName;
+
+    /**
+     * 机器流水号
+     */
+    private Long termRecordId;
+
+    /**
+     * 是否已处理(和灰记录处理有关)
+     */
+    private Long analysisFlag;
+
+    /**
+     * 状态标识(记录消费类型?)
+     */
+    private Long statusFlag;
+
+    /**
+     * 营业员Id
+     */
+    private Long operatorId;
+
+    /**
+     * 营业员名称
+     */
+    private String operatorName;
+
+    /**
+     * 结算账户Id
+     */
+    private Long accountId;
+
+    /**
+     * 结算账户名称
+     */
+    private String accountName;
+
+    /**
+     * 房间Id
+     */
+    private Long roomId;
+
+    /**
+     * 房间名称
+     */
+    private String roomName;
+
+    /**
+     * 钱包类型
+     */
+    private String bagType;
+
+    /**
+     * 餐类Id
+     */
+    private Long mealType;
+
+    /**
+     * 餐类名称
+     */
+    private String mealName;
+
+    /**
+     * 是否发送短信,见 sys_yes_no字典类别
+     */
+    private String smsSend;
+
+    /**
+     * 消费记录标志位?
+     */
+    private Long recordStatus;
+
+    /**
+     * 同步标志,0-未同步,1-已同步
+     */
+    private Long syncStatus;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 删除标志(0-未删除 2-已删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 针对错扣补款的消费记录的id
+     */
+    private String detailId;
+
+}

+ 171 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/XfConsumeDetailOriginal.java

@@ -0,0 +1,171 @@
+package org.dromara.server.hik.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.io.Serial;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 原始消费记录对象 t_xf_consumeDetailOriginal
+ *
+ * @author LionLi
+ * @date 2024-08-15
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("t_xf_consumeDetailOriginal")
+public class XfConsumeDetailOriginal extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 原始记录Id,主键
+     */
+    @TableId(value = "original_id")
+    private String originalId;
+
+    /**
+     * 记录Id,自增长,唯一id值,用于应答给消费设备
+     */
+    private Long recordId;
+
+    /**
+     * 人员Id
+     */
+    private Long userId;
+
+    /**
+     * 学/工号
+     */
+    private String userNumb;
+
+    /**
+     * 用户姓名
+     */
+    private String realName;
+
+    /**
+     * 部门Id
+     */
+    private Long deptId;
+
+    /**
+     * 部门名称
+     */
+    private String deptName;
+
+    /**
+     * 消费日期
+     */
+    private Date consumeDate;
+
+    /**
+     * 消费金额
+     */
+    private BigDecimal consumeMoney;
+
+    /**
+     * 卡流水号
+     */
+    private Long cardNo;
+
+    /**
+     * 物理卡号
+     */
+    private Long factoryId;
+
+    /**
+     * 卡上余额
+     */
+    private BigDecimal cardValue;
+
+    /**
+     * 卡使用次数
+     */
+    private Long cardCount;
+
+    /**
+     * 消费账户金额
+     */
+    private BigDecimal consumeBalance;
+
+    /**
+     * 设备机号
+     */
+    private Long termNo;
+
+    /**
+     * 设备名称
+     */
+    private String termName;
+
+    /**
+     * 机器流水号
+     */
+    private Long termRecordId;
+
+    /**
+     * 是否已处理(和灰记录处理有关)
+     */
+    private Long analysisFlag;
+
+    /**
+     * 消费记录标志
+     */
+    private Long dataFlag;
+
+    /**
+     * 状态标识(记录消费类型?)
+     */
+    private Long statusFlag;
+
+    /**
+     * 防伪验证码(记录消费模式的中文信息)
+     */
+    private String digitalSign;
+
+    /**
+     * 水控金额
+     */
+    private BigDecimal waterValue;
+
+    /**
+     * 水控历史余额
+     */
+    private BigDecimal waterHistoryValue;
+
+    /**
+     * 水控当天总和
+     */
+    private BigDecimal waterDaySum;
+
+    /**
+     * 水控错误余额
+     */
+    private BigDecimal waterErrValue;
+
+    /**
+     * 水控错误消费金额
+     */
+    private BigDecimal waterErrMoney;
+
+    /**
+     * 营业员Id
+     */
+    private Long operatorId;
+
+    /**
+     * 删除标志(0-未删除 2-已删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+
+}

+ 88 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/XfFailedRecord.java

@@ -0,0 +1,88 @@
+package org.dromara.server.hik.domain;
+
+import org.dromara.common.tenant.core.TenantEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import java.io.Serial;
+
+/**
+ * 海康消费失败记录对象 t_xf_failed_record
+ *
+ * @author LionLi
+ * @date 2025-06-09
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("t_xf_failed_record")
+public class XfFailedRecord extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id")
+    private String id;
+
+    /**
+     * 用户流水号
+     */
+    private String userNo;
+
+    /**
+     * 物理卡号
+     */
+    private String factoryId;
+
+    /**
+     * 消费时间
+     */
+    private Date consumeDate;
+
+    /**
+     * 设备记录流水号
+     */
+    private String termRecordId;
+
+    /**
+     * 消费金额
+     */
+    private String consumeMoney;
+
+    /**
+     * 设备mac地址
+     */
+    private String termMac;
+
+    /**
+     * 设备机号
+     */
+    private String termNo;
+
+    /**
+     * 上次失败的原因
+     */
+    private String failMsg;
+
+    /**
+     * 处理成功或者失败,s成功f失败
+     */
+    private String status;
+
+    /**
+     * 删除标志(0-未删除 2-已删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    private String consumeType;
+
+
+
+
+}

+ 367 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/XfTerm.java

@@ -0,0 +1,367 @@
+package org.dromara.server.hik.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.io.Serial;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 消费设备对象 t_xf_term
+ *
+ * @author bing
+ * @date 2024-08-21
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("t_xf_term")
+public class XfTerm extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 设备Id,主键
+     */
+    @TableId(value = "term_id")
+    private Long termId;
+
+    /**
+     * 设备机号
+     */
+    private Long termNo;
+
+    /**
+     * 设备名称
+     */
+    private String termName;
+
+    /**
+     * 所属餐厅Id
+     */
+    private Long roomId;
+
+    /**
+     * 结算账户Id
+     */
+    private Long accountId;
+
+    /**
+     * 设备类型,见XF_TermType字典类型
+     */
+    private String termType;
+
+    /**
+     * 设备二维码
+     */
+    private String qrCode;
+
+    /**
+     * 工作站Id
+     */
+    private Long stationId;
+
+    /**
+     * 扣费类型,见XF_ConsumeType字典类型
+     */
+    private String consumeType;
+
+    /**
+     * 是否自动下载参数,见sys_yew_no字典项
+     */
+    private String autoDown;
+
+    /**
+     * 应用系统,见YYXT数据字典类别
+     */
+    private String useType;
+
+    /**
+     * 允许使用的卡类
+     */
+    private Long cardType;
+
+    /**
+     * 设备IP
+     */
+    private String termIp;
+
+    /**
+     * 设备MAC地址
+     */
+    private String termMac;
+
+    /**
+     * 设备通讯端口
+     */
+    private Long commPort;
+
+    /**
+     * 子网掩码
+     */
+    private String mask;
+
+    /**
+     * 服务器IP
+     */
+    private String serverIp;
+
+    /**
+     * 服务器端口
+     */
+    private Long serverPort;
+
+    /**
+     * 网关IP
+     */
+    private String gatewayIp;
+
+    /**
+     * 心跳间隔,以秒为单位
+     */
+    private Long beatInterval;
+
+    /**
+     * 通讯超时时间,以100毫秒为单位
+     */
+    private Long timeout;
+
+    /**
+     * 允许脱机时间
+     */
+    private Long offlineTime;
+
+    /**
+     * 高级参数
+     */
+    private String advParam;
+
+    /**
+     * 设备参数
+     */
+    private String posParam;
+
+    /**
+     * 费率参数
+     */
+    private String rateParam;
+
+    /**
+     * 消费工作模式,见XF_WorkMode字典类别
+     */
+    private String workMode;
+
+    /**
+     * 开机模式,0-不需要营业员开机,1-需要营业员开机
+     */
+    private String openMode;
+
+    /**
+     * 卡上最大金额
+     */
+    private BigDecimal maxCardMoney;
+
+    /**
+     * 定值消费金额
+     */
+    private BigDecimal constantValue;
+
+    /**
+     * 编号0代表的金额
+     */
+    private BigDecimal rationZero;
+
+    /**
+     * 编号1代表的金额
+     */
+    private BigDecimal rationOne;
+
+    /**
+     * 编号2代表的金额
+     */
+    private BigDecimal rationTwo;
+
+    /**
+     * 编号3代表的金额
+     */
+    private BigDecimal rationThree;
+
+    /**
+     * 编号4代表的金额
+     */
+    private BigDecimal rationFour;
+
+    /**
+     * 编号5代表的金额
+     */
+    private BigDecimal rationFive;
+
+    /**
+     * 编号6代表的金额
+     */
+    private BigDecimal rationSix;
+
+    /**
+     * 编号7代表的金额
+     */
+    private BigDecimal rationSeven;
+
+    /**
+     * 编号8代表的金额
+     */
+    private BigDecimal rationEight;
+
+    /**
+     * 编号9代表的金额
+     */
+    private BigDecimal rationNine;
+
+    /**
+     * 每天最大消费次数,0-不限
+     */
+    private BigDecimal dayCount;
+
+    /**
+     * 每天最大消费金额
+     */
+    private BigDecimal dayMoney;
+
+    /**
+     * 每餐最大消费次数,0-不限
+     */
+    private Long mealCount;
+
+    /**
+     * 单次最大消费金额
+     */
+    private BigDecimal singleMoney;
+
+    /**
+     * 早餐消费金额
+     */
+    private BigDecimal breakfastMoney;
+
+    /**
+     * 午餐消费金额
+     */
+    private BigDecimal lunchMoney;
+
+    /**
+     * 晚餐消费金额
+     */
+    private BigDecimal supperMoney;
+
+    /**
+     * 夜宵消费金额
+     */
+    private BigDecimal nightMoney;
+
+    /**
+     * 早餐开始时间
+     */
+    private Long breakfastBegin;
+
+    /**
+     * 早餐结束时间
+     */
+    private Long breakfastEnd;
+
+    /**
+     * 午餐开始时间
+     */
+    private Long lunchBegin;
+
+    /**
+     * 午餐结束时间
+     */
+    private Long lunchEnd;
+
+    /**
+     * 晚餐开始时间
+     */
+    private Long supperBegin;
+
+    /**
+     * 晚餐结束时间
+     */
+    private Long supperEnd;
+
+    /**
+     * 宵夜开始时间
+     */
+    private Long nightBegin;
+
+    /**
+     * 宵夜结束时间
+     */
+    private Long nightEnd;
+
+    /**
+     * 两次刷卡间隔,0-不限制
+     */
+    private Long swipeInterval;
+
+    /**
+     * 是否启用卡片有效限制,0-禁用 1-启用
+     */
+    private String termValidity;
+
+    /**
+     * 设备记录流水号
+     */
+    private Long recordId;
+
+    /**
+     * 数据上传时间
+     */
+    private Date uploadTime;
+
+    /**
+     * 黑名单下载时间
+     */
+    private Date blackDownTime;
+
+    /**
+     * 最后校时时间
+     */
+    private Date lastCheck;
+
+    /**
+     * 重启时间
+     */
+    private String rebootTime;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 删除标志(0-未删除 2-已删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    @TableField(exist = false)
+    private Long roomName;
+
+    @TableField(exist = false)
+    private Long accountName;
+
+    /*消费机管理账号*/
+    private String adminName;
+
+    /*消费机管理密码*/
+    private String adminPwd;
+
+    /*消费机品牌*/
+    private String brand;
+
+
+}

+ 89 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/bo/XfFailedRecordBo.java

@@ -0,0 +1,89 @@
+package org.dromara.server.hik.domain.bo;
+
+import org.dromara.server.hik.domain.XfFailedRecord;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+/**
+ * 海康消费失败记录业务对象 t_xf_failed_record
+ *
+ * @author LionLi
+ * @date 2025-06-09
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = XfFailedRecord.class, reverseConvertGenerate = false)
+public class XfFailedRecordBo extends BaseEntity {
+
+    /**
+     * 主键
+     */
+    @NotBlank(message = "主键不能为空", groups = { EditGroup.class })
+    private String id;
+
+    /**
+     * 用户流水号
+     */
+    @NotBlank(message = "用户流水号不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String userNo;
+
+    /**
+     * 物理卡号
+     */
+    @NotBlank(message = "物理卡号不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String factoryId;
+
+    /**
+     * 消费时间
+     */
+    @NotNull(message = "消费时间不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Date consumeDate;
+
+    /**
+     * 设备记录流水号
+     */
+    @NotBlank(message = "设备记录流水号不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String termRecordId;
+
+    /**
+     * 消费金额
+     */
+    @NotBlank(message = "消费金额不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String consumeMoney;
+
+    /**
+     * 设备mac地址
+     */
+    @NotBlank(message = "设备mac地址不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String termMac;
+
+    /**
+     * 设备机号
+     */
+    @NotBlank(message = "设备机号不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String termNo;
+
+    /**
+     * 上次失败的原因
+     */
+    @NotBlank(message = "上次失败的原因不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String failMsg;
+
+    /**
+     * 处理成功或者失败,s成功f失败
+     */
+    @NotBlank(message = "处理成功或者失败,s成功f失败不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String status;
+
+    @NotBlank(message = "消费类型", groups = { AddGroup.class, EditGroup.class })
+    private String consumeType;
+
+
+}

+ 74 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/DeviceDto.java

@@ -0,0 +1,74 @@
+package org.dromara.server.hik.domain.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 设备信息数据传输对象
+ * <p>
+ * 交互设备信息数据传输对象定义
+ *
+ * @author luoyibo
+ * @version 2.2.0
+ * @date 2025-05-21
+ * @since JDK17
+ */
+@Data
+public class DeviceDto implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 585302132129673041L;
+
+    /**
+     * 设备编号
+     */
+    private Integer termNo;
+
+    /**
+     * 设备MAC 海康设备 必须要有
+     */
+    private String termMac;
+
+    /**
+     * 管理账号
+     */
+    @NotBlank(message = "管理账号不能为空")
+    private String adminName;
+
+    /**
+     * 管理密码
+     */
+    @NotBlank(message = "管理密码不能为空")
+    private String adminPwd;
+
+    /**
+     * 通讯IP
+     */
+    @NotBlank(message = "设备通讯IP不能为空")
+    private String deviceIp;
+
+    /**
+     * 通讯端口
+     */
+    @NotNull(message = "设备通讯端口不能为空")
+    private Integer devicePort;
+
+    /**
+     * 服务监听IP
+     */
+    private String serverIp;
+
+    /**
+     * 服务监听端口
+     */
+    private Integer serverPort;
+
+    /**
+     * 服务监听地址
+     */
+    private String serverUrl;
+}

+ 44 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/QueryDto.java

@@ -0,0 +1,44 @@
+package org.dromara.server.hik.domain.dto;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 查询条件传输对象
+ * <p>
+ * 向设备请求查询时的查询参数设定
+ *
+ * @author luoyibo
+ * @version 2.2.0
+ * @date 2025-05-22
+ * @since JDK17
+ */
+@Data
+@Accessors(chain = true)
+public class QueryDto implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 搜索记录标识
+     */
+    private String searchID;
+
+    /**
+     * 查询起始值
+     */
+    private Integer pageNo;
+
+    /**
+     * 查询的记录条数
+     */
+    private Integer pageSize;
+
+    /**
+     * 查询设备信息
+     */
+    private DeviceDto device;
+}

+ 54 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/UploadEmpDto.java

@@ -0,0 +1,54 @@
+package org.dromara.server.hik.domain.dto;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.dromara.server.hik.domain.dto.base.CardDto;
+import org.dromara.server.hik.domain.dto.base.EmpInfoDto;
+import org.dromara.server.hik.domain.dto.base.FaceDto;
+import org.dromara.server.hik.domain.dto.base.ValidDto;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 下发人员信息数据传输对象
+ * <p>
+ * 向设备下发的人员信息数据传输对象定义。数据格式包括了下发的设备信息、待下发的人员信息
+ *
+ * @author luoyibo
+ * @version 2.2.0
+ * @date 2025-05-23
+ * @since JDK17
+ */
+@Data
+@Accessors(chain = true)
+public class UploadEmpDto implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 8195137760207081790L;
+
+    /**
+     * 下发设备信息
+     */
+    private DeviceDto device;
+
+    /**
+     * 下发人员信息
+     */
+    private EmpInfoDto employee;
+
+    /**
+     * 下发人员有效期
+     */
+    private ValidDto valid;
+
+    /**
+     * 下发卡片信息
+     */
+    private CardDto card;
+
+    /**
+     * 下发人脸信息
+     */
+    private FaceDto face;
+
+}

+ 33 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/UserInfoDto.java

@@ -0,0 +1,33 @@
+package org.dromara.server.hik.domain.dto;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.dromara.server.hik.domain.dto.base.EmpInfoDto;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 人员数据数据传输类
+ * <p>
+ * 定义下发至设备的人员数据传输对象
+ *
+ * @author luoyibo
+ * @version 2.2.0
+ * @date 2025-05-21
+ * @see EmpInfoDto
+ * @since JDK17
+ */
+@Data
+@Accessors(chain = true)
+public class UserInfoDto implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = -2638389177956051530L;
+
+    /**
+     * 下发到至设备的人员信息对象
+     */
+    private EmpInfoDto UserInfoAndRight;
+
+}

+ 41 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/base/CardDto.java

@@ -0,0 +1,41 @@
+package org.dromara.server.hik.domain.dto.base;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.dromara.server.hik.enums.CardTypeEnum;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 卡片数据传输对象
+ * <p>
+ * 单张卡片的数据传输对象定义类
+ *
+ * @author luoyibo
+ * @version 2.2.0
+ * @date 2025-05-22
+ * @see Object
+ * @since JDK17
+ */
+@Data
+@Accessors(chain = true)
+public class CardDto implements Serializable {
+    @Serial
+    private static final long serialVersionUID = -2123963727462678121L;
+
+    /**
+     * 物理卡号
+     */
+    private String cardNo;
+
+    /**
+     * 是否删除卡片 为true时删除设备上员工此物理卡号对应的卡片
+     */
+    private Boolean deleteCard = Boolean.FALSE;
+
+    /**
+     * 卡类型 默认为普通卡(normalCard)
+     */
+    private String cardType = CardTypeEnum.NORMAL_CARD.getCode();
+}

+ 37 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/base/CardListDto.java

@@ -0,0 +1,37 @@
+package org.dromara.server.hik.domain.dto.base;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 卡片数据传输对象
+ * <p>
+ * 多张张卡片的数据传输对象定义类
+ *
+ * @author luoyibo
+ * @version 2.2.0
+ * @date 2025-05-22
+ * @see Object
+ * @since JDK17
+ */
+@Data
+@Accessors(chain = true)
+public class CardListDto implements Serializable {
+    @Serial
+    private static final long serialVersionUID = -2123963727462678121L;
+
+    /**
+     * 是否删除所有卡片,默认为false;
+     * 如为true则删除设备上对应人员的所有卡片信息;此时如果lit配置了数据,将list中的卡下载到设备
+     */
+    private Boolean deleteAllCard = Boolean.FALSE;
+
+    /**
+     * 卡片对象清单
+     */
+    private List<CardDto> List;
+}

+ 70 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/base/EmpInfoDto.java

@@ -0,0 +1,70 @@
+package org.dromara.server.hik.domain.dto.base;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.dromara.server.hik.enums.AuthenticationEnum;
+import org.dromara.server.hik.enums.EmpTypeEnum;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 人员基本信息数据传输类
+ * <p>
+ * 定义了人员硒信息数据传输对象
+ *
+ * @author luoyibo
+ * @version 2.2.0
+ * @date 2025-05-21
+ * @since JDK17
+ */
+@Data
+@Accessors(chain = true)
+public class EmpInfoDto implements Serializable {
+    @Serial
+    private static final long serialVersionUID = -8026955153254941625L;
+
+    /**
+     * 人员编号,对应一卡通系统中人员流水号
+     */
+    private String employeeNo;
+
+    /**
+     * 人员姓名
+     */
+    private String name;
+
+    /**
+     * 人员类型 默认为普通人
+     *
+     * @see EmpTypeEnum
+     */
+    private String userType = EmpTypeEnum.NORMAL.getCode();
+
+    /**
+     * 删除人员信息,为true时删除该人员及其关联的所有信息,默认为false
+     */
+    private Boolean deleteUser = Boolean.FALSE;
+
+    /**
+     * 人员验证方式
+     *
+     * @see AuthenticationEnum
+     */
+    private String userVerifyMode = AuthenticationEnum.FACE_OR_FP_OR_CARD_OR_PW.getCode();
+
+    /**
+     * 人员的人脸图片信息
+     */
+    private FaceListDto FaceInfo;
+
+    /**
+     * 人员的卡片信息
+     */
+    private CardListDto CardInfo;
+
+    /**
+     * 有效期参数
+     */
+    private ValidDto Valid;
+}

+ 54 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/base/FaceDto.java

@@ -0,0 +1,54 @@
+package org.dromara.server.hik.domain.dto.base;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 人脸数据传输对象
+ * <p>
+ * 单个人脸数据传输对象定义
+ *
+ * @author luoyibo
+ * @version 2.2.0
+ * @date 2025-05-22
+ * @since JDK17
+ */
+@Data
+@Accessors(chain = true)
+public class FaceDto implements Serializable {
+    @Serial
+    private static final long serialVersionUID = -85247027566308465L;
+
+    /**
+     * 人脸库ID,范围1-32 默认为1
+     */
+    private String FDID = "1";
+
+    /**
+     * 人脸Id,从1开始,目前仅支持1
+     */
+    private Long faceID = 1L;
+
+    /**
+     * 人脸照片名称,下发照片数据时使用
+     */
+    private String faceName;
+
+    /**
+     * 人脸图片URL,如果使用picUrl
+     */
+    private String facePicURL;
+
+    /**
+     * 人脸特征库数据
+     */
+    private String modelData;
+
+    /**
+     * 是否删除人脸 为true时删除设备上员工当前人脸信息
+     */
+    private Boolean deleteFace = Boolean.FALSE;
+}

+ 36 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/base/FaceListDto.java

@@ -0,0 +1,36 @@
+package org.dromara.server.hik.domain.dto.base;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 人脸图片数据传输对象
+ * <p>
+ * 多条人脸图片数据传输对象定义类
+ *
+ * @author luoyibo
+ * @version 2.2.0
+ * @date 2025-05-22
+ * @since JDK17
+ */
+@Data
+@Accessors(chain = true)
+public class FaceListDto implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 7030951136309820564L;
+
+    /**
+     * 是否删除所有人脸,默认为false;
+     * 如为true则删除设备上对应人员的所有人脸信息;此时如果lit配置了数据,将list中的卡下载到设备
+     */
+    private Boolean deleteAllFace = Boolean.FALSE;
+
+    /**
+     * 人脸对象清单
+     */
+    private List<FaceDto> List;
+}

+ 41 - 0
ruoyi-server/ruoyi-server-face/src/main/java/org/dromara/server/hik/domain/dto/base/ValidDto.java

@@ -0,0 +1,41 @@
+package org.dromara.server.hik.domain.dto.base;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 有效期数据传输对象
+ * <p>
+ * 人员有效期数据传输对象定义类
+ *
+ * @author luoyibo
+ * @version 2.2.0
+ * @date 2025-05-22
+ * @since JDK17
+ */
+@Data
+@Accessors(chain = true)
+public class ValidDto implements Serializable {
+    @Serial
+    private static final long serialVersionUID = 3233182268013893354L;
+
+    /**
+     * 是否启用校验,默认为true
+     */
+    private Boolean enable = Boolean.TRUE;
+
+    /**
+     * 有效期开始时间
+     */
+    private Date beginTime;
+
+    /**
+     * 有效期结束时间
+     */
+    private Date endTime;
+
+}

Some files were not shown because too many files changed in this diff