Pārlūkot izejas kodu

Merge branch 'refs/heads/master' into openIm

# Conflicts:
#	fs-company/src/main/java/com/fs/company/controller/common/Test.java
#	fs-doctor-app/src/main/java/com/fs/app/controller/InquiryOrderController.java
#	fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
#	fs-service/src/main/java/com/fs/course/param/FsUserCourseOrderDoPayParam.java
#	fs-service/src/main/java/com/fs/his/service/impl/FsPackageOrderServiceImpl.java
#	fs-service/src/main/java/com/fs/hisStore/param/FsStoreOrderPayParam.java
#	fs-service/src/main/java/com/fs/hisStore/param/FsStorePaymentPayParam.java
#	fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
#	fs-user-app/src/main/java/com/fs/app/controller/InquiryOrderController.java
#	fs-user-app/src/main/java/com/fs/app/controller/PatientController.java
#	fs-user-app/src/main/java/com/fs/app/controller/course/CourseFsUserController.java
#	fs-user-app/src/main/java/com/fs/app/controller/store/StoreOrderScrmController.java
caoliqin 1 mēnesi atpakaļ
vecāks
revīzija
77c382c4d7
100 mainītis faili ar 5077 papildinājumiem un 201 dzēšanām
  1. 1 0
      DirectoryV3.xml
  2. 19 0
      fs-ad-api/src/main/java/com/fs/framework/config/RedisConfig.java
  3. 682 36
      fs-admin/src/main/java/com/fs/api/controller/IndexStatisticsController.java
  4. 41 1
      fs-admin/src/main/java/com/fs/company/controller/CompanyController.java
  5. 103 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyTrafficController.java
  6. 49 0
      fs-admin/src/main/java/com/fs/company/controller/CompanyTrafficLogController.java
  7. 41 3
      fs-admin/src/main/java/com/fs/course/controller/FsCoursePlaySourceConfigController.java
  8. 22 3
      fs-admin/src/main/java/com/fs/course/controller/FsCourseRedPacketLogController.java
  9. 1 0
      fs-admin/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java
  10. 1 2
      fs-admin/src/main/java/com/fs/fastGpt/FastgptExtUserTagController.java
  11. 25 3
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyController.java
  12. 124 0
      fs-admin/src/main/java/com/fs/his/controller/FsComplaintTemplateController.java
  13. 98 0
      fs-admin/src/main/java/com/fs/his/controller/FsHisComplaintController.java
  14. 2 1
      fs-admin/src/main/java/com/fs/his/controller/FsPackageOrderController.java
  15. 19 0
      fs-admin/src/main/java/com/fs/his/controller/FsPatientController.java
  16. 40 2
      fs-admin/src/main/java/com/fs/his/controller/FsStoreOrderController.java
  17. 28 0
      fs-admin/src/main/java/com/fs/his/controller/FsUserController.java
  18. 2 0
      fs-admin/src/main/java/com/fs/his/task/Task.java
  19. 24 0
      fs-admin/src/main/java/com/fs/hisStore/FsHisStoreLogController.java
  20. 134 0
      fs-admin/src/main/java/com/fs/hisStore/FsStoreSCRMController.java
  21. 74 1
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreHealthOrderScrmController.java
  22. 83 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  23. 14 2
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductScrmController.java
  24. 108 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreRecommendScrmController.java
  25. 20 3
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreScrmController.java
  26. 11 1
      fs-admin/src/main/java/com/fs/hisStore/controller/SysOperlogScrmController.java
  27. 94 0
      fs-admin/src/main/java/com/fs/qw/FsCourseTask.java
  28. 33 1
      fs-admin/src/main/java/com/fs/qw/controller/QwCompanyController.java
  29. 39 0
      fs-admin/src/main/java/com/fs/task/FsStoreTask.java
  30. 106 0
      fs-admin/src/main/java/com/fs/user/controller/FsUserComplaintController.java
  31. 109 0
      fs-admin/src/main/java/com/fs/user/controller/FsUserComplaintMsgController.java
  32. 14 0
      fs-admin/src/main/java/com/fs/web/controller/system/SysLoginController.java
  33. 1 1
      fs-admin/src/main/resources/application.yml
  34. 18 40
      fs-common-api/src/main/java/com/fs/framework/config/MyBatisConfig.java
  35. 19 0
      fs-common-api/src/main/java/com/fs/framework/config/RedisConfig.java
  36. 2 2
      fs-common-api/src/main/resources/application.yml
  37. 6 2
      fs-common/src/main/java/com/fs/common/core/domain/model/LoginUser.java
  38. 9 5
      fs-common/src/main/java/com/fs/common/core/redis/RedisCache.java
  39. 7 2
      fs-common/src/main/java/com/fs/common/enums/BusinessType.java
  40. 21 0
      fs-company-app/src/main/java/com/fs/core/config/RedisConfig.java
  41. 10 0
      fs-company/src/main/java/com/fs/company/controller/common/Test.java
  42. 1 1
      fs-company/src/main/java/com/fs/company/controller/company/CompanyController.java
  43. 3 2
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseAnswerLogsController.java
  44. 3 2
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseRedPacketLogController.java
  45. 2 0
      fs-company/src/main/java/com/fs/company/controller/course/FsUserCoursePeriodController.java
  46. 1 1
      fs-company/src/main/java/com/fs/company/controller/course/FsUserCourseTrainingCampController.java
  47. 1 1
      fs-company/src/main/java/com/fs/company/controller/course/FsUserOperationLogController.java
  48. 6 1
      fs-company/src/main/java/com/fs/company/controller/crm/CrmMsgController.java
  49. 11 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwSopController.java
  50. 3 1
      fs-company/src/main/java/com/fs/company/controller/qw/QwSopLogsController.java
  51. 5 6
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  52. 80 4
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserVoiceLogController.java
  53. 2 2
      fs-company/src/main/java/com/fs/company/controller/qw/SopUserLogsController.java
  54. 6 5
      fs-company/src/main/java/com/fs/company/controller/store/FsInquiryOrderController.java
  55. 14 1
      fs-company/src/main/java/com/fs/company/controller/store/FsPackageOrderController.java
  56. 1 1
      fs-company/src/main/java/com/fs/company/controller/store/FsStoreOrderController.java
  57. 18 0
      fs-company/src/main/java/com/fs/framework/config/RedisConfig.java
  58. 2 2
      fs-company/src/main/resources/application.yml
  59. 1 10
      fs-doctor-app/src/main/java/com/fs/app/controller/InquiryOrderController.java
  60. 21 0
      fs-doctor-app/src/main/java/com/fs/framework/config/RedisConfig.java
  61. 25 2
      fs-framework/src/main/java/com/fs/framework/aspectj/LogAspect.java
  62. 21 0
      fs-framework/src/main/java/com/fs/framework/config/RedisConfig.java
  63. 7 1
      fs-framework/src/main/java/com/fs/framework/manager/factory/AsyncFactory.java
  64. 11 1
      fs-framework/src/main/java/com/fs/framework/web/exception/GlobalExceptionHandler.java
  65. 21 0
      fs-hospital/src/main/java/com/fs/framework/config/RedisConfig.java
  66. 26 6
      fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java
  67. 2 2
      fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java
  68. 22 0
      fs-ipad-task/src/main/java/com/fs/framework/config/RedisConfig.java
  69. 20 0
      fs-live-app/src/main/java/com/fs/framework/config/RedisConfig.java
  70. 42 38
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  71. 21 0
      fs-qw-api-msg/src/main/java/com/fs/framework/config/RedisConfig.java
  72. 21 0
      fs-qw-api/src/main/java/com/fs/framework/config/RedisConfig.java
  73. 141 0
      fs-qw-mq/pom.xml
  74. 14 0
      fs-qw-mq/src/main/java/com/fs/FSServletInitializer.java
  75. 24 0
      fs-qw-mq/src/main/java/com/fs/FsQwMqApiApplication.java
  76. 58 0
      fs-qw-mq/src/main/java/com/fs/app/controller/CommonController.java
  77. 51 0
      fs-qw-mq/src/main/java/com/fs/app/exception/FSException.java
  78. 81 0
      fs-qw-mq/src/main/java/com/fs/app/exception/FSExceptionHandler.java
  79. 34 0
      fs-qw-mq/src/main/java/com/fs/app/mq/RocketMQConsumerCourseFinishService.java
  80. 188 0
      fs-qw-mq/src/main/java/com/fs/app/mq/courseFinishRtyTaskOne.java
  81. 182 0
      fs-qw-mq/src/main/java/com/fs/framework/aspectj/DataScopeAspect.java
  82. 73 0
      fs-qw-mq/src/main/java/com/fs/framework/aspectj/DataSourceAspect.java
  83. 245 0
      fs-qw-mq/src/main/java/com/fs/framework/aspectj/LogAspect.java
  84. 117 0
      fs-qw-mq/src/main/java/com/fs/framework/aspectj/RateLimiterAspect.java
  85. 31 0
      fs-qw-mq/src/main/java/com/fs/framework/config/ApplicationConfig.java
  86. 85 0
      fs-qw-mq/src/main/java/com/fs/framework/config/CaptchaConfig.java
  87. 100 0
      fs-qw-mq/src/main/java/com/fs/framework/config/DataSourceConfig.java
  88. 72 0
      fs-qw-mq/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  89. 59 0
      fs-qw-mq/src/main/java/com/fs/framework/config/FilterConfig.java
  90. 76 0
      fs-qw-mq/src/main/java/com/fs/framework/config/KaptchaTextCreator.java
  91. 150 0
      fs-qw-mq/src/main/java/com/fs/framework/config/MyBatisConfig.java
  92. 141 0
      fs-qw-mq/src/main/java/com/fs/framework/config/RedisConfig.java
  93. 65 0
      fs-qw-mq/src/main/java/com/fs/framework/config/ResourcesConfig.java
  94. 50 0
      fs-qw-mq/src/main/java/com/fs/framework/config/SecurityConfig.java
  95. 33 0
      fs-qw-mq/src/main/java/com/fs/framework/config/ServerConfig.java
  96. 121 0
      fs-qw-mq/src/main/java/com/fs/framework/config/SwaggerConfig.java
  97. 63 0
      fs-qw-mq/src/main/java/com/fs/framework/config/ThreadPoolConfig.java
  98. 77 0
      fs-qw-mq/src/main/java/com/fs/framework/config/properties/DruidProperties.java
  99. 27 0
      fs-qw-mq/src/main/java/com/fs/framework/datasource/DynamicDataSource.java
  100. 45 0
      fs-qw-mq/src/main/java/com/fs/framework/datasource/DynamicDataSourceContextHolder.java

+ 1 - 0
DirectoryV3.xml

@@ -5,4 +5,5 @@
      <tree path="/fs-admin/src/main/java/com/fs/company/controller" title="公司"/>
      <tree path="/fs-admin/src/main/java/com/fs/live/controller" title="直播"/>
      <tree path="/fs-admin/src/main/java/com/fs/qw" title="企微"/>
+     <tree path="/fs-ad-api" title="广告回传"/>
  </trees>

+ 19 - 0
fs-ad-api/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -15,6 +15,8 @@ import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.data.redis.serializer.GenericToStringSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 
+import java.math.BigDecimal;
+
 /**
  * redis配置
  *
@@ -90,7 +92,24 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+    @Bean
+    public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
 
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        template.afterPropertiesSet();
+        return template;
+    }
     @Bean
     public DefaultRedisScript<Long> limitScript()
     {

+ 682 - 36
fs-admin/src/main/java/com/fs/api/controller/IndexStatisticsController.java

@@ -1,16 +1,32 @@
 package com.fs.api.controller;
 
 import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.entity.SysDept;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.company.constant.CompanyTrafficConstants;
+import com.fs.company.domain.Company;
+import com.fs.company.service.ICompanyService;
+import com.fs.company.service.ICompanyTrafficRecordService;
+import com.fs.company.service.impl.CompanyTrafficRecordServiceImpl;
+import com.fs.his.utils.ConfigUtil;
+import com.fs.hisStore.config.MedicalMallConfig;
 import com.fs.statis.StatisticsRedisConstant;
 import com.fs.statis.dto.*;
+import com.fs.statis.param.StatisticsDeptCompanyParam;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.service.ISysConfigService;
+import com.fs.system.service.ISysDeptService;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.web.bind.annotation.*;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.YearMonth;
+import java.util.*;
+import java.util.function.BinaryOperator;
+import java.util.stream.Collectors;
 
 import static com.fs.statis.StatisticsRedisConstant.*;
 
@@ -19,18 +35,31 @@ import static com.fs.statis.StatisticsRedisConstant.*;
  */
 @RestController
 @RequestMapping("/index/statistics")
