Browse Source

修改配置文件,迁移润天群聊看课,重粉代码

吴树波 2 days ago
parent
commit
c9b9c35306
100 changed files with 3552 additions and 141 deletions
  1. 1 1
      fs-ad-api/src/main/java/com/fs/app/mq/RocketMQConsumerService.java
  2. 3 4
      fs-ad-api/src/main/resources/application.yml
  3. 2 7
      fs-admin/src/main/resources/application.yml
  4. 0 1
      fs-ai-chat/src/main/resources/application.yml
  5. 0 2
      fs-common-api/src/main/resources/application.yml
  6. 0 2
      fs-company-app/src/main/resources/application.yml
  7. 2 2
      fs-company/src/main/java/com/fs/company/controller/qw/QwGroupChatController.java
  8. 16 0
      fs-company/src/main/java/com/fs/company/controller/qw/SopUserLogsController.java
  9. 2 9
      fs-company/src/main/resources/application.yml
  10. 0 2
      fs-doctor-app/src/main/resources/application.yml
  11. 1 2
      fs-hospital/src/main/resources/application.yml
  12. 1 2
      fs-live-app/src/main/resources/application.yml
  13. 0 4
      fs-qw-api-msg/src/main/resources/application.yml
  14. 12 0
      fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java
  15. 3 5
      fs-qw-api/src/main/resources/application.yml
  16. 20 21
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  17. 0 5
      fs-qw-task/src/main/resources/application.yml
  18. 1 2
      fs-qw-voice/src/main/resources/application.yml
  19. 0 3
      fs-qwhook-msg/src/main/resources/application.yml
  20. 0 5
      fs-qwhook-sop/src/main/resources/application.yml
  21. 0 5
      fs-qwhook/src/main/resources/application.yml
  22. 141 0
      fs-repeat-api/pom.xml
  23. 14 0
      fs-repeat-api/src/main/java/com/fs/FSServletInitializer.java
  24. 25 0
      fs-repeat-api/src/main/java/com/fs/FsRepeatApiApplication.java
  25. 12 0
      fs-repeat-api/src/main/java/com/fs/app/annotation/Login.java
  26. 15 0
      fs-repeat-api/src/main/java/com/fs/app/annotation/LoginUser.java
  27. 39 0
      fs-repeat-api/src/main/java/com/fs/app/controller/CommonController.java
  28. 51 0
      fs-repeat-api/src/main/java/com/fs/app/exception/FSException.java
  29. 81 0
      fs-repeat-api/src/main/java/com/fs/app/exception/FSExceptionHandler.java
  30. 55 0
      fs-repeat-api/src/main/java/com/fs/app/mq/RocketMQConsumerService.java
  31. 24 0
      fs-repeat-api/src/main/java/com/fs/app/task/Task.java
  32. 182 0
      fs-repeat-api/src/main/java/com/fs/framework/aspectj/DataScopeAspect.java
  33. 73 0
      fs-repeat-api/src/main/java/com/fs/framework/aspectj/DataSourceAspect.java
  34. 245 0
      fs-repeat-api/src/main/java/com/fs/framework/aspectj/LogAspect.java
  35. 117 0
      fs-repeat-api/src/main/java/com/fs/framework/aspectj/RateLimiterAspect.java
  36. 31 0
      fs-repeat-api/src/main/java/com/fs/framework/config/ApplicationConfig.java
  37. 85 0
      fs-repeat-api/src/main/java/com/fs/framework/config/CaptchaConfig.java
  38. 100 0
      fs-repeat-api/src/main/java/com/fs/framework/config/DataSourceConfig.java
  39. 123 0
      fs-repeat-api/src/main/java/com/fs/framework/config/DruidConfig.java
  40. 72 0
      fs-repeat-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  41. 59 0
      fs-repeat-api/src/main/java/com/fs/framework/config/FilterConfig.java
  42. 76 0
      fs-repeat-api/src/main/java/com/fs/framework/config/KaptchaTextCreator.java
  43. 150 0
      fs-repeat-api/src/main/java/com/fs/framework/config/MyBatisConfig.java
  44. 121 0
      fs-repeat-api/src/main/java/com/fs/framework/config/RedisConfig.java
  45. 65 0
      fs-repeat-api/src/main/java/com/fs/framework/config/ResourcesConfig.java
  46. 50 0
      fs-repeat-api/src/main/java/com/fs/framework/config/SecurityConfig.java
  47. 33 0
      fs-repeat-api/src/main/java/com/fs/framework/config/ServerConfig.java
  48. 121 0
      fs-repeat-api/src/main/java/com/fs/framework/config/SwaggerConfig.java
  49. 63 0
      fs-repeat-api/src/main/java/com/fs/framework/config/ThreadPoolConfig.java
  50. 77 0
      fs-repeat-api/src/main/java/com/fs/framework/config/properties/DruidProperties.java
  51. 27 0
      fs-repeat-api/src/main/java/com/fs/framework/datasource/DynamicDataSource.java
  52. 45 0
      fs-repeat-api/src/main/java/com/fs/framework/datasource/DynamicDataSourceContextHolder.java
  53. 56 0
      fs-repeat-api/src/main/java/com/fs/framework/interceptor/RepeatSubmitInterceptor.java
  54. 126 0
      fs-repeat-api/src/main/java/com/fs/framework/interceptor/impl/SameUrlDataInterceptor.java
  55. 56 0
      fs-repeat-api/src/main/java/com/fs/framework/manager/AsyncManager.java
  56. 40 0
      fs-repeat-api/src/main/java/com/fs/framework/manager/ShutdownManager.java
  57. 103 0
      fs-repeat-api/src/main/java/com/fs/framework/manager/factory/AsyncFactory.java
  58. 1 0
      fs-repeat-api/src/main/resources/META-INF/spring-devtools.properties
  59. 13 0
      fs-repeat-api/src/main/resources/application.yml
  60. 2 0
      fs-repeat-api/src/main/resources/banner.txt
  61. 37 0
      fs-repeat-api/src/main/resources/i18n/messages.properties
  62. 93 0
      fs-repeat-api/src/main/resources/logback.xml
  63. 15 0
      fs-repeat-api/src/main/resources/mybatis/mybatis-config.xml
  64. 4 0
      fs-service/src/main/java/com/fs/ad/service/impl/AdHtmlClickLogServiceImpl.java
  65. 4 2
      fs-service/src/main/java/com/fs/baidu/api/ConvertData.java
  66. 1 0
      fs-service/src/main/java/com/fs/baidu/service/impl/BdAccountServiceImpl.java
  67. 1 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseLink.java
  68. 2 1
      fs-service/src/main/java/com/fs/course/param/FsCourseLinkCreateParam.java
  69. 1 0
      fs-service/src/main/java/com/fs/course/param/FsUserCourseVideoAddKfUParam.java
  70. 10 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseLinkServiceImpl.java
  71. 9 1
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  72. 11 1
      fs-service/src/main/java/com/fs/his/service/impl/FsInquiryOrderServiceImpl.java
  73. 8 0
      fs-service/src/main/java/com/fs/qw/domain/QwExternalContact.java
  74. 1 1
      fs-service/src/main/java/com/fs/qw/service/IQwGroupChatService.java
  75. 19 4
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  76. 36 2
      fs-service/src/main/java/com/fs/qw/service/impl/QwGroupChatServiceImpl.java
  77. 15 0
      fs-service/src/main/java/com/fs/qw/vo/AddSopUserGroupChat.java
  78. 1 0
      fs-service/src/main/java/com/fs/qw/vo/GroupUserExternalVo.java
  79. 14 0
      fs-service/src/main/java/com/fs/qw/vo/UpdateSopUserLogDateVo.java
  80. 56 0
      fs-service/src/main/java/com/fs/repeat/service/RepeatService.java
  81. 18 0
      fs-service/src/main/java/com/fs/repeat/vo/RepeatUploadVo.java
  82. 4 0
      fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsMapper.java
  83. 6 0
      fs-service/src/main/java/com/fs/sop/service/ISopUserLogsService.java
  84. 49 19
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  85. 52 2
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsServiceImpl.java
  86. 2 0
      fs-service/src/main/resources/application-dev.yml
  87. 14 1
      fs-service/src/main/resources/application-druid-hdt.yml
  88. 2 0
      fs-service/src/main/resources/application-druid-myhk-test.yml
  89. 8 6
      fs-service/src/main/resources/application-druid-myhk.yml
  90. 2 0
      fs-service/src/main/resources/application-druid-sft.yml
  91. 2 0
      fs-service/src/main/resources/application-druid-sxjz.yml
  92. 2 0
      fs-service/src/main/resources/application-druid-yzt.yml
  93. 0 11
      fs-service/src/main/resources/application-mq-hdt.yml
  94. 67 0
      fs-service/src/main/resources/config/dev/application-config-dev.yml
  95. 133 0
      fs-service/src/main/resources/config/dev/application-dev.yml
  96. 10 0
      fs-service/src/main/resources/config/dev/application-mq-dev.yml
  97. 1 1
      fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml
  98. 2 3
      fs-service/src/main/resources/mapper/sop/QwSopMapper.xml
  99. 6 0
      fs-service/src/main/resources/mapper/sop/SopUserLogsMapper.xml
  100. 1 2
      fs-store/src/main/resources/application.yml

+ 1 - 1
fs-ad-api/src/main/java/com/fs/app/mq/RocketMQConsumerService.java

@@ -14,7 +14,7 @@ import org.springframework.stereotype.Service;
 @Slf4j
 @Slf4j
 @Service
 @Service
 @AllArgsConstructor
 @AllArgsConstructor
-@RocketMQMessageListener(topic = "${rocketmq.consumer.topic}", consumerGroup = "${rocketmq.consumer.group}")
+@RocketMQMessageListener(topic = "ad-upload", consumerGroup = "${rocketmq.consumer.group}")
 public class RocketMQConsumerService implements RocketMQListener<String> {
 public class RocketMQConsumerService implements RocketMQListener<String> {
 
 
     private final IAdHtmlClickLogService  adHtmlClickLogService;
     private final IAdHtmlClickLogService  adHtmlClickLogService;

+ 3 - 4
fs-ad-api/src/main/resources/application.yml

@@ -4,7 +4,6 @@ server:
 # Spring配置
 # Spring配置
 spring:
 spring:
   profiles:
   profiles:
-    #    active: dev
-    #    include: common,config-dev
-    active: druid-hdt
-    include: common,config-druid-hdt,mq-hdt
+#    active: dev
+#    active: druid-hdt
+    active: druid-myhk

+ 2 - 7
fs-admin/src/main/resources/application.yml

@@ -4,14 +4,9 @@ server:
 # Spring配置
 # Spring配置
 spring:
 spring:
   profiles:
   profiles:
-#    active: dev
-#    include: common,config-myhk
+    active: dev
 #    active: druid-hdt
 #    active: druid-hdt
-#    include: common,config-druid-hdt
 #    active: druid-yzt
 #    active: druid-yzt
-#    include: common,config-druid-yzt
 #    active: druid-sxjz
 #    active: druid-sxjz
-#    include: common,config-druid-sxjz
-    active: druid-sft
-    include: common,config-druid-sft
+#    active: druid-sft
 
 

+ 0 - 1
fs-ai-chat/src/main/resources/application.yml

@@ -5,4 +5,3 @@ server:
 spring:
 spring:
   profiles:
   profiles:
     active: dev
     active: dev
-    include: common,config-dev

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

@@ -5,6 +5,4 @@ server:
 spring:
 spring:
   profiles:
   profiles:
 #    active: dev
 #    active: dev
-#    include: common,config-dev
     active: druid-jnmy
     active: druid-jnmy
-    include: common,config-druid-jnmy

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

@@ -6,6 +6,4 @@ server:
 spring:
 spring:
   profiles:
   profiles:
 #    active: dev
 #    active: dev
-#    include: common,config-dev
     active: druid-sxjz
     active: druid-sxjz
-    include: common,config-druid-sxjz

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

@@ -90,8 +90,8 @@ public class QwGroupChatController extends BaseController
         return AjaxResult.success(list);
         return AjaxResult.success(list);
     }
     }
     @GetMapping("/listAll")
     @GetMapping("/listAll")
-    public AjaxResult listAll(String qwUserIds, String corpId){
-        List<QwGroupChatOptionsVO> list = qwGroupChatService.listAllByQwUserList(qwUserIds, corpId);
+    public AjaxResult listAll(String qwUserIds, String corpId, String sopId){
+        List<QwGroupChatOptionsVO> list = qwGroupChatService.listAllByQwUserList(qwUserIds, corpId, sopId);
         return AjaxResult.success(list);
         return AjaxResult.success(list);
     }
     }
 }
 }

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

@@ -11,6 +11,8 @@ import com.fs.framework.service.TokenService;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.param.SopUserLogsVO;
 import com.fs.qw.param.SopUserLogsVO;
 import com.fs.qw.service.IQwUserService;
 import com.fs.qw.service.IQwUserService;
+import com.fs.qw.vo.AddSopUserGroupChat;
+import com.fs.qw.vo.UpdateSopUserLogDateVo;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.domain.SopUserLogs;
 import com.fs.sop.domain.SopUserLogs;
 import com.fs.sop.mapper.QwSopMapper;
 import com.fs.sop.mapper.QwSopMapper;
@@ -117,4 +119,18 @@ public class SopUserLogsController extends BaseController
     {
     {
         return toAjax(sopUserLogsService.deleteSopUserLogsByIds(ids));
         return toAjax(sopUserLogsService.deleteSopUserLogsByIds(ids));
     }
     }
+
+
+    @Log(title = "修改群营期时间", businessType = BusinessType.UPDATE)
+    @PostMapping("/updateLogDate")
+    public R updateLogDate(@RequestBody UpdateSopUserLogDateVo vo){
+        sopUserLogsService.updateLogDate(vo);
+        return R.ok();
+    }
+    @Log(title = "追加SOP群聊", businessType = BusinessType.UPDATE)
+    @PostMapping("/addGroupChat")
+    public R addGroupChat(@RequestBody AddSopUserGroupChat vo){
+        sopUserLogsService.addGroupChat(vo);
+        return R.ok();
+    }
 }
 }

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

@@ -3,17 +3,10 @@ server:
 # Spring配置
 # Spring配置
 spring:
 spring:
   profiles:
   profiles:
-#    active: dev
-#    include: common,config-dev
+    active: dev
 #    active: druid-jzzx
 #    active: druid-jzzx
-#    include: common,config-druid-jzzx
 #    active: druid-hdt
 #    active: druid-hdt
-#    include: common,config-druid-hdt
 #    active: druid-sxjz
 #    active: druid-sxjz
-#    include: common,config-druid-sxjz
 #    active: druid-yzt
 #    active: druid-yzt
-#    include: common,config-druid-yzt
 #    active: druid-myhk
 #    active: druid-myhk
-#    include: common,config-myhk
-    active: druid-sft
-    include: common,config-druid-sft
+#    active: druid-sft

+ 0 - 2
fs-doctor-app/src/main/resources/application.yml

@@ -6,6 +6,4 @@ server:
 spring:
 spring:
   profiles:
   profiles:
 #    active: dev
 #    active: dev
-#    include: common,config-dev
     active: druid-jnmy
     active: druid-jnmy
-    include: common,config-druid-jnmy

+ 1 - 2
fs-hospital/src/main/resources/application.yml

@@ -5,5 +5,4 @@ server:
 # Spring配置
 # Spring配置
 spring:
 spring:
   profiles:
   profiles:
-    active: dev
-    include: common,config-dev
+    active: dev

+ 1 - 2
fs-live-app/src/main/resources/application.yml

@@ -6,5 +6,4 @@ server:
 # Spring配置
 # Spring配置
 spring:
 spring:
   profiles:
   profiles:
-    active: dev
-    include: common,config-dev
+    active: dev

+ 0 - 4
fs-qw-api-msg/src/main/resources/application.yml

@@ -4,10 +4,6 @@ server:
 spring:
 spring:
   profiles:
   profiles:
     active: dev
     active: dev
-    include: common,config-dev
 #    active: druid-jzzx
 #    active: druid-jzzx
-#    include: common,config-druid-jzzx
 #    active: druid-hdt
 #    active: druid-hdt
-#    include: common,config-druid-
 #    active: druid-sxjz
 #    active: druid-sxjz
-#    include: common,config-druid-sxjz

+ 12 - 0
fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java

@@ -6,6 +6,7 @@ import com.fs.app.util.WXBizMsgCrypt;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.PubFun;
 import com.fs.company.service.ICompanyConfigService;
 import com.fs.company.service.ICompanyConfigService;
 import com.fs.qw.domain.*;
 import com.fs.qw.domain.*;
 import com.fs.qw.mapper.QwCompanyMapper;
 import com.fs.qw.mapper.QwCompanyMapper;
@@ -270,6 +271,13 @@ public class QwDataCallbackService {
                             qwGroupChatUser.setCorpId(corpId);
                             qwGroupChatUser.setCorpId(corpId);
                             //成员入群
                             //成员入群
                             if (updateDetail.equals("add_member")){
                             if (updateDetail.equals("add_member")){
+                                QwGroupChatDetailsResult chat = qwApiService.groupChatDetails(chatId, corpId);
+                                if(chat.getErrCode() != 0){
+                                    log.error("获取群聊信息失败: {}", chat.getErrMsg());
+                                    return;
+                                }
+                                List<QwGroupChatDetailsResult.Member> memberList = chat.getGroupChat().getMemberList();
+                                Map<String, QwGroupChatDetailsResult.Member> memberMap = PubFun.listToMapByGroupObject(memberList, QwGroupChatDetailsResult.Member::getUserId);
 
 
                                 //入群方式
                                 //入群方式
                                 String joinScene = root.getElementsByTagName("JoinScene").item(0).getTextContent();
                                 String joinScene = root.getElementsByTagName("JoinScene").item(0).getTextContent();
@@ -318,8 +326,12 @@ public class QwDataCallbackService {
                                 for (int i = 0; i < items.getLength(); i++) {
                                 for (int i = 0; i < items.getLength(); i++) {
 
 
                                     String userid = items.item(i).getTextContent();
                                     String userid = items.item(i).getTextContent();
+                                    QwGroupChatDetailsResult.Member member = memberMap.get(userid);
                                     qwGroupChatUser.setChatId(chatId);
                                     qwGroupChatUser.setChatId(chatId);
                                     qwGroupChatUser.setUserId(userid);
                                     qwGroupChatUser.setUserId(userid);
+                                    qwGroupChatUser.setType(member.getType() + "");
+                                    qwGroupChatUser.setName(member.getName());
+                                    qwGroupChatUser.setUnionid(member.getUnionid());
                                     qwGroupChatUser.setJoinScene(joinScene);
                                     qwGroupChatUser.setJoinScene(joinScene);
                                     qwGroupChatUser.setJoinTime(formattedDate);
                                     qwGroupChatUser.setJoinTime(formattedDate);
 
 

+ 3 - 5
fs-qw-api/src/main/resources/application.yml

@@ -5,9 +5,7 @@ server:
 # Spring配置
 # Spring配置
 spring:
 spring:
   profiles:
   profiles:
-#    active: dev
-#    include: common,config-dev
+    active: dev
 #    active: druid-hdt
 #    active: druid-hdt
-#    include: common,config-druid-hdt
-    active: druid-sft
-    include: common,config-druid-sft
+#    active: druid-sft
+#    active: druid-myhk

+ 20 - 21
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -3,11 +3,9 @@ package com.fs.app.taskService.impl;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONArray;
 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.app.taskService.SopLogsTaskService;
 import com.fs.app.taskService.SopLogsTaskService;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.base.BaseException;
 import com.fs.common.exception.base.BaseException;
-import com.fs.common.utils.BatchUtils;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.domain.CompanyUser;
@@ -705,19 +703,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             ruleTimeVO.setType(2);
             ruleTimeVO.setType(2);
             if (content.getIndex() == 0) {
             if (content.getIndex() == 0) {
                 QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, groupChat.getChatId(), groupChat.getName(), null, isOfficial, null);
                 QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, groupChat.getChatId(), groupChat.getName(), null, isOfficial, null);
-                try {
-                    groupChat.getChatUserList().stream().filter(e -> e.getUserList() != null && !e.getUserList().isEmpty()).forEach(e -> {
-                        Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(e.getUserList(), GroupUserExternalVo::getUserId);
-                        GroupUserExternalVo vo = userMap.get(groupChat.getOwner());
-                        if(vo != null && vo.getId() != null){
-                            addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, vo.getId().toString(), logVo);
-                        }
-                    });
-                }catch (Exception e){
-                    log.error("群聊创建看课记录失败!", e);
-                }
                 handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                 handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
-                        type, qwUserId, companyUserId, companyId, groupChat.getChatId(), welcomeText, qwUserName, null, true, miniAppId);
+                        type, qwUserId, companyUserId, companyId, groupChat.getChatId(), welcomeText, qwUserName, null, true, miniAppId, groupChat);
             } else {
             } else {
                 if(groupChat.getChatUserList() != null && !groupChat.getChatUserList().isEmpty()){
                 if(groupChat.getChatUserList() != null && !groupChat.getChatUserList().isEmpty()){
                     groupChat.getChatUserList().forEach(user -> {
                     groupChat.getChatUserList().forEach(user -> {
@@ -725,7 +712,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                         ruleTimeVO.setRemark("客户群催课");
                         ruleTimeVO.setRemark("客户群催课");
                         QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, user.getUserId(), user.getName(), null, isOfficial, null);
                         QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, user.getUserId(), user.getName(), null, isOfficial, null);
                         handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                         handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
-                                type, qwUserId, companyUserId, companyId, user.getId().toString(), welcomeText, qwUserName, null, false, miniAppId);
+                                type, qwUserId, companyUserId, companyId, user.getId().toString(), welcomeText, qwUserName, null, false, miniAppId, groupChat);
                     });
                     });
                 }
                 }
             }
             }
@@ -738,7 +725,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     Long fsUserId = contactId.getFsUserId();
                     Long fsUserId = contactId.getFsUserId();
                     QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, contactId.getExternalContactId(), externalUserName, fsUserId, isOfficial, contactId.getExternalId());
                     QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, contactId.getExternalContactId(), externalUserName, fsUserId, isOfficial, contactId.getExternalId());
                     handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                     handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
-                            type, qwUserId, companyUserId, companyId, externalId, welcomeText, qwUserName, fsUserId, false, miniAppId);
+                            type, qwUserId, companyUserId, companyId, externalId, welcomeText, qwUserName, fsUserId, false, miniAppId, null);
                 } catch (Exception e) {
                 } catch (Exception e) {
                     log.error("处理 externalContactId {} 时发生异常: {}", contactId, e.getMessage(), e);
                     log.error("处理 externalContactId {} 时发生异常: {}", contactId, e.getMessage(), e);
                 }
                 }
@@ -826,15 +813,15 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     private void handleLogBasedOnType(QwSopLogs sopLogs, QwSopTempSetting.Content content,
     private void handleLogBasedOnType(QwSopLogs sopLogs, QwSopTempSetting.Content content,
                                       SopUserLogsVo logVo, Date sendTime, Long courseId,
                                       SopUserLogsVo logVo, Date sendTime, Long courseId,
                                       Long videoId, int type, String qwUserId,
                                       Long videoId, int type, String qwUserId,
-                                      String companyUserId, String companyId, String externalId,String welcomeText,
-                                      String qwUserName, Long fsUserId, boolean isGroupChat,String miniAppId) {
+                                      String companyUserId, String companyId, String externalId, String welcomeText,
+                                      String qwUserName, Long fsUserId, boolean isGroupChat, String miniAppId, QwGroupChat groupChat) {
         switch (type) {
         switch (type) {
             case 1:
             case 1:
                 handleNormalMessage(sopLogs, content,companyUserId);
                 handleNormalMessage(sopLogs, content,companyUserId);
                 break;
                 break;
             case 2:
             case 2:
                 handleCourseMessage(sopLogs, content, logVo, sendTime, courseId, videoId,
                 handleCourseMessage(sopLogs, content, logVo, sendTime, courseId, videoId,
-                        qwUserId, companyUserId, companyId, externalId, welcomeText,qwUserName, fsUserId, isGroupChat, miniAppId);
+                        qwUserId, companyUserId, companyId, externalId, welcomeText,qwUserName, fsUserId, isGroupChat, miniAppId, groupChat);
                 break;
                 break;
             case 3:
             case 3:
                 handleOrderMessage(sopLogs, content);
                 handleOrderMessage(sopLogs, content);
@@ -866,8 +853,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     private void handleCourseMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content,
     private void handleCourseMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content,
                                      SopUserLogsVo logVo, Date sendTime, Long courseId,
                                      SopUserLogsVo logVo, Date sendTime, Long courseId,
                                      Long videoId, String qwUserId, String companyUserId,
                                      Long videoId, String qwUserId, String companyUserId,
-                                     String companyId, String externalId,String welcomeText,String qwUserName,
-                                     Long fsUserId, boolean isGroupChat,String miniAppId) {
+                                     String companyId, String externalId, String welcomeText, String qwUserName,
+                                     Long fsUserId, boolean isGroupChat, String miniAppId, QwGroupChat groupChat) {
         // 深拷贝 Content 对象,避免使用 JSON
         // 深拷贝 Content 对象,避免使用 JSON
         QwSopTempSetting.Content clonedContent = deepCopyContent(content);
         QwSopTempSetting.Content clonedContent = deepCopyContent(content);
         if (clonedContent == null) {
         if (clonedContent == null) {
@@ -908,6 +895,18 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                             if (createLink.get("code").equals(500)) {
                             if (createLink.get("code").equals(500)) {
                                 throw new BaseException("链接生成失败!");
                                 throw new BaseException("链接生成失败!");
                             }
                             }
+                            try {
+                                groupChat.getChatUserList().stream().filter(e -> e.getUserList() != null && !e.getUserList().isEmpty()).forEach(e -> {
+                                    Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(e.getUserList(), GroupUserExternalVo::getUserId);
+                                    GroupUserExternalVo vo = userMap.get(groupChat.getOwner());
+                                    if (vo != null && vo.getId() != null) {
+                                        sopLogs.setFsUserId(vo.getFsUserId());
+                                        addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, vo.getId().toString(), logVo);
+                                    }
+                                });
+                            } catch (Exception e) {
+                                log.error("群聊创建看课记录失败!", e);
+                            }
                             link = (String) createLink.get("url");
                             link = (String) createLink.get("url");
                         } else {
                         } else {
                             addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo);
                             addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo);

+ 0 - 5
fs-qw-task/src/main/resources/application.yml

@@ -7,12 +7,7 @@ server:
 spring:
 spring:
   profiles:
   profiles:
 #    active: dev
 #    active: dev
-#    include: common,config-dev
 #    active: druid-hcl
 #    active: druid-hcl
-#    include: common,config-druid-hcl
 #    active: druid-sxjz
 #    active: druid-sxjz
-#    include: common,config-druid-sxjz
 #    active: druid-hdt
 #    active: druid-hdt
-#    include: common,config-druid-hdt
     active: druid-myhk
     active: druid-myhk
-    include: common,config-myhk

+ 1 - 2
fs-qw-voice/src/main/resources/application.yml

@@ -6,5 +6,4 @@ server:
 # Spring配置
 # Spring配置
 spring:
 spring:
   profiles:
   profiles:
-    active: dev
-    include: common,config-dev
+    active: dev

+ 0 - 3
fs-qwhook-msg/src/main/resources/application.yml

@@ -6,8 +6,5 @@ server:
 spring:
 spring:
   profiles:
   profiles:
 #    active: dev
 #    active: dev
-#    include: common,config-dev
 #    active: druid-sxjz
 #    active: druid-sxjz
-#    include: common,config-druid-sxjz
     active: druid-hdt
     active: druid-hdt
-    include: common,config-druid-hdt

+ 0 - 5
fs-qwhook-sop/src/main/resources/application.yml

@@ -7,12 +7,7 @@ server:
 spring:
 spring:
   profiles:
   profiles:
 #    active: dev
 #    active: dev
-#    include: common,config-dev
 #    active: druid-yzt
 #    active: druid-yzt
-#    include: common,config-druid-yzt
 #    active: druid-hdt
 #    active: druid-hdt
-#    include: common,config-druid-hdt
 #    active: druid-sxjz
 #    active: druid-sxjz
-#    include: common,config-druid-sxjz
     active: druid-sft
     active: druid-sft
-    include: common,config-druid-sft

+ 0 - 5
fs-qwhook/src/main/resources/application.yml

@@ -7,12 +7,7 @@ server:
 spring:
 spring:
   profiles:
   profiles:
 #    active: dev
 #    active: dev
-#    include: common,config-dev
 #    active: druid-yzt
 #    active: druid-yzt
-#    include: common,config-druid-yzt
 #    active: druid-hdt
 #    active: druid-hdt
-#    include: common,config-druid-hdt
 #    active: druid-sxjz
 #    active: druid-sxjz
-#    include: common,config-druid-sxjz
     active: druid-sft
     active: druid-sft
-    include: common,config-druid-sft

+ 141 - 0
fs-repeat-api/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-repeat-api</artifactId>
+    <description>
+       重粉业务
+    </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-repeat-api/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(FsRepeatApiApplication.class);
+    }
+}

+ 25 - 0
fs-repeat-api/src/main/java/com/fs/FsRepeatApiApplication.java

@@ -0,0 +1,25 @@
+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 FsRepeatApiApplication
+{
+    public static void main(String[] args)
+    {
+        // System.setProperty("spring.devtools.restart.enabled", "false");
+        SpringApplication.run(FsRepeatApiApplication.class, args);
+        System.out.println("repeat-api启动成功");
+    }
+}

+ 12 - 0
fs-repeat-api/src/main/java/com/fs/app/annotation/Login.java

@@ -0,0 +1,12 @@
+package com.fs.app.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * app登录效验
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Login {
+}

+ 15 - 0
fs-repeat-api/src/main/java/com/fs/app/annotation/LoginUser.java

@@ -0,0 +1,15 @@
+package com.fs.app.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 登录用户信息
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface LoginUser {
+
+}

+ 39 - 0
fs-repeat-api/src/main/java/com/fs/app/controller/CommonController.java

@@ -0,0 +1,39 @@
+package com.fs.app.controller;
+
+
+import com.alibaba.fastjson.JSON;
+import com.fs.ad.enums.AdUploadType;
+import com.fs.ad.service.IAdHtmlClickLogService;
+import com.fs.common.core.domain.R;
+import com.fs.company.service.ICompanyWxChatService;
+import com.fs.qw.vo.AdUploadVo;
+import com.fs.wxUser.service.ICompanyWxUserService;
+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.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+
+@Slf4j
+@Api("公共接口")
+@RestController
+@AllArgsConstructor
+@Ignore
+@RequestMapping(value="/app/common")
+public class CommonController {
+    private IAdHtmlClickLogService adHtmlClickLogService;
+    private RocketMQTemplate rocketMQTemplate;
+
+    @GetMapping("/testSend")
+    public R testSend(String id){
+        AdUploadVo build = AdUploadVo.builder().state(id).type(AdUploadType.ADD_WX).build();
+        rocketMQTemplate.syncSend("ad-upload", JSON.toJSONString(build));
+//        adHtmlClickLogService.upload(id, AdUploadType.ADD_WX, e -> {});
+        return R.ok();
+    }
+
+}

+ 51 - 0
fs-repeat-api/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-repeat-api/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());
+	}
+}

+ 55 - 0
fs-repeat-api/src/main/java/com/fs/app/mq/RocketMQConsumerService.java

@@ -0,0 +1,55 @@
+package com.fs.app.mq;
+
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.fs.ad.service.IAdHtmlClickLogService;
+import com.fs.common.utils.StringUtils;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.vo.AdUploadVo;
+import com.fs.repeat.service.RepeatService;
+import com.fs.repeat.vo.RepeatUploadVo;
+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;
+
+import java.util.List;
+import java.util.function.Function;
+
+@Slf4j
+@Service
+@AllArgsConstructor
+@RocketMQMessageListener(topic = "${rocketmq.consumer.topic}", consumerGroup = "${rocketmq.consumer.group}")
+public class RocketMQConsumerService implements RocketMQListener<String> {
+
+    private final RepeatService repeatService;
+    private final Function<RepeatUploadVo, String> type = e -> {
+        switch (e.getType()) {
+            case 0:
+                return "企微";
+            case 1:
+                return "个微";
+        }
+        return "错误类型!";
+    };
+
+    @Override
+    public void onMessage(String message) {
+        if(StringUtils.isEmpty(message)) return;
+        RepeatUploadVo vo = JSON.parseObject(message, RepeatUploadVo.class);
+        if(vo == null) return;
+        if(vo.getType() == 0){
+            log.info("企微重粉判断:{}", type.apply(vo));
+            repeatService.repeatQw(vo);
+        }
+        if(vo.getType() == 1){
+            log.info("看课重粉判断:{}", type.apply(vo));
+            repeatService.repeatFsUser(vo);
+        }
+        if(vo.getType() == 2){
+            log.info("已购订单判断:{}", type.apply(vo));
+            repeatService.userAddOrder(vo);
+        }
+    }
+}

+ 24 - 0
fs-repeat-api/src/main/java/com/fs/app/task/Task.java

@@ -0,0 +1,24 @@
+package com.fs.app.task;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.fs.ad.domain.AdHtmlClickLog;
+import com.fs.ad.service.IAdHtmlClickLogService;
+import com.fs.ad.service.impl.AdHtmlClickLogServiceImpl;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.core.redis.RedisCacheT;
+import com.fs.live.domain.Live;
+import com.fs.live.service.ILiveService;
+import lombok.AllArgsConstructor;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@Component
+@AllArgsConstructor
+public class Task {
+}

+ 182 - 0
fs-repeat-api/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-repeat-api/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-repeat-api/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-repeat-api/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-repeat-api/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-repeat-api/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-repeat-api/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, 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;
+    }
+}

+ 123 - 0
fs-repeat-api/src/main/java/com/fs/framework/config/DruidConfig.java

@@ -0,0 +1,123 @@
+//package com.fs.framework.config;
+//
+//import com.alibaba.druid.pool.DruidDataSource;
+//import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
+//import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
+//import com.alibaba.druid.util.Utils;
+//import com.fs.common.enums.DataSourceType;
+//import com.fs.common.utils.spring.SpringUtils;
+//import com.fs.framework.config.properties.DruidProperties;
+//import com.fs.framework.datasource.DynamicDataSource;
+//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;
+//
+///**
+// * druid 配置多数据源
+// *
+//
+// */
+//@Configuration
+//public class DruidConfig
+//{
+//    @Bean
+//    @ConfigurationProperties("spring.datasource.druid.master")
+//    public DataSource masterDataSource(DruidProperties druidProperties)
+//    {
+//        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
+//        return druidProperties.dataSource(dataSource);
+//    }
+//
+//    @Bean
+//    @ConfigurationProperties("spring.datasource.druid.slave")
+//    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
+//    public DataSource slaveDataSource(DruidProperties druidProperties)
+//    {
+//        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
+//        return druidProperties.dataSource(dataSource);
+//    }
+//
+//    @Bean(name = "dynamicDataSource")
+//    @Primary
+//    public DynamicDataSource dataSource(DataSource masterDataSource)
+//    {
+//        Map<Object, Object> targetDataSources = new HashMap<>();
+//        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
+//        setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
+//        return new DynamicDataSource(masterDataSource, targetDataSources);
+//    }
+//
+//    /**
+//     * 设置数据源
+//     *
+//     * @param targetDataSources 备选数据源集合
+//     * @param sourceName 数据源名称
+//     * @param beanName bean名称
+//     */
+//    public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
+//    {
+//        try
+//        {
+//            DataSource dataSource = SpringUtils.getBean(beanName);
+//            targetDataSources.put(sourceName, dataSource);
+//        }
+//        catch (Exception e)
+//        {
+//        }
+//    }
+//
+//    /**
+//     * 去除监控页面底部的广告
+//     */
+//    @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-repeat-api/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-repeat-api/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-repeat-api/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-repeat-api/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();
+    }
+}

+ 121 - 0
fs-repeat-api/src/main/java/com/fs/framework/config/RedisConfig.java

@@ -0,0 +1,121 @@
+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;
+
+/**
+ * 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 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-repeat-api/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-repeat-api/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-repeat-api/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-repeat-api/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-repeat-api/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-repeat-api/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-repeat-api/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-repeat-api/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();
+    }
+}

+ 56 - 0
fs-repeat-api/src/main/java/com/fs/framework/interceptor/RepeatSubmitInterceptor.java

@@ -0,0 +1,56 @@
+package com.fs.framework.interceptor;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fs.common.annotation.RepeatSubmit;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.utils.ServletUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.lang.reflect.Method;
+
+/**
+ * 防止重复提交拦截器
+ *
+
+ */
+@Component
+public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter
+{
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
+    {
+        if (handler instanceof HandlerMethod)
+        {
+            HandlerMethod handlerMethod = (HandlerMethod) handler;
+            Method method = handlerMethod.getMethod();
+            RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
+            if (annotation != null)
+            {
+                if (this.isRepeatSubmit(request))
+                {
+                    AjaxResult ajaxResult = AjaxResult.error("不允许重复提交,请稍后再试");
+                    ServletUtils.renderString(response, JSONObject.toJSONString(ajaxResult));
+                    return false;
+                }
+            }
+            return true;
+        }
+        else
+        {
+            return super.preHandle(request, response, handler);
+        }
+    }
+
+    /**
+     * 验证是否重复提交由子类实现具体的防重复提交的规则
+     *
+     * @param request
+     * @return
+     * @throws Exception
+     */
+    public abstract boolean isRepeatSubmit(HttpServletRequest request);
+}

+ 126 - 0
fs-repeat-api/src/main/java/com/fs/framework/interceptor/impl/SameUrlDataInterceptor.java

@@ -0,0 +1,126 @@
+package com.fs.framework.interceptor.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fs.common.constant.Constants;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.filter.RepeatedlyRequestWrapper;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.http.HttpHelper;
+import com.fs.framework.interceptor.RepeatSubmitInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 判断请求url和数据是否和上一次相同,
+ * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。
+ * 
+
+ */
+@Component
+public class SameUrlDataInterceptor extends RepeatSubmitInterceptor
+{
+    public final String REPEAT_PARAMS = "repeatParams";
+
+    public final String REPEAT_TIME = "repeatTime";
+
+    // 令牌自定义标识
+    @Value("${token.header}")
+    private String header;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    /**
+     * 间隔时间,单位:秒 默认10秒
+     * 
+     * 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据
+     */
+    private int intervalTime = 10;
+
+    public void setIntervalTime(int intervalTime)
+    {
+        this.intervalTime = intervalTime;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public boolean isRepeatSubmit(HttpServletRequest request)
+    {
+        String nowParams = "";
+        if (request instanceof RepeatedlyRequestWrapper)
+        {
+            RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
+            nowParams = HttpHelper.getBodyString(repeatedlyRequest);
+        }
+
+        // body参数为空,获取Parameter的数据
+        if (StringUtils.isEmpty(nowParams))
+        {
+            nowParams = JSONObject.toJSONString(request.getParameterMap());
+        }
+        Map<String, Object> nowDataMap = new HashMap<String, Object>();
+        nowDataMap.put(REPEAT_PARAMS, nowParams);
+        nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
+
+        // 请求地址(作为存放cache的key值)
+        String url = request.getRequestURI();
+
+        // 唯一值(没有消息头则使用请求地址)
+        String submitKey = request.getHeader(header);
+        if (StringUtils.isEmpty(submitKey))
+        {
+            submitKey = url;
+        }
+
+        // 唯一标识(指定key + 消息头)
+        String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + submitKey;
+
+        Object sessionObj = redisCache.getCacheObject(cacheRepeatKey);
+        if (sessionObj != null)
+        {
+            Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
+            if (sessionMap.containsKey(url))
+            {
+                Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
+                if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap))
+                {
+                    return true;
+                }
+            }
+        }
+        Map<String, Object> cacheMap = new HashMap<String, Object>();
+        cacheMap.put(url, nowDataMap);
+        redisCache.setCacheObject(cacheRepeatKey, cacheMap, intervalTime, TimeUnit.SECONDS);
+        return false;
+    }
+
+    /**
+     * 判断参数是否相同
+     */
+    private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap)
+    {
+        String nowParams = (String) nowMap.get(REPEAT_PARAMS);
+        String preParams = (String) preMap.get(REPEAT_PARAMS);
+        return nowParams.equals(preParams);
+    }
+
+    /**
+     * 判断两次间隔时间
+     */
+    private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap)
+    {
+        long time1 = (Long) nowMap.get(REPEAT_TIME);
+        long time2 = (Long) preMap.get(REPEAT_TIME);
+        if ((time1 - time2) < (this.intervalTime * 1000))
+        {
+            return true;
+        }
+        return false;
+    }
+}

+ 56 - 0
fs-repeat-api/src/main/java/com/fs/framework/manager/AsyncManager.java

@@ -0,0 +1,56 @@
+package com.fs.framework.manager;
+
+import com.fs.common.utils.Threads;
+import com.fs.common.utils.spring.SpringUtils;
+
+import java.util.TimerTask;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 异步任务管理器
+ * 
+
+ */
+public class AsyncManager
+{
+    /**
+     * 操作延迟10毫秒
+     */
+    private final int OPERATE_DELAY_TIME = 10;
+
+    /**
+     * 异步操作任务调度线程池
+     */
+    private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
+
+    /**
+     * 单例模式
+     */
+    private AsyncManager(){}
+
+    private static AsyncManager me = new AsyncManager();
+
+    public static AsyncManager me()
+    {
+        return me;
+    }
+
+    /**
+     * 执行任务
+     * 
+     * @param task 任务
+     */
+    public void execute(TimerTask task)
+    {
+        executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
+    }
+
+    /**
+     * 停止任务线程池
+     */
+    public void shutdown()
+    {
+        Threads.shutdownAndAwaitTermination(executor);
+    }
+}

+ 40 - 0
fs-repeat-api/src/main/java/com/fs/framework/manager/ShutdownManager.java

@@ -0,0 +1,40 @@
+package com.fs.framework.manager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PreDestroy;
+
+/**
+ * 确保应用退出时能关闭后台线程
+ *
+
+ */
+@Component
+public class ShutdownManager
+{
+    private static final Logger logger = LoggerFactory.getLogger("sys-user");
+
+    @PreDestroy
+    public void destroy()
+    {
+        shutdownAsyncManager();
+    }
+
+    /**
+     * 停止异步执行任务
+     */
+    private void shutdownAsyncManager()
+    {
+        try
+        {
+            logger.info("====关闭后台任务任务线程池====");
+            AsyncManager.me().shutdown();
+        }
+        catch (Exception e)
+        {
+            logger.error(e.getMessage(), e);
+        }
+    }
+}

+ 103 - 0
fs-repeat-api/src/main/java/com/fs/framework/manager/factory/AsyncFactory.java

@@ -0,0 +1,103 @@
+package com.fs.framework.manager.factory;
+
+import com.fs.common.constant.Constants;
+import com.fs.common.utils.LogUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.ip.AddressUtils;
+import com.fs.common.utils.ip.IpUtils;
+import com.fs.common.utils.spring.SpringUtils;
+import com.fs.system.domain.SysLogininfor;
+import com.fs.system.domain.SysOperLog;
+import com.fs.system.service.ISysLogininforService;
+import com.fs.system.service.ISysOperLogService;
+import eu.bitwalker.useragentutils.UserAgent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.TimerTask;
+
+/**
+ * 异步工厂(产生任务用)
+ * 
+
+ */
+public class AsyncFactory
+{
+    private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user");
+
+    /**
+     * 记录登录信息
+     * 
+     * @param username 用户名
+     * @param status 状态
+     * @param message 消息
+     * @param args 列表
+     * @return 任务task
+     */
+    public static TimerTask recordLogininfor(final String username, final String status, final String message,
+            final Object... args)
+    {
+        final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
+        final String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
+        return new TimerTask()
+        {
+            @Override
+            public void run()
+            {
+                String address = AddressUtils.getRealAddressByIP(ip);
+                StringBuilder s = new StringBuilder();
+                s.append(LogUtils.getBlock(ip));
+                s.append(address);
+                s.append(LogUtils.getBlock(username));
+                s.append(LogUtils.getBlock(status));
+                s.append(LogUtils.getBlock(message));
+                // 打印信息到日志
+                sys_user_logger.info(s.toString(), args);
+                // 获取客户端操作系统
+                String os = userAgent.getOperatingSystem().getName();
+                // 获取客户端浏览器
+                String browser = userAgent.getBrowser().getName();
+                // 封装对象
+                SysLogininfor logininfor = new SysLogininfor();
+                logininfor.setUserName(username);
+                logininfor.setIpaddr(ip);
+                logininfor.setLoginLocation(address);
+                logininfor.setBrowser(browser);
+                logininfor.setOs(os);
+                logininfor.setMsg(message);
+                // 日志状态
+                if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))
+                {
+                    logininfor.setStatus(Constants.SUCCESS);
+                }
+                else if (Constants.LOGIN_FAIL.equals(status))
+                {
+                    logininfor.setStatus(Constants.FAIL);
+                }
+                // 插入数据
+                SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor);
+            }
+        };
+    }
+
+    /**
+     * 操作日志记录
+     * 
+     * @param operLog 操作日志信息
+     * @return 任务task
+     */
+    public static TimerTask recordOper(final SysOperLog operLog)
+    {
+        return new TimerTask()
+        {
+            @Override
+            public void run()
+            {
+                // 远程查询操作地点
+                operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
+                SpringUtils.getBean(ISysOperLogService.class).insertOperlog(operLog);
+            }
+        };
+    }
+}

+ 1 - 0
fs-repeat-api/src/main/resources/META-INF/spring-devtools.properties

@@ -0,0 +1 @@
+restart.include.json=/com.alibaba.fastjson.*.jar

+ 13 - 0
fs-repeat-api/src/main/resources/application.yml

@@ -0,0 +1,13 @@
+# 开发环境配置
+server:
+  # 服务器的HTTP端口,默认为8080
+  port: 8010
+
+# Spring配置
+spring:
+  profiles:
+    #    active: dev
+    #    active: druid-yzt
+    #    active: druid-hdt
+    #    active: druid-sxjz
+    active: druid-sft

+ 2 - 0
fs-repeat-api/src/main/resources/banner.txt

@@ -0,0 +1,2 @@
+Application Version: ${fs.version}
+Spring Boot Version: ${spring-boot.version}

+ 37 - 0
fs-repeat-api/src/main/resources/i18n/messages.properties

@@ -0,0 +1,37 @@
+#错误消息
+not.null=* 必须填写
+user.jcaptcha.error=验证码错误
+user.jcaptcha.expire=验证码已失效
+user.not.exists=用户不存在/密码错误
+user.password.not.match=用户不存在/密码错误
+user.password.retry.limit.count=密码输入错误{0}次
+user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定10分钟
+user.password.delete=对不起,您的账号已被删除
+user.blocked=用户已封禁,请联系管理员
+role.blocked=角色已封禁,请联系管理员
+user.logout.success=退出成功
+
+length.not.valid=长度必须在{min}到{max}个字符之间
+
+user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头
+user.password.not.valid=* 5-50个字符
+ 
+user.email.not.valid=邮箱格式错误
+user.mobile.phone.number.not.valid=手机号格式错误
+user.login.success=登录成功
+user.register.success=注册成功
+user.notfound=请重新登录
+user.forcelogout=管理员强制退出,请重新登录
+user.unknown.error=未知错误,请重新登录
+
+##文件上传消息
+upload.exceed.maxSize=上传的文件大小超出限制的文件大小!<br/>允许的文件最大大小是:{0}MB!
+upload.filename.exceed.length=上传的文件名最长{0}个字符
+
+##权限
+no.permission=您没有数据的权限,请联系管理员添加权限 [{0}]
+no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}]
+no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}]
+no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}]
+no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}]
+no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]

+ 93 - 0
fs-repeat-api/src/main/resources/logback.xml

@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <!-- 日志存放路径 -->
+	<property name="log.path" value="/home/fs-repeat-api/logs" />
+    <!-- 日志输出格式 -->
+	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
+
+	<!-- 控制台输出 -->
+	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+	</appender>
+
+	<!-- 系统日志输出 -->
+	<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-info.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+			<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 60天 -->
+			<maxHistory>60</maxHistory>
+		</rollingPolicy>
+		<encoder>
+			<pattern>${log.pattern}</pattern>
+		</encoder>
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>INFO</level>
+            <!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+	</appender>
+
+	<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+	    <file>${log.path}/sys-error.log</file>
+        <!-- 循环政策:基于时间创建日志文件 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志文件名格式 -->
+            <fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
+			<!-- 日志最大的历史 60天 -->
+			<maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 过滤的级别 -->
+            <level>ERROR</level>
+			<!-- 匹配时的操作:接收(记录) -->
+            <onMatch>ACCEPT</onMatch>
+			<!-- 不匹配时的操作:拒绝(不记录) -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+	<!-- 用户访问日志输出  -->
+    <appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${log.path}/sys-user.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 按天回滚 daily -->
+            <fileNamePattern>${log.path}/sys-user.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 日志最大的历史 60天 -->
+            <maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+    </appender>
+
+	<!-- 系统模块日志级别控制  -->
+	<logger name="com.fs" level="info" />
+	<!-- Spring日志级别控制  -->
+	<logger name="org.springframework" level="warn" />
+
+	<root level="info">
+		<appender-ref ref="console" />
+	</root>
+
+	<!--系统操作日志-->
+    <root level="info">
+        <appender-ref ref="file_info" />
+        <appender-ref ref="file_error" />
+    </root>
+
+	<!--系统用户操作日志-->
+    <logger name="sys-user" level="info">
+        <appender-ref ref="sys-user"/>
+    </logger>
+</configuration>

+ 15 - 0
fs-repeat-api/src/main/resources/mybatis/mybatis-config.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE configuration
+PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-config.dtd">
+<configuration>
+	
+	<settings>
+		<setting name="cacheEnabled"             value="true" />  <!-- 全局映射器启用缓存 -->
+		<setting name="useGeneratedKeys"         value="true" />  <!-- 允许 JDBC 支持自动生成主键 -->
+		<setting name="defaultExecutorType"      value="REUSE" /> <!-- 配置默认的执行器 -->
+		<setting name="logImpl"                  value="SLF4J" /> <!-- 指定 MyBatis 所用日志的具体实现 -->
+		 <setting name="mapUnderscoreToCamelCase" value="true"/>
+	</settings>
+	
+</configuration>

+ 4 - 0
fs-service/src/main/java/com/fs/ad/service/impl/AdHtmlClickLogServiceImpl.java

@@ -162,12 +162,16 @@ public class AdHtmlClickLogServiceImpl extends ServiceImpl<AdHtmlClickLogMapper,
                 switch (type){
                 switch (type){
                     case 0: // 百度
                     case 0: // 百度
                         uploadBaiDu(one, clickType);
                         uploadBaiDu(one, clickType);
+                        break;
                     case 1:// 优酷
                     case 1:// 优酷
                         uploadYouKu(one, clickType);
                         uploadYouKu(one, clickType);
+                        break;
                     case 2:// 爱奇艺
                     case 2:// 爱奇艺
                         uploadIqiyi(one, clickType);
                         uploadIqiyi(one, clickType);
+                        break;
                     case 3:// 巨量(抖音)
                     case 3:// 巨量(抖音)
                         uploadDy(one, clickType);
                         uploadDy(one, clickType);
+                        break;
                 }
                 }
                 one.setUploadType(uploadType);
                 one.setUploadType(uploadType);
                 saveLog(key, one, type);
                 saveLog(key, one, type);

+ 4 - 2
fs-service/src/main/java/com/fs/baidu/api/ConvertData.java

@@ -6,6 +6,7 @@ import com.google.gson.Gson;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 import com.google.gson.JsonParser;
 import com.google.gson.reflect.TypeToken;
 import com.google.gson.reflect.TypeToken;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.http.HttpResponse;
 import org.apache.http.HttpResponse;
 import org.apache.http.HttpStatus;
 import org.apache.http.HttpStatus;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpPost;
@@ -23,6 +24,7 @@ import java.util.List;
 /**
 /**
  * API 回传数据Demo
  * API 回传数据Demo
  */
  */
+@Slf4j
 public class ConvertData {
 public class ConvertData {
 
 
     private static final Integer RETRY_TIMES = 3;
     private static final Integer RETRY_TIMES = 3;
@@ -44,7 +46,7 @@ public class ConvertData {
                 new Gson().toJsonTree(conversionTypeList, new TypeToken<List<ConversionType>>() {}.getType()).getAsJsonArray());
                 new Gson().toJsonTree(conversionTypeList, new TypeToken<List<ConversionType>>() {}.getType()).getAsJsonArray());
         // 发送的完整请求数据
         // 发送的完整请求数据
         // do some log
         // do some log
-        System.out.println("req data: " + data);
+        log.info("req data: {}", data);
         // 向百度发送数据
         // 向百度发送数据
         return sendWithRetry(data.toString());
         return sendWithRetry(data.toString());
 
 
@@ -70,7 +72,7 @@ public class ConvertData {
                     JsonObject returnData = JsonParser.parseString(res).getAsJsonObject();
                     JsonObject returnData = JsonParser.parseString(res).getAsJsonObject();
                     // 打印返回结果
                     // 打印返回结果
                     // do some log
                     // do some log
-                    System.out.println("retry times :" + i + ", res data: " + res);
+                    log.info("retry times :{}, res data: {}", i, res);
 
 
                     int status = returnData.getAsJsonObject("header").get("status").getAsInt();
                     int status = returnData.getAsJsonObject("header").get("status").getAsInt();
                     // status为4,代表服务端异常,可添加重试
                     // status为4,代表服务端异常,可添加重试

+ 1 - 0
fs-service/src/main/java/com/fs/baidu/service/impl/BdAccountServiceImpl.java

@@ -82,6 +82,7 @@ public class BdAccountServiceImpl extends ServiceImpl<BdAccountMapper, BdAccount
     @Override
     @Override
     public List<BdAccount> selectBdAccountList(BdAccount bdAccount){
     public List<BdAccount> selectBdAccountList(BdAccount bdAccount){
         List<BdAccount> list = baseMapper.selectBdAccountList(bdAccount);
         List<BdAccount> list = baseMapper.selectBdAccountList(bdAccount);
+        if(list.isEmpty()) return Collections.emptyList();
         List<Long> longs = PubFun.listToNewList(list, BdAccount::getAccountId);
         List<Long> longs = PubFun.listToNewList(list, BdAccount::getAccountId);
         List<BdPlan> planList = bdPlanService.list(new QueryWrapper<BdPlan>().in("account_id", longs));
         List<BdPlan> planList = bdPlanService.list(new QueryWrapper<BdPlan>().in("account_id", longs));
         Map<Long, Long> planMap = PubFun.listToMapByGroupCount(planList, BdPlan::getAccountId);
         Map<Long, Long> planMap = PubFun.listToMapByGroupCount(planList, BdPlan::getAccountId);

+ 1 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseLink.java

@@ -55,6 +55,7 @@ public class FsCourseLink extends BaseEntity
     private Integer linkType; //链接类型 0:正常链接  1:应急链接  2:小程序链接
     private Integer linkType; //链接类型 0:正常链接  1:应急链接  2:小程序链接
 
 
     private Integer isRoom;//是否发群
     private Integer isRoom;//是否发群
+    private String chatId;//是否发群
 
 
 //    private String link_uuid;
 //    private String link_uuid;
 
 

+ 2 - 1
fs-service/src/main/java/com/fs/course/param/FsCourseLinkCreateParam.java

@@ -12,11 +12,13 @@ public class FsCourseLinkCreateParam {
 
 
     private Integer days;
     private Integer days;
 
 
+    private Long qwUserIdLong;
     private String qwUserId;
     private String qwUserId;
 
 
     private String corpId;
     private String corpId;
 
 
     private Long companyId;
     private Long companyId;
+    private String chatId;
 
 
     private Long companyUserId;
     private Long companyUserId;
 
 
@@ -27,7 +29,6 @@ public class FsCourseLinkCreateParam {
     private Integer sendType;
     private Integer sendType;
 
 
     private Date sendTime;
     private Date sendTime;
-    private String chatId;
 
 
 
 
 }
 }

+ 1 - 0
fs-service/src/main/java/com/fs/course/param/FsUserCourseVideoAddKfUParam.java

@@ -58,6 +58,7 @@ public class FsUserCourseVideoAddKfUParam implements Serializable {
 
 
 
 
     private Long linkId;
     private Long linkId;
+    private String link;
 
 
     private Integer isRoom;
     private Integer isRoom;
 
 

+ 10 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseLinkServiceImpl.java

@@ -3,6 +3,7 @@ package com.fs.course.service.impl;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.json.JSONUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.StringUtils;
@@ -202,6 +203,15 @@ public class FsCourseLinkServiceImpl implements IFsCourseLinkService
 
 
     @Override
     @Override
     public R createRoomLinkUrl(FsCourseLinkCreateParam param) {
     public R createRoomLinkUrl(FsCourseLinkCreateParam param) {
+        QwUser qwUser;
+        if(param.getQwUserIdLong() != null){
+            qwUser = qwUserMapper.selectById(param.getQwUserIdLong());
+        }else{
+            qwUser = qwUserMapper.selectOne(new QueryWrapper<QwUser>().eq("qw_user_id", param.getQwUserId()).eq("corp_id", param.getCorpId()));
+        }
+        if(qwUser == null){
+            return R.error("未找到企微账号");
+        }
         String json = configService.selectConfigByKey("course.config");
         String json = configService.selectConfigByKey("course.config");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
         FsCourseLink link = new FsCourseLink();
         FsCourseLink link = new FsCourseLink();

+ 9 - 1
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -45,6 +45,7 @@ import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qwApi.Result.QwAddContactWayResult;
 import com.fs.qwApi.Result.QwAddContactWayResult;
 import com.fs.qwApi.param.QwAddContactWayParam;
 import com.fs.qwApi.param.QwAddContactWayParam;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.qwApi.service.QwApiService;
+import com.fs.repeat.vo.RepeatUploadVo;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.SopUserLogsInfoMapper;
 import com.fs.sop.mapper.SopUserLogsInfoMapper;
 import com.fs.sop.service.ISopUserLogsInfoService;
 import com.fs.sop.service.ISopUserLogsInfoService;
@@ -52,6 +53,7 @@ import com.fs.system.service.ISysConfigService;
 import com.fs.voice.utils.StringUtil;
 import com.fs.voice.utils.StringUtil;
 import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
 import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.slf4j.Logger;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.BeanUtils;
@@ -93,6 +95,8 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     @Autowired
     @Autowired
     private IFsUserService fsUserService;
     private IFsUserService fsUserService;
     @Autowired
     @Autowired
+    private RocketMQTemplate rocketMQTemplate;
+    @Autowired
     private FsUserCourseStudyLogMapper courseStudyLogMapper;
     private FsUserCourseStudyLogMapper courseStudyLogMapper;
 
 
     @Autowired
     @Autowired
@@ -384,7 +388,11 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
 
         String msg = "<div style=\"color: red;margin-bottom: 15px;font-weight: bold;\">本课程为会员独享<br>请长按二维码</div>\n" +
         String msg = "<div style=\"color: red;margin-bottom: 15px;font-weight: bold;\">本课程为会员独享<br>请长按二维码</div>\n" +
                 "\t\t\t\t\t<div style=\"color: #999;font-size: 14px;font-weight: bold;\">添加伴学助手免费领取会员权限</div>";
                 "\t\t\t\t\t<div style=\"color: #999;font-size: 14px;font-weight: bold;\">添加伴学助手免费领取会员权限</div>";
-
+        try {
+            new Thread(() -> rocketMQTemplate.syncSend("repeat-upload", JSON.toJSONString(RepeatUploadVo.builder().type(1).fsUserId(param.getUserId()).build()))).start();
+        }catch (Exception e){
+            logger.error("看课重粉提交mq失败", e);
+        }
         Integer isRoom = param.getIsRoom();
         Integer isRoom = param.getIsRoom();
 
 
         // 处理逻辑
         // 处理逻辑

+ 11 - 1
fs-service/src/main/java/com/fs/his/service/impl/FsInquiryOrderServiceImpl.java

@@ -41,6 +41,7 @@ import com.fs.huifuPay.service.HuiFuService;
 import com.fs.im.dto.*;
 import com.fs.im.dto.*;
 import com.fs.im.service.IImService;
 import com.fs.im.service.IImService;
 import com.fs.jpush.service.JpushService;
 import com.fs.jpush.service.JpushService;
+import com.fs.repeat.vo.RepeatUploadVo;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
 import com.fs.system.mapper.SysConfigMapper;
 import com.fs.tzBankPay.doman.*;
 import com.fs.tzBankPay.doman.*;
@@ -61,6 +62,7 @@ import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.binarywang.wxpay.service.WxPayService;
 import com.github.binarywang.wxpay.service.WxPayService;
 import com.google.gson.Gson;
 import com.google.gson.Gson;
 import lombok.Synchronized;
 import lombok.Synchronized;
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.slf4j.Logger;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.BeanUtils;
@@ -98,6 +100,8 @@ public class FsInquiryOrderServiceImpl implements IFsInquiryOrderService
     @Autowired
     @Autowired
     private WxPayProperties wxPayProperties;
     private WxPayProperties wxPayProperties;
     @Autowired
     @Autowired
+    private RocketMQTemplate rocketMQTemplate;
+    @Autowired
     private TzBankService tzBankService;
     private TzBankService tzBankService;
     @Autowired
     @Autowired
     private ApplicationEventPublisher publisher;
     private ApplicationEventPublisher publisher;
@@ -719,7 +723,7 @@ public class FsInquiryOrderServiceImpl implements IFsInquiryOrderService
     @Transactional
     @Transactional
     public R payConfirm(String orderSn,String payCode, String tradeNo,String payType,Integer type) {
     public R payConfirm(String orderSn,String payCode, String tradeNo,String payType,Integer type) {
         try {
         try {
-            FsInquiryOrder order=null;
+            FsInquiryOrder order;
             if(type.equals(1)){
             if(type.equals(1)){
                 FsStorePayment storePayment = fsStorePaymentMapper.selectFsStorePaymentByPaymentCode(payCode);
                 FsStorePayment storePayment = fsStorePaymentMapper.selectFsStorePaymentByPaymentCode(payCode);
                 if (storePayment!=null){
                 if (storePayment!=null){
@@ -744,15 +748,20 @@ public class FsInquiryOrderServiceImpl implements IFsInquiryOrderService
                         }
                         }
                         fsStorePaymentMapper.updateFsStorePayment(paymentMap);
                         fsStorePaymentMapper.updateFsStorePayment(paymentMap);
                         order=fsInquiryOrderMapper.selectFsInquiryOrderByOrderId(Long.parseLong(storePayment.getBusinessId()));
                         order=fsInquiryOrderMapper.selectFsInquiryOrderByOrderId(Long.parseLong(storePayment.getBusinessId()));
+                    } else {
+                        order = null;
                     }
                     }
                 }
                 }
                 else{
                 else{
+                    order = null;
 
 
                     return R.error("支付单号不存在");
                     return R.error("支付单号不存在");
                 }
                 }
             }
             }
             else if(type.equals(2)){
             else if(type.equals(2)){
                 order=fsInquiryOrderMapper.selectFsInquiryOrderByOrderSn(orderSn);
                 order=fsInquiryOrderMapper.selectFsInquiryOrderByOrderSn(orderSn);
+            } else {
+                order = null;
             }
             }
             if(!order.getStatus().equals(FsInquiryOrderStatusEnum.STATUS_1.getValue())){
             if(!order.getStatus().equals(FsInquiryOrderStatusEnum.STATUS_1.getValue())){
                 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
@@ -801,6 +810,7 @@ public class FsInquiryOrderServiceImpl implements IFsInquiryOrderService
                 String redisKey = String.valueOf(StrUtil.format("{}{}", FsConstants.REDIS_INQUIRY_ORDER_OUTTIME_UNRECEIVE, order.getOrderId()));
                 String redisKey = String.valueOf(StrUtil.format("{}{}", FsConstants.REDIS_INQUIRY_ORDER_OUTTIME_UNRECEIVE, order.getOrderId()));
                 redisCache.setCacheObject(redisKey,order.getOrderId(),configDTO.getUnReceiveCancelTime(), TimeUnit.MINUTES);
                 redisCache.setCacheObject(redisKey,order.getOrderId(),configDTO.getUnReceiveCancelTime(), TimeUnit.MINUTES);
             }
             }
+            new Thread(() -> rocketMQTemplate.syncSend("repeat-upload", JSON.toJSONString(RepeatUploadVo.builder().type(2).fsUserId(order.getUserId()).build()))).start();
             return R.ok();
             return R.ok();
         }catch (Exception e){
         }catch (Exception e){
             TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
             TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

+ 8 - 0
fs-service/src/main/java/com/fs/qw/domain/QwExternalContact.java

@@ -124,4 +124,12 @@ public class QwExternalContact extends BaseEntity
     //看课评论状态,1-拉黑;0-正常
     //看课评论状态,1-拉黑;0-正常
     private Integer commentStatus;
     private Integer commentStatus;
 
 
+    // 企微重粉
+    private Integer isRepeat;
+    private String repeatNo;
+
+    // 小程序(看课)重粉
+    private Integer userRepeat;
+    // 是否已购0 否 1程序内下单 2程序外下单
+    private Integer payOrder;
 }
 }

+ 1 - 1
fs-service/src/main/java/com/fs/qw/service/IQwGroupChatService.java

@@ -67,7 +67,7 @@ public interface IQwGroupChatService
     public int deleteQwGroupChatByChatIdAndCompanyId(String chatId,String corpId);
     public int deleteQwGroupChatByChatIdAndCompanyId(String chatId,String corpId);
 
 
     List<QwGroupChatOptionsVO> selectGroupChatOptionsVOList(String corpId);
     List<QwGroupChatOptionsVO> selectGroupChatOptionsVOList(String corpId);
-    List<QwGroupChatOptionsVO> listAllByQwUserList(String qwUserIds, String corpId);
+    List<QwGroupChatOptionsVO> listAllByQwUserList(String qwUserIds, String corpId, String sopId);
 
 
     List<QwGroupChat> selectQwGroupChatByChatIds(String[] ids);
     List<QwGroupChat> selectQwGroupChatByChatIds(String[] ids);
 
 

+ 19 - 4
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -38,6 +38,7 @@ import com.fs.qwApi.domain.*;
 import com.fs.qwApi.domain.inner.*;
 import com.fs.qwApi.domain.inner.*;
 import com.fs.qwApi.param.*;
 import com.fs.qwApi.param.*;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.qwApi.service.QwApiService;
+import com.fs.repeat.vo.RepeatUploadVo;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.domain.SopUserLogs;
 import com.fs.sop.domain.SopUserLogs;
 import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.domain.SopUserLogsInfo;
@@ -1554,8 +1555,9 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
             }
             }
         }
         }
         try {
         try {
-            logger.info("上传mq");
-            rocketMQTemplate.syncSend("ad-upload", JSON.toJSONString(AdUploadVo.builder().state(state).type(AdUploadType.ADD_WX).build()));
+            AdUploadVo vo = AdUploadVo.builder().state(state).type(AdUploadType.ADD_WX).build();
+            logger.info("上传mq:{}", vo);
+            rocketMQTemplate.syncSend("ad-upload", JSON.toJSONString(vo));
         }catch (Exception e){
         }catch (Exception e){
             logger.error("广告回调上传",e);
             logger.error("广告回调上传",e);
         }
         }
@@ -1639,11 +1641,24 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         qwExternalContact.setCreateTime(new Date());
         qwExternalContact.setCreateTime(new Date());
 
 
         try {
         try {
-            logger.info("上传mq");
-            rocketMQTemplate.syncSend("ad-upload", JSON.toJSONString(AdUploadVo.builder().state(state).type(AdUploadType.ADD_WX).build()));
+            AdUploadVo vo = AdUploadVo.builder().state(state).type(AdUploadType.ADD_WX).build();
+            logger.info("上传mq:{}", vo);
+            rocketMQTemplate.syncSend("ad-upload", JSON.toJSONString(vo));
         }catch (Exception e){
         }catch (Exception e){
             logger.error("广告回调上传",e);
             logger.error("广告回调上传",e);
         }
         }
+        try {
+            new Thread(() -> {
+                try {
+                    Thread.sleep(3000);
+                } catch (InterruptedException e) {
+                    logger.error("添加等待时长错误", e);
+                }
+                rocketMQTemplate.syncSend("repeat-upload", JSON.toJSONString(RepeatUploadVo.builder().type(0).externalUserId(externalUserID).build()));
+            }).start();
+        }catch (Exception e){
+            logger.error("重粉提交mq失败", e);
+        }
 //        iAdHtmlClickLogService.upload(state, AdUploadType.ADD_WX, e -> finalQwExternalContact.setUploadAddWxStatus(1));
 //        iAdHtmlClickLogService.upload(state, AdUploadType.ADD_WX, e -> finalQwExternalContact.setUploadAddWxStatus(1));
 
 
         //先入一次库
         //先入一次库

+ 36 - 2
fs-service/src/main/java/com/fs/qw/service/impl/QwGroupChatServiceImpl.java

@@ -5,6 +5,12 @@ import com.fs.common.exception.CustomException;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.DateUtils;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.domain.CompanyUser;
 import com.fs.qw.domain.*;
 import com.fs.qw.domain.*;
+import com.fs.common.utils.StringUtils;
+import com.fs.company.service.ICompanyConfigService;
+import com.fs.course.service.IFsCourseLinkService;
+import com.fs.qw.domain.QwCompany;
+import com.fs.qw.domain.QwGroupChat;
+import com.fs.qw.domain.QwGroupChatUser;
 import com.fs.qw.mapper.*;
 import com.fs.qw.mapper.*;
 import com.fs.qw.param.ChatParam;
 import com.fs.qw.param.ChatParam;
 import com.fs.qw.param.QwGroupChatParam;
 import com.fs.qw.param.QwGroupChatParam;
@@ -19,6 +25,15 @@ import com.fs.qwApi.Result.QwGroupChatListResult;
 import com.fs.qwApi.domain.QwGroupChatTransferResult;
 import com.fs.qwApi.domain.QwGroupChatTransferResult;
 import com.fs.qwApi.param.QwGroupChatTransferParam;
 import com.fs.qwApi.param.QwGroupChatTransferParam;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.qwApi.service.QwApiService;
+import com.fs.sop.domain.QwSop;
+import com.fs.sop.mapper.QwSopMapper;
+import com.fs.sop.mapper.SopUserLogsInfoMapper;
+import com.fs.sop.service.IQwSopLogsService;
+import com.fs.sop.service.IQwSopService;
+import com.fs.sop.service.ISopUserLogsInfoService;
+import com.fs.sop.service.ISopUserLogsService;
+import com.fs.sop.service.impl.QwSopLogsServiceImpl;
+import com.fs.sop.service.impl.QwSopServiceImpl;
 import com.fs.voice.utils.StringUtil;
 import com.fs.voice.utils.StringUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
@@ -28,6 +43,11 @@ import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
 import java.time.LocalDateTime;
 import java.util.*;
 import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
 
 
 /**
 /**
  * 客户群详情Service业务层处理
  * 客户群详情Service业务层处理
@@ -42,6 +62,9 @@ public class QwGroupChatServiceImpl implements IQwGroupChatService
     private QwGroupChatMapper qwGroupChatMapper;
     private QwGroupChatMapper qwGroupChatMapper;
     @Autowired
     @Autowired
     private QwGroupChatUserMapper qwGroupChatUserMapper;
     private QwGroupChatUserMapper qwGroupChatUserMapper;
+    @Autowired
+    private QwSopMapper sopMapper;
+
     @Autowired
     @Autowired
     private QwApiService qwApiService;
     private QwApiService qwApiService;
     @Autowired
     @Autowired
@@ -372,8 +395,19 @@ public class QwGroupChatServiceImpl implements IQwGroupChatService
 
 
 
 
     @Override
     @Override
-    public List<QwGroupChatOptionsVO> listAllByQwUserList(String qwUserIds, String corpId) {
-        return qwGroupChatMapper.listAllByQwUserList(qwUserIds, corpId);
+    public List<QwGroupChatOptionsVO> listAllByQwUserList(String qwUserIds, String corpId, String sopId) {
+        List<QwGroupChatOptionsVO> list = qwGroupChatMapper.listAllByQwUserList(qwUserIds, corpId);
+        if(StringUtils.isNotEmpty(sopId)){
+            QwSop qwSop = sopMapper.selectQwSopById(sopId);
+            List<String> chatIds;
+            if(StringUtils.isEmpty(qwSop.getChatId())){
+                chatIds = new ArrayList<>();
+            }else{
+                chatIds = Arrays.asList(qwSop.getChatId().split(","));
+            }
+            list = list.stream().filter(e -> !chatIds.contains(e.getChatId())).collect(Collectors.toList());
+        }
+        return list;
     }
     }
 
 
     @Override
     @Override

+ 15 - 0
fs-service/src/main/java/com/fs/qw/vo/AddSopUserGroupChat.java

@@ -0,0 +1,15 @@
+package com.fs.qw.vo;
+
+import lombok.Data;
+
+import java.time.LocalDate;
+
+@Data
+public class AddSopUserGroupChat {
+
+    private String id;
+    private String qwUserIds;
+    private String chatIds;
+    private LocalDate date;
+
+}

+ 1 - 0
fs-service/src/main/java/com/fs/qw/vo/GroupUserExternalVo.java

@@ -8,5 +8,6 @@ public class GroupUserExternalVo {
     private Long id;
     private Long id;
     private String userId;
     private String userId;
     private String externalUserId;
     private String externalUserId;
+    private Long fsUserId;
 
 
 }
 }

+ 14 - 0
fs-service/src/main/java/com/fs/qw/vo/UpdateSopUserLogDateVo.java

@@ -0,0 +1,14 @@
+package com.fs.qw.vo;
+
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.util.List;
+
+@Data
+public class UpdateSopUserLogDateVo {
+
+    private List<String> ids;
+    private LocalDate date;
+
+}

+ 56 - 0
fs-service/src/main/java/com/fs/repeat/service/RepeatService.java

@@ -0,0 +1,56 @@
+package com.fs.repeat.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.fs.common.utils.uuid.IdUtils;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.service.IQwExternalContactService;
+import com.fs.repeat.vo.RepeatUploadVo;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Slf4j
+@Service
+@AllArgsConstructor
+public class RepeatService {
+    private final IQwExternalContactService qwExternalContactService;
+
+    public void repeatQw(RepeatUploadVo vo) {
+        List<QwExternalContact> externalContactList = qwExternalContactService.list(new QueryWrapper<QwExternalContact>().eq("external_user_id", vo.getExternalUserId()));
+        if(!externalContactList.isEmpty()){
+            String no = IdUtils.simpleUUID();
+            log.info("外部联系人:{}重粉,外部联系人识别编号:{}", vo.getExternalUserId(), no);
+            externalContactList.forEach(e -> {
+                e.setRepeatNo(no);
+                e.setIsRepeat(1);
+            });
+            qwExternalContactService.updateBatchById(externalContactList);
+        }else{
+            log.info("外部联系人:{}并非重粉", vo.getExternalUserId());
+        }
+    }
+
+    public void repeatFsUser(RepeatUploadVo vo) {
+        List<QwExternalContact> qwExternalContacts = qwExternalContactService.list(new QueryWrapper<QwExternalContact>().eq("fs_user_id", vo.getFsUserId()));
+        if(qwExternalContacts.size() > 1){
+            String no = IdUtils.simpleUUID();
+            qwExternalContacts.forEach(e -> {
+                e.setRepeatNo(no);
+                e.setIsRepeat(1);
+                e.setUserRepeat(1);
+            });
+            log.info("fs_user:{}重粉,外部联系人识别编号:{}", vo.getFsUserId(), no);
+            qwExternalContactService.updateBatchById(qwExternalContacts);
+        }else{
+            log.info("fs_user:{}并非重粉", vo.getFsUserId());
+        }
+    }
+
+    public void userAddOrder(RepeatUploadVo vo) {
+        List<QwExternalContact> qwExternalContacts = qwExternalContactService.list(new QueryWrapper<QwExternalContact>().eq("fs_user_id", vo.getFsUserId()));
+        qwExternalContacts.forEach(e -> e.setPayOrder(1));
+        qwExternalContactService.updateBatchById(qwExternalContacts);
+    }
+}

+ 18 - 0
fs-service/src/main/java/com/fs/repeat/vo/RepeatUploadVo.java

@@ -0,0 +1,18 @@
+package com.fs.repeat.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class RepeatUploadVo {
+    // 判断类型 0:企微外部联系人ID 1:小程序 2:下单
+    private Integer type;
+    private String externalUserId;
+    private Long fsUserId;
+
+}

+ 4 - 0
fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsMapper.java

@@ -3,6 +3,7 @@ package com.fs.sop.mapper;
 import com.fs.common.annotation.DataSource;
 import com.fs.common.annotation.DataSource;
 import com.fs.common.enums.DataSourceType;
 import com.fs.common.enums.DataSourceType;
 import com.fs.qw.param.SopUserLogsVO;
 import com.fs.qw.param.SopUserLogsVO;
+import com.fs.qw.vo.UpdateSopUserLogDateVo;
 import com.fs.sop.domain.SopUserLogs;
 import com.fs.sop.domain.SopUserLogs;
 import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.params.SopUserLogsList;
 import com.fs.sop.params.SopUserLogsList;
@@ -225,4 +226,7 @@ public interface SopUserLogsMapper {
 
 
     @DataSource(DataSourceType.SOP)
     @DataSource(DataSourceType.SOP)
     void batchInsertSopUserLogs(@Param("list") List<SopUserLogs> list);
     void batchInsertSopUserLogs(@Param("list") List<SopUserLogs> list);
+
+    @DataSource(DataSourceType.SOP)
+    void updateSopuserLogsDateById(UpdateSopUserLogDateVo vo);
 }
 }

+ 6 - 0
fs-service/src/main/java/com/fs/sop/service/ISopUserLogsService.java

@@ -2,6 +2,8 @@ package com.fs.sop.service;
 
 
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
 import com.fs.qw.param.SopUserLogsVO;
 import com.fs.qw.param.SopUserLogsVO;
+import com.fs.qw.vo.AddSopUserGroupChat;
+import com.fs.qw.vo.UpdateSopUserLogDateVo;
 import com.fs.sop.domain.SopUserLogs;
 import com.fs.sop.domain.SopUserLogs;
 import com.fs.sop.params.SopUserLogsList;
 import com.fs.sop.params.SopUserLogsList;
 import com.fs.sop.params.SopUserLogsParam;
 import com.fs.sop.params.SopUserLogsParam;
@@ -55,4 +57,8 @@ public interface ISopUserLogsService {
      * Sop客户评级
      * Sop客户评级
      */
      */
     public R ratingUserLogs(String sopId);
     public R ratingUserLogs(String sopId);
+
+    void updateLogDate(UpdateSopUserLogDateVo vo);
+
+    void addGroupChat(AddSopUserGroupChat vo);
 }
 }

+ 49 - 19
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -447,17 +447,36 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         List<QwSopLogs> sopLogsList;
         List<QwSopLogs> sopLogsList;
         if(param.getFilterMode() != null && param.getFilterMode() == 2 && param.getChatIds() != null && param.getChatIds().length > 0){
         if(param.getFilterMode() != null && param.getFilterMode() == 2 && param.getChatIds() != null && param.getChatIds().length > 0){
             List<QwGroupChat> groupList = qwGroupChatMapper.selectQwGroupChatByChatIds(param.getChatIds());
             List<QwGroupChat> groupList = qwGroupChatMapper.selectQwGroupChatByChatIds(param.getChatIds());
-            if(param.getSendType() != null && param.getSendType() == 2){
-                Map<String, QwGroupChat> groupMap = PubFun.listToMapByGroupObject(groupList, QwGroupChat::getChatId);
-                List<QwGroupChatUser> groupUserList = qwGroupChatUserMapper.selectQwGroupChatUserByChatIds(groupList.stream().map(QwGroupChat::getChatId).toArray(String[]::new));
-                List<String> groupChatUserIds = PubFun.listToNewList(groupUserList, QwGroupChatUser::getUserId);
-                if(!groupChatUserIds.isEmpty()){
-                    List<GroupUserExternalVo> userList = qwExternalContactMapper.selectByGroupUser(groupChatUserIds);
-                    Map<String, List<GroupUserExternalVo>> userMap = PubFun.listToMapByGroupList(userList, GroupUserExternalVo::getExternalUserId);
-                    groupUserList.forEach(e -> {
-                        e.setUserList(userMap.getOrDefault(e.getUserId(), Collections.emptyList()));
+
+            Map<String, QwGroupChat> groupMap = PubFun.listToMapByGroupObject(groupList, QwGroupChat::getChatId);
+            List<QwGroupChatUser> groupUserList = qwGroupChatUserMapper.selectQwGroupChatUserByChatIds(groupList.stream().map(QwGroupChat::getChatId).toArray(String[]::new));
+            Map<String, List<QwGroupChatUser>> groupUserMap = PubFun.listToMapByGroupList(groupUserList, QwGroupChatUser::getChatId);
+            groupList.stream().filter(e -> groupUserMap.containsKey(e.getChatId())).forEach(e -> {
+                e.setChatUserList(groupUserMap.get(e.getChatId()));
+            });
+            List<String> groupChatUserIds = PubFun.listToNewList(groupUserList, QwGroupChatUser::getUserId);
+            if (!groupChatUserIds.isEmpty()) {
+                List<GroupUserExternalVo> userList = qwExternalContactMapper.selectByGroupUser(groupChatUserIds);
+                Map<String, List<GroupUserExternalVo>> userMap = PubFun.listToMapByGroupList(userList, GroupUserExternalVo::getExternalUserId);
+                groupUserList.forEach(e -> {
+                    e.setUserList(userMap.getOrDefault(e.getUserId(), Collections.emptyList()));
+                });
+            }
+            try {
+                groupList.forEach(groupChat -> {
+                    QwUser qwUser = qwUserMapper.selectQwUserByIdByWeComeText2(groupChat.getOwner(), groupChat.getCorpId());
+                    groupChat.getChatUserList().stream().filter(e -> e.getUserList() != null && !e.getUserList().isEmpty()).forEach(e -> {
+                        Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(e.getUserList(), GroupUserExternalVo::getUserId);
+                        GroupUserExternalVo vo = userMap.get(groupChat.getOwner());
+                        if (vo != null && vo.getId() != null) {
+                            addWatchLogIfNeeded(param.getSopId(), param.getVideoId(), param.getCourseId(), vo.getFsUserId(), qwUser.getId().toString(), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(), vo.getId(), param.getStartTime(), createTime);
+                        }
                     });
                     });
-                }
+                });
+            } catch (Exception e) {
+                log.error("群聊创建看课记录失败!", e);
+            }
+            if (param.getSendType() != null && param.getSendType() == 2) {
                 sopLogsList = groupUserList.stream().map(groupUser -> {
                 sopLogsList = groupUserList.stream().map(groupUser -> {
                     QwGroupChat qwGroupChat = groupMap.get(groupUser.getChatId());
                     QwGroupChat qwGroupChat = groupMap.get(groupUser.getChatId());
 
 
@@ -472,6 +491,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 //                    }
 //                    }
                     Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(groupUser.getUserList(), GroupUserExternalVo::getUserId);
                     Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(groupUser.getUserList(), GroupUserExternalVo::getUserId);
                     GroupUserExternalVo vo = userMap.get(qwGroupChat.getOwner());
                     GroupUserExternalVo vo = userMap.get(qwGroupChat.getOwner());
+                    if (vo == null) return null;
                     QwSopLogs sopLogs = new QwSopLogs();
                     QwSopLogs sopLogs = new QwSopLogs();
 
 
                     sopLogs.setQwUserid(qwUser.getQwUserId());
                     sopLogs.setQwUserid(qwUser.getQwUserId());
@@ -514,11 +534,22 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                 if ("1".equals(st.getIsBindUrl())) {
                                 if ("1".equals(st.getIsBindUrl())) {
                                     String qwUserId = qwUser.getQwUserId();
                                     String qwUserId = qwUser.getQwUserId();
                                     String companyId = qwUser.getCompanyId().toString();
                                     String companyId = qwUser.getCompanyId().toString();
-                                    Long externalUserId = Long.parseLong(vo.getExternalUserId());
-                                    addWatchLogIfNeeded(param.getSopId(), param.getVideoId(), param.getCourseId(), null, qwUserId, companyUserId, companyId, externalUserId, param.getStartTime(), createTime);
-
-                                    String sortLink = generateShortLink(st, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
-                                            qwUserId, companyUserId, companyId, domainName, externalUserId, config);
+                                    Long externalUserId = vo.getId();
+//                                    addWatchLogIfNeeded(param.getSopId(), param.getVideoId(), param.getCourseId(), null, qwUserId, companyUserId, companyId, externalUserId, param.getStartTime(), createTime);
+                                    FsCourseLinkCreateParam createParam = new FsCourseLinkCreateParam();
+                                    createParam.setCourseId(param.getCourseId().longValue());
+                                    createParam.setVideoId(param.getVideoId().longValue());
+                                    createParam.setCorpId(qwSop.getCorpId());
+                                    createParam.setCompanyUserId(Long.parseLong(companyUserId));
+                                    createParam.setCompanyId(Long.parseLong(companyId));
+                                    createParam.setChatId(groupUser.getChatId());
+                                    createParam.setQwUserId(qwUser.getQwUserId());
+                                    createParam.setDays(st.getExpiresDays());
+                                    R createLink = courseLinkService.createRoomLinkUrl(createParam);
+                                    if (createLink.get("code").equals(500)) {
+                                        throw new BaseException("链接生成失败!");
+                                    }
+                                    String sortLink = (String) createLink.get("url");
 
 
                                     if (StringUtils.isNotEmpty(sortLink)) {
                                     if (StringUtils.isNotEmpty(sortLink)) {
                                         if ("3".equals(st.getContentType())) {
                                         if ("3".equals(st.getContentType())) {
@@ -559,10 +590,10 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     setting.setCourseType(param.getCourseType());
                     setting.setCourseType(param.getCourseType());
                     sopLogs.setContentJson(JSON.toJSONString(setting));
                     sopLogs.setContentJson(JSON.toJSONString(setting));
                     return sopLogs;
                     return sopLogs;
-                }).collect(Collectors.toList());
+                }).filter(Objects::nonNull).collect(Collectors.toList());
 
 
 
 
-            }else{
+            } else {
                 sopLogsList = groupList.stream().map(groupChat -> {
                 sopLogsList = groupList.stream().map(groupChat -> {
                     QwUser qwUser = qwUserMapper.selectQwUserByIdByWeComeText2(groupChat.getOwner(), groupChat.getCorpId());
                     QwUser qwUser = qwUserMapper.selectQwUserByIdByWeComeText2(groupChat.getOwner(), groupChat.getCorpId());
                     QwSopLogs sopLogs = new QwSopLogs();
                     QwSopLogs sopLogs = new QwSopLogs();
@@ -578,7 +609,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                     sopLogs.setSopId(param.getSopId());
                     sopLogs.setSopId(param.getSopId());
                     sopLogs.setCorpId(groupChat.getCorpId());
                     sopLogs.setCorpId(groupChat.getCorpId());
                     sopLogs.setSort(2);
                     sopLogs.setSort(2);
-                    sopLogs.setSendType(6);
+                    sopLogs.setSendType(12);
                     sopLogs.setExternalUserName(groupChat.getName());
                     sopLogs.setExternalUserName(groupChat.getName());
 
 
                     QwSopCourseFinishTempSetting setting = new QwSopCourseFinishTempSetting();
                     QwSopCourseFinishTempSetting setting = new QwSopCourseFinishTempSetting();
@@ -596,7 +627,6 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             replaceContent(st.getContentType(), st.getLinkTitle(), st::setLinkTitle, words); // 替换 linkTitle
                             replaceContent(st.getContentType(), st.getLinkTitle(), st::setLinkTitle, words); // 替换 linkTitle
                             replaceContent(st.getContentType(), st.getLinkDescribe(), st::setLinkDescribe, words); // 替换 linkTitle
                             replaceContent(st.getContentType(), st.getLinkDescribe(), st::setLinkDescribe, words); // 替换 linkTitle
                         }
                         }
-
                         switch (st.getContentType()) {
                         switch (st.getContentType()) {
                             //文字和短链一起
                             //文字和短链一起
                             case "1":
                             case "1":

+ 52 - 2
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsServiceImpl.java

@@ -4,7 +4,9 @@ import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.PubFun;
+import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.date.DateUtil;
 import com.fs.common.utils.date.DateUtil;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
@@ -19,8 +21,8 @@ import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.param.SopUserLogsVO;
 import com.fs.qw.param.SopUserLogsVO;
 import com.fs.qw.service.IQwGroupChatService;
 import com.fs.qw.service.IQwGroupChatService;
-import com.fs.qw.vo.QwSopRuleTimeVO;
-import com.fs.qw.vo.QwSopTempSetting;
+import com.fs.qw.service.IQwUserService;
+import com.fs.qw.vo.*;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.domain.QwSopLogs;
 import com.fs.sop.domain.QwSopLogs;
@@ -76,6 +78,9 @@ public class SopUserLogsServiceImpl  implements ISopUserLogsService {
     @Autowired
     @Autowired
     private SopUserLogsInfoMapper sopUserLogsInfoMapper;
     private SopUserLogsInfoMapper sopUserLogsInfoMapper;
 
 
+    @Autowired
+    private IQwUserService qwUserService;
+
     @Autowired
     @Autowired
     private QwSopMapper sopMapper;
     private QwSopMapper sopMapper;
     @Autowired
     @Autowired
@@ -905,6 +910,51 @@ public class SopUserLogsServiceImpl  implements ISopUserLogsService {
         return R.ok();
         return R.ok();
     }
     }
 
 
+    @Override
+    public void updateLogDate(UpdateSopUserLogDateVo vo) {
+        if(vo.getDate() == null){
+            throw new BaseException("修改时间不能为空");
+        }
+        if(vo.getIds() == null || vo.getIds().isEmpty()){
+            throw new BaseException("营期ID不能为空");
+        }
+        sopUserLogsMapper.updateSopuserLogsDateById(vo);
+    }
+
+    @Override
+    public void addGroupChat(AddSopUserGroupChat vo) {
+        QwSop qwSop = sopMapper.selectQwSopById(vo.getId());
+        QwSop updateSop = new QwSop();
+        String qwUserIds = qwSop.getQwUserIds() + "," + vo.getQwUserIds();
+        updateSop.setQwUserIds(Arrays.stream(qwUserIds.split(",")).filter(StringUtils::isNotEmpty).distinct().collect(Collectors.joining(",")));
+        String chatIds = qwSop.getChatId() + "," + vo.getChatIds();
+        updateSop.setChatId(Arrays.stream(chatIds.split(",")).filter(StringUtils::isNotEmpty).distinct().collect(Collectors.joining(",")));
+        updateSop.setId(vo.getId());
+        sopMapper.updateQwSop(updateSop);
+        List<QwUserVO> qwUserVOList = qwUserService.selectQwUserVOByIds(Arrays.stream(vo.getQwUserIds().split(",")).map(Long::parseLong).toArray(Long[]::new));
+        Map<String, QwUserVO> qwUserMap = PubFun.listToMapByGroupObject(qwUserVOList, QwUserVO::getQwUserId);
+        List<QwGroupChat> qwGroupChatList = qwGroupChatService.selectQwGroupChatByChatIds(vo.getChatIds().split(","));
+        Map<String, QwGroupChat> groupChatMap = PubFun.listToMapByGroupObject(qwGroupChatList, QwGroupChat::getChatId);
+
+        List<SopUserLogs> list = Arrays.stream(vo.getChatIds().split(",")).map(e -> {
+            SopUserLogs sopUserLogs = new SopUserLogs();
+            sopUserLogs.setSopId(qwSop.getId());
+            sopUserLogs.setSopTempId(qwSop.getTempId());
+            QwGroupChat groupChat = groupChatMap.get(e);
+            sopUserLogs.setQwUserId(groupChat.getOwner());
+            sopUserLogs.setChatId(e);
+            sopUserLogs.setCorpId(qwSop.getCorpId());
+            sopUserLogs.setStartTime(qwSop.getStartTime());
+            sopUserLogs.setStatus(1);
+            QwUserVO qwUserVO = qwUserMap.get(groupChat.getOwner());
+            if(qwUserVO != null){
+                sopUserLogs.setUserId(qwUserVO.getId() + "|" + qwUserVO.getCompanyUserId() + "|" + qwUserVO.getCompanyId());
+            }
+            return sopUserLogs;
+        }).collect(Collectors.toList());
+        sopUserLogsMapper.batchInsertSopUserLogs(list);
+    }
+
     //批量更新
     //批量更新
     private void batchUpdateQwExternalContact(List<QwExternalContact> notInExternalUseridList) {
     private void batchUpdateQwExternalContact(List<QwExternalContact> notInExternalUseridList) {
         // 定义批量插入的大小
         // 定义批量插入的大小

+ 2 - 0
fs-service/src/main/resources/application-dev.yml

@@ -1,5 +1,7 @@
 # 数据源配置
 # 数据源配置
 spring:
 spring:
+    profiles:
+        include: common,config-dev
 #    profiles:
 #    profiles:
 #        include: config-dev,common
 #        include: config-dev,common
     # redis 配置
     # redis 配置

+ 14 - 1
fs-service/src/main/resources/application-druid-hdt.yml

@@ -1,5 +1,7 @@
 # 数据源配置
 # 数据源配置
 spring:
 spring:
+    profiles:
+        include: config-druid-hdt,common
     # redis 配置
     # redis 配置
     redis:
     redis:
         # 地址  localhost
         # 地址  localhost
@@ -137,4 +139,15 @@ spring:
                         merge-sql: true
                         merge-sql: true
                     wall:
                     wall:
                         config:
                         config:
-                            multi-statement-allow: true
+                            multi-statement-allow: true
+rocketmq:
+    name-server: 192.168.0.26:8100 # RocketMQ NameServer 地址
+    producer:
+        group: my-producer-group   # 生产者组名(必须唯一)
+        access-key: default
+        secret-key: aZS4z!88dNndKTfhITzTpTxRrVUShtH3
+    consumer:
+        topic: ad-upload
+        group: ad-group
+        access-key: default
+        secret-key: aZS4z!88dNndKTfhITzTpTxRrVUShtH3

+ 2 - 0
fs-service/src/main/resources/application-druid-myhk-test.yml

@@ -1,5 +1,7 @@
 # 数据源配置
 # 数据源配置
 spring:
 spring:
+    profiles:
+        include: config-druid-myhk,common
     # redis 配置
     # redis 配置
     redis:
     redis:
 #        host: 172.27.0.6
 #        host: 172.27.0.6

+ 8 - 6
fs-service/src/main/resources/application-druid-myhk.yml

@@ -1,5 +1,7 @@
 # 数据源配置
 # 数据源配置
 spring:
 spring:
+    profiles:
+        include: config-druid-myhk,common
     # redis 配置
     # redis 配置
     redis:
     redis:
         host: 172.27.0.6
         host: 172.27.0.6
@@ -135,13 +137,13 @@ spring:
                         config:
                         config:
                             multi-statement-allow: true
                             multi-statement-allow: true
 rocketmq:
 rocketmq:
-    name-server: rmq-1243b25nj.rocketmq.gz.public.tencenttdmq.com:8080 # RocketMQ NameServer 地址
+    name-server: rmq-16xj8o92zp.rocketmq.cd.qcloud.tencenttdmq.com:8080
     producer:
     producer:
         group: my-producer-group
         group: my-producer-group
-        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
-        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
+        access-key: ak16xj8o92zp984557f83ba2 # 替换为实际的 accessKey
+        secret-key: sk2ff1c6b15b74b888 # 替换为实际的 secretKey
     consumer:
     consumer:
-        group: voice-group
-        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
-        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
+        group: common-group
+        access-key: ak16xj8o92zp984557f83ba2 # 替换为实际的 accessKey
+        secret-key: sk2ff1c6b15b74b888 # 替换为实际的 secretKey
 
 

+ 2 - 0
fs-service/src/main/resources/application-druid-sft.yml

@@ -1,6 +1,8 @@
 
 
 # 数据源配置
 # 数据源配置
 spring:
 spring:
+    profiles:
+        include: config-druid-sft,common
     # redis 配置
     # redis 配置
     redis:
     redis:
         host: 172.30.0.13
         host: 172.30.0.13

+ 2 - 0
fs-service/src/main/resources/application-druid-sxjz.yml

@@ -1,5 +1,7 @@
 # 数据源配置
 # 数据源配置
 spring:
 spring:
+    profiles:
+        include: config-druid-sxjz,common
     # redis 配置
     # redis 配置
     redis:
     redis:
         host: 172.27.0.13
         host: 172.27.0.13

+ 2 - 0
fs-service/src/main/resources/application-druid-yzt.yml

@@ -1,5 +1,7 @@
 # 数据源配置
 # 数据源配置
 spring:
 spring:
+    profiles:
+        include: config-druid-yzt,common
     # redis 配置
     # redis 配置
     redis:
     redis:
         host: r-wz9syugp027unhsdmepd.redis.rds.aliyuncs.com
         host: r-wz9syugp027unhsdmepd.redis.rds.aliyuncs.com

+ 0 - 11
fs-service/src/main/resources/application-mq-hdt.yml

@@ -1,11 +0,0 @@
-rocketmq:
-  name-server: 192.168.0.26:8100 # RocketMQ NameServer 地址
-  producer:
-    group: my-producer-group   # 生产者组名(必须唯一)
-    access-key: default
-    secret-key: aZS4z!88dNndKTfhITzTpTxRrVUShtH3
-  consumer:
-    topic: ad-upload
-    group: ad-group
-    access-key: default
-    secret-key: aZS4z!88dNndKTfhITzTpTxRrVUShtH3

+ 67 - 0
fs-service/src/main/resources/config/dev/application-config-dev.yml

@@ -0,0 +1,67 @@
+baidu:
+  token: 12313231232
+#配置
+logging:
+  level:
+    org.springframework.web: INFO
+    com.github.binarywang.demo.wx.cp: DEBUG
+    me.chanjar.weixin: DEBUG
+wx:
+  cp:
+    corpId: wwb2a1055fb6c9a7c2
+    appConfigs:
+      - agentId: 1000005
+        secret: ec7okROXJqkNafq66-L6aKNv0asTzQIG0CYrj3vyBbo
+        token: PPKOdAlCoMO
+        aesKey: PKvaxtpSv8NGpfTDm7VUHIK8Wok2ESyYX24qpXJAdMP
+  pay:
+    appId: wx73f85f8d62769119 #微信公众号或者小程序等的appid
+    mchId: 1611402045 #微信支付商户号
+    mchKey: 8cab128997a3547c1363b0898b877f38 #微信支付商户密钥
+    subAppId:  #服务商模式下的子商户公众账号ID
+    subMchId:  #服务商模式下的子商户号
+    keyPath: c:\\cert\\apiclient_cert.p12 # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)
+    notifyUrl: https://userapp.his.runtzh.com/app/wxpay/wxPayNotify
+  mp:
+    useRedis: false
+    redisConfig:
+      host: 127.0.0.1
+      port: 6379
+      timeout: 2000
+    configs:
+      - appId: wx894a6220c608f5c1 # 第一个公众号的appid  //公众号名称:成都九州在线互联网医院
+        secret: dabd5168d58c66e97be1ab1eee346b20 # 公众号的appsecret
+        token: PPKOdAlCoMO # 接口配置里的Token值
+        aesKey: Eswa6VjwtVMCcw03qZy6fWllgrv5aytIA1SZPEU0kU2 # 接口配置里的EncodingAESKey值
+aifabu:  #爱链接
+  appKey: 7b471be905ab17e00f3b858c6710dd117601d008
+watch:
+  watchUrl: watch.ylrzcloud.com/prod-api
+#  account: tcloud
+#  password: mdf-m2h_6yw2$hq
+  account1: ccif #866655060138751
+  password1: cp-t5or_6xw7$mt
+  account2: tcloud #rt500台
+  password2: mdf-m2h_6yw2$hq
+  account3: whr
+  password3: v9xsKuqn_$d2y
+
+fs :
+  commonApi: http://172.16.0.16:8010
+  h5CommonApi: http://119.29.195.254:8010
+nuonuo:
+  key: 10924508
+  secret: A2EB20764D304D16
+
+# 存储捅配置
+tencent_cloud_config:
+  secret_id: AKIDiMq9lDf2EOM9lIfqqfKo7FNgM5meD0sT
+  secret_key: u5SuS80342xzx8FRBukza9lVNHKNMSaB
+  bucket: 1323137866
+  app_id: 1323137866
+  region: chongqing
+  proxy: hzyy
+cloud_host:
+  company_name: 润天
+headerImg:
+  imgUrl: https://jz-cos-1356808054.cos.ap-chengdu.myqcloud.com/fs/20250515/0877754b59814ea8a428fa3697b20e68.png

+ 133 - 0
fs-service/src/main/resources/config/dev/application-dev.yml

@@ -0,0 +1,133 @@
+# 数据源配置
+spring:
+#    profiles:
+#        include: config-dev,common
+    # redis 配置
+    redis:
+        # 地址
+        host: localhost
+        # 端口,默认为6379
+        port: 6379
+        # 数据库索引
+        database: 0
+        # 密码
+        password:
+        # 连接超时时间
+        timeout: 20s
+        lettuce:
+            pool:
+                # 连接池中的最小空闲连接
+                min-idle: 0
+                # 连接池中的最大空闲连接
+                max-idle: 8
+                # 连接池的最大数据库连接数
+                max-active: 8
+                # #连接池最大阻塞等待时间(使用负值表示没有限制)
+                max-wait: -1ms
+    datasource:
+        clickhouse:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.clickhouse.jdbc.ClickHouseDriver
+            url: jdbc:clickhouse://1.14.104.71:8123/sop_test?compress=0&use_server_time_zone=true&use_client_time_zone=false&timezone=Asia/Shanghai
+            username: default
+            password: rt2024
+            initialSize: 10
+            maxActive: 100
+            minIdle: 10
+            maxWait: 6000
+        mysql:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://139.186.77.83:3306/ylrz_his_scrm?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: Rtroot
+                    password: Rtroot
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 20
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: true
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+        sop:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://139.186.77.83:3306/his_sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: Rtroot
+                    password: Rtroot
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 20
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: true
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true

+ 10 - 0
fs-service/src/main/resources/config/dev/application-mq-dev.yml

@@ -0,0 +1,10 @@
+rocketmq:
+  name-server: rmq-1243b25nj.rocketmq.gz.public.tencenttdmq.com:8080 # RocketMQ NameServer 地址
+  producer:
+    group: my-producer-group
+    access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
+    secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
+  consumer:
+    group: test-group
+    access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
+    secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey

+ 1 - 1
fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml

@@ -318,7 +318,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         group by external_user_id
         group by external_user_id
     </select>
     </select>
     <select id="selectByGroupUser" resultType="com.fs.qw.vo.GroupUserExternalVo">
     <select id="selectByGroupUser" resultType="com.fs.qw.vo.GroupUserExternalVo">
-        select id,user_id,external_user_id from qw_external_contact where external_user_id in
+        select id,user_id,external_user_id,fs_user_id from qw_external_contact where external_user_id in
         <foreach collection="ids" open="(" separator="," close=")" item="item">#{item}</foreach>
         <foreach collection="ids" open="(" separator="," close=")" item="item">#{item}</foreach>
         GROUP BY user_id,external_user_id
         GROUP BY user_id,external_user_id
     </select>
     </select>

+ 2 - 3
fs-service/src/main/resources/mapper/sop/QwSopMapper.xml

@@ -31,13 +31,12 @@
         <result property="stopTime"    column="stop_time"    />
         <result property="stopTime"    column="stop_time"    />
         <result property="isRating"    column="is_rating"    />
         <result property="isRating"    column="is_rating"    />
         <result property="courseDay"    column="course_day"    />
         <result property="courseDay"    column="course_day"    />
+        <result property="chatId"    column="chat_id"    />
         <result property="openCommentStatus"    column="open_comment_status"    />
         <result property="openCommentStatus"    column="open_comment_status"    />
     </resultMap>
     </resultMap>
 
 
     <sql id="selectQwSopVo">
     <sql id="selectQwSopVo">
-        select id,name,status,type,qw_user_ids,create_by,create_time,company_id,send_type,tags,exclude_tags,start_time,
-               filter_mode,filter_type,temp_id,corp_id,expiry_time,is_auto_sop,auto_sop_time,min_conversion_day,
-               max_conversion_day,min_send,max_send,stop_time,is_rating,course_day,open_comment_status
+        select *
         from qw_sop
         from qw_sop
     </sql>
     </sql>
 
 

+ 6 - 0
fs-service/src/main/resources/mapper/sop/SopUserLogsMapper.xml

@@ -178,6 +178,12 @@
         SET  user_id= #{data.userId}
         SET  user_id= #{data.userId}
         where id = #{data.id}
         where id = #{data.id}
     </update>
     </update>
+    <update id="updateSopuserLogsDateById">
+        update sop_user_logs
+        set start_time = #{date}
+        where id in <foreach collection="ids" item="item" open="(" separator="," close=")">#{item}</foreach>
+    </update>
+
 
 
     <select id="repairSopUserLogsList" resultType="com.fs.sop.domain.SopUserLogs">
     <select id="repairSopUserLogsList" resultType="com.fs.sop.domain.SopUserLogs">
         select id,qw_user_id,corp_id,user_id from sop_user_logs  where status=1 and user_id like '%null%'
         select id,qw_user_id,corp_id,user_id from sop_user_logs  where status=1 and user_id like '%null%'

+ 1 - 2
fs-store/src/main/resources/application.yml

@@ -6,5 +6,4 @@ server:
 # Spring配置
 # Spring配置
 spring:
 spring:
   profiles:
   profiles:
-    active: dev
-    include: common,config-dev
+    active: dev

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