+@Slf4j
 public class IndexStatisticsController {
     @Autowired
     private RedisCache redisCache;
 
     @Autowired
     private ISysConfigService sysConfigService;
+
+    @Autowired
+    private ConfigUtil configUtil;
+
+    @Autowired
+    private ISysDeptService deptService;
+
+    @Autowired
+    private ICompanyService companyService;
+
+    @Autowired
+    private MedicalMallConfig medicalMallConfig;
     /**
      * 分析概览
      */
     @PostMapping("/analysisPreview")
     public R analysisPreview(@RequestBody AnalysisPreviewQueryDTO param){
-        AnalysisPreviewDTO analysisPreviewDTO = null;
+        AnalysisPreviewDTO analysisPreviewDTO = new AnalysisPreviewDTO();
         Integer type = param.getType();
         Integer userType = param.getUserType();
 
@@ -41,7 +70,69 @@ public class IndexStatisticsController {
         if(userType == null) {
             userType = 0;
         }
-        analysisPreviewDTO = redisCache.getCacheObject(String.format("%s:%d:%d",DATA_OVERVIEW_DEALER_ANALYSISPREVIEW,type,userType));
+        if(medicalMallConfig.isStatics()|| (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            analysisPreviewDTO = redisCache.getCacheObject(String.format("%s:%d:%d",DATA_OVERVIEW_DEALER_ANALYSISPREVIEW,type,userType));
+        }else if(param.getCompanyId() != null){
+            analysisPreviewDTO = redisCache.getCacheObject(String.format("%s:%d:%d:%d",DATA_OVERVIEW_DEALER_ANALYSISPREVIEW,type,userType,param.getCompanyId()));
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            for(Long companyId : companyIds){
+                AnalysisPreviewDTO clildDTO = redisCache.getCacheObject(String.format("%s:%d:%d:%d",DATA_OVERVIEW_DEALER_ANALYSISPREVIEW,type,userType,companyId));
+                if (clildDTO != null) {
+                    //将除了completedRate、correctRate和watchRate之外的数据相加,并计算completedRate、correctRate和watchRate
+                    analysisPreviewDTO.setWatchUserCount(
+                            analysisPreviewDTO.getWatchUserCount() == 0 ? 0 : analysisPreviewDTO.getWatchUserCount()
+                                +(clildDTO.getWatchUserCount() == 0 ? 0 : clildDTO.getWatchUserCount())
+                    );
+                    analysisPreviewDTO.setCompletedUserCount(
+                            analysisPreviewDTO.getCompletedUserCount() == 0 ? 0 : analysisPreviewDTO.getCompletedUserCount()
+                                +(clildDTO.getCompletedUserCount() == 0 ? 0 : clildDTO.getCompletedUserCount())
+                    );
+                    analysisPreviewDTO.setCompletedCount(
+                            analysisPreviewDTO.getCompletedCount() == 0 ? 0 : analysisPreviewDTO.getCompletedCount()
+                                +(clildDTO.getCompletedCount() == 0 ? 0 : clildDTO.getCompletedCount())
+                    );
+                    analysisPreviewDTO.setWatchCount(
+                            analysisPreviewDTO.getWatchCount() == 0 ? 0 : analysisPreviewDTO.getWatchCount()
+                                +(clildDTO.getWatchCount() == 0 ? 0 : clildDTO.getWatchCount())
+                    );
+                    analysisPreviewDTO.setAnswerMemberCount(
+                            analysisPreviewDTO.getAnswerMemberCount() == 0 ? 0 : analysisPreviewDTO.getAnswerMemberCount()
+                                +(clildDTO.getAnswerMemberCount() == 0 ? 0 : clildDTO.getAnswerMemberCount())
+                    );
+                    analysisPreviewDTO.setCorrectUserCount(
+                            analysisPreviewDTO.getCorrectUserCount() == 0 ? 0 : analysisPreviewDTO.getCorrectUserCount()
+                                +(clildDTO.getCorrectUserCount() == 0 ? 0 : clildDTO.getCorrectUserCount())
+                    );
+                    analysisPreviewDTO.setRewardCount(
+                            analysisPreviewDTO.getRewardCount() == 0 ? 0 : analysisPreviewDTO.getRewardCount()
+                                +(clildDTO.getRewardCount() == 0 ? 0 : clildDTO.getRewardCount())
+                    );
+                    analysisPreviewDTO.setRewardMoney(
+                            analysisPreviewDTO.getRewardMoney() == null ? BigDecimal.ZERO : analysisPreviewDTO.getRewardMoney()
+                                .add(clildDTO.getRewardMoney() == null ? BigDecimal.ZERO : clildDTO.getRewardMoney())
+                    );
+                }
+            }
+            if(analysisPreviewDTO.getAnswerMemberCount() != 0){
+                analysisPreviewDTO.setCorrectRate(
+                        analysisPreviewDTO.getCorrectUserCount() == 0 ? "0" : String.format("%.2f", analysisPreviewDTO.getCorrectUserCount() * 100.0 / analysisPreviewDTO.getAnswerMemberCount())
+                );
+            }
+            if(analysisPreviewDTO.getWatchUserCount() != 0){
+                analysisPreviewDTO.setCompletedRate(
+                        analysisPreviewDTO.getCompletedUserCount() == 0 ? "0" : String.format("%.2f", analysisPreviewDTO.getCompletedUserCount() * 100.0 / analysisPreviewDTO.getWatchUserCount())
+                );
+            }
+            if(analysisPreviewDTO.getWatchCount() != 0){
+                analysisPreviewDTO.setWatchRate(
+                        analysisPreviewDTO.getCompletedCount() == 0 ? "0" : String.format("%.2f", analysisPreviewDTO.getCompletedCount() * 100.0 / analysisPreviewDTO.getWatchCount())
+                );
+            }
+
+        }
 
         return R.ok().put("data",analysisPreviewDTO);
     }
@@ -51,8 +142,32 @@ public class IndexStatisticsController {
      * 消费余额
      */
     @GetMapping("/rechargeComsumption")
-    public R rechargeComsumption(){
-        ConsumptionBalanceDataDTO consumptionBalanceDataDTO = redisCache.getCacheObject(StatisticsRedisConstant.DATA_OVERVIEW_DEALER_BALANCE);
+    public R rechargeComsumption(StatisticsDeptCompanyParam param){
+        ConsumptionBalanceDataDTO consumptionBalanceDataDTO = new ConsumptionBalanceDataDTO();
+        if(medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)) {
+            consumptionBalanceDataDTO = redisCache.getCacheObject(StatisticsRedisConstant.DATA_OVERVIEW_DEALER_BALANCE);
+        }else if(param.getCompanyId() != null){
+            consumptionBalanceDataDTO = redisCache.getCacheObject(String.format("%s:%d",DATA_OVERVIEW_DEALER_BALANCE,param.getCompanyId()));
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            for(Long companyId : companyIds){
+                ConsumptionBalanceDataDTO clildDTO = redisCache.getCacheObject(String.format("%s:%d",DATA_OVERVIEW_DEALER_BALANCE,companyId));
+                consumptionBalanceDataDTO.setBalance(
+                    (consumptionBalanceDataDTO.getBalance() == null ? BigDecimal.ZERO : consumptionBalanceDataDTO.getBalance())
+                    .add(clildDTO.getBalance() == null ? BigDecimal.ZERO : clildDTO.getBalance())
+                );
+                consumptionBalanceDataDTO.setTodayComsumption(
+                    (consumptionBalanceDataDTO.getTodayComsumption() == null ? BigDecimal.ZERO : consumptionBalanceDataDTO.getTodayComsumption())
+                    .add(clildDTO.getTodayComsumption() == null ? BigDecimal.ZERO : clildDTO.getTodayComsumption())
+                );
+                consumptionBalanceDataDTO.setYesterdayComsumption(
+                    (consumptionBalanceDataDTO.getYesterdayComsumption() == null ? BigDecimal.ZERO : consumptionBalanceDataDTO.getYesterdayComsumption())
+                    .add(clildDTO.getYesterdayComsumption() == null ? BigDecimal.ZERO : clildDTO.getYesterdayComsumption())
+                );
+            }
+        }
 
         return R.ok().put("data", consumptionBalanceDataDTO);
     }
@@ -62,15 +177,58 @@ public class IndexStatisticsController {
      * @return
      */
     @GetMapping("/trafficLog")
-    public R getTrafficLog(){
-        TrafficLogDTO trafficLogDTO = redisCache.getCacheObject(DATA_OVERVIEW_TRAFFIC_LOG);
-        SysConfig sysConfig = sysConfigService.selectConfigByConfigKey("redPacket.Traffic.config");
-        if(trafficLogDTO == null || sysConfig == null) {
-            return null;
+    public R getTrafficLog(StatisticsDeptCompanyParam  param){
+        TrafficLogDTO result = new TrafficLogDTO();
+        if(!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)) {
+            result = redisCache.getCacheObject(DATA_OVERVIEW_TRAFFIC_LOG);
+            if (!medicalMallConfig.isStatics()) {
+                SysConfig sysConfig = sysConfigService.selectConfigByConfigKey("redPacket.Traffic.config");
+                if(result == null || sysConfig == null) {
+                    return null;
+                }
+                String configValue = sysConfig.getConfigValue();
+                result.setTraffic(configValue);
+            } else {
+                //所有部门流量之和
+                SysDept dept = new SysDept();
+                dept.setParentId(1L);
+                Long[] deptIds = deptService.selectDeptList(dept).stream().map(SysDept::getDeptId).toArray(Long[]::new);
+                long trafficTemp = 0L;
+                for(Long deptId : deptIds){
+                    Object clildTraffic = redisCache.getCacheObject(CompanyTrafficConstants.CACHE_KEY+":"+deptId);
+                    if(clildTraffic != null){
+                        trafficTemp += Long.parseLong(clildTraffic.toString());
+                    }
+                }
+                result.setTraffic(String.format("%.2f",trafficTemp * 1024.0));
+            }
+        }else if(param.getCompanyId() != null){
+            Company company = companyService.selectCompanyById(param.getCompanyId());
+            getTrafficLogResult(result,CompanyTrafficConstants.CACHE_KEY+":"+company.getDeptId()+":"+param.getCompanyId());
+        }else{
+            getTrafficLogResult(result,CompanyTrafficConstants.CACHE_KEY+":"+param.getDeptId());
         }
-        String configValue = sysConfig.getConfigValue();
-        trafficLogDTO.setTraffic(configValue);
-        return R.ok().put("data",trafficLogDTO);
+        return R.ok().put("data",result);
+    }
+
+    private void getTrafficLogResult(TrafficLogDTO result, String key){
+        //昨天
+        LocalDate yesterday = LocalDate.now().minusDays(1);
+        Object yesterdayCount = redisCache.getCacheObject(key+":"+yesterday);
+        Object todayCount = redisCache.getCacheObject(key+":"+LocalDate.now());
+        Object thisMonthCount = redisCache.getCacheObject(key+":"+YearMonth.now());
+        Object traffic = redisCache.getCacheObject(key);
+        result.setYesterday(parseRedisNumberValueToLong(yesterdayCount));
+        //今天
+        result.setToday(parseRedisNumberValueToLong(todayCount));
+        //本月
+        result.setThisMonth(parseRedisNumberValueToLong(thisMonthCount));
+        //剩余
+        result.setTraffic(traffic == null?"0":traffic.toString());
+    }
+
+    private Long parseRedisNumberValueToLong(Object value){
+        return value == null ? 0 : Long.parseLong(value.toString());
     }
 
     /**
@@ -87,9 +245,73 @@ public class IndexStatisticsController {
         if(userType == null){
             userType = 0;
         }
-        String key = String.format("%s:%d:%d", DATA_OVERVIEW_DEALER_CHARTS, type,userType);
-        List<DeaMemberTopTenDTO> deaMemberTopTenDTOS = redisCache.getCacheObject(key);
-        return R.ok().put("data", deaMemberTopTenDTOS);
+        List<WatchEndPlayTrendDTO> watchEndPlayTrendDTOS;
+        // 参考watchCourseTopTen方法的处理逻辑
+        if (!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            String key = String.format("%s:%d:%d", DATA_OVERVIEW_DEALER_CHARTS, type,userType);
+            watchEndPlayTrendDTOS = redisCache.getCacheObject(key);
+        }else if(param.getCompanyId() != null){
+            String key = String.format("%s:%d:%d:%d", DATA_OVERVIEW_DEALER_CHARTS, type,userType,param.getCompanyId());
+            watchEndPlayTrendDTOS = redisCache.getCacheObject(key);
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            List<WatchEndPlayTrendDTO> tempDTOS = new ArrayList<>();
+            for(Long companyId : companyIds){
+                String key = String.format("%s:%d:%d:%d", DATA_OVERVIEW_DEALER_CHARTS, type,userType,companyId);
+                List<WatchEndPlayTrendDTO> companyData = redisCache.getCacheObject(key);
+                if (companyData != null) {
+                    tempDTOS.addAll(companyData);
+                }
+            }
+            // 根据startDate 和 x 分组,合并watchUserCount和completedUserCount 限制最多返回10条记录
+            watchEndPlayTrendDTOS = tempDTOS.stream()
+                    .collect(Collectors.groupingBy(
+                            dto -> dto.getStartDate() + ":" + dto.getX(),  // 根据startDate和x分组
+                            Collectors.reducing(new WatchEndPlayTrendDTO(), (dto1, dto2) -> {
+                                // 合并watchUserCount和completedUserCount
+                                WatchEndPlayTrendDTO result = new WatchEndPlayTrendDTO();
+                                // 复制分组标识字段
+                                if (dto2 != null && dto2.getStartDate() != null) {
+                                    result.setStartDate(dto2.getStartDate());
+                                } else if (dto1 != null) {
+                                    result.setStartDate(dto1.getStartDate());
+                                }
+
+                                if (dto2 != null && dto2.getX() != null) {
+                                    result.setX(dto2.getX());
+                                } else if (dto1 != null) {
+                                    result.setX(dto1.getX());
+                                }
+
+                                // 合并数值字段
+                                result.setWatchUserCount(
+                                        (dto1 == null || dto1.getWatchUserCount() == null ? 0 : dto1.getWatchUserCount()) +
+                                                (dto2 == null || dto2.getWatchUserCount() == null ? 0 : dto2.getWatchUserCount())
+                                );
+
+                                result.setCompletedUserCount(
+                                        (dto1 == null || dto1.getCompletedUserCount() == null ? 0 : dto1.getCompletedUserCount()) +
+                                                (dto2 == null || dto2.getCompletedUserCount() == null ? 0 : dto2.getCompletedUserCount())
+                                );
+
+                                return result;
+                            })
+                    ))
+                    .values()
+                    .stream()
+                    .filter(Objects::nonNull)  // 过滤掉null值
+                    .limit(10)
+                    .sorted(Comparator.comparing(WatchEndPlayTrendDTO::getX))
+                    .collect(Collectors.toList());
+        }
+
+        if(watchEndPlayTrendDTOS == null){
+            watchEndPlayTrendDTOS = new ArrayList<>();
+        }
+
+        return R.ok().put("data", watchEndPlayTrendDTOS);
     }
 
     /**
@@ -107,8 +329,49 @@ public class IndexStatisticsController {
         if(userType == null){
             userType = 0;
         }
+        List<DeaMemberTopTenDTO> deaMemberTopTenDTOS = new ArrayList<>();
+        // 参考deaMemberTopTen方法处理逻辑
+        if (!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            String key = String.format("%s:%d:%d:%d", CHARTS_MEMBER_TOP_TEN_WATCH, type, statisticalType,userType);
+            deaMemberTopTenDTOS =  redisCache.getCacheObject(key);
+        }else if(param.getCompanyId() != null){
+            String key = String.format("%s:%d:%d:%d:%d", CHARTS_MEMBER_TOP_TEN_WATCH, type, statisticalType,userType,param.getCompanyId());
+            deaMemberTopTenDTOS = redisCache.getCacheObject(key);
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            List<DeaMemberTopTenDTO> tempDTOS = new ArrayList<>();
+            for(Long companyId : companyIds){
+                String key = String.format("%s:%d:%d:%d:%d", CHARTS_MEMBER_TOP_TEN_WATCH, type, statisticalType,userType,companyId);
+                List<DeaMemberTopTenDTO> companyData = redisCache.getCacheObject(key);
+                if (companyData != null) {
+                    tempDTOS.addAll(companyData);
+                }
+            }
+            // companyId 和 companyName 分组,合并watchUserCount 限制最多返回10条记录
+            deaMemberTopTenDTOS = tempDTOS.stream()
+                    .collect(Collectors.groupingBy(
+                            dto -> dto.getCompanyId() + ":" + dto.getCompanyName(),  // 根据companyId和companyName分组
+                            Collectors.reducing(new DeaMemberTopTenDTO(), (dto1, dto2) -> {
+                                DeaMemberTopTenDTO result = new DeaMemberTopTenDTO();
+                                result.setCompanyId(dto1.getCompanyId());
+                                result.setCompanyName(dto1.getCompanyName());
+                                result.setWatchUserCount(
+                                        (dto1.getWatchUserCount() == null ? 0 : dto1.getWatchUserCount()) +
+                                                (dto2 == null || dto2.getWatchUserCount() == null ? 0 : dto2.getWatchUserCount())
+                                );
+                                return result;
+                            })
+                    ))
+                    .values()
+                    .stream()
+                    .filter(Objects::nonNull)  // 过滤掉null值
+                    .limit(10)
+                    .sorted(Comparator.comparing(DeaMemberTopTenDTO::getWatchUserCount, Comparator.nullsLast(Comparator.reverseOrder())))
+                    .collect(Collectors.toList());
+        }
 
-        List<DeaMemberTopTenDTO> deaMemberTopTenDTOS = redisCache.getCacheObject(String.format("%s:%d:%d:%d", CHARTS_MEMBER_TOP_TEN_WATCH, type, statisticalType,userType));
         if(deaMemberTopTenDTOS == null){
             deaMemberTopTenDTOS = new ArrayList<>();
         }
@@ -123,8 +386,50 @@ public class IndexStatisticsController {
         Integer type = param.getType();
         Integer dataType = param.getDataType();
         Integer userType = param.getUserType();
+        List<RewardMoneyTopTenDTO> rewardMoneyTopTenDTOS = new ArrayList<>();
+        // 参考rewardMoneyTopTen方法处理逻辑
+        if(!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            String key = String.format("%s:%d:%d:%d", CHARTS_REWARD_MONEY_TOP_TEN, type,dataType,userType);
+            rewardMoneyTopTenDTOS = redisCache.getCacheObject(key);
+        }else if(param.getCompanyId() != null){
+            String key = String.format("%s:%d:%d:%d:%d", CHARTS_REWARD_MONEY_TOP_TEN, type,dataType,userType,param.getCompanyId());
+            rewardMoneyTopTenDTOS = redisCache.getCacheObject(key);
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            List<RewardMoneyTopTenDTO> tempDTOS = new ArrayList<>();
+            for(Long companyId : companyIds){
+                String key = String.format("%s:%d:%d:%d:%d", CHARTS_REWARD_MONEY_TOP_TEN, type,dataType,userType,companyId);
+                List<RewardMoneyTopTenDTO> companyData = redisCache.getCacheObject(key);
+                if (companyData != null) {
+                    tempDTOS.addAll(companyData);
+                }
+            }
+            rewardMoneyTopTenDTOS = tempDTOS.stream()
+                    .collect(Collectors.groupingBy(
+                            dto -> dto.getCompanyId() + ":" + dto.getCompanyName(),  // 根据companyId和companyName分组
+                            Collectors.reducing(new RewardMoneyTopTenDTO(), (dto1, dto2) -> {
+                                RewardMoneyTopTenDTO result = new RewardMoneyTopTenDTO();
+                                result.setCompanyId(dto1.getCompanyId());
+                                result.setCompanyName(dto1.getCompanyName());
+                                result.setRewardMoney(
+                                        (dto1.getRewardMoney() == null ? BigDecimal.ZERO : dto1.getRewardMoney())
+                                        .add(
+                                                (dto2 == null || dto2.getRewardMoney() == null ? BigDecimal.ZERO : dto2.getRewardMoney())
+                                        )
+                                );
+                                return result;
+                            })
+                    ))
+                    .values()
+                    .stream()
+                    .filter(Objects::nonNull)  // 过滤掉null值
+                    .limit(10)
+                    .sorted(Comparator.comparing(RewardMoneyTopTenDTO::getRewardMoney, Comparator.nullsLast(Comparator.reverseOrder())))
+                    .collect(Collectors.toList());
+        }
 
-        List<RewardMoneyTopTenDTO> rewardMoneyTopTenDTOS = redisCache.getCacheObject( String.format("%s:%d:%d:%d", CHARTS_REWARD_MONEY_TOP_TEN, type,dataType,userType));
         return R.ok().put("data", rewardMoneyTopTenDTOS);
     }
 
@@ -135,7 +440,47 @@ public class IndexStatisticsController {
     public R rewardMoneyTrend(@RequestBody AnalysisPreviewQueryDTO param){
         Integer type = param.getType();
         Integer userType = param.getUserType();
-        List<RewardMoneyTrendDTO> rewardMoneyTrendDTOS = redisCache.getCacheObject( String.format("%s:%d:%d", CHARTS_REWARD_MONEY_TREND, type,userType));
+        List<RewardMoneyTrendDTO> rewardMoneyTrendDTOS = new ArrayList<>();
+        // 参考rewardMoneyTrend方法处理逻辑
+        if(!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            String key = String.format("%s:%d:%d", CHARTS_REWARD_MONEY_TREND, type,userType);
+            rewardMoneyTrendDTOS = redisCache.getCacheObject(key);
+        }else if(param.getCompanyId() != null){
+            String key = String.format("%s:%d:%d:%d", CHARTS_REWARD_MONEY_TREND, type,userType,param.getCompanyId());
+            rewardMoneyTrendDTOS = redisCache.getCacheObject(key);
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            List<RewardMoneyTrendDTO> tempDTOS = new ArrayList<>();
+            for(Long companyId : companyIds){
+                String key = String.format("%s:%d:%d:%d", CHARTS_REWARD_MONEY_TREND, type,userType,companyId);
+                List<RewardMoneyTrendDTO> companyData = redisCache.getCacheObject(key);
+                if (companyData != null) {
+                    tempDTOS.addAll(companyData);
+                }
+            }
+            //根据startDate和x分组,合并rewardMoney,根据x排序,限制10
+            rewardMoneyTrendDTOS = tempDTOS.stream()
+                    .collect(Collectors.groupingBy(dto -> dto.getStartDate() + ":" + dto.getX(),
+                            Collectors.reducing(new RewardMoneyTrendDTO(), (dto1, dto2) -> {
+                        RewardMoneyTrendDTO result = new RewardMoneyTrendDTO();
+                        result.setStartDate(dto1.getStartDate());
+                        result.setX(dto1.getX());
+                        result.setRewardMoney(
+                                (dto1.getRewardMoney() == null ? BigDecimal.ZERO : dto1.getRewardMoney())
+                                .add(
+                                        (dto2 == null || dto2.getRewardMoney() == null ? BigDecimal.ZERO : dto2.getRewardMoney())
+                                )
+                        );
+                        return result;
+                    })))
+                    .values()
+                    .stream()
+                    .sorted(Comparator.comparing(RewardMoneyTrendDTO::getX))
+                    .limit(10)
+                    .collect(Collectors.toList());
+        }
         return R.ok().put("data", rewardMoneyTrendDTOS);
     }
 
@@ -148,29 +493,196 @@ public class IndexStatisticsController {
         String sort = param.getSort();
         Integer statisticalType = param.getStatisticalType();
         Integer userType = param.getUserType();
+        List<CourseStatsDTO> courseStatsDTOS;
+        if (!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            courseStatsDTOS = redisCache.getCacheObject(String.format("%s:%d:%d:%d:%s", CHARTS_WATCH_TOP_TEN, type,statisticalType,userType,sort));
+        }else if(param.getCompanyId() != null){
+            courseStatsDTOS = redisCache.getCacheObject(String.format("%s:%d:%d:%d:%s:%d", CHARTS_WATCH_TOP_TEN, type,statisticalType,userType,sort,param.getCompanyId()));
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            List<CourseStatsDTO> tempDTOS = new ArrayList<>();
+            for(Long companyId : companyIds){
+                List<CourseStatsDTO> companyDTO = redisCache.getCacheObject(String.format("%s:%d:%d:%d:%s:%d", CHARTS_WATCH_TOP_TEN, type,statisticalType,userType,sort,companyId));
+                if (companyDTO != null) {
+                    tempDTOS.addAll(companyDTO);
+                }
+            }
+            // courseId和courseName分组,合并watchUserCount、completedUserCount、answerUserCount、correctUserCount 限制最多返回10条记录
+            courseStatsDTOS = tempDTOS.stream()
+                    .collect(Collectors.groupingBy(dto -> dto.getCourseId() + ":" + dto.getCourseName()))
+                    .values()
+                    .stream()
+                    .map(group -> {
+                        if (group.isEmpty()) {
+                            return null;
+                        }
+
+                        // 取第一个作为基础对象
+                        CourseStatsDTO result = new CourseStatsDTO();
+                        CourseStatsDTO first = group.get(0);
+                        result.setCourseId(first.getCourseId());
+                        result.setCourseName(first.getCourseName());
+
+                        // 合并所有数值字段
+                        int watchUserCount = 0;
+                        int completedUserCount = 0;
+                        int answerUserCount = 0;
+                        int correctUserCount = 0;
+
+                        for (CourseStatsDTO dto : group) {
+                            watchUserCount += (dto.getWatchUserCount() == null ? 0 : dto.getWatchUserCount());
+                            completedUserCount += (dto.getCompletedUserCount() == null ? 0 : dto.getCompletedUserCount());
+                            answerUserCount += (dto.getAnswerUserCount() == null ? 0 : dto.getAnswerUserCount());
+                            correctUserCount += (dto.getCorrectUserCount() == null ? 0 : dto.getCorrectUserCount());
+                        }
+
+                        result.setWatchUserCount(watchUserCount);
+                        result.setCompletedUserCount(completedUserCount);
+                        result.setAnswerUserCount(answerUserCount);
+                        result.setCorrectUserCount(correctUserCount);
+
+                        return result;
+                    })
+                    .filter(Objects::nonNull)
+                    .sorted(Comparator.comparing(CourseStatsDTO::getWatchUserCount, Comparator.nullsLast(Comparator.reverseOrder())))
+                    .limit(10)
+                    .collect(Collectors.toList());
+
+        }
 
-        List<CourseStatsDTO> courseStatsDTOS = redisCache.getCacheObject(String.format("%s:%d:%d:%d:%s", CHARTS_WATCH_TOP_TEN, type,statisticalType,userType,sort));
         return R.ok().put("data", courseStatsDTOS);
     }
 
+    private List<CourseStatsDTO> groupAndSumCourseStatsWithStream(List<CourseStatsDTO> courseStatsList) {
+        // 定义合并逻辑
+        BinaryOperator<CourseStatsDTO> mergeFunction = (s1, s2) -> {
+            s1.setWatchUserCount(
+                    (s1.getWatchUserCount() == null ? 0 : s1.getWatchUserCount()) +
+                            (s2.getWatchUserCount() == null ? 0 : s2.getWatchUserCount())
+            );
+
+            s1.setCompletedUserCount(
+                    (s1.getCompletedUserCount() == null ? 0 : s1.getCompletedUserCount()) +
+                            (s2.getCompletedUserCount() == null ? 0 : s2.getCompletedUserCount())
+            );
+
+            s1.setAnswerUserCount(
+                    (s1.getAnswerUserCount() == null ? 0 : s1.getAnswerUserCount()) +
+                            (s2.getAnswerUserCount() == null ? 0 : s2.getAnswerUserCount())
+            );
+
+            s1.setCorrectUserCount(
+                    (s1.getCorrectUserCount() == null ? 0 : s1.getCorrectUserCount()) +
+                            (s2.getCorrectUserCount() == null ? 0 : s2.getCorrectUserCount())
+            );
+
+            return s1;
+        };
+
+        // 分组、合并并排序、限制10条
+        return courseStatsList.stream()
+                .collect(Collectors.toMap(
+                        CourseStatsDTO::getCourseId,
+                        dto -> {
+                            CourseStatsDTO copy = new CourseStatsDTO();
+                            copy.setCourseId(dto.getCourseId());
+                            copy.setCourseName(dto.getCourseName());
+                            copy.setWatchUserCount(dto.getWatchUserCount() == null ? 0 : dto.getWatchUserCount());
+                            copy.setCompletedUserCount(dto.getCompletedUserCount() == null ? 0 : dto.getCompletedUserCount());
+                            copy.setAnswerUserCount(dto.getAnswerUserCount() == null ? 0 : dto.getAnswerUserCount());
+                            copy.setCorrectUserCount(dto.getCorrectUserCount() == null ? 0 : dto.getCorrectUserCount());
+                            return copy;
+                        },
+                        mergeFunction,
+                        LinkedHashMap::new
+                ))
+                .values()
+                .stream()
+                .sorted(Comparator.comparing(CourseStatsDTO::getWatchUserCount, Comparator.nullsLast(Comparator.reverseOrder())))
+                .limit(10)
+                .collect(Collectors.toList());
+    }
+
     /**
      * 数据概览
      */
     @GetMapping("/dealerAggregated")
-    public R dealerAggregated(){
-        DealerAggregatedDTO dealerAggregatedDTO = redisCache.getCacheObject(StatisticsRedisConstant.DATA_OVERVIEW_DEALER_AGGREGATED);
+    public R dealerAggregated(StatisticsDeptCompanyParam param){
+        DealerAggregatedDTO result = new DealerAggregatedDTO();
+        if (!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)) {
+            result = redisCache.getCacheObject(StatisticsRedisConstant.DATA_OVERVIEW_DEALER_AGGREGATED);
+        }else if (param.getCompanyId() != null) {
+            result = redisCache.getCacheObject(String.format("%s:%d", DATA_OVERVIEW_DEALER_AGGREGATED, param.getCompanyId()));
+        //没选中销售公司,部门不为总公司
+        }else{
+            //Long padMaxNum = deptLimiteService.selectById(param.getDeptId()).getMaxPadNum();
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            for(Long companyId : companyIds) {
+                DealerAggregatedDTO dealerAggregatedDTO = redisCache.getCacheObject(String.format("%s:%d", DATA_OVERVIEW_DEALER_AGGREGATED, companyId));
+                // 添加空值检查
+                if (dealerAggregatedDTO != null) {
+                    result.setDealderCount((result.getDealderCount() == null ? 0 : result.getDealderCount())
+                            + (dealerAggregatedDTO.getDealderCount() == null ? 0 : dealerAggregatedDTO.getDealderCount()));
+                    result.setGroupMgrCount((result.getGroupMgrCount() == null ? 0 : result.getGroupMgrCount())
+                            + (dealerAggregatedDTO.getGroupMgrCount() == null ? 0 : dealerAggregatedDTO.getGroupMgrCount()));
+                    result.setMemberCount((result.getMemberCount() == null ? 0 : result.getMemberCount())
+                            + (dealerAggregatedDTO.getMemberCount() == null ? 0 : dealerAggregatedDTO.getMemberCount()));
+                    result.setNormalNum((result.getNormalNum() == null ? 0 : result.getNormalNum())
+                            + (dealerAggregatedDTO.getNormalNum() == null ? 0 : dealerAggregatedDTO.getNormalNum()));
+                    result.setBlackNum((result.getBlackNum() == null ? 0 : result.getBlackNum())
+                            + (dealerAggregatedDTO.getBlackNum() == null ? 0 : dealerAggregatedDTO.getBlackNum()));
+                    result.setQwMemberNum((result.getQwMemberNum() == null ? 0 : result.getQwMemberNum())
+                            + (dealerAggregatedDTO.getQwMemberNum() == null ? 0 : dealerAggregatedDTO.getQwMemberNum()));
+                    result.setTodayIncreaseUserNum((result.getTodayIncreaseUserNum() == null ? 0 : result.getTodayIncreaseUserNum())
+                            + (dealerAggregatedDTO.getTodayIncreaseUserNum() == null ? 0 : dealerAggregatedDTO.getTodayIncreaseUserNum()));
+                    result.setOrderTotalNum((result.getOrderTotalNum() == null ? 0 : result.getOrderTotalNum())
+                            + (dealerAggregatedDTO.getOrderTotalNum() == null ? 0 : dealerAggregatedDTO.getOrderTotalNum()));
+                    result.setTodayOrderNum((result.getTodayOrderNum() == null ? 0 : result.getTodayOrderNum())
+                            + (dealerAggregatedDTO.getTodayOrderNum() == null ? 0 : dealerAggregatedDTO.getTodayOrderNum()));
+                    result.setRecvTodayNum((result.getRecvTodayNum() == null ? 0 : result.getRecvTodayNum())
+                            + (dealerAggregatedDTO.getRecvTodayNum() == null ? 0 : dealerAggregatedDTO.getRecvTodayNum()));
+                    result.setRecvTotalNum((result.getRecvTotalNum() == null ? 0 : result.getRecvTotalNum())
+                            + (dealerAggregatedDTO.getRecvTotalNum() == null ? 0 : dealerAggregatedDTO.getRecvTotalNum()));
+                    result.setGoodsTotalNum((result.getGoodsTotalNum() == null ? 0 : result.getGoodsTotalNum())
+                            + (dealerAggregatedDTO.getGoodsTotalNum() == null ? 0 : dealerAggregatedDTO.getGoodsTotalNum()));
+                    result.setTodayGoodsNum((result.getTodayGoodsNum() == null ? 0 : result.getTodayGoodsNum())
+                            + (dealerAggregatedDTO.getTodayGoodsNum() == null ? 0 : dealerAggregatedDTO.getTodayGoodsNum()));
+                    result.setPadUsedNum((result.getPadUsedNum() == null ? 0 : result.getPadUsedNum())
+                            + (dealerAggregatedDTO.getPadUsedNum() == null ? 0 : dealerAggregatedDTO.getPadUsedNum()));
+                }
+            }
+        }
 
-        return R.ok().put("data",dealerAggregatedDTO);
+        return R.ok().put("data",result);
     }
 
+
     /**
      * 短信余额
      */
     @GetMapping("/smsBalance")
-    public R smsBalance(){
-        Long smsBalance = redisCache.getCacheObject(StatisticsRedisConstant.DATA_OVERVIEW_DEALER_SMS_BALANCE);
-
-        return R.ok().put("data", smsBalance);
+    public R smsBalance(StatisticsDeptCompanyParam param){
+        if (!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            return R.ok().put("data", redisCache.getCacheObject(StatisticsRedisConstant.DATA_OVERVIEW_DEALER_SMS_BALANCE));
+        }else if(param.getCompanyId() != null){
+            return R.ok().put("data", redisCache.getCacheObject(String.format("%s:%d", DATA_OVERVIEW_DEALER_SMS_BALANCE, param.getCompanyId())));
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            long smsBalance = 0L;
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            for(Long companyId : companyIds) {
+                Long smsBalanceCompany = redisCache.getCacheObject(String.format("%s:%d", DATA_OVERVIEW_DEALER_SMS_BALANCE, companyId));
+                if (smsBalanceCompany != null) {
+                    smsBalance += smsBalanceCompany;
+                }
+            }
+            return R.ok().put("data", smsBalance);
+        }
     }
 
 
@@ -178,8 +690,33 @@ public class IndexStatisticsController {
      * 授权信息
      */
     @GetMapping("/authorizationInfo")
-    public R authorizationInfo(){
-        AuthorizationInfoDTO authorizationInfoDTO = redisCache.getCacheObject(StatisticsRedisConstant.DATA_OVERVIEW_DEALER_AUTHORIZATION_INFO);
+    public R authorizationInfo(StatisticsDeptCompanyParam  param){
+        AuthorizationInfoDTO authorizationInfoDTO = new AuthorizationInfoDTO();
+        if (!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            return R.ok().put("data", redisCache.getCacheObject(StatisticsRedisConstant.DATA_OVERVIEW_DEALER_AUTHORIZATION_INFO));
+        }else if(param.getCompanyId() != null){
+            return R.ok().put("data", redisCache.getCacheObject(String.format("%s:%d", DATA_OVERVIEW_DEALER_AUTHORIZATION_INFO, param.getCompanyId())));
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            AuthorizationInfoDTO authorizationInfoDTO1 = new AuthorizationInfoDTO();
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            for(Long companyId : companyIds) {
+                AuthorizationInfoDTO companyDTO = redisCache.getCacheObject(String.format("%s:%d", DATA_OVERVIEW_DEALER_AUTHORIZATION_INFO, companyId));
+                log.info("授权信息:{}", authorizationInfoDTO);
+                if (companyDTO != null) {
+                    authorizationInfoDTO1.setTodayWatchUserCount(
+                            (authorizationInfoDTO1.getTodayWatchUserCount() == null ? 0 : authorizationInfoDTO1.getTodayWatchUserCount()) +
+                                    (companyDTO.getTodayWatchUserCount() == null ? 0 : companyDTO.getTodayWatchUserCount())
+                    );
+
+                    authorizationInfoDTO1.setVersionLimit(
+                            (authorizationInfoDTO1.getVersionLimit() == null ? 0 : authorizationInfoDTO1.getVersionLimit()) +
+                                    (companyDTO.getVersionLimit() == null ? 0 : companyDTO.getVersionLimit())
+                    );
+                }
+            }
+        }
 
         return R.ok().put("data", authorizationInfoDTO);
     }
@@ -190,9 +727,29 @@ public class IndexStatisticsController {
      * @return
      */
     @GetMapping("/thisMonthOrderCount")
-    public R thisMonthOrderCount(){
-        R result = redisCache.getCacheObject(StatisticsRedisConstant.THIS_MONTH_ORDER_COUNT);
-        return result;
+    public R thisMonthOrderCount(StatisticsDeptCompanyParam  param){
+        if (!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            return redisCache.getCacheObject(StatisticsRedisConstant.THIS_MONTH_ORDER_COUNT);
+        }else if(param.getCompanyId() != null){
+            return redisCache.getCacheObject(String.format("%s:%d", THIS_MONTH_ORDER_COUNT, param.getCompanyId()));
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            List<String> datesList = new ArrayList<>();
+            List<Integer> orderCountList = new ArrayList<>();
+            List<Integer> payPriceList = new ArrayList<>();
+            for(Long companyId : companyIds) {
+                R result = redisCache.getCacheObject(String.format("%s:%d", THIS_MONTH_ORDER_COUNT, companyId));
+                Object datas = result.get("datas");
+                Object orderCount = result.get("orderCount");
+                Object payPrice = result.get("payPrice");
+                if(datas != null){
+                    mergeDataListsForInteger(datesList, orderCountList, payPriceList, (List<String>)datas, (List<Integer>)orderCount, (List<Integer>)payPrice);
+                }
+            }
+            return R.ok().put("dates", datesList).put("orderCount", orderCountList).put("payPrice", payPriceList);
+        }
     }
 
     /**
@@ -201,8 +758,97 @@ public class IndexStatisticsController {
      */
 
     @GetMapping("/thisMonthRecvCount")
-    public R thisMonthRecvCount(){
-        R result = redisCache.getCacheObject(StatisticsRedisConstant.THIS_MONTH_RECV_COUNT);
-        return result;
+    public R thisMonthRecvCount(StatisticsDeptCompanyParam  param){
+        if (!medicalMallConfig.isStatics() || (param.getCompanyId() == null && param.getDeptId() == null) || (param.getCompanyId() == null && param.getDeptId() == 1)){
+            return redisCache.getCacheObject(StatisticsRedisConstant.THIS_MONTH_RECV_COUNT);
+        }else if(param.getCompanyId() != null){
+            return redisCache.getCacheObject(String.format("%s:%d", THIS_MONTH_RECV_COUNT, param.getCompanyId()));
+        }else{
+            Company company = new Company();
+            company.setDeptId(param.getDeptId());
+            Long[] companyIds = companyService.selectCompanyList(company).stream().map(Company::getCompanyId).toArray(Long[]::new);
+            List<String> datesList = new ArrayList<>();
+            List<Integer> orderCountList = new ArrayList<>();
+            List<Float> payMoneyList = new ArrayList<>();
+            for(Long companyId : companyIds) {
+               R result = redisCache.getCacheObject(String.format("%s:%d", THIS_MONTH_RECV_COUNT, companyId));
+               Object datas = result.get("datas");
+               Object orderCount = result.get("orderCount");
+               Object payMoney = result.get("payMoney");
+               if(datas != null){
+                   mergeDataLists(datesList, orderCountList, payMoneyList, (List<String>)datas, (List<Integer>)orderCount, (List<Float>)payMoney);
+               }
+            }
+            return R.ok().put("dates", datesList).put("orderCount", orderCountList).put("payMoney", payMoneyList);
+        }
+    }
+
+    private void mergeDataLists(List<String> datasList, List<Integer> orderCountList, List<Float> payMoneyList,
+                               List<String> datas, List<Integer> orderCount, List<Float> payMoney) {
+
+        // 遍历新数据
+        for (int i = 0; i < datas.size(); i++) {
+            String newData = datas.get(i);
+            Integer newOrderCount = orderCount.get(i);
+            Float newPayMoney = payMoney.get(i);
+
+            // 查找在现有列表中的位置
+            int existingIndex = datasList.indexOf(newData);
+
+            if (existingIndex != -1) {
+                // 如果存在,将orderCount和payMoney相加
+                Integer existingOrderCount = orderCountList.get(existingIndex);
+                Float existingPayMoney = payMoneyList.get(existingIndex);
+
+                // 累加orderCount
+                orderCountList.set(existingIndex,
+                        (existingOrderCount == null ? 0 : existingOrderCount) +
+                                (newOrderCount == null ? 0 : newOrderCount));
+
+                // 累加payMoney
+                payMoneyList.set(existingIndex,
+                        (existingPayMoney == null ? 0.0f : existingPayMoney) +
+                                (newPayMoney == null ? 0.0f : newPayMoney));
+            } else {
+                // 如果不存在,直接添加新项
+                datasList.add(newData);
+                orderCountList.add(newOrderCount);
+                payMoneyList.add(newPayMoney);
+            }
+        }
+    }
+
+    private void mergeDataListsForInteger(List<String> datasList, List<Integer> orderCountList, List<Integer> payMoneyList,
+                                          List<String> datas, List<Integer> orderCount, List<Integer> payMoney) {
+        // 遍历新数据
+        for (int i = 0; i < datas.size(); i++) {
+            String newData = datas.get(i);
+            Integer newOrderCount = orderCount.get(i);
+            Integer newPayMoney = payMoney.get(i);
+
+            // 查找在现有列表中的位置
+            int existingIndex = datasList.indexOf(newData);
+
+            if (existingIndex != -1) {
+                // 如果存在,将orderCount和payMoney相加
+                Integer existingOrderCount = orderCountList.get(existingIndex);
+                Integer existingPayMoney = payMoneyList.get(existingIndex);
+
+                // 累加orderCount
+                orderCountList.set(existingIndex,
+                        (existingOrderCount == null ? 0 : existingOrderCount) +
+                                (newOrderCount == null ? 0 : newOrderCount));
+
+                // 累加payMoney
+                payMoneyList.set(existingIndex,
+                        (existingPayMoney == null ? 0 : existingPayMoney) +
+                                (newPayMoney == null ? 0 : newPayMoney));
+            } else {
+                // 如果不存在,直接添加新项
+                datasList.add(newData);
+                orderCountList.add(newOrderCount);
+                payMoneyList.add(newPayMoney);
+            }
+        }
     }
 }

+ 41 - 1
fs-admin/src/main/java/com/fs/company/controller/CompanyController.java

@@ -1,6 +1,7 @@
 package com.fs.company.controller;
 
 import cn.hutool.core.util.IdUtil;
+import cn.hutool.json.JSONUtil;
 import com.fs.common.annotation.Log;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.controller.BaseController;
@@ -24,8 +25,10 @@ import com.fs.company.vo.CompanyCrmVO;
 import com.fs.company.vo.CompanyVO;
 import com.fs.company.vo.CompanyVoiceCallerListVO;
 import com.fs.core.utils.OrderCodeUtils;
+import com.fs.course.config.CourseConfig;
 import com.fs.framework.web.service.TokenService;
 import com.fs.his.vo.OptionsVO;
+import com.fs.system.service.ISysConfigService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
@@ -55,6 +58,8 @@ public class CompanyController extends BaseController
     private ICompanyDeductService deductService;
     @Autowired
     private ICompanyVoiceCallerService callerService;
+    @Autowired
+    private ISysConfigService configService;
     /**
      * 查询企业列表
      */
@@ -63,6 +68,12 @@ public class CompanyController extends BaseController
     public TableDataInfo list(CompanyParam param)
     {
         startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if(!loginUser.isAdmin() && config.getIsBound() != null && config.getIsBound()){
+            param.setDeptId(loginUser.getDeptId());
+        }
         List<CompanyVO> list = companyService.selectCompanyVOList(param);
         return getDataTable(list);
     }
@@ -75,6 +86,12 @@ public class CompanyController extends BaseController
     @GetMapping("/export")
     public AjaxResult export(CompanyParam company)
     {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if(!loginUser.isAdmin() && config.getIsBound() != null && config.getIsBound()){
+            company.setDeptId(loginUser.getDeptId());
+        }
         List<CompanyVO> list = companyService.selectCompanyVOList(company);
         ExcelUtil<CompanyVO> util = new ExcelUtil<CompanyVO>(CompanyVO.class);
         return util.exportExcel(list, "company");
@@ -98,6 +115,10 @@ public class CompanyController extends BaseController
     @PostMapping
     public R add(@RequestBody Company company)
     {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        if(company.getDeptId() != null){
+            company.setDeptId(loginUser.getDeptId());
+        }
         company.setPassword(SecurityUtils.encryptPassword(company.getPassword()));
         company.setAppId(Md5Utils.hash(company.getUserName()));
         company.setAppKey(Md5Utils.hash(company.getPassword()));
@@ -154,6 +175,12 @@ public class CompanyController extends BaseController
     {
         Company map=new Company();
         map.setIsDel(0);
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if(!loginUser.isAdmin() && config.getIsBound() != null && config.getIsBound()){
+            map.setDeptId(loginUser.getDeptId());
+        }
         List<Company> list = companyService.selectCompanyList(map);
         return R.ok().put("data",list);
     }
@@ -164,6 +191,12 @@ public class CompanyController extends BaseController
     public TableDataInfo companyCrmDayCountList(CompanyParam param)
     {
         startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if(!loginUser.isAdmin() && config.getIsBound() != null && config.getIsBound()){
+            param.setDeptId(loginUser.getDeptId());
+        }
         List<CompanyCrmVO> list = companyService.selectCompanyCrmDayCountList(param);
         return getDataTable(list);
     }
@@ -239,7 +272,14 @@ public class CompanyController extends BaseController
     @GetMapping("/allList")
     public TableDataInfo getHospital()
     {
-        List<OptionsVO> list = companyService.selectAllCompanyList();
+        Long deptId = null;
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if(!loginUser.isAdmin() && config.getIsBound() != null && config.getIsBound()){
+            deptId = loginUser.getDeptId();
+        }
+        List<OptionsVO> list = companyService.selectAllCompanyList(deptId);
         return getDataTable(list);
     }
 }

+ 103 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyTrafficController.java

@@ -0,0 +1,103 @@
+package com.fs.company.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
+import com.fs.company.domain.CompanyTrafficRecord;
+import com.fs.company.param.CompanyTrafficRecordChargeParam;
+import com.fs.company.param.CompanyTrafficRecordQueryParam;
+import com.fs.company.service.ICompanyTrafficRecordService;
+import com.fs.framework.web.service.TokenService;
+import com.fs.hisStore.config.MedicalMallConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.PostConstruct;
+import java.util.List;
+
+@RestController
+@RequestMapping("/company/traffic")
+public class CompanyTrafficController extends BaseController {
+
+    @Autowired
+    private ICompanyTrafficRecordService companyTrafficRecordService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    @Autowired
+    private MedicalMallConfig medicalMallConfig;
+
+    /**
+     * 每天扣除流量余额
+     * */
+    @Scheduled(cron = "0 1 0 * * ?")
+    public void refreshTraffic(){
+        if(medicalMallConfig.isStatics())
+            companyTrafficRecordService.refreshTraffic();
+    }
+
+    /**
+     * 重启时重新计算并更新公司缓存
+     * */
+    @PostConstruct
+    public void init() {
+        if(medicalMallConfig.isStatics())
+            companyTrafficRecordService.init();
+    }
+
+    /** 充值流量 */
+    @PreAuthorize("@ss.hasPermi('company:traffic:charge')")
+    @Log(title = "公司流量充值", businessType = BusinessType.INSERT)
+    @PostMapping(value = "/rechargeTraffic")
+    public R rechargeTraffic(@RequestBody CompanyTrafficRecordChargeParam companyTrafficRecord) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        companyTrafficRecord.setUserId(loginUser.getUser().getUserId());
+        companyTrafficRecord.setUserName(loginUser.getUser().getUserName());
+        companyTrafficRecordService.recharge(companyTrafficRecord);
+        return R.ok();
+    }
+
+    /** 流量充值记录查询 */
+    @PreAuthorize("@ss.hasPermi('company:traffic:list')")
+    @GetMapping(value = "/list")
+    public TableDataInfo list(CompanyTrafficRecordQueryParam param) {
+        startPage();
+        List<CompanyTrafficRecord> list = companyTrafficRecordService.selectList(param);
+        return getDataTable(list);
+    }
+
+    /** 流量充值详情 */
+    @PreAuthorize("@ss.hasPermi('company:traffic:detail')")
+    @GetMapping(value = "/detail/{logId}")
+    public R detail(@PathVariable("logId") Long logId) {
+        CompanyTrafficRecord record = companyTrafficRecordService.selectById(logId);
+        return R.ok().put("data", record);
+    }
+
+    /** 流量换算*/
+    @GetMapping(value = "/trafficConversion")
+    public R trafficConversion(@RequestParam("traffic") Long amount) {
+        Long trafficKB = companyTrafficRecordService.trafficConversion(amount);
+        return R.ok().put("data", trafficKB);
+    }
+
+    /** 刷新单价*/
+    @GetMapping(value = "/refreshPrice")
+    public R refreshPrice() {
+        companyTrafficRecordService.refreshPrice();
+        return R.ok();
+    }
+
+    /** 流量统计*/
+    @GetMapping(value = "/trafficStatistics")
+    public R trafficStatistics(CompanyTrafficRecord record) {
+        return R.ok().put("tatol","").put("dept","").put("company","");
+    }
+}

+ 49 - 0
fs-admin/src/main/java/com/fs/company/controller/CompanyTrafficLogController.java

@@ -0,0 +1,49 @@
+package com.fs.company.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.CompanyTrafficRecordLog;
+import com.fs.company.param.CompanyTrafficRecordLogQueryParam;
+import com.fs.company.service.ICompanyTrafficRecordLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/company/trafficLog")
+public class CompanyTrafficLogController extends BaseController {
+
+    @Autowired
+    private ICompanyTrafficRecordLogService companyTrafficRecordLogService;
+
+
+    /** 流量充值记录查询 */
+    @PreAuthorize("@ss.hasPermi('company:trafficLog:list')")
+    @GetMapping(value = "/list")
+    public TableDataInfo list(CompanyTrafficRecordLogQueryParam param) {
+        startPage();
+        List<CompanyTrafficRecordLog> list = companyTrafficRecordLogService.selectList(param);
+        return getDataTable(list);
+    }
+
+
+    /** 导出 */
+    @PreAuthorize("@ss.hasPermi('company:trafficLog:export')")
+    @Log(title = "流量充值记录导出", businessType = BusinessType.EXPORT)
+    @GetMapping(value = "/export")
+    public AjaxResult export(CompanyTrafficRecordLogQueryParam param) {
+        List<CompanyTrafficRecordLog> list = companyTrafficRecordLogService.selectList(param);
+        ExcelUtil<CompanyTrafficRecordLog> util = new ExcelUtil<>(CompanyTrafficRecordLog.class);
+        return util.exportExcel(list, "流量充值记录");
+    }
+
+
+
+}

+ 41 - 3
fs-admin/src/main/java/com/fs/course/controller/FsCoursePlaySourceConfigController.java

@@ -1,21 +1,29 @@
 package com.fs.course.controller;
 
+import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.bean.BeanUtils;
+import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.FsCoursePlaySourceConfig;
 import com.fs.course.param.FsCoursePlaySourceConfigCreateParam;
 import com.fs.course.param.FsCoursePlaySourceConfigEditParam;
 import com.fs.course.service.IFsCoursePlaySourceConfigService;
 import com.fs.course.vo.FsCoursePlaySourceConfigVO;
+import com.fs.framework.web.service.TokenService;
+import com.fs.system.service.ISysConfigService;
 import com.github.pagehelper.PageHelper;
 import lombok.AllArgsConstructor;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -31,16 +39,33 @@ import java.util.*;
 public class FsCoursePlaySourceConfigController extends BaseController {
 
     private final IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService;
+    private final TokenService tokenService;
+    private final ISysConfigService configService;
 
     @PreAuthorize("@ss.hasPermi('course:playSourceConfig:list')")
     @GetMapping("/list")
     public TableDataInfo list(@RequestParam(required = false) String name,
                               @RequestParam(required = false) String appid,
+                              @RequestParam(required = false) Integer isMall,
                               @RequestParam(required = false, defaultValue = "1") Integer pageNum,
                               @RequestParam(required = false, defaultValue = "10") Integer pageSize) {
         Map<String, Object> params = new HashMap<>();
         params.put("name", name);
         params.put("appid", appid);
+        params.put("isMall", isMall);
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        Long userId = null;
+        Long deptId = null;
+        if(!loginUser.isAdmin() && config.getIsBound() != null && config.getIsBound()){
+            deptId = loginUser.getDeptId();
+            if(config.getDept() == null || !config.getDept()){
+                userId = loginUser.getUserId();
+            }
+        }
+        params.put("userId", userId);
+        params.put("deptId", deptId);
 
         PageHelper.startPage(pageNum, pageSize);
         List<FsCoursePlaySourceConfigVO> list = fsCoursePlaySourceConfigService.selectCoursePlaySourceConfigVOListByMap(params);
@@ -70,8 +95,10 @@ public class FsCoursePlaySourceConfigController extends BaseController {
         if (fsCoursePlaySourceConfigService.count(queryWrapper) > 0) {
             return AjaxResult.error("appid已存在");
         }
-
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         FsCoursePlaySourceConfig config = new FsCoursePlaySourceConfig();
+        config.setCreateUserId(loginUser.getUserId());
+        config.setCreateDeptId(loginUser.getDeptId());
         BeanUtils.copyProperties(param, config);
 
         config.setIsDel(0);
@@ -114,8 +141,19 @@ public class FsCoursePlaySourceConfigController extends BaseController {
         fsCoursePlaySourceConfigService.update(updateWrapper);
         return AjaxResult.success();
     }
+
     @GetMapping("/listAll")
-    public R listAll() {
-        return R.ok().put("data", fsCoursePlaySourceConfigService.list(new QueryWrapper<FsCoursePlaySourceConfig>().eq("is_del", 0)));
+    public R listAll(Long companyId) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        QueryWrapper<FsCoursePlaySourceConfig> queryWrapper = new QueryWrapper<FsCoursePlaySourceConfig>().eq("is_del", 0);
+        if(!loginUser.isAdmin() && config.getIsBound() != null && config.getIsBound()){
+            queryWrapper.eq("create_user_id", loginUser.getUserId()).eq(config.getDept() == null || !config.getDept(), "create_dept_id", loginUser.getDeptId());
+        }
+        if(companyId != null){
+            queryWrapper.and(e -> e.eq("company_id", companyId).or().isNull("company_id"));
+        }
+        return R.ok().put("data", fsCoursePlaySourceConfigService.list(queryWrapper));
     }
 }

+ 22 - 3
fs-admin/src/main/java/com/fs/course/controller/FsCourseRedPacketLogController.java

@@ -1,14 +1,22 @@
 package com.fs.course.controller;
 
+import java.util.ArrayList;
 import java.util.List;
 
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONUtil;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.utils.ServletUtils;
+import com.fs.course.config.CourseConfig;
 import com.fs.course.mapper.FsUserCourseMapper;
 import com.fs.course.mapper.FsUserCourseVideoMapper;
 import com.fs.course.param.FsCourseRedPacketLogParam;
 import com.fs.course.vo.FsCourseRedPacketLogListPVO;
+import com.fs.framework.web.service.TokenService;
 import com.fs.his.utils.PhoneUtil;
 import com.fs.his.vo.OptionsVO;
+import com.fs.system.service.ISysConfigService;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -45,9 +53,11 @@ public class FsCourseRedPacketLogController extends BaseController
     @Autowired
     FsUserCourseMapper fsUserCourseMapper;
     @Autowired
+    TokenService tokenService;
+    @Autowired
+    ISysConfigService configService;
+    @Autowired
     FsUserCourseVideoMapper fsUserCourseVideoMapper;
-
-
     /**
      * 查询短链课程看课记录列表
      */
@@ -135,7 +145,16 @@ public class FsCourseRedPacketLogController extends BaseController
     @GetMapping("/courseList")
     public R courseList()
     {
-        List<OptionsVO> optionsVOS = fsUserCourseMapper.selectFsUserCourseAllList();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long userId = loginUser.getUser().getUserId();
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        List<OptionsVO> optionsVOS;
+        if (ObjectUtil.isNotEmpty(config.getIsBound())&&config.getIsBound()){
+            optionsVOS = fsUserCourseMapper.selectFsUserCourseAllListByUserId(userId);
+        }else{
+            optionsVOS = fsUserCourseMapper.selectFsUserCourseAllList();
+        }
         return R.ok().put("list", optionsVOS);
     }
 

+ 1 - 0
fs-admin/src/main/java/com/fs/course/controller/FsUserCourseVideoController.java

@@ -202,6 +202,7 @@ public class FsUserCourseVideoController extends BaseController
         return R.ok();
     }
     @PostMapping("/batchUpdateRed")
+    @Log(title = "按课程批量保存设置红包金额", businessType = BusinessType.UPDATE)
     public R batchUpdateRed(@RequestBody List<BatchRedUpdate> list){
         fsUserCourseVideoService.batchUpdateRed(list);
         return R.ok();

+ 1 - 2
fs-admin/src/main/java/com/fs/fastGpt/FastgptExtUserTagController.java

@@ -4,7 +4,6 @@ import java.util.List;
 
 import com.fs.common.core.domain.R;
 import com.fs.fastGpt.vo.FastgptExtUserTagVO;
-import com.fs.framework.web.service.TokenService;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.vo.QwOptionsVO;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -64,7 +63,7 @@ public class FastgptExtUserTagController extends BaseController
     @GetMapping("/getMyQwUserList")
     public R getMyQwUserList()
     {
-        List<QwOptionsVO> list = qwCompanyService.selectQwCompanyListOptionsVO();
+        List<QwOptionsVO> list = qwCompanyService.selectQwCompanyListOptionsVO(null, null);
         return  R.ok().put("data",list);
     }
 

+ 25 - 3
fs-admin/src/main/java/com/fs/his/controller/FsCompanyController.java

@@ -2,7 +2,7 @@ package com.fs.his.controller;
 
 import java.util.List;
 
-import cn.hutool.core.util.IdUtil;
+import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
@@ -22,10 +22,11 @@ import com.fs.company.service.ICompanyService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.company.vo.CompanyVO;
 import com.fs.core.utils.OrderCodeUtils;
+import com.fs.course.config.CourseConfig;
 import com.fs.framework.web.service.TokenService;
-import com.fs.his.domain.FsDoctor;
 import com.fs.his.mapper.FsDoctorMapper;
 import com.fs.his.vo.OptionsVO;
+import com.fs.system.service.ISysConfigService;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
@@ -67,6 +68,8 @@ public class FsCompanyController extends BaseController
     private FsDoctorMapper fsDoctorMapper;
     @Autowired
     private ICompanyDeductService deductService;
+    @Autowired
+    private ISysConfigService configService;
     /**
      * 查询诊所管理列表
      */
@@ -75,6 +78,12 @@ public class FsCompanyController extends BaseController
     public TableDataInfo list(Company company)
     {
         startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if(!loginUser.isAdmin() && config.getIsBound() != null && config.getIsBound()){
+            company.setDeptId(loginUser.getDeptId());
+        }
         List<CompanyVO> list = companyService.selectCompanyListVO(company);
         return getDataTable(list);
     }
@@ -83,7 +92,14 @@ public class FsCompanyController extends BaseController
     public R companyList()
     {
 
-        List<OptionsVO> list = companyService.selectAllCompanyList();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long depId = null;
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if(!loginUser.isAdmin() && config.getIsBound() != null && config.getIsBound()){
+            depId = loginUser.getDeptId();
+        }
+        List<OptionsVO> list = companyService.selectAllCompanyList(depId);
         return R.ok().put("data",list);
     }
     /**
@@ -133,6 +149,12 @@ public class FsCompanyController extends BaseController
     @PostMapping
     public R add(@RequestBody Company company)
     {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if(!loginUser.isAdmin() && config.getIsBound() != null && config.getIsBound()){
+            company.setDeptId(loginUser.getDeptId());
+        }
         return companyService.insertCompany(company);
     }
 

+ 124 - 0
fs-admin/src/main/java/com/fs/his/controller/FsComplaintTemplateController.java

@@ -0,0 +1,124 @@
+package com.fs.his.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.his.domain.FsComplaintTemplate;
+import com.fs.his.service.IFsComplaintTemplateService;
+import com.fs.his.utils.ComplaintTreeUtil;
+import com.fs.his.vo.FsComplaintTemplateVO;
+import com.google.common.collect.Lists;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 投诉模板Controller
+ *
+ * @author fs
+ * @date 2025-06-09
+ */
+@RestController
+@RequestMapping("/his/template")
+public class FsComplaintTemplateController extends BaseController
+{
+    @Autowired
+    private IFsComplaintTemplateService fsComplaintTemplateService;
+
+    /**
+     * 查询投诉模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:template:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsComplaintTemplate fsComplaintTemplate)
+    {
+        startPage();
+        fsComplaintTemplate.setIsDel(0L);
+        List<FsComplaintTemplate> list = fsComplaintTemplateService.selectFsComplaintTemplateList(fsComplaintTemplate);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询投诉模板树形结果
+     */
+    @PreAuthorize("@ss.hasPermi('his:template:list')")
+    @GetMapping("/treeList")
+    public R treeList(FsComplaintTemplate fsComplaintTemplate)
+    {
+        List<FsComplaintTemplate> list = fsComplaintTemplateService.selectFsComplaintTemplateList(fsComplaintTemplate);
+        List<FsComplaintTemplateVO> templateVOS = Lists.newArrayList();
+        for (FsComplaintTemplate template : list){
+            FsComplaintTemplateVO templateVO = new FsComplaintTemplateVO();
+            templateVO.setId(template.getId());
+            templateVO.setName(template.getName());
+            templateVO.setParentId(template.getParentId());
+            templateVO.setDescription(template.getDescription());
+            templateVO.setSort(template.getSort());
+            templateVOS.add(templateVO);
+        }
+        return R.ok().put("data", ComplaintTreeUtil.list2TreeConverter(templateVOS, 0));
+    }
+
+
+    /**
+     * 导出投诉模板列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:template:export')")
+    @Log(title = "投诉模板", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsComplaintTemplate fsComplaintTemplate)
+    {
+        List<FsComplaintTemplate> list = fsComplaintTemplateService.selectFsComplaintTemplateList(fsComplaintTemplate);
+        ExcelUtil<FsComplaintTemplate> util = new ExcelUtil<FsComplaintTemplate>(FsComplaintTemplate.class);
+        return util.exportExcel(list, "投诉模板数据");
+    }
+
+    /**
+     * 获取投诉模板详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:template:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsComplaintTemplateService.selectFsComplaintTemplateById(id));
+    }
+
+    /**
+     * 新增投诉模板
+     */
+    @PreAuthorize("@ss.hasPermi('his:template:add')")
+    @Log(title = "投诉模板", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsComplaintTemplate fsComplaintTemplate)
+    {
+        return toAjax(fsComplaintTemplateService.insertFsComplaintTemplate(fsComplaintTemplate));
+    }
+
+    /**
+     * 修改投诉模板
+     */
+    @PreAuthorize("@ss.hasPermi('his:template:edit')")
+    @Log(title = "投诉模板", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsComplaintTemplate fsComplaintTemplate)
+    {
+        return toAjax(fsComplaintTemplateService.updateFsComplaintTemplate(fsComplaintTemplate));
+    }
+
+    /**
+     * 删除投诉模板
+     */
+    @PreAuthorize("@ss.hasPermi('his:template:remove')")
+    @Log(title = "投诉模板", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsComplaintTemplateService.deleteFsComplaintTemplateByIds(ids));
+    }
+}

+ 98 - 0
fs-admin/src/main/java/com/fs/his/controller/FsHisComplaintController.java

@@ -0,0 +1,98 @@
+package com.fs.his.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.his.domain.FsHisComplaint;
+import com.fs.his.service.IFsHisComplaintService;
+import com.fs.his.vo.FsUserComplaintVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 用户投诉Controller
+ *
+ * @author fs
+ * @date 2025-06-09
+ */
+@RestController
+@RequestMapping("/his/complaint")
+public class FsHisComplaintController extends BaseController
+{
+    @Autowired
+    private IFsHisComplaintService fsUserComplaintService;
+
+    /**
+     * 查询用户投诉列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:complaint:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsHisComplaint fsHisComplaint)
+    {
+        startPage();
+        List<FsUserComplaintVo> list = fsUserComplaintService.selectFsUserComplaintList(fsHisComplaint);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出用户投诉列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:complaint:export')")
+    @Log(title = "用户投诉", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsHisComplaint fsHisComplaint)
+    {
+        List<FsUserComplaintVo> list = fsUserComplaintService.selectFsUserComplaintList(fsHisComplaint);
+        ExcelUtil<FsUserComplaintVo> util = new ExcelUtil<FsUserComplaintVo>(FsUserComplaintVo.class);
+        return util.exportExcel(list, "用户投诉数据");
+    }
+
+    /**
+     * 获取用户投诉详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:complaint:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsUserComplaintService.selectFsUserComplaintById(id));
+    }
+
+    /**
+     * 新增用户投诉
+     */
+    @PreAuthorize("@ss.hasPermi('his:complaint:add')")
+    @Log(title = "用户投诉", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsHisComplaint fsHisComplaint)
+    {
+        return toAjax(fsUserComplaintService.insertFsUserComplaint(fsHisComplaint));
+    }
+
+    /**
+     * 修改用户投诉
+     */
+    @PreAuthorize("@ss.hasPermi('his:complaint:edit')")
+    @Log(title = "用户投诉", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsHisComplaint fsHisComplaint)
+    {
+        return toAjax(fsUserComplaintService.updateFsUserComplaint(fsHisComplaint));
+    }
+
+    /**
+     * 删除用户投诉
+     */
+    @PreAuthorize("@ss.hasPermi('his:complaint:remove')")
+    @Log(title = "用户投诉", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsUserComplaintService.deleteFsUserComplaintByIds(ids));
+    }
+}

+ 2 - 1
fs-admin/src/main/java/com/fs/his/controller/FsPackageOrderController.java

@@ -193,7 +193,8 @@ public class FsPackageOrderController extends BaseController
     @GetMapping("storeRefund/{orderId}")
     public AjaxResult storeRefund(@PathVariable("orderId") Long orderId)
     {
-        return AjaxResult.success(fsPackageOrderService.PackageStoreOrderRefund(orderId));
+        String nickName = getLoginUser().getUser().getNickName();
+        return AjaxResult.success(fsPackageOrderService.PackageStoreOrderRefund(orderId,nickName));
     }
 
     /**

+ 19 - 0
fs-admin/src/main/java/com/fs/his/controller/FsPatientController.java

@@ -67,6 +67,25 @@ public class FsPatientController extends BaseController
         return getDataTable(list);
     }
 
+    @GetMapping("/userList")
+    public TableDataInfo userList(FsPatientVO fsPatient)
+    {
+        startPage();
+        List<FsPatientVO> list = fsPatientService.selectFsPatientListVO(fsPatient);
+        for (FsPatientVO fsPatientVO : list){
+            if (fsPatientVO.getPhone()!=null){
+                fsPatientVO.setPhone(fsPatientVO.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+            }
+            if (fsPatientVO.getIdCard()!=null){
+                fsPatientVO.setIdCard(fsPatientVO.getIdCard().replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1**********$2"));
+            }
+            if (fsPatientVO.getMobile()!=null){
+                fsPatientVO.setMobile(fsPatientVO.getMobile().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+            }
+        }
+        return getDataTable(list);
+    }
+
     /**
      * 导出病人列表
      */

+ 40 - 2
fs-admin/src/main/java/com/fs/his/controller/FsStoreOrderController.java

@@ -1,9 +1,11 @@
 package com.fs.his.controller;
 
+import java.math.BigDecimal;
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 import cn.hutool.core.util.StrUtil;
@@ -35,6 +37,7 @@ import com.fs.his.domain.*;
 import com.fs.his.dto.ExpressInfoDTO;
 import com.fs.his.dto.StoreOrderExpressExportDTO;
 import com.fs.his.dto.TracesDTO;
+import com.fs.his.enums.FsStoreOrderLogEnum;
 import com.fs.his.enums.ShipperCodeEnum;
 import com.fs.his.param.FsFollowMsgParam;
 import com.fs.his.param.FsStoreOrderParam;
@@ -48,6 +51,7 @@ import com.fs.system.mapper.SysConfigMapper;
 import com.fs.system.service.ISysRoleService;
 import com.github.pagehelper.PageHelper;
 import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -120,11 +124,14 @@ public class FsStoreOrderController extends BaseController
 
     @Autowired
     private CloudHostProper cloudHostProper;
+
+    @Autowired
+    private IFsStoreOrderLogsService fsStoreOrderLogsService;
     /**
      * 查询订单列表
      */
     @PostMapping("/list")
-    public TableDataInfo list(@RequestBody FsStoreOrderParam fsStoreOrder)
+    public FsStoreOrderListAndStatisticsVo list(@RequestBody FsStoreOrderParam fsStoreOrder)
     {
         PageHelper.startPage(fsStoreOrder);
         if (fsStoreOrder.getUserPhoneMk()!=null&& !fsStoreOrder.getUserPhoneMk().isEmpty()){
@@ -151,7 +158,29 @@ public class FsStoreOrderController extends BaseController
             }
             dataTable.setMsg("jnmy");
         }
-        return dataTable;
+        FsStoreOrderListAndStatisticsVo vo = new FsStoreOrderListAndStatisticsVo();
+        BeanUtils.copyProperties(dataTable, vo);
+        if (dataTable.getTotal()>0){
+            Map<String,BigDecimal> statistics= fsStoreOrderService.selectFsStoreOrderStatistics(fsStoreOrder);
+            if (statistics != null && statistics.size() >= 3){
+                vo.setPayPriceTotal(statistics.get("pay_price").toString());
+                vo.setPayMoneyTotal(statistics.get("pay_money").toString());
+                vo.setPayRemainTotal(statistics.get("pay_remain").toString());
+            }else {
+                vo.setPayPriceTotal("0");
+                vo.setPayMoneyTotal("0");
+                vo.setPayRemainTotal("0");
+            }
+            //商品数量合计
+            String productStatistics= fsStoreOrderService.selectFsStoreOrderProductStatistics(fsStoreOrder);
+            if (StringUtils.isNotBlank(productStatistics)){
+                vo.setProductInfo(productStatistics);
+            } else {
+                vo.setProductInfo("");
+            }
+
+        }
+        return vo;
     }
 
     /**
@@ -598,6 +627,7 @@ public class FsStoreOrderController extends BaseController
     @PostMapping(value = "/batchCreateErpOrder")
     public R batchCreateErpOrder(@RequestBody FsStoreOrderSetErpPhoneParam param)
     {
+        String nickName = getLoginUser().getUser().getNickName();
         String loginAccount = param.getLoginAccount();
         if (StringUtils.isBlank(loginAccount)){
             return R.error("未选择推送erp账户");
@@ -629,8 +659,12 @@ public class FsStoreOrderController extends BaseController
                 FsStoreOrderDf temp = fsStoreOrderDfService.selectFsStoreOrderDfByOrderId(df.getOrderId());
                 if (temp == null){
                     fsStoreOrderDfService.insertFsStoreOrderDf(df);
+                    fsStoreOrderLogsService.create(orderId, FsStoreOrderLogEnum.SET_PUSH_ACCOUNT.getValue(),
+                            nickName + " " +FsStoreOrderLogEnum.SET_PUSH_ACCOUNT.getDesc() + ":" + df.getLoginAccount());
                 }
                 fsStoreOrderService.createOmsOrder(orderId);
+                fsStoreOrderLogsService.create(orderId, FsStoreOrderLogEnum.PUSH_ORDER_ERP.getValue(),
+                        nickName + " " +FsStoreOrderLogEnum.PUSH_ORDER_ERP.getDesc() + ":" + df.getLoginAccount());
             } catch (ParseException e) {
                 throw new RuntimeException(e);
             }
@@ -645,6 +679,7 @@ public class FsStoreOrderController extends BaseController
     @PostMapping(value = "/batchSetErpOrder")
     public R batchSetErpOrder(@RequestBody FsStoreOrderSetErpPhoneParam param)
     {
+        String nickName = getLoginUser().getUser().getNickName();
         String loginAccount = param.getLoginAccount();
         if (StringUtils.isBlank(loginAccount)){
             return R.error("未选择erp账户");
@@ -673,6 +708,8 @@ public class FsStoreOrderController extends BaseController
             } else {
                 fsStoreOrderDfService.insertFsStoreOrderDf(df);
             }
+            fsStoreOrderLogsService.create(orderId, FsStoreOrderLogEnum.SET_PUSH_ACCOUNT.getValue(),
+                    nickName + " " +FsStoreOrderLogEnum.SET_PUSH_ACCOUNT.getDesc() + ":" + df.getLoginAccount());
         });
         return R.ok();
     }
@@ -855,6 +892,7 @@ public class FsStoreOrderController extends BaseController
     @PostMapping("/editErpPhone")
     public AjaxResult editErpPhone(@RequestBody FsStoreOrderSetErpPhoneParam param)
     {
+        param.setOpeName(getLoginUser().getUser().getNickName());
         List<String> erpPhone = param.getErpPhone();
         if (erpPhone == null || erpPhone.isEmpty()) {
             return AjaxResult.error("请选择手机号");

+ 28 - 0
fs-admin/src/main/java/com/fs/his/controller/FsUserController.java

@@ -166,6 +166,34 @@ public class FsUserController extends BaseController
         return getDataTable(list);
     }
 
+    @PreAuthorize("@ss.hasPermi('his:user:export')")
+    @GetMapping("/exportListProject")
+    public AjaxResult exportListProject(FsUser fsUser)
+    {
+        if(StringUtils.isNotEmpty(fsUser.getPhone())){
+            fsUser.setPhone(encryptPhone(fsUser.getPhone()));
+        }
+        List<FsUserVO> list = fsUserService.selectFsUserVOListByProject(fsUser);
+        boolean checkPhone = isCheckPhone();
+        for (FsUserVO fsUserVO : list) {
+            if(fsUserVO.getPhone() != null&&fsUserVO.getPhone()!=""){
+                if (!checkPhone){
+                    if (fsUserVO.getPhone().length()>11){
+                        fsUserVO.setPhone(decryptPhoneMk(fsUserVO.getPhone()));
+                    }else {
+                        fsUserVO.setPhone(fsUserVO.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+                    }
+                } else {
+                    if (fsUserVO.getPhone().length()>11) {
+                        fsUserVO.setPhone(decryptPhone(fsUserVO.getPhone()));
+                    }
+                }
+            }
+        }
+        ExcelUtil<FsUserVO> util = new ExcelUtil<FsUserVO>(FsUserVO.class);
+        return util.exportExcel(list, "项目会员数据");
+    }
+
     /**
      * 导出用户列表
      */

+ 2 - 0
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -573,10 +573,12 @@ public class Task {
     public void CreateOmsAndHis()
     {
         List<Long> omsList = fsStoreOrderMapper.selectFsStoreOrderNoCreateOms();
+        logger.info("推送订单id====>{}",omsList);
         for (Long l : omsList) {
             try {
                 fsStoreOrderService.createOmsOrder(l);
             } catch (Exception e) {
+                logger.error("推送订单异常:",e);
             }
         }
 //        List<Long> tuiOrderList = fsStoreOrderMapper.selectFsStoreOrderNoTuiOrder();

+ 24 - 0
fs-admin/src/main/java/com/fs/hisStore/FsHisStoreLogController.java

@@ -0,0 +1,24 @@
+package com.fs.hisStore;
+
+import com.fs.common.core.domain.R;
+import com.fs.hisStore.domain.SysOperLogScrm;
+import com.fs.hisStore.service.IFsHisStoreAuditLogScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/store/hisStoreLog")
+public class FsHisStoreLogController {
+
+    @Autowired
+    private IFsHisStoreAuditLogScrmService fsHisStoreLogScrmService;
+    @RequestMapping("/{mainId}")
+    public R getInfo(@PathVariable("mainId") Long mainId) {
+        List<SysOperLogScrm> sysOperLogScrms = fsHisStoreLogScrmService.selectOperLogByMainId(mainId);
+        return R.ok().put("data",sysOperLogScrms);
+    }
+}

+ 134 - 0
fs-admin/src/main/java/com/fs/hisStore/FsStoreSCRMController.java

@@ -0,0 +1,134 @@
+package com.fs.hisStore;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ParseUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.his.domain.FsStore;
+import com.fs.his.param.FsStoreAuditParam;
+import com.fs.his.service.IFsStoreService;
+import com.fs.hisStore.domain.FsStoreScrm;
+import com.fs.hisStore.service.IFsStoreScrmService;
+import com.fs.hisStore.vo.FsStoreScrmVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 店铺管理Controller
+ *
+ * @author fs
+ * @date 2023-06-15
+ */
+@RestController
+@RequestMapping("/store/store")
+public class FsStoreSCRMController extends BaseController
+{
+    @Autowired
+    private IFsStoreScrmService fsStoreService;
+
+    /**
+     * 查询店铺管理列表
+     */
+    @PreAuthorize("@ss.hasPermi('store:store:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreScrm fsStore)
+    {
+        startPage();
+        List<FsStoreScrm> list = fsStoreService.selectFsStoreList(fsStore);
+        for (FsStoreScrm store : list) {
+            store.setPhone(ParseUtils.parsePhone(store.getPhone()));
+        }
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出店铺管理列表
+     */
+    @PreAuthorize("@ss.hasPermi('store:store:export')")
+    @Log(title = "店铺管理", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreScrm fsStore)
+    {
+        List<FsStoreScrm> list = fsStoreService.selectFsStoreList(fsStore);
+        for (FsStoreScrm store : list) {
+            store.setPhone(ParseUtils.parsePhone(store.getPhone()));
+        }
+        ExcelUtil<FsStoreScrm> util = new ExcelUtil<FsStoreScrm>(FsStoreScrm.class);
+        return util.exportExcel(list, "店铺管理数据");
+    }
+
+    /**
+     * 获取店铺管理详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('store:store:query')")
+    @GetMapping(value = "/{storeId}")
+    public AjaxResult getInfo(@PathVariable("storeId") Long storeId)
+    {
+        FsStoreScrm fsStore = fsStoreService.selectFsStoreByStoreId(storeId);
+        fsStore.setPhone(ParseUtils.parsePhone(fsStore.getPhone()));
+        return AjaxResult.success(fsStore);
+    }
+
+    /**
+     * 新增店铺管理
+     */
+    @PreAuthorize("@ss.hasPermi('store:store:add')")
+    @Log(title = "店铺管理", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreScrm fsStore)
+    {
+        return AjaxResult.success(fsStoreService.insertFsStore(fsStore));
+    }
+
+    /**
+     * 修改店铺管理
+     */
+    @PreAuthorize("@ss.hasPermi('store:store:edit')")
+    @Log(title = "店铺管理", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreScrm fsStore)
+    {
+
+        if (fsStore.getPhone()!=null&&fsStore.getPhone().contains("*")){
+            fsStore.setPhone(null);
+        }
+        return toAjax(fsStoreService.updateFsStore(fsStore));
+    }
+
+    /**
+     * 删除店铺管理
+     */
+    @PreAuthorize("@ss.hasPermi('store:store:remove')")
+    @Log(title = "店铺管理", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{storeIds}")
+    public AjaxResult remove(@PathVariable Long[] storeIds)
+    {
+        return toAjax(fsStoreService.deleteFsStoreByStoreIds(storeIds));
+    }
+
+    /**
+     * 店铺审核
+     */
+    @PreAuthorize("@ss.hasPermi('store:store:audit')")
+    @Log(title = "店铺管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/audit")
+    public AjaxResult audit(@RequestBody FsStoreAuditParam fsStore)
+    {
+        return toAjax(fsStoreService.updateFsStoreAudit(fsStore));
+    }
+
+    @GetMapping("/storeList")
+    public R storeList(){
+        List<FsStoreScrmVO> list = fsStoreService.selectAllStore();
+        return R.ok().put("data",list);
+    }
+
+
+}

+ 74 - 1
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreHealthOrderScrmController.java

@@ -135,6 +135,38 @@ public class FsStoreHealthOrderScrmController extends BaseController {
         return util.exportExcel(list, "订单数据");
     }
 
+    /**
+     * 导出健康商城列表(明文)
+     */
+    @PreAuthorize("@ss.hasPermi('store:healthStoreOrder:export:details')")
+    @Log(title = "健康商城订单", businessType = BusinessType.EXPORT)
+    @GetMapping("/healthExportDetails")
+    public AjaxResult healthExportDetails(FsStoreOrderParam param) {
+        if ("".equals(param.getBeginTime()) && "".equals(param.getEndTime())){
+            param.setBeginTime(null);
+            param.setEndTime(null);
+        }
+        if (fsStoreOrderService.isEntityNull(param)){
+            return AjaxResult.error("请筛选数据导出");
+        }
+        if(!StringUtils.isEmpty(param.getCreateTimeRange())){
+            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getPayTimeRange())){
+            param.setPayTimeList(param.getPayTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getDeliverySendTimeRange())){
+            param.setDeliverySendTimeList(param.getDeliverySendTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getDeliveryImportTimeRange())){
+            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
+        }
+        param.setIsHealth("1");
+        List<FsStoreOrderExportVO> list = fsStoreOrderService.selectFsStoreOrderListVOByExport(param);
+        ExcelUtil<FsStoreOrderExportVO> util = new ExcelUtil<FsStoreOrderExportVO>(FsStoreOrderExportVO.class);
+        return util.exportExcel(list, "订单数据");
+    }
+
 
     @PreAuthorize("@ss.hasPermi('store:healthStoreOrder:exportItems')")
     @Log(title = "商城订单明细导出", businessType = BusinessType.EXPORT)
@@ -181,6 +213,47 @@ public class FsStoreHealthOrderScrmController extends BaseController {
         return util.exportExcel(list, "订单明细数据");
     }
 
+    @PreAuthorize("@ss.hasPermi('store:healthStoreOrder:exportItems:details')")
+    @Log(title = "商城订单明细导出", businessType = BusinessType.EXPORT)
+    @GetMapping("/healthExportItemsDetails")
+    public AjaxResult healthExportItemsDetails(FsStoreOrderParam param) {
+        if ("".equals(param.getBeginTime()) && "".equals(param.getEndTime())){
+            param.setBeginTime(null);
+            param.setEndTime(null);
+        }
+        if (fsStoreOrderService.isEntityNull(param)){
+            return AjaxResult.error("请筛选数据导出");
+        }
+        if(!StringUtils.isEmpty(param.getCreateTimeRange())){
+            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getPayTimeRange())){
+            param.setPayTimeList(param.getPayTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getDeliverySendTimeRange())){
+            param.setDeliverySendTimeList(param.getDeliverySendTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getDeliveryImportTimeRange())){
+            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
+        }
+        param.setIsHealth("1");
+        List<FsStoreOrderItemExportVO> list = orderItemService.selectFsStoreOrderItemListExportVO(param);
+        //对手机号脱敏
+        if (list != null) {
+            for (FsStoreOrderItemExportVO vo : list) {
+                if (!StringUtils.isEmpty(vo.getJsonInfo())) {
+                    try {
+                        StoreOrderProductDTO orderProductDTO = JSONObject.parseObject(vo.getJsonInfo(), StoreOrderProductDTO.class);
+                        BeanUtil.copyProperties(orderProductDTO, vo);
+                    } catch (Exception e) {
+                    }
+                }
+            }
+        }
+        ExcelUtil<FsStoreOrderItemExportVO> util = new ExcelUtil<FsStoreOrderItemExportVO>(FsStoreOrderItemExportVO.class);
+        return util.exportExcel(list, "订单明细数据");
+    }
+
     //订单发货批量导入
     @Log(title = "发货同步导入", businessType = BusinessType.IMPORT)
     @PostMapping("/importDeliveryNoteExpress")
@@ -203,7 +276,7 @@ public class FsStoreHealthOrderScrmController extends BaseController {
         try {
             List<FsOrderDeliveryNoteDTO> dtoList = util.importExcel(file.getInputStream());
             if(!dtoList.isEmpty()){
-                fsStoreOrderService.importDeliveryNoteExpress(dtoList);
+               return fsStoreOrderService.importDeliveryNoteExpress(dtoList);
             }else {
                 R.error("操作失败,导入数据不能小于1条!");
             }

+ 83 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java

@@ -7,9 +7,11 @@ import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ParseUtils;
+import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.param.CompanyStoreOrderMoneyLogsListParam;
@@ -20,6 +22,7 @@ import com.fs.erp.domain.ErpOrderQuery;
 import com.fs.erp.dto.ErpOrderQueryRequert;
 import com.fs.erp.dto.ErpOrderQueryResponse;
 import com.fs.erp.service.IErpOrderService;
+import com.fs.framework.web.service.TokenService;
 import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsExpressService;
 import com.fs.his.service.IFsUserService;
@@ -101,6 +104,9 @@ public class FsStoreOrderScrmController extends BaseController {
     @Autowired
     private IFsStoreOrderAuditLogScrmService orderAuditLogService;
 
+    @Autowired
+    private TokenService tokenService;
+
     private IErpOrderService getErpService(){
         //判断是否开启erp
         IErpOrderService erpOrderService = null;
@@ -255,6 +261,9 @@ public class FsStoreOrderScrmController extends BaseController {
         List<FsStoreOrderExportVO> list = fsStoreOrderService.selectFsStoreOrderListVOByExport(param);
         //对手机号脱敏
         if (list != null) {
+            //获取当前账号角色权限
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+
             for (FsStoreOrderExportVO vo : list) {
                 if (vo.getPhone() != null) {
                     vo.setPhone(vo.getPhone().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
@@ -272,6 +281,37 @@ public class FsStoreOrderScrmController extends BaseController {
     }
 
 
+    /**
+     * 导出订单列表(明文)
+     */
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:export:details')")
+    @Log(title = "订单", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportDetails")
+    public AjaxResult exportDetails(FsStoreOrderParam param) {
+        if ("".equals(param.getBeginTime()) && "".equals(param.getEndTime())){
+            param.setBeginTime(null);
+            param.setEndTime(null);
+        }
+        if (fsStoreOrderService.isEntityNull(param)){
+            return AjaxResult.error("请筛选数据导出");
+        }
+        if(!StringUtils.isEmpty(param.getCreateTimeRange())){
+            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getPayTimeRange())){
+            param.setPayTimeList(param.getPayTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getDeliverySendTimeRange())){
+            param.setDeliverySendTimeList(param.getDeliverySendTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getDeliveryImportTimeRange())){
+            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
+        }
+        param.setNotHealth(1);
+        List<FsStoreOrderExportVO> list = fsStoreOrderService.selectFsStoreOrderListVOByExport(param);
+        ExcelUtil<FsStoreOrderExportVO> util = new ExcelUtil<FsStoreOrderExportVO>(FsStoreOrderExportVO.class);
+        return util.exportExcel(list, "订单数据");
+    }
 
     @PreAuthorize("@ss.hasPermi('store:storeOrder:exportItems')")
     @Log(title = "订单明细导出", businessType = BusinessType.EXPORT)
@@ -321,6 +361,49 @@ public class FsStoreOrderScrmController extends BaseController {
         return util.exportExcel(list, "订单明细数据");
     }
 
+    /**
+     * 订单明细导出(明文)
+     * **/
+    @PreAuthorize("@ss.hasPermi('store:storeOrder:exportItems:details')")
+    @Log(title = "订单明细导出", businessType = BusinessType.EXPORT)
+    @GetMapping("/exportItemsDetails")
+    public AjaxResult exportItemsDetails(FsStoreOrderParam param) {
+        if ("".equals(param.getBeginTime()) && "".equals(param.getEndTime())){
+            param.setBeginTime(null);
+            param.setEndTime(null);
+        }
+        if (fsStoreOrderService.isEntityNull(param)){
+            return AjaxResult.error("请筛选数据导出");
+        }
+        if(!StringUtils.isEmpty(param.getCreateTimeRange())){
+            param.setCreateTimeList(param.getCreateTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getPayTimeRange())){
+            param.setPayTimeList(param.getPayTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getDeliveryImportTimeRange())){
+            param.setDeliveryImportTimeList(param.getDeliveryImportTimeRange().split("--"));
+        }
+        if(!StringUtils.isEmpty(param.getDeliverySendTimeRange())){
+            param.setDeliverySendTimeList(param.getDeliverySendTimeRange().split("--"));
+        }
+        param.setNotHealth(1);
+        List<FsStoreOrderItemExportVO> list = orderItemService.selectFsStoreOrderItemListExportVO(param);
+        //对手机号脱敏
+        if (list != null) {
+            for (FsStoreOrderItemExportVO vo : list) {
+                if (!StringUtils.isEmpty(vo.getJsonInfo())) {
+                    try {
+                        StoreOrderProductDTO orderProductDTO = JSONObject.parseObject(vo.getJsonInfo(), StoreOrderProductDTO.class);
+                        BeanUtil.copyProperties(orderProductDTO, vo);
+                    } catch (Exception e) {
+                    }
+                }
+            }
+        }
+        ExcelUtil<FsStoreOrderItemExportVO> util = new ExcelUtil<FsStoreOrderItemExportVO>(FsStoreOrderItemExportVO.class);
+        return util.exportExcel(list, "订单明细数据");
+    }
 
     @GetMapping("/orderItemsNum")
     public R orderItemsNum(FsStoreOrderParam param) {

+ 14 - 2
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductScrmController.java

@@ -21,6 +21,7 @@ import com.fs.hisStore.param.FsStoreTuiProductAttrValueParam;
 import com.fs.hisStore.service.IFsStoreProductAttrScrmService;
 import com.fs.hisStore.service.IFsStoreProductAttrValueScrmService;
 import com.fs.hisStore.service.IFsStoreProductScrmService;
+import com.fs.statis.dto.ProductAuditDTO;
 import com.mysql.cj.util.StringUtils;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
@@ -69,6 +70,17 @@ public class FsStoreProductScrmController extends BaseController
         return R.ok();
     }
 
+    @PreAuthorize("@ss.hasPermi('store:storeProduct:list')")
+    @PostMapping("/batchAudit")
+    @Log(title = "商品审核", businessType = BusinessType.AUDIT,isStoreLog = true,logParam = {"商品","批量审核商品信息"},
+    logParamExpression = "#p0.getProductIds().size()>1?"
+            +"(#p0.isAudit==1?new String[]{'商品','商品批量审核通过'}: new String[]{'商品','商品批量审核退回'}):" +
+            "(#p0.isAudit==1?new String[]{'商品','商品审核通过'}: new String[]{'商品','商品审核退回'})")
+    public R batchAudit(@RequestBody ProductAuditDTO auditDTO){
+        fsStoreProductService.batchAudit(auditDTO);
+        return R.ok();
+    }
+
     /**
      * 查询商品列表
      */
@@ -169,8 +181,8 @@ public class FsStoreProductScrmController extends BaseController
 
     @ApiOperation(value = "生成属性")
     @PostMapping(value = "/genFormatAttr/{productId}")
-    public ResponseEntity genFormatAttr(@PathVariable Long productId, @RequestBody String jsonStr){
-        return new ResponseEntity<>(fsStoreProductService.getFormatAttr(productId,jsonStr), HttpStatus.OK);
+    public ResponseEntity genFormatAttr(@PathVariable Long productId, @RequestBody String jsonStr,Long[] stores){
+        return new ResponseEntity<>(fsStoreProductService.getFormatAttr(productId,jsonStr, stores), HttpStatus.OK);
     }
 
 

+ 108 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreRecommendScrmController.java

@@ -0,0 +1,108 @@
+package com.fs.hisStore.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.domain.FsStoreRecommendScrm;
+import com.fs.hisStore.service.IFsStoreRecommendScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 店铺推荐Controller
+ *
+ * @author fs
+ * @date 2023-06-15
+ */
+@RestController
+@RequestMapping("/store/recommend")
+public class FsStoreRecommendScrmController extends BaseController
+{
+    @Autowired
+    private IFsStoreRecommendScrmService fsStoreRecommendService;
+
+    /**
+     * 查询店铺推荐列表
+     */
+    @PreAuthorize("@ss.hasPermi('store:recommend:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreRecommendScrm fsStoreRecommendScrm)
+    {
+        startPage();
+        List<FsStoreRecommendScrm> list = fsStoreRecommendService.selectFsStoreRecommendScrmList(fsStoreRecommendScrm);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取店铺推荐详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('store:recommend:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsStoreRecommendService.selectFsStoreRecommendScrmById(id));
+    }
+
+    /**
+     * 新增店铺推荐
+     */
+    @PreAuthorize("@ss.hasPermi('store:recommend:add')")
+    @Log(title = "店铺推荐", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreRecommendScrm fsStoreRecommendScrm)
+    {
+        return toAjax(fsStoreRecommendService.insertFsStoreRecommendScrm(fsStoreRecommendScrm));
+    }
+
+    /**
+     * 修改店铺推荐
+     */
+    @PreAuthorize("@ss.hasPermi('store:recommend:edit')")
+    @Log(title = "店铺推荐", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreRecommendScrm fsStoreRecommendScrm)
+    {
+        return toAjax(fsStoreRecommendService.updateFsStoreRecommendScrm(fsStoreRecommendScrm));
+    }
+
+    /**
+     * 删除店铺推荐
+     */
+    @PreAuthorize("@ss.hasPermi('store:recommend:delete')")
+    @Log(title = "店铺推荐", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreRecommendService.deleteFsStoreRecommendScrmByIds(ids));
+    }
+
+    /**
+     * 查询有效的推荐店铺列表(用于前端展示)
+     */
+    @GetMapping("/validList")
+    public TableDataInfo validList(FsStoreRecommendScrm fsStoreRecommendScrm)
+    {
+        startPage();
+        List<FsStoreRecommendScrm> list = fsStoreRecommendService.selectValidRecommendList(fsStoreRecommendScrm);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出
+     * */
+    @PreAuthorize("@ss.hasPermi('store:recommend:export')")
+    @Log(title = "店铺推荐", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreRecommendScrm fsStoreRecommendScrm)
+        {
+        List<FsStoreRecommendScrm> list = fsStoreRecommendService.selectFsStoreRecommendScrmList(fsStoreRecommendScrm);
+        ExcelUtil<FsStoreRecommendScrm> util = new ExcelUtil<>(FsStoreRecommendScrm.class);
+        return util.exportExcel(list, "店铺推荐数据");
+    }
+}

+ 20 - 3
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreScrmController.java

@@ -3,6 +3,7 @@ package com.fs.hisStore.controller;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ParseUtils;
@@ -10,6 +11,7 @@ import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.hisStore.domain.FsStoreScrm;
 import com.fs.his.param.FsStoreAuditParam;
 import com.fs.hisStore.service.IFsStoreScrmService;
+import com.fs.hisStore.utils.StoreAuditLogUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -29,6 +31,9 @@ public class FsStoreScrmController extends BaseController
     @Autowired
     private IFsStoreScrmService fsStoreService;
 
+    @Autowired
+    private StoreAuditLogUtil storeAuditLogUtil;
+
     /**
      * 查询店铺管理列表
      */
@@ -52,6 +57,7 @@ public class FsStoreScrmController extends BaseController
     @GetMapping("/export")
     public AjaxResult export(FsStoreScrm fsStore)
     {
+        storeAuditLogUtil.generateOperId();
         List<FsStoreScrm> list = fsStoreService.selectFsStoreList(fsStore);
         for (FsStoreScrm store : list) {
             store.setPhone(ParseUtils.parsePhone(store.getPhone()));
@@ -80,7 +86,8 @@ public class FsStoreScrmController extends BaseController
     @PostMapping
     public AjaxResult add(@RequestBody FsStoreScrm fsStore)
     {
-        return toAjax(fsStoreService.insertFsStore(fsStore));
+        storeAuditLogUtil.addOperLog(fsStoreService.insertFsStore(fsStore));
+        return AjaxResult.success();
     }
 
     /**
@@ -113,7 +120,8 @@ public class FsStoreScrmController extends BaseController
      * 店铺审核
      */
     @PreAuthorize("@ss.hasPermi('his:store:audit')")
-    @Log(title = "店铺审核", businessType = BusinessType.UPDATE,logParam = {"店铺","店铺审核"},isStoreLog = true)
+    @Log(title = "店铺审核", businessType = BusinessType.AUDIT,logParam = {"店铺","店铺审核"},isStoreLog = true
+    ,logParamExpression = "#p0.getIsAudit()==1?new String[]{'店铺','店铺审核通过'}: new String[]{'店铺','店铺审核退回'}")
     @PutMapping("/audit")
     public AjaxResult audit(@RequestBody FsStoreAuditParam fsStore)
     {
@@ -126,9 +134,18 @@ public class FsStoreScrmController extends BaseController
     @PreAuthorize("@ss.hasPermi('his:store:refresh')")
     @Log(title = "店铺管理", businessType = BusinessType.UPDATE,logParam = {"店铺","重置店铺密码"},isStoreLog = true)
     @PutMapping("/refresh/{storeId}")
-    public AjaxResult refresh(Long storeId)
+    public AjaxResult refresh(@PathVariable Long storeId)
     {
         return toAjax(fsStoreService.refreshFsStore(storeId));
     }
 
+
+    /**
+     * 店铺审核日志
+     * */
+    @PreAuthorize("@ss.hasPermi('his:store:auditLog')")
+    @GetMapping("/auditLog/{storeId}")
+    public R auditLog(@PathVariable Long storeId){
+        return R.ok().put("auditLog",storeAuditLogUtil.selectOperLogByMainId(storeId));
+    }
 }

+ 11 - 1
fs-admin/src/main/java/com/fs/hisStore/controller/SysOperlogScrmController.java

@@ -3,11 +3,14 @@ package com.fs.hisStore.controller;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.hisStore.param.StoreOperMainQueryParam;
 import com.fs.hisStore.service.ISysOperLogScrmService;
 import com.fs.hisStore.domain.SysOperLogScrm;
+import com.fs.hisStore.vo.StoreOperMainVO;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
@@ -28,7 +31,7 @@ public class SysOperlogScrmController extends BaseController
         this.operLogService = operLogService;
     }
 
-    @PreAuthorize("@ss.hasPermi('his:storeLog:export')")
+    @PreAuthorize("@ss.hasPermi('his:storeLog:list')")
     @GetMapping("/list")
     public TableDataInfo list(SysOperLogScrm operLog)
     {
@@ -36,6 +39,13 @@ public class SysOperlogScrmController extends BaseController
         List<SysOperLogScrm> list = operLogService.selectOperLogList(operLog);
         return getDataTable(list);
     }
+    @PreAuthorize("@ss.hasPermi('his:storeLog:list')")
+    @GetMapping("/getMains")
+    public R getMains(StoreOperMainQueryParam operLog)
+    {
+        List<StoreOperMainVO> list = operLogService.getMains(operLog);
+        return R.ok().put("data", list);
+    }
 
     @Log(title = "操作日志", businessType = BusinessType.EXPORT)
     @PreAuthorize("@ss.hasPermi('his:storeLog:export')")

+ 94 - 0
fs-admin/src/main/java/com/fs/qw/FsCourseTask.java

@@ -1,11 +1,27 @@
 package com.fs.qw;
 
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.qw.service.IQwWorkTaskService;
+import com.fs.system.domain.SysConfig;
+import com.fs.system.mapper.SysConfigMapper;
+import io.jsonwebtoken.lang.Assert;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.apache.http.util.Asserts;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
 /**
  * 后台统计相关 定时任务
  */
@@ -16,7 +32,85 @@ public class FsCourseTask {
     private IFsCourseWatchLogService fsCourseWatchLogService;
     @Autowired
     private IQwWorkTaskService qwWorkTaskService;
+    @Autowired
+    private RedisCache redisCache;
+    private static final String REDPACKET_COMPANY_MONEY_CHANGE = "redpacket_money_CHANGE";
+    @Autowired
+    private SysConfigMapper sysConfigMapper;
+    private static final String REDPACKET_POOL_LOCK = "redpacket_pool_lock";
+    private static final String REDPACKET_COMPANY_MONEY = "redpacket_money";
+
+    @Autowired
+    private RedissonClient redissonClient;
+
+    /**
+     * 润天公司账户充值
+     * @param chargeMoney 充值金额
+     */
+    public void rechargeRedpacketMoney(String chargeMoney){
+        Assert.notNull(chargeMoney,"充值金额不能为空!");
+
+        log.info("润天公司账户充值 充值金额: {}",chargeMoney);
+
+        RLock lock = redissonClient.getLock(REDPACKET_POOL_LOCK);
+        try{
+            boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
+
+            if (!locked) {
+                log.error("获取锁失败...");
+                return;
+            }
+
+            BigDecimal redPacketCompanyMoney = redisCache.getCacheObject(REDPACKET_COMPANY_MONEY);
+            if(ObjectUtils.isNull(redPacketCompanyMoney)){
+                redPacketCompanyMoney = BigDecimal.ZERO;
+            }
+            BigDecimal value = redPacketCompanyMoney.add(new BigDecimal(chargeMoney));
+
+            log.info("润天公司账户充值成功 目前余额: {}",value);
 
+            redisCache.setCacheObject(REDPACKET_COMPANY_MONEY,value);
+
+
+            // 保存到数据库
+            SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("company.money");
+
+            sysConfig.setConfigValue(value.setScale(4, RoundingMode.HALF_UP).toPlainString());
+            sysConfig.setConfigKey("company.money");
+            sysConfigMapper.updateConfig(sysConfig);
+
+        }catch (Exception e){
+            log.error("充值失败 原因:{}", ExceptionUtils.getFullStackTrace(e),e);
+            throw new RuntimeException(e);
+        }finally {
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+            }
+        }
+    }
+    /**
+     * 公司红包金额变更
+     */
+    public void redpacketCompanyMoneyChange(){
+        SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("company.money");
+        Asserts.notNull(sysConfig,"公司账户配置不能为空!");
+
+        String configValue = sysConfig.getConfigValue();
+        Asserts.notNull(configValue,"公司账户余额不能为空");
+
+        BigDecimal oldBigDecimal = new BigDecimal(configValue);
+
+        BigDecimal redPacketCompanyMoney = redisCache.getCacheObject(REDPACKET_COMPANY_MONEY);
+
+        log.info("公司账户红包余额变更 {} -> {}",oldBigDecimal,redPacketCompanyMoney);
+
+        // 如果两者不一致才同步到数据库
+        if(oldBigDecimal.compareTo(redPacketCompanyMoney) != 0){
+            sysConfig.setConfigValue(redPacketCompanyMoney.setScale(4, RoundingMode.HALF_UP).toPlainString());
+            sysConfig.setConfigKey("company.money");
+            sysConfigMapper.updateConfig(sysConfig);
+        }
+    }
     /**
      * 添加企微观看日志
      * @throws Exception

+ 33 - 1
fs-admin/src/main/java/com/fs/qw/controller/QwCompanyController.java

@@ -2,8 +2,14 @@ package com.fs.qw.controller;
 
 import java.util.List;
 
+import cn.hutool.json.JSONUtil;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.utils.ServletUtils;
+import com.fs.course.config.CourseConfig;
+import com.fs.framework.web.service.TokenService;
 import com.fs.qw.vo.QwOptionsVO;
+import com.fs.system.service.ISysConfigService;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -35,6 +41,10 @@ public class QwCompanyController extends BaseController
 {
     @Autowired
     private IQwCompanyService qwCompanyService;
+    @Autowired
+    private TokenService tokenService;
+    @Autowired
+    private ISysConfigService configService;
 
     /**
      * 查询企微主体列表
@@ -44,6 +54,13 @@ public class QwCompanyController extends BaseController
     public TableDataInfo list(QwCompany qwCompany)
     {
         startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if(!loginUser.isAdmin() && config.getIsBound() != null && config.getIsBound()){
+            qwCompany.setCreateDeptId(loginUser.getDeptId());
+            qwCompany.setCreateUserId(loginUser.getUserId());
+        }
         List<QwCompany> list = qwCompanyService.selectQwCompanyList(qwCompany);
         return getDataTable(list);
     }
@@ -52,7 +69,18 @@ public class QwCompanyController extends BaseController
     @GetMapping("/all")
     public R all()
     {
-        List<QwOptionsVO> list = qwCompanyService.selectQwCompanyListOptionsVO();
+        Long userId = null;
+        Long deptId = null;
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if(!loginUser.isAdmin() && config.getIsBound() != null && config.getIsBound()){
+            deptId = loginUser.getDeptId();
+            if(config.getDept() == null || !config.getDept()){
+                userId = loginUser.getUserId();
+            }
+        }
+        List<QwOptionsVO> list = qwCompanyService.selectQwCompanyListOptionsVO(userId, deptId);
         return R.ok().put("data", list);
     }
     /**
@@ -86,6 +114,10 @@ public class QwCompanyController extends BaseController
     @PostMapping
     public AjaxResult add(@RequestBody QwCompany qwCompany)
     {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+
+        qwCompany.setCreateUserId(loginUser.getUserId());
+        qwCompany.setCreateDeptId(loginUser.getDeptId());
         return toAjax(qwCompanyService.insertQwCompany(qwCompany));
     }
 

+ 39 - 0
fs-admin/src/main/java/com/fs/task/FsStoreTask.java

@@ -0,0 +1,39 @@
+package com.fs.task;
+
+import com.fs.hisStore.domain.FsStoreScrm;
+import com.fs.hisStore.service.IFsStoreScrmService;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Component;
+import com.fs.common.utils.PubFun;
+
+import java.time.LocalDate;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@AllArgsConstructor
+@Component("storeTaskScrm")
+public class FsStoreTask {
+
+    private final IFsStoreScrmService fsStoreScrmService;
+
+
+    public void store(){
+        LocalDate now = LocalDate.now();
+        List<FsStoreScrm> storeList = fsStoreScrmService.selectOverList();
+        List<FsStoreScrm> collect = storeList.stream().filter(e -> {
+            // 过期判定:任一证件到期日为 null 或 早于今天
+            return e.getBusinessLicenseExpireEnd() == null
+                    || e.getDrugLicenseExpiryEnd() == null
+                    || e.getMedicalLicenseExpiryEnd() == null
+                    || e.getBusinessLicenseExpireEnd().isBefore(now)
+                    || e.getDrugLicenseExpiryEnd().isBefore(now)
+                    || e.getMedicalLicenseExpiryEnd().isBefore(now);
+        }).collect(Collectors.toList());
+
+        if (!collect.isEmpty()) {
+            List<Long> ids = PubFun.listToNewList(collect, FsStoreScrm::getStoreId);
+            fsStoreScrmService.batchUpdateStatusByIds(ids, 0);
+        }
+    }
+
+}

+ 106 - 0
fs-admin/src/main/java/com/fs/user/controller/FsUserComplaintController.java

@@ -0,0 +1,106 @@
+package com.fs.user.controller;
+
+import java.util.List;
+
+import com.fs.user.domain.param.FsUserComplaintParam;
+import com.fs.user.domain.vo.FsUserComplaintVo;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.user.domain.FsUserComplaint;
+import com.fs.user.service.IFsUserComplaintService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 用户投诉Controller
+ *
+ * @author fs
+ * @date 2025-08-27
+ */
+@RestController
+@RequestMapping("/user/complaint")
+public class FsUserComplaintController extends BaseController
+{
+    @Autowired
+    private IFsUserComplaintService fsUserComplaintService;
+
+    /**
+     * 查询用户投诉列表
+     */
+    @PreAuthorize("@ss.hasPermi('user:complaint:list')")
+    @PostMapping("/list")
+    public TableDataInfo list(@RequestBody FsUserComplaintParam param)
+    {
+        startPage();
+        List<FsUserComplaintVo> list = fsUserComplaintService.selectFsUserComplaintVoList(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出用户投诉列表
+     */
+    @PreAuthorize("@ss.hasPermi('user:complaint:export')")
+    @Log(title = "用户投诉", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsUserComplaint fsUserComplaint)
+    {
+        List<FsUserComplaint> list = fsUserComplaintService.selectFsUserComplaintList(fsUserComplaint);
+        ExcelUtil<FsUserComplaint> util = new ExcelUtil<FsUserComplaint>(FsUserComplaint.class);
+        return util.exportExcel(list, "用户投诉数据");
+    }
+
+    /**
+     * 获取用户投诉详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('user:complaint:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsUserComplaintService.selectFsUserComplaintVoById(id));
+    }
+
+    /**
+     * 新增用户投诉
+     */
+    @PreAuthorize("@ss.hasPermi('user:complaint:add')")
+    @Log(title = "用户投诉", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsUserComplaint fsUserComplaint)
+    {
+        return toAjax(fsUserComplaintService.insertFsUserComplaint(fsUserComplaint));
+    }
+
+    /**
+     * 修改用户投诉
+     */
+    @PreAuthorize("@ss.hasPermi('user:complaint:edit')")
+    @Log(title = "用户投诉", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsUserComplaint fsUserComplaint)
+    {
+        return toAjax(fsUserComplaintService.updateFsUserComplaint(fsUserComplaint));
+    }
+
+    /**
+     * 删除用户投诉
+     */
+    @PreAuthorize("@ss.hasPermi('user:complaint:remove')")
+    @Log(title = "用户投诉", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsUserComplaintService.deleteFsUserComplaintByIds(ids));
+    }
+}

+ 109 - 0
fs-admin/src/main/java/com/fs/user/controller/FsUserComplaintMsgController.java

@@ -0,0 +1,109 @@
+package com.fs.user.controller;
+
+import java.util.List;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.user.domain.FsUserComplaintMsg;
+import com.fs.user.service.IFsUserComplaintMsgService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 投诉消息记录Controller
+ *
+ * @author fs
+ * @date 2025-09-08
+ */
+@RestController
+@RequestMapping("/user/msg")
+public class FsUserComplaintMsgController extends BaseController
+{
+    @Autowired
+    private IFsUserComplaintMsgService fsUserComplaintMsgService;
+
+    /**
+     * 查询投诉消息记录列表
+     */
+//    @PreAuthorize("@ss.hasPermi('user:msg:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsUserComplaintMsg fsUserComplaintMsg)
+    {
+        startPage();
+        List<FsUserComplaintMsg> list = fsUserComplaintMsgService.selectFsUserComplaintMsgList(fsUserComplaintMsg);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出投诉消息记录列表
+     */
+//    @PreAuthorize("@ss.hasPermi('user:msg:export')")
+    @Log(title = "投诉消息记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsUserComplaintMsg fsUserComplaintMsg)
+    {
+        List<FsUserComplaintMsg> list = fsUserComplaintMsgService.selectFsUserComplaintMsgList(fsUserComplaintMsg);
+        ExcelUtil<FsUserComplaintMsg> util = new ExcelUtil<FsUserComplaintMsg>(FsUserComplaintMsg.class);
+        return util.exportExcel(list, "投诉消息记录数据");
+    }
+
+    /**
+     * 获取投诉消息记录详细信息
+     */
+//    @PreAuthorize("@ss.hasPermi('user:msg:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsUserComplaintMsgService.selectFsUserComplaintMsgById(id));
+    }
+
+    /**
+     * 新增投诉消息记录
+     */
+    @PreAuthorize("@ss.hasPermi('user:msg:add')")
+    @Log(title = "投诉消息记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsUserComplaintMsg fsUserComplaintMsg)
+    {
+        if (fsUserComplaintMsg.getComplaintId() == null){
+            return AjaxResult.error("未查询到相关投诉!");
+        }
+        Long userId = getUserId();
+        fsUserComplaintMsg.setUserId(userId);
+        fsUserComplaintMsg.setSendType(2); //平台
+        return toAjax(fsUserComplaintMsgService.insertFsUserComplaintMsg(fsUserComplaintMsg));
+    }
+
+    /**
+     * 修改投诉消息记录
+     */
+    @PreAuthorize("@ss.hasPermi('user:msg:edit')")
+    @Log(title = "投诉消息记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsUserComplaintMsg fsUserComplaintMsg)
+    {
+        return toAjax(fsUserComplaintMsgService.updateFsUserComplaintMsg(fsUserComplaintMsg));
+    }
+
+    /**
+     * 删除投诉消息记录
+     */
+    @PreAuthorize("@ss.hasPermi('user:msg:remove')")
+    @Log(title = "投诉消息记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsUserComplaintMsgService.deleteFsUserComplaintMsgByIds(ids));
+    }
+}

+ 14 - 0
fs-admin/src/main/java/com/fs/web/controller/system/SysLoginController.java

@@ -3,9 +3,12 @@ package com.fs.web.controller.system;
 import java.util.List;
 import java.util.Set;
 
+import com.alibaba.fastjson.JSONObject;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.entity.SysRole;
 import com.fs.common.utils.PatternUtils;
+import com.fs.his.utils.ConfigUtil;
+import com.fs.hisStore.config.MedicalMallConfig;
 import com.fs.system.service.ISysRoleService;
 import lombok.Synchronized;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -45,6 +48,12 @@ public class SysLoginController
     @Autowired
     private ISysRoleService roleService;
 
+    @Autowired
+    private ConfigUtil configUtil;
+
+    @Autowired
+    private MedicalMallConfig medicalMallConfig;
+
     /**
      * 登录方法
      *
@@ -78,7 +87,12 @@ public class SysLoginController
         Set<String> roles = permissionService.getRolePermission(user);
         // 权限集合
         Set<String> permissions = permissionService.getMenuPermission(user);
+        //药品商城参数
         AjaxResult ajax = AjaxResult.success();
+
+        if(medicalMallConfig!=null){
+            ajax.put("medicalMallConfig", medicalMallConfig);
+        }
         ajax.put("user", user);
         Integer isAdmin = 0;
         if (permissions.contains("*:*:*")){

+ 1 - 1
fs-admin/src/main/resources/application.yml

@@ -10,5 +10,5 @@ spring:
 #    active: druid-sxjz
 #    active: druid-sft
 #    active: druid-fby
-    active: dev
+    active: dev-yjb
 

+ 18 - 40
fs-common-api/src/main/java/com/fs/framework/config/MyBatisConfig.java

@@ -32,82 +32,60 @@ import java.util.List;
 
  */
 @Configuration
-public class MyBatisConfig
-{
+public class MyBatisConfig {
     @Autowired
     private Environment env;
 
     static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
 
-    public static String setTypeAliasesPackage(String typeAliasesPackage)
-    {
+    public static String setTypeAliasesPackage(String typeAliasesPackage) {
         ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
         MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
         List<String> allResult = new ArrayList<String>();
-        try
-        {
-            for (String aliasesPackage : typeAliasesPackage.split(","))
-            {
+        try {
+            for (String aliasesPackage : typeAliasesPackage.split(",")) {
                 List<String> result = new ArrayList<String>();
                 aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
                         + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
                 Resource[] resources = resolver.getResources(aliasesPackage);
-                if (resources != null && resources.length > 0)
-                {
+                if (resources != null && resources.length > 0) {
                     MetadataReader metadataReader = null;
-                    for (Resource resource : resources)
-                    {
-                        if (resource.isReadable())
-                        {
+                    for (Resource resource : resources) {
+                        if (resource.isReadable()) {
                             metadataReader = metadataReaderFactory.getMetadataReader(resource);
-                            try
-                            {
+                            try {
                                 result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
-                            }
-                            catch (ClassNotFoundException e)
-                            {
+                            } catch (ClassNotFoundException e) {
                                 e.printStackTrace();
                             }
                         }
                     }
                 }
-                if (result.size() > 0)
-                {
+                if (result.size() > 0) {
                     HashSet<String> hashResult = new HashSet<String>(result);
                     allResult.addAll(hashResult);
                 }
             }
-            if (allResult.size() > 0)
-            {
+            if (allResult.size() > 0) {
                 typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0]));
-            }
-            else
-            {
+            } else {
                 throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
             }
-        }
-        catch (IOException e)
-        {
+        } catch (IOException e) {
             e.printStackTrace();
         }
         return typeAliasesPackage;
     }
 
-    public Resource[] resolveMapperLocations(String[] mapperLocations)
-    {
+    public Resource[] resolveMapperLocations(String[] mapperLocations) {
         ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
         List<Resource> resources = new ArrayList<Resource>();
-        if (mapperLocations != null)
-        {
-            for (String mapperLocation : mapperLocations)
-            {
-                try
-                {
+        if (mapperLocations != null) {
+            for (String mapperLocation : mapperLocations) {
+                try {
                     Resource[] mappers = resourceResolver.getResources(mapperLocation);
                     resources.addAll(Arrays.asList(mappers));
-                }
-                catch (IOException e)
-                {
+                } catch (IOException e) {
                     // ignore
                 }
             }

+ 19 - 0
fs-common-api/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -15,6 +15,8 @@ import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.data.redis.serializer.GenericToStringSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 
+import java.math.BigDecimal;
+
 /**
  * redis配置
  *
@@ -90,7 +92,24 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+    @Bean
+    public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
 
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        template.afterPropertiesSet();
+        return template;
+    }
     @Bean
     public DefaultRedisScript<Long> limitScript()
     {

+ 2 - 2
fs-common-api/src/main/resources/application.yml

@@ -4,5 +4,5 @@ server:
 # Spring配置
 spring:
   profiles:
-#    active: dev
-    active: druid-jnmy
+    active: dev-yjb
+#    active: druid-jnmy

+ 6 - 2
fs-common/src/main/java/com/fs/common/core/domain/model/LoginUser.java

@@ -1,7 +1,7 @@
 package com.fs.common.core.domain.model;
 
-import java.util.Collection;
-import java.util.Set;
+import java.util.*;
+
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.userdetails.UserDetails;
 import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -14,6 +14,7 @@ import com.fs.common.core.domain.entity.SysUser;
  */
 public class LoginUser implements UserDetails
 {
+    private static final List<String> ADMIN_USER_NAME = Collections.singletonList("admin");
     private static final long serialVersionUID = 1L;
 
     /**
@@ -254,6 +255,9 @@ public class LoginUser implements UserDetails
     {
         return user;
     }
+    public boolean isAdmin(){
+        return ADMIN_USER_NAME.contains(user.getUserName());
+    }
 
     public void setUser(SysUser user)
     {

+ 9 - 5
fs-common/src/main/java/com/fs/common/core/redis/RedisCache.java

@@ -1,10 +1,6 @@
 package com.fs.common.core.redis;
 
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.BoundSetOperations;
@@ -236,4 +232,12 @@ public class RedisCache
     public Boolean setIfAbsent(String key, String value, long timeout, TimeUnit unit) {
         return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
     }
+
+    public Long decr(final String key,final Long delta) {
+        return redisTemplate.opsForValue().decrement(key, delta);
+    }
+
+    public Long incr(final String key,final Long delta) {
+        return redisTemplate.opsForValue().increment(key, delta);
+    }
 }

+ 7 - 2
fs-common/src/main/java/com/fs/common/enums/BusinessType.java

@@ -2,7 +2,7 @@ package com.fs.common.enums;
 
 /**
  * 业务操作类型
- * 
+ *
 
  */
 public enum BusinessType
@@ -51,9 +51,14 @@ public enum BusinessType
      * 生成代码
      */
     GENCODE,
-    
+
     /**
      * 清空数据
      */
     CLEAN,
+
+    /**
+     * 审核
+     * */
+    AUDIT,
 }

+ 21 - 0
fs-company-app/src/main/java/com/fs/core/config/RedisConfig.java

@@ -12,6 +12,8 @@ import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.serializer.GenericToStringSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 
+import java.math.BigDecimal;
+
 /**
  * redis配置
  *
@@ -66,6 +68,25 @@ public class RedisConfig extends CachingConfigurerSupport
         return template;
     }
 
+    @Bean
+    public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        template.afterPropertiesSet();
+        return template;
+    }
+
     @Bean
     @SuppressWarnings(value = { "unchecked", "rawtypes" })
     public RedisTemplate<String, Object> redisTemplateForObject(RedisConnectionFactory connectionFactory) {

+ 10 - 0
fs-company/src/main/java/com/fs/company/controller/common/Test.java

@@ -3,17 +3,20 @@ package com.fs.company.controller.common;
 import com.alibaba.fastjson.JSON;
 import com.fs.ad.enums.AdUploadType;
 import com.fs.common.annotation.DataSource;
+import com.fs.common.core.domain.R;
 import com.fs.common.enums.DataSourceType;
 import com.fs.company.service.ICompanyService;
 import com.fs.company.vo.RedPacketMoneyVO;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
 import com.fs.erp.service.IErpOrderService;
 import com.fs.his.utils.ConfigUtil;
+import com.fs.his.utils.qrcode.QRCodeUtils;
 import com.fs.qw.vo.AdUploadVo;
 import com.fs.sop.service.IQwSopTempContentService;
 import com.fs.sop.service.IQwSopTempDayService;
 import com.fs.sop.service.IQwSopTempRulesService;
 import com.fs.sop.service.IQwSopTempService;
+import com.google.zxing.WriterException;
 import lombok.AllArgsConstructor;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -22,6 +25,12 @@ import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
 
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
 @RestController
 public class Test {
     @Autowired
@@ -42,4 +51,5 @@ public class Test {
             companyService.subtractCompanyMoney(redPacketMoneyVO.getMoney(),redPacketMoneyVO.getCompanyId());
         }
     }
+
 }

+ 1 - 1
fs-company/src/main/java/com/fs/company/controller/company/CompanyController.java

@@ -107,7 +107,7 @@ public class CompanyController extends BaseController
     @GetMapping("/allList")
     public TableDataInfo getHospital()
     {
-        List<OptionsVO> list = companyService.selectAllCompanyList();
+        List<OptionsVO> list = companyService.selectAllCompanyList(null);
         return getDataTable(list);
     }
 

+ 3 - 2
fs-company/src/main/java/com/fs/company/controller/course/FsCourseAnswerLogsController.java

@@ -49,8 +49,9 @@ public class FsCourseAnswerLogsController extends BaseController
             param.setPhone(param.getPhoneMk());
         }
 
-        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        param.setCompanyId( loginUser.getCompany().getCompanyId());
+//        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        // 如果他可以看两个销售公司就会查不出来
+//        param.setCompanyId( loginUser.getCompany().getCompanyId());
 
         List<FsCourseAnswerLogsListVO> list = fsCourseAnswerLogsService.selectFsCourseAnswerLogsListVONew(param);
         TableDataInfo rspData = new TableDataInfo();

+ 3 - 2
fs-company/src/main/java/com/fs/company/controller/course/FsCourseRedPacketLogController.java

@@ -57,8 +57,9 @@ public class FsCourseRedPacketLogController extends BaseController
         if (fsCourseRedPacketLog.getPhoneMk() != null && fsCourseRedPacketLog.getPhoneMk() != "") {
             fsCourseRedPacketLog.setPhone(encryptPhone(fsCourseRedPacketLog.getPhoneMk()));
         }
-        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        fsCourseRedPacketLog.setCompanyId( loginUser.getCompany().getCompanyId());
+//        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        // 如果他可以看两个销售公司就会查不出来
+//        fsCourseRedPacketLog.setCompanyId( loginUser.getCompany().getCompanyId());
 
         List<FsCourseRedPacketLogListPVO> list = fsCourseRedPacketLogService.selectFsCourseRedPacketLogListVO(fsCourseRedPacketLog);
         for (FsCourseRedPacketLogListPVO fsCourseRedPacketLogListPVO : list) {

+ 2 - 0
fs-company/src/main/java/com/fs/company/controller/course/FsUserCoursePeriodController.java

@@ -191,6 +191,7 @@ public class FsUserCoursePeriodController extends BaseController {
 
     @ApiOperation("按课程批量保存设置红包金额")
     @PostMapping("/batchRedPacket")
+    @Log(title = "按课程批量保存设置红包金额", businessType = BusinessType.UPDATE)
     public R batchRedPacketMoney(@RequestBody List<FsUserCourseVideoRedPackage> videoRedPackageList) {
         try {
             fsUserCourseVideoRedPackageService.batchSaveCompanyRedPackage(videoRedPackageList);
@@ -203,6 +204,7 @@ public class FsUserCoursePeriodController extends BaseController {
 
     @ApiOperation("按营期批量保存设置红包金额")
     @PostMapping("/batchRedPacket/byPeriod")
+    @Log(title = "按营期批量保存设置红包金额", businessType = BusinessType.UPDATE)
     public R batchRedPacketByPeriod(@RequestBody List<FsBatchPeriodRedPackageParam> periodRedPackageList) {
         try {
             fsUserCourseVideoRedPackageService.batchRedPacketByPeriod(periodRedPackageList);

+ 1 - 1
fs-company/src/main/java/com/fs/company/controller/course/FsUserCourseTrainingCampController.java

@@ -36,7 +36,7 @@ public class FsUserCourseTrainingCampController {
     /**
      * 查询训练营列表
      */
-    @PreAuthorize("@ss.hasPermi('course:trainingCamp:list')")
+    //@PreAuthorize("@ss.hasPermi('course:trainingCamp:list')")
     @GetMapping("/list")
     public AjaxResult list(@RequestParam(required = false) String trainingCampName,
                            @RequestParam(required = false) String userId,

+ 1 - 1
fs-company/src/main/java/com/fs/company/controller/course/FsUserOperationLogController.java

@@ -25,7 +25,7 @@ public class FsUserOperationLogController extends BaseController {
     @Autowired
     private IFsUserOperationLogService fsUserOperationLogService;
 
-    @PreAuthorize("@ss.hasPermi('his:userOperationLog:list')")
+    //@PreAuthorize("@ss.hasPermi('his:userOperationLog:list')")
     @GetMapping("/list")
     public TableDataInfo list(FsUserOperationLog fsUserOperationLog)
     {

+ 6 - 1
fs-company/src/main/java/com/fs/company/controller/crm/CrmMsgController.java

@@ -64,7 +64,12 @@ public class CrmMsgController extends BaseController
     }
 
 
-
+    /**
+     * 获取站内销售消息列表
+     * @param request
+     * @param type
+     * @return
+     */
     @GetMapping("/getMsgList")
     public R getMsgList(
             HttpServletRequest request,

+ 11 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwSopController.java

@@ -278,6 +278,17 @@ public class QwSopController extends BaseController
         return toAjax(qwSopService.deleteQwSopByIds(ids));
     }
 
+    /**
+     * 批量删除SOP 任务下面的执行记录
+     */
+    @PreAuthorize("@ss.hasPermi('qw:sopLogs:removeAll')")
+    @Log(title = "企微sop", businessType = BusinessType.DELETE)
+	@DeleteMapping("/delSopLogs/{ids}")
+    public AjaxResult removeAll(@PathVariable String[] ids)
+    {
+        return toAjax(qwSopService.deleteQwSopLogsBySopIds(ids));
+    }
+
 
     /**
      * 批量执行SOP

+ 3 - 1
fs-company/src/main/java/com/fs/company/controller/qw/QwSopLogsController.java

@@ -149,7 +149,9 @@ public class QwSopLogsController extends BaseController
      * 我的自动化任务 和部门自动化任务 就只显示自己的或者部门的
      */
     private List<Long> getQwUserKeyList(QwSopLogsParam param, LoginUser loginUser) {
-
+        if(param.getFilterSopType() == null){
+            return new ArrayList<>(); // 返回空列表而不是null
+        }
         switch (param.getFilterSopType()) {
             case 2:
                 CompanyUser companyUser = iCompanyUserService.selectCompanyUserById(loginUser.getUser().getUserId());

+ 5 - 6
fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java

@@ -659,12 +659,11 @@ public class QwUserController extends BaseController
                     companyUserMapper.updateCompanyUser(user);
 
 
-                    QwUser qw = new QwUser();
-                    qw.setCompanyUserId(qwUserParam.getCompanyUserId());
-                    qw.setId(Long.parseLong(paramId));
-                    qw.setStatus(1);
-                    qw.setCompanyId(companyUser.getCompanyId());
-                    qwUserService.updateQwUser(qw);
+
+                    qu.setCompanyUserId(qwUserParam.getCompanyUserId());
+                    qu.setStatus(1);
+                    qu.setCompanyId(companyUser.getCompanyId());
+                    qwUserService.updateQwUser(qu);
 
                 }
 

+ 80 - 4
fs-company/src/main/java/com/fs/company/controller/qw/QwUserVoiceLogController.java

@@ -1,12 +1,18 @@
 package com.fs.company.controller.qw;
 
 import com.fs.common.annotation.Log;
+import com.fs.common.constant.HttpStatus;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.PageDomain;
 import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.core.page.TableSupport;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyUserService;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
 import com.fs.qw.domain.QwUserVoiceLog;
@@ -22,8 +28,10 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  * 企微用户通话记录Controller
@@ -44,6 +52,9 @@ public class QwUserVoiceLogController extends BaseController
     @Autowired
     private IQwTagService iQwTagService;
 
+    @Autowired
+    private ICompanyUserService userService;
+
     /**
      * 查询企微用户通话记录列表
      */
@@ -137,23 +148,88 @@ public class QwUserVoiceLogController extends BaseController
     @GetMapping("/totalList")
     public TableDataInfo totalList(QwUserVoiceLogTotalVo qwUserVoiceLog)
     {
-        startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         qwUserVoiceLog.setCompanyId(loginUser.getCompany().getCompanyId());
         qwUserVoiceLog.setQwUserId(1L);
         List<QwUserVoiceLogTotalVo> list = qwUserVoiceLogService.selectQwUserVoiceLogTotalList(qwUserVoiceLog);
-        return getDataTable(list);
+        list.forEach(item->{
+            if(item.getQwExternalContact() != null){
+                if (item.getQwExternalContact().getTagIds() != null && !Objects.equals(item.getQwExternalContact().getTagIds(), "[]")) {
+                    QwTagSearchParam param = new QwTagSearchParam();
+                    Gson gson = new Gson();
+                    List<String> tagIds = gson.fromJson(
+                            item.getQwExternalContact().getTagIds(),
+                            new TypeToken<List<String>>() {
+                            }.getType()
+                    );
+                    param.setTagIds(tagIds);
+                    item.setTagIdsName(iQwTagService.selectQwTagListByTagIds(param));
+                }
+            }
+        });
+
+        // 获取分页参数
+        PageDomain pageDomain = TableSupport.buildPageRequest();
+        Integer pageNum = pageDomain.getPageNum();
+        Integer pageSize = pageDomain.getPageSize();
+
+        int total = list.size();
+        // 在内存中进行分页处理
+        if (pageNum != null && pageSize != null) {
+            int fromIndex = (pageNum - 1) * pageSize;
+            int toIndex = Math.min(fromIndex + pageSize, total);
+
+            // 确保索引不越界
+            if (fromIndex < total) {
+                list = list.subList(fromIndex, toIndex);
+            } else {
+                list = new ArrayList<>(); // 返回空列表
+            }
+        }
+
+        // 构造返回结果
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(HttpStatus.SUCCESS);
+        rspData.setMsg("查询成功");
+        rspData.setRows(list);
+        rspData.setTotal(total);
+        return rspData;
     }
 
 
     @GetMapping("/sellTotalList")
     public TableDataInfo sellTotalList(QwUserVoiceLogTotalVo qwUserVoiceLog)
     {
-        startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         qwUserVoiceLog.setCompanyId(loginUser.getCompany().getCompanyId());
         List<QwUserVoiceLogTotalVo> list = qwUserVoiceLogService.selectQwUserVoiceLogTotalList(qwUserVoiceLog);
-        return getDataTable(list);
+
+        // 获取分页参数
+        PageDomain pageDomain = TableSupport.buildPageRequest();
+        Integer pageNum = pageDomain.getPageNum();
+        Integer pageSize = pageDomain.getPageSize();
+
+        int total = list.size();
+        // 在内存中进行分页处理
+        if (pageNum != null && pageSize != null) {
+            int fromIndex = (pageNum - 1) * pageSize;
+            int toIndex = Math.min(fromIndex + pageSize, total);
+
+            // 确保索引不越界
+            if (fromIndex < total) {
+                list = list.subList(fromIndex, toIndex);
+            } else {
+                list = new ArrayList<>(); // 返回空列表
+            }
+        }
+
+        // 构造返回结果
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(HttpStatus.SUCCESS);
+        rspData.setMsg("查询成功");
+        rspData.setRows(list);
+        rspData.setTotal(total);
+        return rspData;
     }
 
     @PreAuthorize("@ss.hasPermi('qw:qwUserVoiceLog:sellTotalExport')")

+ 2 - 2
fs-company/src/main/java/com/fs/company/controller/qw/SopUserLogsController.java

@@ -83,10 +83,10 @@ public class SopUserLogsController extends BaseController
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
 
         List<String> qwUserIdList=null;
-        if (sopUserLogs.getType()==2){
+        if (sopUserLogs.getType() != null && sopUserLogs.getType()==2){
             qwUserIdList = iQwUserService.selectQwUserListByCompanyUserId(loginUser.getUser().getUserId(), sopUserLogs.getCorpId());
         }
-        if (qwUserIdList!=null&& !qwUserIdList.isEmpty() && sopUserLogs.getType()==2 ){
+        if (qwUserIdList!=null&& !qwUserIdList.isEmpty() && sopUserLogs.getType() != null &&  sopUserLogs.getType()==2 ){
             sopUserLogs.setQwIdsList(qwUserIdList);
         }
 

+ 6 - 5
fs-company/src/main/java/com/fs/company/controller/store/FsInquiryOrderController.java

@@ -364,16 +364,17 @@ public class FsInquiryOrderController extends BaseController
 
 
     /**
-    * 生成订单二维码
+    * 生成问诊付款二维码
     */
     @PreAuthorize("@ss.hasPermi('store:inquiryOrder:wxaCodeInquiryOrder')")
     @GetMapping("/getWxaCodeInquiryOrderUnLimit/{orderId}")
-    public AjaxResult getWxaCodeInquiryOrderUnLimit(@PathVariable("orderId") Long orderId)
+    public R getWxaCodeInquiryOrderUnLimit(@PathVariable("orderId") Long orderId)
     {
 
-        byte[] bytes = fsInquiryOrderService.getWxaCodeInquiryOrderUnLimit(orderId);
-        String base64 = Base64.getEncoder().encodeToString(bytes);
-        return AjaxResult.success("成功",base64);
+        //        byte[] bytes = fsInquiryOrderService.getWxaCodeInquiryOrderUnLimit(orderId);
+//        String base64 = Base64.getEncoder().encodeToString(bytes);
+        return fsInquiryOrderService.getWxaCodeInquiryOrderUnLimitR(orderId);
 
     }
+
 }

+ 14 - 1
fs-company/src/main/java/com/fs/company/controller/store/FsPackageOrderController.java

@@ -235,7 +235,8 @@ public class FsPackageOrderController extends BaseController
     @GetMapping("storeRefund/{orderId}")
     public AjaxResult storeRefund(@PathVariable("orderId") Long orderId)
     {
-        return AjaxResult.success(fsPackageOrderService.PackageStoreOrderRefund(orderId));
+        String nickName = getLoginUser().getUser().getNickName();
+        return AjaxResult.success(fsPackageOrderService.PackageStoreOrderRefund(orderId,nickName));
     }
 
 
@@ -247,4 +248,16 @@ public class FsPackageOrderController extends BaseController
         String base64 = Base64.getEncoder().encodeToString(bytes);
         return AjaxResult.success("成功",base64);
     }
+
+    /**
+     * 生成套餐包付款二维码
+     */
+    @PreAuthorize("@ss.hasPermi('store:packageOrder:wxaCodePackageOrder')")
+    @GetMapping("/getWxaCodePackageOrderUnLimit/{orderId}")
+    public R getWxaCodePackageOrderUnLimit(@PathVariable("orderId") Long orderId)
+    {
+
+        return fsPackageOrderService.getWxaCodePackageOrderUnLimit(orderId);
+
+    }
 }

+ 1 - 1
fs-company/src/main/java/com/fs/company/controller/store/FsStoreOrderController.java

@@ -390,7 +390,7 @@ public class FsStoreOrderController extends BaseController
         }
         LoginUser loginUser = SecurityUtils.getLoginUser();
         fsStoreOrder.setCompanyId(loginUser.getCompany().getCompanyId());
-        fsStoreOrder.setOperator(loginUser.getUser().getNickName());
+        fsStoreOrder.setOperator("销售端:" + loginUser.getUser().getNickName());
         return toAjax(fsStoreOrderService.afterSales(fsStoreOrder));
     }
 

+ 18 - 0
fs-company/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -19,6 +19,7 @@ import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.data.redis.serializer.GenericToStringSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 
+import java.math.BigDecimal;
 import java.util.Arrays;
 
 /**
@@ -96,7 +97,24 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+    @Bean
+    public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
 
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        template.afterPropertiesSet();
+        return template;
+    }
     @Bean
     public DefaultRedisScript<Long> limitScript()
     {

+ 2 - 2
fs-company/src/main/resources/application.yml

@@ -4,11 +4,11 @@ server:
 spring:
   profiles:
 #    active: druid-fcky-test
-#    active: dev
+    active: dev
 #    active: druid-jzzx-test
 #    active: druid-hdt
 #    active: druid-sxjz
 #    active: druid-yzt
 #    active: druid-myhk
 #    active: druid-sft
-    active: dev
+#    active: dev-yjb

+ 1 - 10
fs-doctor-app/src/main/java/com/fs/app/controller/InquiryOrderController.java

@@ -1,29 +1,21 @@
 package com.fs.app.controller;
 
 
-import cn.hutool.core.util.IdUtil;
-import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fs.app.annotation.Login;
-import com.fs.app.param.InquiryOrderMsgListParam;
 import com.fs.common.BeanCopyUtils;
 import com.fs.common.annotation.Log;
-import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
-import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
-import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.StringUtils;
-import com.fs.common.utils.ip.IpUtils;
 import com.fs.company.service.ICompanyService;
 import com.fs.core.config.WxPayProperties;
 import com.fs.core.utils.OrderCodeUtils;
 import com.fs.his.domain.*;
 import com.fs.his.dto.FsInquiryOrderPatientDTO;
-import com.fs.his.enums.FsInquiryOrderStatusEnum;
 import com.fs.his.param.*;
 import com.fs.his.service.*;
 import com.fs.his.vo.*;
@@ -45,7 +37,6 @@ import io.swagger.annotations.ApiOperation;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
@@ -128,7 +119,7 @@ public class InquiryOrderController extends  AppBaseController {
     @GetMapping("/getCompanyList")
     public R getCompanyList()
     {
-        List<OptionsVO> list = companyService.selectAllCompanyList();
+        List<OptionsVO> list = companyService.selectAllCompanyList(null);
         return R.ok().put("list",list);
     }
     @Login

+ 21 - 0
fs-doctor-app/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -15,6 +15,8 @@ import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.data.redis.serializer.GenericToStringSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 
+import java.math.BigDecimal;
+
 /**
  * redis配置
  *
@@ -92,6 +94,25 @@ public class RedisConfig extends CachingConfigurerSupport
         return template;
     }
 
+    @Bean
+    public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        template.afterPropertiesSet();
+        return template;
+    }
+
     @Bean
     public DefaultRedisScript<Long> limitScript()
     {

+ 25 - 2
fs-framework/src/main/java/com/fs/framework/aspectj/LogAspect.java

@@ -1,6 +1,8 @@
 package com.fs.framework.aspectj;
 
 import java.lang.reflect.Method;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.Map;
@@ -11,6 +13,8 @@ import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.bean.BeanUtils;
 import com.fs.framework.web.domain.server.Sys;
 import com.fs.hisStore.domain.SysOperLogScrm;
+import com.fs.statis.dto.ProductAuditDTO;
+import com.fs.statis.dto.StoreAuditDTO;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.ProceedingJoinPoint;
 import org.aspectj.lang.Signature;
@@ -18,6 +22,7 @@ import org.aspectj.lang.annotation.*;
 import org.aspectj.lang.reflect.MethodSignature;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.slf4j.MDC;
 import org.springframework.expression.EvaluationContext;
 import org.springframework.expression.ExpressionParser;
 import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -39,6 +44,8 @@ import com.fs.framework.manager.AsyncManager;
 import com.fs.framework.manager.factory.AsyncFactory;
 import com.fs.system.domain.SysOperLog;
 
+import static com.fs.common.utils.SecurityUtils.getUserId;
+
 /**
  * 操作日志记录处理
  *
@@ -123,9 +130,17 @@ public class LogAspect
             getControllerMethodDescription(joinPoint, controllerLog, operLog);
             // 保存数据库
             AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
-            if(controllerLog.isStoreLog()){
+            if(controllerLog.isStoreLog() ){
                 SysOperLogScrm operLogScrm = new SysOperLogScrm();
+                //插入operId
+                String operId = MDC.get("operIds");
                 BeanUtils.copyProperties(operLog, operLogScrm);
+                Long[] operIds = new Long[operId.split(",").length];
+                String[] operIdStrs = operId.split(",");
+                for (int i = 0; i < operIdStrs.length; i++) {
+                    operIds[i] = Long.parseLong(operIdStrs[i]);
+                }
+                operLogScrm.setOperIds(operIds);
                 resolveParam((ProceedingJoinPoint)joinPoint,operLogScrm);
                 AsyncManager.me().execute(AsyncFactory.recordOperScrm(operLogScrm));
             }
@@ -258,7 +273,7 @@ public class LogAspect
 
     private final ExpressionParser parser = new SpelExpressionParser();
 
-    public void resolveParam(ProceedingJoinPoint joinPoint, SysOperLogScrm operLog) throws Throwable {
+    public void resolveParam(ProceedingJoinPoint joinPoint, SysOperLogScrm operLog) {
         // 1. 获取注解实例
         Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
         Log annotation = method.getAnnotation(Log.class);
@@ -300,4 +315,12 @@ public class LogAspect
         operLog.setMainType(logParam[0]);
         operLog.setDes(logParam[1]);
     }
+
+
+    @After("logPointCut()")
+    public void after(JoinPoint joinPoint) throws Throwable {
+        // 移除 MDC 中的 logId
+        MDC.remove("operIds");
+    }
+
 }

+ 21 - 0
fs-framework/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -15,6 +15,8 @@ import com.fasterxml.jackson.annotation.PropertyAccessor;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
 
+import java.math.BigDecimal;
+
 /**
  * redis配置
  *
@@ -92,6 +94,25 @@ public class RedisConfig extends CachingConfigurerSupport
         return template;
     }
 
+    @Bean
+    public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        template.afterPropertiesSet();
+        return template;
+    }
+
     @Bean
     public DefaultRedisScript<Long> limitScript()
     {

+ 7 - 1
fs-framework/src/main/java/com/fs/framework/manager/factory/AsyncFactory.java

@@ -118,8 +118,14 @@ public class AsyncFactory
             {
                 // 远程查询操作地点
                 operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
-                SpringUtils.getBean(ISysOperLogScrmService.class).insertOperlog(operLog);
+                SpringUtils.getBean(ISysOperLogScrmService.class).updateOperLog(operLog);
             }
         };
     }
+
+    /**
+     * 生成日志记录
+     * */
+
+
 }

+ 11 - 1
fs-framework/src/main/java/com/fs/framework/web/exception/GlobalExceptionHandler.java

@@ -1,6 +1,8 @@
 package com.fs.framework.web.exception;
 
 import javax.servlet.http.HttpServletRequest;
+
+import com.fs.common.exception.CustomException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.security.access.AccessDeniedException;
@@ -17,7 +19,7 @@ import com.fs.common.utils.StringUtils;
 
 /**
  * 全局异常处理器
- * 
+ *
 
  */
 @RestControllerAdvice
@@ -70,6 +72,14 @@ public class GlobalExceptionHandler
         return AjaxResult.error(e.getMessage());
     }
 
+    @ExceptionHandler(CustomException.class)
+    public AjaxResult handleCustomException(CustomException e, HttpServletRequest request)
+    {
+        String requestURI = request.getRequestURI();
+        log.error("请求地址'{}',发生未知异常.", requestURI, e);
+        return AjaxResult.error(e.getMessage());
+    }
+
     /**
      * 系统异常
      */

+ 21 - 0
fs-hospital/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -16,6 +16,8 @@ import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.data.redis.serializer.GenericToStringSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 
+import java.math.BigDecimal;
+
 /**
  * redis配置
  *
@@ -93,6 +95,25 @@ public class RedisConfig extends CachingConfigurerSupport
         return template;
     }
 
+    @Bean
+    public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        template.afterPropertiesSet();
+        return template;
+    }
+
     @Bean
     public DefaultRedisScript<Long> limitScript()
     {

+ 26 - 6
fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java

@@ -1,9 +1,12 @@
 package com.fs.app.service;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.date.DateUtil;
-import com.fs.course.config.CourseMaConfig;
+import com.fs.company.domain.CompanyMiniapp;
+import com.fs.company.service.ICompanyMiniappService;
 import com.fs.course.domain.FsCoursePlaySourceConfig;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.service.IFsCourseWatchLogService;
@@ -15,7 +18,6 @@ import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.IQwUserService;
 import com.fs.qw.service.IQwUserVideoService;
-import com.fs.qw.service.impl.QwUserServiceImpl;
 import com.fs.qw.vo.QwSopCourseFinishTempSetting;
 import com.fs.qwApi.param.QwExternalContactHParam;
 import com.fs.sop.domain.QwSopLogs;
@@ -27,6 +29,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDateTime;
+import java.util.List;
 import java.util.Map;
 
 @Slf4j
@@ -42,9 +45,26 @@ public class IpadSendServer {
     private final IFsCourseWatchLogService watchLogService;
     private final IQwUserVideoService qwUserVideoService;
     private final RedisCache redisCache;
+    private final ICompanyMiniappService companyMiniappService;
 
-    private void sendMiniProgram(BaseVo vo, QwSopCourseFinishTempSetting.Setting content, Map<String, FsCoursePlaySourceConfig> miniMap) {
-        FsCoursePlaySourceConfig courseMaConfig = miniMap.get(content.getMiniprogramAppid());
+    private void sendMiniProgram(BaseVo vo, QwSopCourseFinishTempSetting.Setting content, Map<String, FsCoursePlaySourceConfig> miniMap, Long companyId) {
+        String appid = content.getMiniprogramAppid();
+        if(companyId != null && content.getMiniType() != null){
+            List<CompanyMiniapp> list = companyMiniappService.list(new QueryWrapper<CompanyMiniapp>().eq("company_id", companyId).eq("type", content.getMiniType()));
+            if(!list.isEmpty() && list.get(0) != null && StringUtils.isNotEmpty(list.get(0).getAppId())){
+                appid = list.get(0).getAppId();
+            }
+        }
+        FsCoursePlaySourceConfig courseMaConfig = miniMap.get(appid);
+        if(courseMaConfig == null){
+            List<CompanyMiniapp> list = companyMiniappService.list(new QueryWrapper<CompanyMiniapp>().eq("company_id", companyId).eq("type", 1));
+            if(!list.isEmpty() && list.get(0) != null && StringUtils.isNotEmpty(list.get(0).getAppId())){
+                courseMaConfig = miniMap.get(list.get(0).getAppId());
+            }
+        }
+        if(courseMaConfig == null){
+            throw new BaseException("未找到小程序配置:{}", appid);
+        }
         // 小程序
         MiniProgramVo miniProgramVo = MiniProgramVo.builder()
                 .desc(content.getMiniprogramTitle())
@@ -53,7 +73,7 @@ public class IpadSendServer {
                 .imgUrl(content.getMiniprogramPicUrl())
                 .username(courseMaConfig.getOriginalId() + "@app")
                 .pagepath(content.getMiniprogramPage())
-                .appid(content.getMiniprogramAppid())
+                .appid(courseMaConfig.getAppid())
                 .build();
         miniProgramVo.setBase(vo);
         WxWorkResponseDTO<WxWorkSendAppMsgRespDTO> resp = ipadSendUtils.sendMiniProgram(miniProgramVo);
@@ -337,7 +357,7 @@ public class IpadSendServer {
                     break;
                 case "4":
                 case "10":
-                    sendMiniProgram(vo, content, miniMap);
+                    sendMiniProgram(vo, content, miniMap, qwUser.getCompanyId());
                     break;
                 case "5":
                     // 文件

+ 2 - 2
fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java

@@ -92,8 +92,8 @@ public class SendMsg {
 
     private Map<String, FsCoursePlaySourceConfig> getMiniMap() {
         List<FsCoursePlaySourceConfig> list = fsCoursePlaySourceConfigService.list(new QueryWrapper<FsCoursePlaySourceConfig>().ne("type", 2).eq("is_del", 0));
-//        SysConfig maConfig = sysConfigMapper.selectConfigByConfigKey("courseMa.config");
-//        List<CourseMaConfig> courseMaConfigs = JSON.parseArray(maConfig.getConfigValue(), CourseMaConfig.class);
+        log.info("获取到的小程序配置:{}", JSON.toJSONString(list));
+        log.info("获取到的小程序配置:{}", JSON.toJSONString(list));
         return PubFun.listToMapByGroupObject(list, FsCoursePlaySourceConfig::getAppid);
     }
 

+ 22 - 0
fs-ipad-task/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -15,6 +15,8 @@ import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.data.redis.serializer.GenericToStringSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 
+import java.math.BigDecimal;
+
 /**
  * redis配置
  *
@@ -91,6 +93,26 @@ public class RedisConfig extends CachingConfigurerSupport
         return template;
     }
 
+    @Bean
+    public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        template.afterPropertiesSet();
+        return template;
+    }
+
+
     @Bean
     public DefaultRedisScript<Long> limitScript()
     {

+ 20 - 0
fs-live-app/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -15,6 +15,8 @@ import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.data.redis.serializer.GenericToStringSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 
+import java.math.BigDecimal;
+
 /**
  * redis配置
  *
@@ -91,6 +93,24 @@ public class RedisConfig extends CachingConfigurerSupport
         return template;
     }
 
+    @Bean
+    public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        template.afterPropertiesSet();
+        return template;
+    }
     @Bean
     public DefaultRedisScript<Long> limitScript()
     {

+ 42 - 38
fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java

@@ -206,43 +206,43 @@ public class QwMsgController {
         }
         switch (type){
             case 100001:
-                System.out.println("扫码返回");
+                log.info("id:{}, 扫码返回", id);
                 break;
             case 100002:
-                System.out.println("取消扫码");
+                log.info("id:{}, 取消扫码", id);
               //  redisCache.setCacheObject("qrCodeUid:"+wxWorkMsgResp.getUuid(),100002,10, TimeUnit.MINUTES);
                 break;
             case 100003:
-                System.out.println("确认扫码返回");
+                log.info("id:{}, 确认扫码返回", id);
 
                 break;
             case 104001:
-                System.out.println("登录成功");
+                log.info("id:{}, 登录成功", id);
 
                 JSONObject jsonObject = new JSONObject(wxWorkMsgResp.getJson());
                 QwUser qu = qwUserMapper.selectQwUserById(id);
                 Long corpId = jsonObject.getLong("Corpid");
-                System.out.println("回调中的"+corpId);
+                log.info("id:{}, 回调中的"+corpId, id);
                 String sCorpId = wxWorkService.getCorpId(wxWorkMsgResp.getUuid(), corpId,serverId);
                 if (sCorpId==null&& sCorpId.isEmpty()){
                     break;
                 }
                 if (!qu.getCorpId().equals(sCorpId)){
                     redisCache.setCacheObject("qrCodeUid:"+wxWorkMsgResp.getUuid(),22,10, TimeUnit.MINUTES);
-                    System.out.println("公司不匹配不给登录");
+                    log.info("id:{}, 公司不匹配不给登录", id);
                     WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
                     wxWorkGetQrCodeDTO.setUuid(wxWorkMsgResp.getUuid());
                     wxWorkService.LoginOut(wxWorkGetQrCodeDTO,serverId);
-                    log.info("调用退出登录");
+                    log.info("id:{}, 调用退出登录", id);
                     break;
                 }
                 if (!qu.getQwUserId().equals(jsonObject.get("acctid"))){
                     redisCache.setCacheObject("qrCodeUid:"+wxWorkMsgResp.getUuid(),23,10, TimeUnit.MINUTES);
-                    System.out.println("账号不匹配不给登录");
+                    log.info("id:{}, 账号不匹配不给登录", id);
                     WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
                     wxWorkGetQrCodeDTO.setUuid(wxWorkMsgResp.getUuid());
                     wxWorkService.LoginOut(wxWorkGetQrCodeDTO,serverId);
-                    log.info("调用退出登录");
+                    log.info("id:{}, 调用退出登录", id);
                     break;
                 }
                 QwUser qwUser = new QwUser();
@@ -250,44 +250,44 @@ public class QwMsgController {
                 qwUser.setVid(jsonObject.get("Vid").toString());
                 qwUser.setIpadStatus(1);
                 qwUserMapper.updateQwUser(qwUser);
-                System.out.println("存Vid");
+                log.info("id:{}, 存Vid", id);
                 redisCache.setCacheObject("qrCodeUid:"+wxWorkMsgResp.getUuid(),104001,10, TimeUnit.MINUTES);
                 break;
             case 100006:
 
                 break;
             case 100004:
-                System.out.println("需要验证二维码消息");
+                log.info("id:{}, 需要验证二维码消息", id);
                 redisCache.setCacheObject("qrCodeUid:"+wxWorkMsgResp.getUuid(),100004,10, TimeUnit.MINUTES);
                 break;
             case 100012:
-                log.info("需要二次验证:"+wxWorkMsgResp.getJson());
+                log.info("id:{}, 需要二次验证:"+wxWorkMsgResp.getJson(), id);
 
                 redisCache.setCacheObject("qrCodeUid:"+wxWorkMsgResp.getUuid(),100012,10, TimeUnit.MINUTES);
 
                 break;
             case 100005:
 
-                log.info("手机端结束登录:"+wxWorkMsgResp.getJson());
+                log.info("id:{}, 手机端结束登录:"+wxWorkMsgResp.getJson(), id);
                 JSONObject jsonObject1 = new JSONObject(wxWorkMsgResp.getJson());
                 qwUserStatus(wxWorkMsgResp.getUuid(),0, jsonObject1.getString("msg"));
                 break;
             case 100008:
                 QwUser vidUser = qwUserMapper.selectQwUserById(id);
                 if (vidUser.getUid().equals(wxWorkMsgResp.getUuid())){
-                    log.info("当前账号在其他设备登录:"+wxWorkMsgResp.getJson());
+                    log.info("id:{}, 当前账号在其他设备登录:"+wxWorkMsgResp.getJson(), id);
                     JSONObject jsonObject2 = new JSONObject(wxWorkMsgResp.getJson());
                     qwUserStatus(wxWorkMsgResp.getUuid(),0, jsonObject2.getString("msg"));
                 }
-                log.info("当前账号重新登录:"+wxWorkMsgResp.getJson());
+                log.info("id:{}, 当前账号重新登录:"+wxWorkMsgResp.getJson(), id);
                 break;
             case 100007:
-                log.info("异常断开:"+wxWorkMsgResp.getJson());
+                log.info("id:{}, 异常断开:"+wxWorkMsgResp.getJson(), id);
                 JSONObject jsonObject3 = new JSONObject(wxWorkMsgResp.getJson());
                 qwUserStatus(wxWorkMsgResp.getUuid(),0, "异常断开 - " + jsonObject3.getString("msg"));
                 break;
             case 100009:
-                log.info("二次验证:"+wxWorkMsgResp.getJson());
+                log.info("id:{}, 二次验证:"+wxWorkMsgResp.getJson(), id);
                 JSONObject jsonObject4 = new JSONObject(wxWorkMsgResp.getJson());
                 qwUserStatus(wxWorkMsgResp.getUuid(),0, "二次验证 - " + jsonObject4.getString("msg"));
                 break;
@@ -307,9 +307,9 @@ public class QwMsgController {
                 if (wxWorkMessageDTO.getMsgtype()==2||wxWorkMessageDTO.getMsgtype()==0||wxWorkMessageDTO.getMsgtype()==16||wxWorkMessageDTO.getMsgtype() == 101||wxWorkMessageDTO.getMsgtype() == 104){
 
                     String content = wxWorkMessageDTO.getContent();
-                    System.out.println("接收人:"+wxWorkMessageDTO.getReceiver());
-                    System.out.println("发送人:"+wxWorkMessageDTO.getSender());
-                    System.out.println("内容:"+content);
+                    log.info("id:{}, 接收人:"+wxWorkMessageDTO.getReceiver(), id);
+                    log.info("id:{}, 发送人:"+wxWorkMessageDTO.getSender(), id);
+                    log.info("id:{}, 内容:"+content, id);
                     Long receiver = wxWorkMessageDTO.getReceiver();
                     Long sender = wxWorkMessageDTO.getSender();
                     if(wxWorkMessageDTO.getMsgtype()==16){
@@ -323,29 +323,32 @@ public class QwMsgController {
                                 TimeUnit.SECONDS.sleep(1); // 阻塞1秒
                             } catch (InterruptedException e) {
                                 Thread.currentThread().interrupt(); // 处理中断异常
-                                System.out.println("第一次语音转换失败");
+                                log.info("id:{}, 第一次语音转换失败", id);
                             }
                             dto = wxWorkService.SpeechToTextEntity(ste, serverId);
                         }
                         WxwSpeechToTextEntityRespDTO data = dto.getData();
                         content = data.getText();
-                        System.out.println("语音消息"+content);
+                        if(content == null || content.isEmpty()){
+                            content = "==语音转换失败==";
+                        }
+                        log.info("id:{}, 语音消息"+content, id);
                     }
                     else if (wxWorkMessageDTO.getMsgtype() == 101){
                         content = processImageMessage(serverId, wxWorkMessageDTO, wxWorkMsgResp);
-                        System.out.println("用户发送图片"+content);
+                        log.info("id:{}, 用户发送图片"+content, id);
                     }
                     // gif 表情消息
                     else if (wxWorkMessageDTO.getMsgtype() == 104){
                         content = wxWorkMessageDTO.getUrl();
-                        System.out.println("用户发送表情"+content);
+                        log.info("id:{}, 用户发送表情"+content, id);
                     }
 
                     if (2000000000000000L-receiver>0){
-                        System.out.println("客户发送");
+                        log.info("id:{}, 客户发送", id);
                         aiHookService.qwHookNotifyAiReply(id,sender,content,wxWorkMsgResp.getUuid(),wxWorkMessageDTO.getMsgtype());
                     }else {
-                        System.out.println("销售发送");
+                        log.info("id:{}, 销售发送", id);
                         aiHookService.qwHookNotifyAddMsg(id,receiver,content,wxWorkMsgResp.getUuid());
                     }
 
@@ -357,12 +360,12 @@ public class QwMsgController {
                     }
                     Long receiver = wxWorkMessageDTO.getReceiver();
                     Long extId=null;
-                    Integer totalSeconds=0;
+                    long totalSeconds=0L;
                     if (2000000000000000L-receiver>0){
                          extId = wxWorkMessageDTO.getSender();
-                        System.out.println("客户发起");
+                        log.info("id:{}, 客户发起", id);
                     }else {
-                        System.out.println("销售发起");
+                        log.info("id:{}, 销售发起", id);
                         extId = wxWorkMessageDTO.getReceiver();
                     }
                     Integer recordType = wxWorkMessageDTO.getRecordtype();
@@ -388,12 +391,12 @@ public class QwMsgController {
                                     minutes = Integer.parseInt(matcher.group(1));
                                     seconds = Integer.parseInt(matcher.group(2));
                                 }
-                                totalSeconds = hours * 3600 + minutes * 60 + seconds;
-                                System.out.println("总通话秒数: " + totalSeconds);
+                                totalSeconds = hours * 3600L + minutes * 60L + seconds;
+                                log.info("id:{}, 总通话秒数: " + totalSeconds, id);
                             }
                         }
                     } else if (recordType==2){
-                        System.out.println("通话挂断");
+                        log.info("id:{}, 通话挂断", id);
                     }else {
                         break;
                     }
@@ -422,15 +425,15 @@ public class QwMsgController {
         QwUser user = qwUserMapper.selectQwUserById(qwUserId);
         //查询接收人
         if(user==null){
-            System.out.println("查询接收人为空");
+            log.info("查询接收人为空");
         }
         if(user.getFastGptRoleId()==null){
-            System.out.println("未绑定角色");
+            log.info("未绑定角色");
         }
         Long serverId = user.getServerId();
-        System.out.println("服务器id"+serverId);
+        log.info("服务器id"+serverId);
         if (serverId == null) {
-            System.out.println("服务id为空");
+            log.info("服务id为空");
         }
 
         WxWorkVid2UserIdDTO wxWorkVid2UserIdDTO = new WxWorkVid2UserIdDTO();
@@ -441,13 +444,13 @@ public class QwMsgController {
         List<WxWorkVid2UserIdRespDTO> data = WxWorkVid2UserIdRespDTO.getData();
         if (data==null|| data.isEmpty()){
 
-            System.out.println("未获取到extId"+wxWorkVid2UserIdDTO);
+            log.info("未获取到extId"+wxWorkVid2UserIdDTO);
         }
         com.fs.wxwork.dto.WxWorkVid2UserIdRespDTO dto = data.get(0);
 
         QwExternalContact qwExternalContacts = qwExternalContactMapper.selectQwExternalContactByExternalUserIdAndQwUserId(dto.getOpenid(), user.getCorpId(),user.getQwUserId());
         if (qwExternalContacts==null){
-            System.out.println("没有外部联系人");
+            log.info("没有外部联系人");
         }
 
         //处理拉黑的
@@ -516,6 +519,7 @@ public class QwMsgController {
         Long id = redisCache.getCacheObject("qrCode:uuid:"+uid);
         QwUser qwUser = new QwUser();
         qwUser.setId(id);
+        qwUser.setRemark(msg);
         qwUser.setIpadStatus(status);
         qwUserMapper.updateQwUser(qwUser);
         QwUser qwUser1 = qwUserMapper.selectQwUserById(id);

+ 21 - 0
fs-qw-api-msg/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -15,6 +15,8 @@ import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.data.redis.serializer.GenericToStringSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 
+import java.math.BigDecimal;
+
 /**
  * redis配置
  *
@@ -66,6 +68,25 @@ public class RedisConfig extends CachingConfigurerSupport
         return template;
     }
 
+    @Bean
+    public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        template.afterPropertiesSet();
+        return template;
+    }
+
     @Bean
     @SuppressWarnings(value = { "unchecked", "rawtypes" })
     public RedisTemplate<String, Object> redisTemplateForObject(RedisConnectionFactory connectionFactory) {

+ 21 - 0
fs-qw-api/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -15,6 +15,8 @@ import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.data.redis.serializer.GenericToStringSerializer;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 
+import java.math.BigDecimal;
+
 /**
  * redis配置
  *
@@ -91,6 +93,25 @@ public class RedisConfig extends CachingConfigurerSupport
         return template;
     }
 
+    @Bean
+    public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        template.afterPropertiesSet();
+        return template;
+    }
+
     @Bean
     public DefaultRedisScript<Long> limitScript()
     {

+ 141 - 0
fs-qw-mq/pom.xml

@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>fs</artifactId>
+        <groupId>com.fs</groupId>
+        <version>1.1.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>fs-qw-mq</artifactId>
+    <description>
+       企业微信mq
+    </description>
+    <dependencies>
+
+        <!-- spring-boot-devtools -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <optional>true</optional> <!-- 表示依赖不会传递 -->
+        </dependency>
+        <!-- swagger2-->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+        </dependency>
+
+        <!-- swagger2-UI-->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>swagger-bootstrap-ui</artifactId>
+            <version>1.9.3</version>
+        </dependency>
+
+
+        <!-- Mysql驱动包 -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+
+        <!-- SpringBoot Web容器 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <!-- SpringBoot 拦截器 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+        <!-- 阿里数据库连接池 -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- 验证码 -->
+        <dependency>
+            <groupId>com.github.penggle</groupId>
+            <artifactId>kaptcha</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>javax.servlet-api</artifactId>
+                    <groupId>javax.servlet</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- 获取系统信息 -->
+        <dependency>
+            <groupId>com.github.oshi</groupId>
+            <artifactId>oshi-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fs</groupId>
+            <artifactId>fs-service</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.tencentyun</groupId>
+            <artifactId>tls-sig-api-v2</artifactId>
+            <version>2.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-websocket</artifactId>
+            <version>5.1.10.RELEASE</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.rocketmq</groupId>
+            <artifactId>rocketmq-spring-boot-starter</artifactId>
+            <version>2.2.3</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.1.1.RELEASE</version>
+                <configuration>
+                    <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-war-plugin</artifactId>
+                <version>3.1.0</version>
+                <configuration>
+                    <failOnMissingWebXml>false</failOnMissingWebXml>
+                    <warName>${project.artifactId}</warName>
+                </configuration>
+            </plugin>
+        </plugins>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+
+</project>

+ 14 - 0
fs-qw-mq/src/main/java/com/fs/FSServletInitializer.java

@@ -0,0 +1,14 @@
+package com.fs;
+
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+
+public class FSServletInitializer extends SpringBootServletInitializer
+{
+    @Override
+    protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
+    {
+        return application.sources(FsQwMqApiApplication.class);
+    }
+}

+ 24 - 0
fs-qw-mq/src/main/java/com/fs/FsQwMqApiApplication.java

@@ -0,0 +1,24 @@
+package com.fs;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+/**
+ * 启动程序
+ */
+@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
+@EnableTransactionManagement
+@EnableAsync
+@EnableScheduling
+public class FsQwMqApiApplication
+{
+    public static void main(String[] args)
+    {
+        SpringApplication.run(FsQwMqApiApplication.class, args);
+        System.out.println("qw-mq启动成功");
+    }
+}

+ 58 - 0
fs-qw-mq/src/main/java/com/fs/app/controller/CommonController.java

@@ -0,0 +1,58 @@
+package com.fs.app.controller;
+
+
+import com.alibaba.fastjson.JSON;
+import com.fs.ad.service.IAdHtmlClickLogService;
+import com.fs.common.core.domain.R;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.service.IQwExternalContactService;
+import com.fs.qw.service.impl.QwExternalContactServiceImpl;
+import com.fs.qwApi.param.QwExternalContactRemarkParam;
+import com.fs.qwApi.service.QwApiService;
+import com.fs.voice.utils.StringUtil;
+import io.swagger.annotations.Api;
+import jdk.nashorn.internal.ir.annotations.Ignore;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+@Slf4j
+@Api("公共接口")
+@RestController
+@AllArgsConstructor
+@Ignore
+@RequestMapping(value="/app/common")
+public class CommonController {
+    private IAdHtmlClickLogService adHtmlClickLogService;
+    private RocketMQTemplate rocketMQTemplate;
+
+
+    @PostMapping("/testSend")
+    public R testSend(@RequestBody FsCourseWatchLog watchLog){
+
+
+        try {
+            rocketMQTemplate.syncSend("course-finish-notes", JSON.toJSONString(watchLog));
+        }catch (Exception e){
+            log.error("添加完课打备注失败",e);
+        }
+
+        return R.ok();
+    }
+
+}

+ 51 - 0
fs-qw-mq/src/main/java/com/fs/app/exception/FSException.java

@@ -0,0 +1,51 @@
+package com.fs.app.exception;
+
+/**
+ * 自定义异常
+ */
+public class FSException extends RuntimeException {
+	private static final long serialVersionUID = 1L;
+	
+    private String msg;
+    private int code = 500;
+    
+    public FSException(String msg) {
+		super(msg);
+		this.msg = msg;
+	}
+	
+	public FSException(String msg, Throwable e) {
+		super(msg, e);
+		this.msg = msg;
+	}
+	
+	public FSException(String msg, int code) {
+		super(msg);
+		this.msg = msg;
+		this.code = code;
+	}
+	
+	public FSException(String msg, int code, Throwable e) {
+		super(msg, e);
+		this.msg = msg;
+		this.code = code;
+	}
+
+	public String getMsg() {
+		return msg;
+	}
+
+	public void setMsg(String msg) {
+		this.msg = msg;
+	}
+
+	public int getCode() {
+		return code;
+	}
+
+	public void setCode(int code) {
+		this.code = code;
+	}
+	
+	
+}

+ 81 - 0
fs-qw-mq/src/main/java/com/fs/app/exception/FSExceptionHandler.java

@@ -0,0 +1,81 @@
+package com.fs.app.exception;
+
+
+
+
+import com.fs.common.core.domain.R;
+import com.fs.common.exception.CustomException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.validation.BindException;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.servlet.NoHandlerFoundException;
+
+
+/**
+ * 异常处理器
+ */
+@RestControllerAdvice
+public class FSExceptionHandler {
+	private Logger logger = LoggerFactory.getLogger(getClass());
+
+	/**
+	 * 处理自定义异常
+	 */
+	@ExceptionHandler(FSException.class)
+	public R handleRRException(FSException e){
+		R r = new R();
+		r.put("code", e.getCode());
+		r.put("msg", e.getMessage());
+
+		return r;
+	}
+
+	@ExceptionHandler(NoHandlerFoundException.class)
+	public R handlerNoFoundException(Exception e) {
+		logger.error(e.getMessage(), e);
+		return R.error(404, "路径不存在,请检查路径是否正确");
+	}
+
+	@ExceptionHandler(DuplicateKeyException.class)
+	public R handleDuplicateKeyException(DuplicateKeyException e){
+		logger.error(e.getMessage(), e);
+		return R.error("数据库中已存在该记录");
+	}
+
+
+	@ExceptionHandler(Exception.class)
+	public R handleException(Exception e){
+		logger.error(e.getMessage(), e);
+		return R.error();
+	}
+	@ExceptionHandler(AccessDeniedException.class)
+	public R handleAccessDeniedException(AccessDeniedException e){
+		logger.error(e.getMessage(), e);
+		return R.error("没有权限");
+	}
+
+	@ExceptionHandler(BindException.class)
+	public R bindExceptionHandler(BindException e) {
+		FieldError error = e.getFieldError();
+		String message = String.format("%s",  error.getDefaultMessage());
+		return R.error(message);
+	}
+
+	@ExceptionHandler(MethodArgumentNotValidException.class)
+	public R exceptionHandler(MethodArgumentNotValidException e) {
+		FieldError error = e.getBindingResult().getFieldError();
+		String message = String.format("%s",  error.getDefaultMessage());
+		return R.error(message);
+	}
+	@ExceptionHandler(CustomException.class)
+	public R handleException(CustomException e){
+
+		return R.error(e.getMessage());
+	}
+}

+ 34 - 0
fs-qw-mq/src/main/java/com/fs/app/mq/RocketMQConsumerCourseFinishService.java

@@ -0,0 +1,34 @@
+package com.fs.app.mq;
+
+
+import com.alibaba.fastjson.JSON;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.service.IFsCourseFinishTempService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
+import org.apache.rocketmq.spring.core.RocketMQListener;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@AllArgsConstructor
+@RocketMQMessageListener(topic = "course-finish-notes", consumerGroup = "course-finish-group")
+public class RocketMQConsumerCourseFinishService implements RocketMQListener<String> {
+
+    private final IFsCourseFinishTempService courseFinishTempService;
+
+    @Override
+    public void onMessage(String message) {
+
+
+        log.info("收到消息1:" + message);
+
+        FsCourseWatchLog watchLog = JSON.parseObject(message, FsCourseWatchLog.class);
+        if (watchLog == null || watchLog.getQwExternalContactId() == null) {
+            return;
+        }
+        courseFinishTempService.finishCourseExtContactIdByRemark(watchLog);
+
+    }
+}

+ 188 - 0
fs-qw-mq/src/main/java/com/fs/app/mq/courseFinishRtyTaskOne.java

@@ -0,0 +1,188 @@
+package com.fs.app.mq;
+
+
+import com.fs.common.core.redis.RedisCache;
+import com.fs.qw.domain.QwCourseFinishRemarkRty;
+import com.fs.qw.service.IQwCourseFinishRemarkRtyService;
+import com.fs.qwApi.domain.QwExternalContactRemarkResult;
+import com.fs.qwApi.param.QwExternalContactRemarkParam;
+import com.fs.qwApi.service.QwApiService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.*;
+
+@Component
+@Slf4j
+public class courseFinishRtyTaskOne {
+
+    @Autowired
+    private IQwCourseFinishRemarkRtyService finishRemarkRtyService;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @Autowired
+    private QwApiService qwApiService;
+
+    /**
+     * 完课打备注失败的 重复打 一直到失败 (这个 是销售1的 启动项)
+     */
+
+    @Scheduled(cron = "0 0/15 * * * ?") // 每15分钟
+//    @Scheduled(cron = "0/1 * * * * ? ") // 每15分钟
+    public void selectSopUserLogsListByTimeOne() {
+
+        System.out.println("schedule.task1-enabled111111111");
+
+        // 获取当前时间
+        LocalDateTime now = LocalDateTime.now();
+        // 计算10分钟前
+        LocalDateTime tenMinutesBefore = now.minusMinutes(10);
+
+        // 创建线程池
+        int threadCount = Math.min(8, Runtime.getRuntime().availableProcessors() * 2);
+        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
+
+        try {
+            // 使用线程安全的List来收集需要批量更新的数据
+            List<Long> batchUpdateList = Collections.synchronizedList(new ArrayList<>());
+
+            // 存储Future对象以便检查所有任务完成情况
+            List<Future<?>> futures = new ArrayList<>();
+
+            // 1. 批量查询所有用户数据
+            List<QwCourseFinishRemarkRty> remarkRtyList=new ArrayList<>();
+            try {
+                remarkRtyList = finishRemarkRtyService.selectQwCourseFinishRemarkRtyListByTime(tenMinutesBefore,1);
+                if (remarkRtyList == null || remarkRtyList.isEmpty()) {
+                     return;
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+                log.info("批量查询用户数据失败:" + e.getMessage());
+            }
+
+            // 直接遍历contacts而不是userIds
+            for (QwCourseFinishRemarkRty remarkRty : remarkRtyList) {
+                futures.add(executor.submit(() -> {
+                    try {
+
+                        QwExternalContactRemarkParam remarkParam = new QwExternalContactRemarkParam();
+                        remarkParam.setRemark(remarkRty.getRemark().trim());
+                        remarkParam.setUserid(remarkRty.getQwUserId().trim());
+                        remarkParam.setExternal_userid(remarkRty.getExternalUserId().trim());
+
+                        QwExternalContactRemarkResult qwResult = qwApiService.externalcontactRemark(remarkParam, remarkRty.getCorpId());
+                        if (qwResult.getErrcode() == 0) {
+                            // 添加到批量更新列表
+                            batchUpdateList.add(remarkRty.getId());
+                        }else {
+                            log.error("客户添加备注失败task,userId: {}{}{}", remarkRty.getQwUserId(), remarkRty.getExternalUserId(), qwResult.getErrmsg());
+                        }
+                    } catch (Exception e) {
+                        log.error("客户添加备注失败task,userId: " + remarkRty.getQwUserId() +
+                                ", externalUserId: " + remarkRty.getExternalUserId() +
+                                ", 错误信息: " + e.getMessage());
+                    }
+                }));
+            }
+
+
+            // 等待所有任务完成
+            for (Future<?> future : futures) {
+                try {
+                    future.get();
+                } catch (InterruptedException | ExecutionException e) {
+                    log.error("任务执行异常: " + e.getMessage());
+                    Thread.currentThread().interrupt();
+                }
+            }
+
+            // 批量更新数据库
+            if (!batchUpdateList.isEmpty()) {
+                try {
+                    // 分批处理,避免单次批量过大
+                    int batchSize = 500; // 根据数据库性能调整
+                    for (int i = 0; i < batchUpdateList.size(); i += batchSize) {
+                        int end = Math.min(i + batchSize, batchUpdateList.size());
+                        List<Long> subList = batchUpdateList.subList(i, end);
+
+                        finishRemarkRtyService.deleteQwCourseFinishRemarkRtyByIds(subList.toArray(new Long[0]));
+                    }
+                } catch (Exception e) {
+                    log.error("批量更新失败: " + e.getMessage());
+                }
+            }
+
+            // 关闭线程池
+            executor.shutdown();
+
+        } catch (Exception e) {
+            log.error("打备注任务执行异常: " + e.getMessage());
+        } finally {
+            // 7. 确保线程池关闭
+            try {
+                // 先尝试正常关闭
+                executor.shutdown();
+                if (!executor.awaitTermination(15, TimeUnit.SECONDS)) {
+                    // 超时后强制关闭
+                    executor.shutdownNow();
+                    log.warn("线程池强制关闭");
+                }
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                executor.shutdownNow();
+            }
+        }
+        try {
+            Thread.sleep(3000);
+        } catch (InterruptedException ex) {
+            log.info("线程被中断");
+        }
+
+
+
+    }
+
+
+
+
+    /**
+     * 批量删除 掉 前一天的没打上标签的
+     */
+    @Scheduled(cron = "0 30 0 * * ?")
+    public void deleteFinishCourseRemark() {
+        long startTimeMillis = System.currentTimeMillis();
+        log.info("====== 批量删除 掉 前一天的没打上标签的 ======");
+
+        List<Long> batchUpdateList = finishRemarkRtyService.selectQwCourseFinishRemarkRtyListByOutTime();
+
+        // 批量更新数据库
+        if (!batchUpdateList.isEmpty()) {
+            try {
+                // 分批处理,避免单次批量过大
+                int batchSize = 500; // 根据数据库性能调整
+                for (int i = 0; i < batchUpdateList.size(); i += batchSize) {
+                    int end = Math.min(i + batchSize, batchUpdateList.size());
+                    List<Long> subList = batchUpdateList.subList(i, end);
+
+                    finishRemarkRtyService.deleteQwCourseFinishRemarkRtyByIds(subList.toArray(new Long[0]));
+                }
+            } catch (Exception e) {
+                log.error("批量更新失败: " + e.getMessage());
+            }
+        }
+
+        long endTimeMillis = System.currentTimeMillis();
+        log.info("====== 更批量删除 掉 前一天的没打上标签的,耗时 {} 毫秒 ======", (endTimeMillis - startTimeMillis));
+    }
+
+}
+

+ 182 - 0
fs-qw-mq/src/main/java/com/fs/framework/aspectj/DataScopeAspect.java

@@ -0,0 +1,182 @@
+package com.fs.framework.aspectj;
+
+import com.fs.common.annotation.DataScope;
+import com.fs.common.core.domain.BaseEntity;
+import com.fs.common.core.domain.entity.SysRole;
+import com.fs.common.core.domain.entity.SysUser;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.common.utils.StringUtils;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+
+/**
+ * 数据过滤处理
+ *
+
+ */
+@Aspect
+@Component
+public class DataScopeAspect
+{
+    /**
+     * 全部数据权限
+     */
+    public static final String DATA_SCOPE_ALL = "1";
+
+    /**
+     * 自定数据权限
+     */
+    public static final String DATA_SCOPE_CUSTOM = "2";
+
+    /**
+     * 部门数据权限
+     */
+    public static final String DATA_SCOPE_DEPT = "3";
+
+    /**
+     * 部门及以下数据权限
+     */
+    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
+
+    /**
+     * 仅本人数据权限
+     */
+    public static final String DATA_SCOPE_SELF = "5";
+
+    /**
+     * 数据权限过滤关键字
+     */
+    public static final String DATA_SCOPE = "dataScope";
+
+    // 配置织入点
+    @Pointcut("@annotation(com.fs.common.annotation.DataScope)")
+    public void dataScopePointCut()
+    {
+    }
+
+    @Before("dataScopePointCut()")
+    public void doBefore(JoinPoint point) throws Throwable
+    {
+        clearDataScope(point);
+        handleDataScope(point);
+    }
+
+    protected void handleDataScope(final JoinPoint joinPoint)
+    {
+        // 获得注解
+        DataScope controllerDataScope = getAnnotationLog(joinPoint);
+        if (controllerDataScope == null)
+        {
+            return;
+        }
+        // 获取当前的用户
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (StringUtils.isNotNull(loginUser))
+        {
+            SysUser currentUser = loginUser.getUser();
+            // 如果是超级管理员,则不过滤数据
+            if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
+            {
+                dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
+                        controllerDataScope.userAlias());
+            }
+        }
+    }
+
+    /**
+     * 数据范围过滤
+     *
+     * @param joinPoint 切点
+     * @param user 用户
+     * @param userAlias 别名
+     */
+    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
+    {
+        StringBuilder sqlString = new StringBuilder();
+
+        for (SysRole role : user.getRoles())
+        {
+            String dataScope = role.getDataScope();
+            if (DATA_SCOPE_ALL.equals(dataScope))
+            {
+                sqlString = new StringBuilder();
+                break;
+            }
+            else if (DATA_SCOPE_CUSTOM.equals(dataScope))
+            {
+                sqlString.append(StringUtils.format(
+                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
+                        role.getRoleId()));
+            }
+            else if (DATA_SCOPE_DEPT.equals(dataScope))
+            {
+                sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
+            }
+            else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
+            {
+                sqlString.append(StringUtils.format(
+                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
+                        deptAlias, user.getDeptId(), user.getDeptId()));
+            }
+            else if (DATA_SCOPE_SELF.equals(dataScope))
+            {
+                if (StringUtils.isNotBlank(userAlias))
+                {
+                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
+                }
+                else
+                {
+                    // 数据权限为仅本人且没有userAlias别名不查询任何数据
+                    sqlString.append(" OR 1=0 ");
+                }
+            }
+        }
+
+        if (StringUtils.isNotBlank(sqlString.toString()))
+        {
+            Object params = joinPoint.getArgs()[0];
+            if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
+            {
+                BaseEntity baseEntity = (BaseEntity) params;
+                baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
+            }
+        }
+    }
+
+    /**
+     * 是否存在注解,如果存在就获取
+     */
+    private DataScope getAnnotationLog(JoinPoint joinPoint)
+    {
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+
+        if (method != null)
+        {
+            return method.getAnnotation(DataScope.class);
+        }
+        return null;
+    }
+
+    /**
+     * 拼接权限sql前先清空params.dataScope参数防止注入
+     */
+    private void clearDataScope(final JoinPoint joinPoint)
+    {
+        Object params = joinPoint.getArgs()[0];
+        if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
+        {
+            BaseEntity baseEntity = (BaseEntity) params;
+            baseEntity.getParams().put(DATA_SCOPE, "");
+        }
+    }
+}

+ 73 - 0
fs-qw-mq/src/main/java/com/fs/framework/aspectj/DataSourceAspect.java

@@ -0,0 +1,73 @@
+package com.fs.framework.aspectj;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.utils.StringUtils;
+import com.fs.framework.datasource.DynamicDataSourceContextHolder;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+import java.util.Objects;
+
+/**
+ * 多数据源处理
+ * 
+
+ */
+@Aspect
+@Order(1)
+@Component
+public class DataSourceAspect
+{
+    protected Logger logger = LoggerFactory.getLogger(getClass());
+
+    @Pointcut("@annotation(com.fs.common.annotation.DataSource)"
+            + "|| @within(com.fs.common.annotation.DataSource)")
+    public void dsPointCut()
+    {
+
+    }
+
+    @Around("dsPointCut()")
+    public Object around(ProceedingJoinPoint point) throws Throwable
+    {
+        DataSource dataSource = getDataSource(point);
+
+        if (StringUtils.isNotNull(dataSource))
+        {
+            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
+        }
+
+        try
+        {
+            return point.proceed();
+        }
+        finally
+        {
+            // 销毁数据源 在执行方法之后
+            DynamicDataSourceContextHolder.clearDataSourceType();
+        }
+    }
+
+    /**
+     * 获取需要切换的数据源
+     */
+    public DataSource getDataSource(ProceedingJoinPoint point)
+    {
+        MethodSignature signature = (MethodSignature) point.getSignature();
+        DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
+        if (Objects.nonNull(dataSource))
+        {
+            return dataSource;
+        }
+
+        return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
+    }
+}

+ 245 - 0
fs-qw-mq/src/main/java/com/fs/framework/aspectj/LogAspect.java

@@ -0,0 +1,245 @@
+package com.fs.framework.aspectj;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.enums.BusinessStatus;
+import com.fs.common.enums.HttpMethod;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.ip.IpUtils;
+
+import com.fs.framework.manager.AsyncManager;
+import com.fs.framework.manager.factory.AsyncFactory;
+import com.fs.system.domain.SysOperLog;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.servlet.HandlerMapping;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * 操作日志记录处理
+ * 
+
+ */
+@Aspect
+@Component
+public class LogAspect
+{
+    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
+
+    // 配置织入点
+    @Pointcut("@annotation(com.fs.common.annotation.Log)")
+    public void logPointCut()
+    {
+    }
+
+    /**
+     * 处理完请求后执行
+     *
+     * @param joinPoint 切点
+     */
+    @AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
+    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult)
+    {
+        handleLog(joinPoint, null, jsonResult);
+    }
+
+    /**
+     * 拦截异常操作
+     * 
+     * @param joinPoint 切点
+     * @param e 异常
+     */
+    @AfterThrowing(value = "logPointCut()", throwing = "e")
+    public void doAfterThrowing(JoinPoint joinPoint, Exception e)
+    {
+        handleLog(joinPoint, e, null);
+    }
+
+    protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult)
+    {
+        try
+        {
+            // 获得注解
+            Log controllerLog = getAnnotationLog(joinPoint);
+            if (controllerLog == null)
+            {
+                return;
+            }
+
+            // 获取当前的用户
+            LoginUser loginUser = SecurityUtils.getLoginUser();
+
+            // *========数据库日志=========*//
+            SysOperLog operLog = new SysOperLog();
+            operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
+            // 请求的地址
+            String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
+            operLog.setOperIp(ip);
+            // 返回参数
+            operLog.setJsonResult(JSON.toJSONString(jsonResult));
+
+            operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
+            if (loginUser != null)
+            {
+                operLog.setOperName(loginUser.getUsername());
+            }
+
+            if (e != null)
+            {
+                operLog.setStatus(BusinessStatus.FAIL.ordinal());
+                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
+            }
+            // 设置方法名称
+            String className = joinPoint.getTarget().getClass().getName();
+            String methodName = joinPoint.getSignature().getName();
+            operLog.setMethod(className + "." + methodName + "()");
+            // 设置请求方式
+            operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
+            // 处理设置注解上的参数
+            getControllerMethodDescription(joinPoint, controllerLog, operLog);
+            // 保存数据库
+            AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
+        }
+        catch (Exception exp)
+        {
+            // 记录本地异常日志
+            log.error("==前置通知异常==");
+            log.error("异常信息:{}", exp.getMessage());
+            exp.printStackTrace();
+        }
+    }
+
+    /**
+     * 获取注解中对方法的描述信息 用于Controller层注解
+     * 
+     * @param log 日志
+     * @param operLog 操作日志
+     * @throws Exception
+     */
+    public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog) throws Exception
+    {
+        // 设置action动作
+        operLog.setBusinessType(log.businessType().ordinal());
+        // 设置标题
+        operLog.setTitle(log.title());
+        // 设置操作人类别
+        operLog.setOperatorType(log.operatorType().ordinal());
+        // 是否需要保存request,参数和值
+        if (log.isSaveRequestData())
+        {
+            // 获取参数的信息,传入到数据库中。
+            setRequestValue(joinPoint, operLog);
+        }
+    }
+
+    /**
+     * 获取请求的参数,放到log中
+     * 
+     * @param operLog 操作日志
+     * @throws Exception 异常
+     */
+    private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception
+    {
+        String requestMethod = operLog.getRequestMethod();
+        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))
+        {
+            String params = argsArrayToString(joinPoint.getArgs());
+            operLog.setOperParam(StringUtils.substring(params, 0, 2000));
+        }
+        else
+        {
+            Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
+            operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
+        }
+    }
+
+    /**
+     * 是否存在注解,如果存在就获取
+     */
+    private Log getAnnotationLog(JoinPoint joinPoint) throws Exception
+    {
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+
+        if (method != null)
+        {
+            return method.getAnnotation(Log.class);
+        }
+        return null;
+    }
+
+    /**
+     * 参数拼装
+     */
+    private String argsArrayToString(Object[] paramsArray)
+    {
+        String params = "";
+        if (paramsArray != null && paramsArray.length > 0)
+        {
+            for (int i = 0; i < paramsArray.length; i++)
+            {
+                if (StringUtils.isNotNull(paramsArray[i]) && !isFilterObject(paramsArray[i]))
+                {
+                    Object jsonObj = JSON.toJSON(paramsArray[i]);
+                    params += jsonObj.toString() + " ";
+                }
+            }
+        }
+        return params.trim();
+    }
+
+    /**
+     * 判断是否需要过滤的对象。
+     * 
+     * @param o 对象信息。
+     * @return 如果是需要过滤的对象,则返回true;否则返回false。
+     */
+    @SuppressWarnings("rawtypes")
+    public boolean isFilterObject(final Object o)
+    {
+        Class<?> clazz = o.getClass();
+        if (clazz.isArray())
+        {
+            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
+        }
+        else if (Collection.class.isAssignableFrom(clazz))
+        {
+            Collection collection = (Collection) o;
+            for (Iterator iter = collection.iterator(); iter.hasNext();)
+            {
+                return iter.next() instanceof MultipartFile;
+            }
+        }
+        else if (Map.class.isAssignableFrom(clazz))
+        {
+            Map map = (Map) o;
+            for (Iterator iter = map.entrySet().iterator(); iter.hasNext();)
+            {
+                Map.Entry entry = (Map.Entry) iter.next();
+                return entry.getValue() instanceof MultipartFile;
+            }
+        }
+        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
+                || o instanceof BindingResult;
+    }
+}

+ 117 - 0
fs-qw-mq/src/main/java/com/fs/framework/aspectj/RateLimiterAspect.java

@@ -0,0 +1,117 @@
+package com.fs.framework.aspectj;
+
+import com.fs.common.annotation.RateLimiter;
+import com.fs.common.enums.LimitType;
+import com.fs.common.exception.ServiceException;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.ip.IpUtils;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.RedisScript;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 限流处理
+ *
+
+ */
+@Aspect
+@Component
+public class RateLimiterAspect
+{
+    private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
+
+    private RedisTemplate<Object, Object> redisTemplate;
+
+    private RedisScript<Long> limitScript;
+
+    @Autowired
+    public void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate)
+    {
+        this.redisTemplate = redisTemplate;
+    }
+
+    @Autowired
+    public void setLimitScript(RedisScript<Long> limitScript)
+    {
+        this.limitScript = limitScript;
+    }
+
+    // 配置织入点
+    @Pointcut("@annotation(com.fs.common.annotation.RateLimiter)")
+    public void rateLimiterPointCut()
+    {
+    }
+
+    @Before("rateLimiterPointCut()")
+    public void doBefore(JoinPoint point) throws Throwable
+    {
+        RateLimiter rateLimiter = getAnnotationRateLimiter(point);
+        String key = rateLimiter.key();
+        int time = rateLimiter.time();
+        int count = rateLimiter.count();
+
+        String combineKey = getCombineKey(rateLimiter, point);
+        List<Object> keys = Collections.singletonList(combineKey);
+        try
+        {
+            Long number = redisTemplate.execute(limitScript, keys, count, time);
+            if (StringUtils.isNull(number) || number.intValue() > count)
+            {
+                throw new ServiceException("访问过于频繁,请稍后再试");
+            }
+            log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), key);
+        }
+        catch (ServiceException e)
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("服务器限流异常,请稍后再试");
+        }
+    }
+
+    /**
+     * 是否存在注解,如果存在就获取
+     */
+    private RateLimiter getAnnotationRateLimiter(JoinPoint joinPoint)
+    {
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+
+        if (method != null)
+        {
+            return method.getAnnotation(RateLimiter.class);
+        }
+        return null;
+    }
+
+    public String getCombineKey(RateLimiter rateLimiter, JoinPoint point)
+    {
+        StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());
+        if (rateLimiter.limitType() == LimitType.IP)
+        {
+            stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest()));
+        }
+        MethodSignature signature = (MethodSignature) point.getSignature();
+        Method method = signature.getMethod();
+        Class<?> targetClass = method.getDeclaringClass();
+        stringBuffer.append("-").append(targetClass.getName()).append("- ").append(method.getName());
+        return stringBuffer.toString();
+    }
+}

+ 31 - 0
fs-qw-mq/src/main/java/com/fs/framework/config/ApplicationConfig.java

@@ -0,0 +1,31 @@
+package com.fs.framework.config;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+
+import java.util.TimeZone;
+
+/**
+ * 程序注解配置
+ *
+
+ */
+@Configuration
+// 表示通过aop框架暴露该代理对象,AopContext能够访问
+@EnableAspectJAutoProxy(exposeProxy = true)
+// 指定要扫描的Mapper类的包的路径
+@MapperScan("com.fs.**.mapper")
+public class ApplicationConfig
+{
+    /**
+     * 时区配置
+     */
+    @Bean
+    public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization()
+    {
+        return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
+    }
+}

+ 85 - 0
fs-qw-mq/src/main/java/com/fs/framework/config/CaptchaConfig.java

@@ -0,0 +1,85 @@
+package com.fs.framework.config;
+
+import com.google.code.kaptcha.impl.DefaultKaptcha;
+import com.google.code.kaptcha.util.Config;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.Properties;
+
+import static com.google.code.kaptcha.Constants.*;
+
+/**
+ * 验证码配置
+ * 
+
+ */
+@Configuration
+public class CaptchaConfig
+{
+    @Bean(name = "captchaProducer")
+    public DefaultKaptcha getKaptchaBean()
+    {
+        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
+        Properties properties = new Properties();
+        // 是否有边框 默认为true 我们可以自己设置yes,no
+        properties.setProperty(KAPTCHA_BORDER, "yes");
+        // 验证码文本字符颜色 默认为Color.BLACK
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
+        // 验证码图片宽度 默认为200
+        properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
+        // 验证码图片高度 默认为50
+        properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
+        // 验证码文本字符大小 默认为40
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38");
+        // KAPTCHA_SESSION_KEY
+        properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode");
+        // 验证码文本字符长度 默认为5
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
+        // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
+        // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
+        properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
+        Config config = new Config(properties);
+        defaultKaptcha.setConfig(config);
+        return defaultKaptcha;
+    }
+
+    @Bean(name = "captchaProducerMath")
+    public DefaultKaptcha getKaptchaBeanMath()
+    {
+        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
+        Properties properties = new Properties();
+        // 是否有边框 默认为true 我们可以自己设置yes,no
+        properties.setProperty(KAPTCHA_BORDER, "yes");
+        // 边框颜色 默认为Color.BLACK
+        properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
+        // 验证码文本字符颜色 默认为Color.BLACK
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
+        // 验证码图片宽度 默认为200
+        properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
+        // 验证码图片高度 默认为50
+        properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
+        // 验证码文本字符大小 默认为40
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
+        // KAPTCHA_SESSION_KEY
+        properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
+        // 验证码文本生成器
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.fs.framework.config.KaptchaTextCreator");
+        // 验证码文本字符间距 默认为2
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
+        // 验证码文本字符长度 默认为5
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
+        // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
+        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
+        // 验证码噪点颜色 默认为Color.BLACK
+        properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
+        // 干扰实现类
+        properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
+        // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
+        properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
+        Config config = new Config(properties);
+        defaultKaptcha.setConfig(config);
+        return defaultKaptcha;
+    }
+}

+ 100 - 0
fs-qw-mq/src/main/java/com/fs/framework/config/DataSourceConfig.java

@@ -0,0 +1,100 @@
+package com.fs.framework.config;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
+import com.alibaba.druid.util.Utils;
+import com.fs.common.enums.DataSourceType;
+import com.fs.framework.datasource.DynamicDataSource;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+
+import javax.servlet.*;
+import javax.sql.DataSource;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class DataSourceConfig {
+
+    @Bean
+    @ConfigurationProperties(prefix = "spring.datasource.clickhouse")
+    public DataSource clickhouseDataSource() {
+        return new DruidDataSource();
+    }
+
+    @Bean
+    @ConfigurationProperties(prefix = "spring.datasource.mysql.druid.master")
+    public DataSource masterDataSource() {
+        return new DruidDataSource();
+    }
+
+    @Bean
+    @ConfigurationProperties(prefix = "spring.datasource.mysql.druid.slave")
+    public DataSource slaveDataSource() {
+        return new DruidDataSource();
+    }
+
+    @Bean
+    @Primary
+    public DynamicDataSource dataSource(@Qualifier("clickhouseDataSource") DataSource clickhouseDataSource,
+                                        @Qualifier("masterDataSource") DataSource masterDataSource,
+                                        @Qualifier("slaveDataSource") DataSource slaveDataSource) {
+        Map<Object, Object> targetDataSources = new HashMap<>();
+        targetDataSources.put(DataSourceType.MASTER, masterDataSource);
+        targetDataSources.put(DataSourceType.SLAVE.name(), slaveDataSource);
+        targetDataSources.put(DataSourceType.CLICKHOUSE.name(), clickhouseDataSource); // Ensure matching key
+        return new DynamicDataSource(masterDataSource, targetDataSources);
+    }
+
+    /**
+     * 去除监控页面底部的广告
+     */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Bean
+    @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
+    public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties)
+    {
+        // 获取web监控页面的参数
+        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
+        // 提取common.js的配置路径
+        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
+        String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
+        final String filePath = "support/http/resources/js/common.js";
+        // 创建filter进行过滤
+        Filter filter = new Filter()
+        {
+            @Override
+            public void init(javax.servlet.FilterConfig filterConfig) throws ServletException
+            {
+            }
+            @Override
+            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+                    throws IOException, ServletException
+            {
+                chain.doFilter(request, response);
+                // 重置缓冲区,响应头不会被重置
+                response.resetBuffer();
+                // 获取common.js
+                String text = Utils.readFromResource(filePath);
+                // 正则替换banner, 除去底部的广告信息
+                text = text.replaceAll("<a.*?banner\"></a><br/>", "");
+                text = text.replaceAll("powered.*?shrek.wang</a>", "");
+                response.getWriter().write(text);
+            }
+            @Override
+            public void destroy()
+            {
+            }
+        };
+        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
+        registrationBean.setFilter(filter);
+        registrationBean.addUrlPatterns(commonJsPattern);
+        return registrationBean;
+    }
+}

+ 72 - 0
fs-qw-mq/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java

@@ -0,0 +1,72 @@
+package com.fs.framework.config;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.parser.ParserConfig;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+import org.springframework.util.Assert;
+
+import java.nio.charset.Charset;
+
+/**
+ * Redis使用FastJson序列化
+ * 
+
+ */
+public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
+{
+    @SuppressWarnings("unused")
+    private ObjectMapper objectMapper = new ObjectMapper();
+
+    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+
+    private Class<T> clazz;
+
+    static
+    {
+        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
+    }
+
+    public FastJson2JsonRedisSerializer(Class<T> clazz)
+    {
+        super();
+        this.clazz = clazz;
+    }
+
+    @Override
+    public byte[] serialize(T t) throws SerializationException
+    {
+        if (t == null)
+        {
+            return new byte[0];
+        }
+        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
+    }
+
+    @Override
+    public T deserialize(byte[] bytes) throws SerializationException
+    {
+        if (bytes == null || bytes.length <= 0)
+        {
+            return null;
+        }
+        String str = new String(bytes, DEFAULT_CHARSET);
+
+        return JSON.parseObject(str, clazz);
+    }
+
+    public void setObjectMapper(ObjectMapper objectMapper)
+    {
+        Assert.notNull(objectMapper, "'objectMapper' must not be null");
+        this.objectMapper = objectMapper;
+    }
+
+    protected JavaType getJavaType(Class<?> clazz)
+    {
+        return TypeFactory.defaultInstance().constructType(clazz);
+    }
+}

+ 59 - 0
fs-qw-mq/src/main/java/com/fs/framework/config/FilterConfig.java

@@ -0,0 +1,59 @@
+package com.fs.framework.config;
+
+import com.fs.common.filter.RepeatableFilter;
+import com.fs.common.filter.XssFilter;
+import com.fs.common.utils.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.servlet.DispatcherType;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Filter配置
+ *
+
+ */
+@Configuration
+@ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
+public class FilterConfig
+{
+    @Value("${xss.excludes}")
+    private String excludes;
+
+    @Value("${xss.urlPatterns}")
+    private String urlPatterns;
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Bean
+    public FilterRegistrationBean xssFilterRegistration()
+    {
+        FilterRegistrationBean registration = new FilterRegistrationBean();
+        registration.setDispatcherTypes(DispatcherType.REQUEST);
+        registration.setFilter(new XssFilter());
+        registration.addUrlPatterns(StringUtils.split(urlPatterns, ","));
+        registration.setName("xssFilter");
+        registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
+        Map<String, String> initParameters = new HashMap<String, String>();
+        initParameters.put("excludes", excludes);
+        registration.setInitParameters(initParameters);
+        return registration;
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Bean
+    public FilterRegistrationBean someFilterRegistration()
+    {
+        FilterRegistrationBean registration = new FilterRegistrationBean();
+        registration.setFilter(new RepeatableFilter());
+        registration.addUrlPatterns("/*");
+        registration.setName("repeatableFilter");
+        registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
+        return registration;
+    }
+
+}

+ 76 - 0
fs-qw-mq/src/main/java/com/fs/framework/config/KaptchaTextCreator.java

@@ -0,0 +1,76 @@
+package com.fs.framework.config;
+
+import com.google.code.kaptcha.text.impl.DefaultTextCreator;
+
+import java.util.Random;
+
+/**
+ * 验证码文本生成器
+ * 
+
+ */
+public class KaptchaTextCreator extends DefaultTextCreator
+{
+    private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");
+
+    @Override
+    public String getText()
+    {
+        Integer result = 0;
+        Random random = new Random();
+        int x = random.nextInt(10);
+        int y = random.nextInt(10);
+        StringBuilder suChinese = new StringBuilder();
+        int randomoperands = (int) Math.round(Math.random() * 2);
+        if (randomoperands == 0)
+        {
+            result = x * y;
+            suChinese.append(CNUMBERS[x]);
+            suChinese.append("*");
+            suChinese.append(CNUMBERS[y]);
+        }
+        else if (randomoperands == 1)
+        {
+            if (!(x == 0) && y % x == 0)
+            {
+                result = y / x;
+                suChinese.append(CNUMBERS[y]);
+                suChinese.append("/");
+                suChinese.append(CNUMBERS[x]);
+            }
+            else
+            {
+                result = x + y;
+                suChinese.append(CNUMBERS[x]);
+                suChinese.append("+");
+                suChinese.append(CNUMBERS[y]);
+            }
+        }
+        else if (randomoperands == 2)
+        {
+            if (x >= y)
+            {
+                result = x - y;
+                suChinese.append(CNUMBERS[x]);
+                suChinese.append("-");
+                suChinese.append(CNUMBERS[y]);
+            }
+            else
+            {
+                result = y - x;
+                suChinese.append(CNUMBERS[y]);
+                suChinese.append("-");
+                suChinese.append(CNUMBERS[x]);
+            }
+        }
+        else
+        {
+            result = x + y;
+            suChinese.append(CNUMBERS[x]);
+            suChinese.append("+");
+            suChinese.append(CNUMBERS[y]);
+        }
+        suChinese.append("=?@" + result);
+        return suChinese.toString();
+    }
+}

+ 150 - 0
fs-qw-mq/src/main/java/com/fs/framework/config/MyBatisConfig.java

@@ -0,0 +1,150 @@
+package com.fs.framework.config;
+
+import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
+import com.fs.common.utils.StringUtils;
+import org.apache.ibatis.io.VFS;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.mybatis.spring.SqlSessionFactoryBean;
+import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+import org.springframework.util.ClassUtils;
+
+import javax.sql.DataSource;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Mybatis支持*匹配扫描包
+ *
+
+ */
+@Configuration
+public class MyBatisConfig
+{
+    @Autowired
+    private Environment env;
+
+    static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
+
+    public static String setTypeAliasesPackage(String typeAliasesPackage)
+    {
+        ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
+        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
+        List<String> allResult = new ArrayList<String>();
+        try
+        {
+            for (String aliasesPackage : typeAliasesPackage.split(","))
+            {
+                List<String> result = new ArrayList<String>();
+                aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+                        + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
+                Resource[] resources = resolver.getResources(aliasesPackage);
+                if (resources != null && resources.length > 0)
+                {
+                    MetadataReader metadataReader = null;
+                    for (Resource resource : resources)
+                    {
+                        if (resource.isReadable())
+                        {
+                            metadataReader = metadataReaderFactory.getMetadataReader(resource);
+                            try
+                            {
+                                result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
+                            }
+                            catch (ClassNotFoundException e)
+                            {
+                                e.printStackTrace();
+                            }
+                        }
+                    }
+                }
+                if (result.size() > 0)
+                {
+                    HashSet<String> hashResult = new HashSet<String>(result);
+                    allResult.addAll(hashResult);
+                }
+            }
+            if (allResult.size() > 0)
+            {
+                typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0]));
+            }
+            else
+            {
+                throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
+            }
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+        }
+        return typeAliasesPackage;
+    }
+
+    public Resource[] resolveMapperLocations(String[] mapperLocations)
+    {
+        ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
+        List<Resource> resources = new ArrayList<Resource>();
+        if (mapperLocations != null)
+        {
+            for (String mapperLocation : mapperLocations)
+            {
+                try
+                {
+                    Resource[] mappers = resourceResolver.getResources(mapperLocation);
+                    resources.addAll(Arrays.asList(mappers));
+                }
+                catch (IOException e)
+                {
+                    // ignore
+                }
+            }
+        }
+        return resources.toArray(new Resource[resources.size()]);
+    }
+
+//    @Bean
+//    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception
+//    {
+//        String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
+//        String mapperLocations = env.getProperty("mybatis.mapperLocations");
+//        String configLocation = env.getProperty("mybatis.configLocation");
+//        typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
+//        VFS.addImplClass(SpringBootVFS.class);
+//
+//        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
+//        sessionFactory.setDataSource(dataSource);
+//        sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
+//        sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
+//        sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
+//        return sessionFactory.getObject();
+//    }
+    @Bean
+    public SqlSessionFactory sqlSessionFactorys(DataSource dataSource) throws Exception
+    {
+        String typeAliasesPackage = env.getProperty("mybatis-plus.typeAliasesPackage");
+        String mapperLocations = env.getProperty("mybatis-plus.mapperLocations");
+        String configLocation = env.getProperty("mybatis-plus.configLocation");
+        typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
+        VFS.addImplClass(SpringBootVFS.class);
+
+        final MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
+        sessionFactory.setDataSource(dataSource);
+        sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
+        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
+        sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
+        return sessionFactory.getObject();
+    }
+}

+ 141 - 0
fs-qw-mq/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -0,0 +1,141 @@
+package com.fs.framework.config;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.data.redis.serializer.GenericToStringSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+import java.math.BigDecimal;
+
+/**
+ * redis配置
+ *
+
+ */
+@Configuration
+@EnableCaching
+public class RedisConfig extends CachingConfigurerSupport
+{
+    @Bean
+    @SuppressWarnings(value = { "unchecked", "rawtypes" })
+    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
+    {
+        RedisTemplate<Object, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
+
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
+        serializer.setObjectMapper(mapper);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setValueSerializer(serializer);
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(serializer);
+
+        template.afterPropertiesSet();
+        return template;
+    }
+    @Bean
+    public RedisTemplate<String, Boolean> redisTemplateForBoolean(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Boolean> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(new GenericToStringSerializer<>(Boolean.class));
+
+        template.afterPropertiesSet();
+        return template;
+    }
+
+    @Bean
+    @SuppressWarnings(value = { "unchecked", "rawtypes" })
+    public RedisTemplate<String, Object> redisTemplateForObject(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
+
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
+        serializer.setObjectMapper(mapper);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setValueSerializer(serializer);
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(serializer);
+
+        template.afterPropertiesSet();
+        return template;
+    }
+    @Bean
+    public RedisTemplate<String, BigDecimal> redisTemplateForBigDecimal(RedisConnectionFactory connectionFactory) {
+        RedisTemplate<String, BigDecimal> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+
+        // 使用GenericToStringSerializer保证BigDecimal精度不丢失
+        template.setValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(new GenericToStringSerializer<>(BigDecimal.class));
+
+        template.afterPropertiesSet();
+        return template;
+    }
+
+    @Bean
+    public DefaultRedisScript<Long> limitScript()
+    {
+        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
+        redisScript.setScriptText(limitScriptText());
+        redisScript.setResultType(Long.class);
+        return redisScript;
+    }
+
+    /**
+     * 限流脚本
+     */
+    private String limitScriptText()
+    {
+        return "local key = KEYS[1]\n" +
+                "local count = tonumber(ARGV[1])\n" +
+                "local time = tonumber(ARGV[2])\n" +
+                "local current = redis.call('get', key);\n" +
+                "if current and tonumber(current) > count then\n" +
+                "    return current;\n" +
+                "end\n" +
+                "current = redis.call('incr', key)\n" +
+                "if tonumber(current) == 1 then\n" +
+                "    redis.call('expire', key, time)\n" +
+                "end\n" +
+                "return current;";
+    }
+}

+ 65 - 0
fs-qw-mq/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -0,0 +1,65 @@
+package com.fs.framework.config;
+
+import com.fs.common.config.FSConfig;
+import com.fs.common.constant.Constants;
+import com.fs.framework.interceptor.RepeatSubmitInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * 通用配置
+ * 
+
+ */
+@Configuration
+public class ResourcesConfig implements WebMvcConfigurer
+{
+    @Autowired
+    private RepeatSubmitInterceptor repeatSubmitInterceptor;
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry)
+    {
+        /** 本地文件上传路径 */
+        registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**").addResourceLocations("file:" + FSConfig.getProfile() + "/");
+
+        /** swagger配置 */
+        registry.addResourceHandler("/swagger-ui/**").addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/");
+    }
+
+    /**
+     * 自定义拦截规则
+     */
+    @Override
+    public void addInterceptors(InterceptorRegistry registry)
+    {
+        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
+    }
+
+    /**
+     * 跨域配置
+     */
+    @Bean
+    public CorsFilter corsFilter()
+    {
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        CorsConfiguration config = new CorsConfiguration();
+        config.setAllowCredentials(true);
+        // 设置访问源地址
+        config.addAllowedOrigin("*");
+        // 设置访问源请求头
+        config.addAllowedHeader("*");
+        // 设置访问源请求方法
+        config.addAllowedMethod("*");
+        // 对接口配置跨域设置
+        source.registerCorsConfiguration("/**", config);
+        return new CorsFilter(source);
+    }
+}

+ 50 - 0
fs-qw-mq/src/main/java/com/fs/framework/config/SecurityConfig.java

@@ -0,0 +1,50 @@
+package com.fs.framework.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.BeanIds;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+/**
+ * spring security配置
+ * 
+
+ */
+@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
+public class SecurityConfig extends WebSecurityConfigurerAdapter
+{
+
+    /**
+     * anyRequest          |   匹配所有请求路径
+     * access              |   SpringEl表达式结果为true时可以访问
+     * anonymous           |   匿名可以访问
+     * denyAll             |   用户不能访问
+     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
+     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
+     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
+     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
+     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
+     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
+     * permitAll           |   用户可以任意访问
+     * rememberMe          |   允许通过remember-me登录的用户访问
+     * authenticated       |   用户登录后可访问
+     */
+    @Override
+    protected void configure(HttpSecurity http) throws Exception
+    {
+        http.authorizeRequests()
+                .antMatchers("/**").permitAll()
+                .anyRequest().authenticated()
+                .and().csrf().disable();
+    }
+
+    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
+    @Override
+    public AuthenticationManager authenticationManagerBean() throws Exception {
+        return super.authenticationManagerBean();
+    }
+
+
+}

+ 33 - 0
fs-qw-mq/src/main/java/com/fs/framework/config/ServerConfig.java

@@ -0,0 +1,33 @@
+package com.fs.framework.config;
+
+import com.fs.common.utils.ServletUtils;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 服务相关配置
+ * 
+
+ */
+@Component
+public class ServerConfig
+{
+    /**
+     * 获取完整的请求路径,包括:域名,端口,上下文访问路径
+     * 
+     * @return 服务地址
+     */
+    public String getUrl()
+    {
+        HttpServletRequest request = ServletUtils.getRequest();
+        return getDomain(request);
+    }
+
+    public static String getDomain(HttpServletRequest request)
+    {
+        StringBuffer url = request.getRequestURL();
+        String contextPath = request.getServletContext().getContextPath();
+        return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString();
+    }
+}

+ 121 - 0
fs-qw-mq/src/main/java/com/fs/framework/config/SwaggerConfig.java

@@ -0,0 +1,121 @@
+package com.fs.framework.config;
+
+import com.fs.common.config.FSConfig;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.models.auth.In;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.*;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spi.service.contexts.SecurityContext;
+import springfox.documentation.spring.web.plugins.Docket;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Swagger2的接口配置
+ * 
+
+ */
+@Configuration
+public class SwaggerConfig
+{
+    /** 系统基础配置 */
+    @Autowired
+    private FSConfig fsConfig;
+
+    /** 是否开启swagger */
+    @Value("${swagger.enabled}")
+    private boolean enabled;
+
+    /** 设置请求的统一前缀 */
+    @Value("${swagger.pathMapping}")
+    private String pathMapping;
+
+    /**
+     * 创建API
+     */
+    @Bean
+    public Docket createRestApi()
+    {
+        return new Docket(DocumentationType.SWAGGER_2)
+                // 是否启用Swagger
+                .enable(enabled)
+                // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
+                .apiInfo(apiInfo())
+                // 设置哪些接口暴露给Swagger展示
+                .select()
+                // 扫描所有有注解的api,用这种方式更灵活
+                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+                // 扫描指定包中的swagger注解
+                // .apis(RequestHandlerSelectors.basePackage("com.fs.project.tool.swagger"))
+                // 扫描所有 .apis(RequestHandlerSelectors.any())
+                .paths(PathSelectors.any())
+                .build()
+                /* 设置安全模式,swagger可以设置访问token */
+                .securitySchemes(securitySchemes())
+                .securityContexts(securityContexts())
+                .pathMapping(pathMapping);
+    }
+
+    /**
+     * 安全模式,这里指定token通过Authorization头请求头传递
+     */
+    private List<ApiKey> securitySchemes()
+    {
+        List<ApiKey> apiKeyList = new ArrayList<ApiKey>();
+        apiKeyList.add(new ApiKey("Authorization", "Authorization", "header"));
+        return apiKeyList;
+    }
+
+    /**
+     * 安全上下文
+     */
+    private List<SecurityContext> securityContexts()
+    {
+        List<SecurityContext> securityContexts = new ArrayList<>();
+        securityContexts.add(
+                SecurityContext.builder()
+                        .securityReferences(defaultAuth())
+                        .forPaths(PathSelectors.regex("^(?!auth).*$"))
+                        .build());
+        return securityContexts;
+    }
+
+    /**
+     * 默认的安全上引用
+     */
+    private List<SecurityReference> defaultAuth()
+    {
+        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
+        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
+        authorizationScopes[0] = authorizationScope;
+        List<SecurityReference> securityReferences = new ArrayList<>();
+        securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
+        return securityReferences;
+    }
+
+    /**
+     * 添加摘要信息
+     */
+    private ApiInfo apiInfo()
+    {
+        // 用ApiInfoBuilder进行定制
+        return new ApiInfoBuilder()
+                // 设置标题
+                .title("标题:FS管理系统_接口文档")
+                // 描述
+                .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
+                // 作者信息
+                .contact(new Contact(fsConfig.getName(), null, null))
+                // 版本
+                .version("版本号:" + fsConfig.getVersion())
+                .build();
+    }
+}

+ 63 - 0
fs-qw-mq/src/main/java/com/fs/framework/config/ThreadPoolConfig.java

@@ -0,0 +1,63 @@
+package com.fs.framework.config;
+
+import com.fs.common.utils.Threads;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 线程池配置
+ *
+
+ **/
+@Configuration
+public class ThreadPoolConfig
+{
+    // 核心线程池大小
+    private int corePoolSize = 50;
+
+    // 最大可创建的线程数
+    private int maxPoolSize = 200;
+
+    // 队列最大长度
+    private int queueCapacity = 1000;
+
+    // 线程池维护线程所允许的空闲时间
+    private int keepAliveSeconds = 300;
+
+    @Bean(name = "threadPoolTaskExecutor")
+    public ThreadPoolTaskExecutor threadPoolTaskExecutor()
+    {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        executor.setMaxPoolSize(maxPoolSize);
+        executor.setCorePoolSize(corePoolSize);
+        executor.setQueueCapacity(queueCapacity);
+        executor.setKeepAliveSeconds(keepAliveSeconds);
+        // 线程池对拒绝任务(无线程可用)的处理策略
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        return executor;
+    }
+
+    /**
+     * 执行周期性或定时任务
+     */
+    @Bean(name = "scheduledExecutorService")
+    protected ScheduledExecutorService scheduledExecutorService()
+    {
+        return new ScheduledThreadPoolExecutor(corePoolSize,
+                new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build())
+        {
+            @Override
+            protected void afterExecute(Runnable r, Throwable t)
+            {
+                super.afterExecute(r, t);
+                Threads.printException(r, t);
+            }
+        };
+    }
+}

+ 77 - 0
fs-qw-mq/src/main/java/com/fs/framework/config/properties/DruidProperties.java

@@ -0,0 +1,77 @@
+package com.fs.framework.config.properties;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * druid 配置属性
+ *
+
+ */
+@Configuration
+public class DruidProperties
+{
+    @Value("${spring.datasource.mysql.druid.initialSize}")
+    private int initialSize;
+
+    @Value("${spring.datasource.mysql.druid.minIdle}")
+    private int minIdle;
+
+    @Value("${spring.datasource.mysql.druid.maxActive}")
+    private int maxActive;
+
+    @Value("${spring.datasource.mysql.druid.maxWait}")
+    private int maxWait;
+
+    @Value("${spring.datasource.mysql.druid.timeBetweenEvictionRunsMillis}")
+    private int timeBetweenEvictionRunsMillis;
+
+    @Value("${spring.datasource.mysql.druid.minEvictableIdleTimeMillis}")
+    private int minEvictableIdleTimeMillis;
+
+    @Value("${spring.datasource.mysql.druid.maxEvictableIdleTimeMillis}")
+    private int maxEvictableIdleTimeMillis;
+
+    @Value("${spring.datasource.mysql.druid.validationQuery}")
+    private String validationQuery;
+
+    @Value("${spring.datasource.mysql.druid.testWhileIdle}")
+    private boolean testWhileIdle;
+
+    @Value("${spring.datasource.mysql.druid.testOnBorrow}")
+    private boolean testOnBorrow;
+
+    @Value("${spring.datasource.mysql.druid.testOnReturn}")
+    private boolean testOnReturn;
+
+    public DruidDataSource dataSource(DruidDataSource datasource)
+    {
+        /** 配置初始化大小、最小、最大 */
+        datasource.setInitialSize(initialSize);
+        datasource.setMaxActive(maxActive);
+        datasource.setMinIdle(minIdle);
+
+        /** 配置获取连接等待超时的时间 */
+        datasource.setMaxWait(maxWait);
+
+        /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
+        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
+
+        /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
+        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
+        datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
+
+        /**
+         * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
+         */
+        datasource.setValidationQuery(validationQuery);
+        /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
+        datasource.setTestWhileIdle(testWhileIdle);
+        /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
+        datasource.setTestOnBorrow(testOnBorrow);
+        /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
+        datasource.setTestOnReturn(testOnReturn);
+        return datasource;
+    }
+}

+ 27 - 0
fs-qw-mq/src/main/java/com/fs/framework/datasource/DynamicDataSource.java

@@ -0,0 +1,27 @@
+package com.fs.framework.datasource;
+
+import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
+
+import javax.sql.DataSource;
+import java.util.Map;
+
+/**
+ * 动态数据源
+ * 
+
+ */
+public class DynamicDataSource extends AbstractRoutingDataSource
+{
+    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
+    {
+        super.setDefaultTargetDataSource(defaultTargetDataSource);
+        super.setTargetDataSources(targetDataSources);
+        super.afterPropertiesSet();
+    }
+
+    @Override
+    protected Object determineCurrentLookupKey()
+    {
+        return DynamicDataSourceContextHolder.getDataSourceType();
+    }
+}

+ 45 - 0
fs-qw-mq/src/main/java/com/fs/framework/datasource/DynamicDataSourceContextHolder.java

@@ -0,0 +1,45 @@
+package com.fs.framework.datasource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 数据源切换处理
+ * 
+
+ */
+public class DynamicDataSourceContextHolder
+{
+    public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
+
+    /**
+     * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
+     *  所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
+     */
+    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
+
+    /**
+     * 设置数据源的变量
+     */
+    public static void setDataSourceType(String dsType)
+    {
+//        log.info("切换到{}数据源", dsType);
+        CONTEXT_HOLDER.set(dsType);
+    }
+
+    /**
+     * 获得数据源的变量
+     */
+    public static String getDataSourceType()
+    {
+        return CONTEXT_HOLDER.get();
+    }
+
+    /**
+     * 清空数据源变量
+     */
+    public static void clearDataSourceType()
+    {
+        CONTEXT_HOLDER.remove();
+    }
+}

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels