Просмотр исходного кода

Merge remote-tracking branch 'origin/master'

yfh 1 месяц назад
Родитель
Сommit
858263eb02
100 измененных файлов с 3826 добавлено и 169 удалено
  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. 12 1
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralGoodsController.java
  4. 2 7
      fs-admin/src/main/resources/application.yml
  5. 0 1
      fs-ai-chat/src/main/resources/application.yml
  6. 0 2
      fs-common-api/src/main/resources/application.yml
  7. 0 2
      fs-company-app/src/main/resources/application.yml
  8. 2 2
      fs-company/src/main/java/com/fs/company/controller/qw/QwGroupChatController.java
  9. 68 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwGroupChatTransferController.java
  10. 28 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwGroupChatTransferLogController.java
  11. 9 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  12. 16 0
      fs-company/src/main/java/com/fs/company/controller/qw/SopUserLogsController.java
  13. 2 9
      fs-company/src/main/resources/application.yml
  14. 15 0
      fs-doctor-app/src/main/java/com/fs/app/controller/IndexController.java
  15. 0 2
      fs-doctor-app/src/main/resources/application.yml
  16. 1 2
      fs-hospital/src/main/resources/application.yml
  17. 1 2
      fs-live-app/src/main/resources/application.yml
  18. 0 4
      fs-qw-api-msg/src/main/resources/application.yml
  19. 12 0
      fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java
  20. 3 5
      fs-qw-api/src/main/resources/application.yml
  21. 20 21
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  22. 0 5
      fs-qw-task/src/main/resources/application.yml
  23. 1 2
      fs-qw-voice/src/main/resources/application.yml
  24. 0 3
      fs-qwhook-msg/src/main/resources/application.yml
  25. 1 1
      fs-qwhook-sop/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  26. 0 5
      fs-qwhook-sop/src/main/resources/application.yml
  27. 0 5
      fs-qwhook/src/main/resources/application.yml
  28. 141 0
      fs-repeat-api/pom.xml
  29. 14 0
      fs-repeat-api/src/main/java/com/fs/FSServletInitializer.java
  30. 25 0
      fs-repeat-api/src/main/java/com/fs/FsRepeatApiApplication.java
  31. 12 0
      fs-repeat-api/src/main/java/com/fs/app/annotation/Login.java
  32. 15 0
      fs-repeat-api/src/main/java/com/fs/app/annotation/LoginUser.java
  33. 39 0
      fs-repeat-api/src/main/java/com/fs/app/controller/CommonController.java
  34. 51 0
      fs-repeat-api/src/main/java/com/fs/app/exception/FSException.java
  35. 81 0
      fs-repeat-api/src/main/java/com/fs/app/exception/FSExceptionHandler.java
  36. 55 0
      fs-repeat-api/src/main/java/com/fs/app/mq/RocketMQConsumerService.java
  37. 24 0
      fs-repeat-api/src/main/java/com/fs/app/task/Task.java
  38. 182 0
      fs-repeat-api/src/main/java/com/fs/framework/aspectj/DataScopeAspect.java
  39. 73 0
      fs-repeat-api/src/main/java/com/fs/framework/aspectj/DataSourceAspect.java
  40. 245 0
      fs-repeat-api/src/main/java/com/fs/framework/aspectj/LogAspect.java
  41. 117 0
      fs-repeat-api/src/main/java/com/fs/framework/aspectj/RateLimiterAspect.java
  42. 31 0
      fs-repeat-api/src/main/java/com/fs/framework/config/ApplicationConfig.java
  43. 85 0
      fs-repeat-api/src/main/java/com/fs/framework/config/CaptchaConfig.java
  44. 100 0
      fs-repeat-api/src/main/java/com/fs/framework/config/DataSourceConfig.java
  45. 123 0
      fs-repeat-api/src/main/java/com/fs/framework/config/DruidConfig.java
  46. 72 0
      fs-repeat-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  47. 59 0
      fs-repeat-api/src/main/java/com/fs/framework/config/FilterConfig.java
  48. 76 0
      fs-repeat-api/src/main/java/com/fs/framework/config/KaptchaTextCreator.java
  49. 150 0
      fs-repeat-api/src/main/java/com/fs/framework/config/MyBatisConfig.java
  50. 121 0
      fs-repeat-api/src/main/java/com/fs/framework/config/RedisConfig.java
  51. 65 0
      fs-repeat-api/src/main/java/com/fs/framework/config/ResourcesConfig.java
  52. 50 0
      fs-repeat-api/src/main/java/com/fs/framework/config/SecurityConfig.java
  53. 33 0
      fs-repeat-api/src/main/java/com/fs/framework/config/ServerConfig.java
  54. 121 0
      fs-repeat-api/src/main/java/com/fs/framework/config/SwaggerConfig.java
  55. 63 0
      fs-repeat-api/src/main/java/com/fs/framework/config/ThreadPoolConfig.java
  56. 77 0
      fs-repeat-api/src/main/java/com/fs/framework/config/properties/DruidProperties.java
  57. 27 0
      fs-repeat-api/src/main/java/com/fs/framework/datasource/DynamicDataSource.java
  58. 45 0
      fs-repeat-api/src/main/java/com/fs/framework/datasource/DynamicDataSourceContextHolder.java
  59. 56 0
      fs-repeat-api/src/main/java/com/fs/framework/interceptor/RepeatSubmitInterceptor.java
  60. 126 0
      fs-repeat-api/src/main/java/com/fs/framework/interceptor/impl/SameUrlDataInterceptor.java
  61. 56 0
      fs-repeat-api/src/main/java/com/fs/framework/manager/AsyncManager.java
  62. 40 0
      fs-repeat-api/src/main/java/com/fs/framework/manager/ShutdownManager.java
  63. 103 0
      fs-repeat-api/src/main/java/com/fs/framework/manager/factory/AsyncFactory.java
  64. 1 0
      fs-repeat-api/src/main/resources/META-INF/spring-devtools.properties
  65. 13 0
      fs-repeat-api/src/main/resources/application.yml
  66. 2 0
      fs-repeat-api/src/main/resources/banner.txt
  67. 37 0
      fs-repeat-api/src/main/resources/i18n/messages.properties
  68. 93 0
      fs-repeat-api/src/main/resources/logback.xml
  69. 15 0
      fs-repeat-api/src/main/resources/mybatis/mybatis-config.xml
  70. 7 0
      fs-service/pom.xml
  71. 29 0
      fs-service/src/main/java/com/cloud/host/CloudHostConfig.java
  72. 77 0
      fs-service/src/main/java/com/cloud/host/PoolInfoEnum.java
  73. 7 0
      fs-service/src/main/java/com/fs/ad/service/impl/AdHtmlClickLogServiceImpl.java
  74. 4 2
      fs-service/src/main/java/com/fs/baidu/api/ConvertData.java
  75. 1 0
      fs-service/src/main/java/com/fs/baidu/service/impl/BdAccountServiceImpl.java
  76. 5 1
      fs-service/src/main/java/com/fs/baidu/service/impl/BdApiServiceImpl.java
  77. 1 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseLink.java
  78. 2 1
      fs-service/src/main/java/com/fs/course/param/FsCourseLinkCreateParam.java
  79. 1 0
      fs-service/src/main/java/com/fs/course/param/FsUserCourseVideoAddKfUParam.java
  80. 10 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseLinkServiceImpl.java
  81. 10 3
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  82. 11 1
      fs-service/src/main/java/com/fs/his/service/impl/FsInquiryOrderServiceImpl.java
  83. 6 0
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralGoodsServiceImpl.java
  84. 8 0
      fs-service/src/main/java/com/fs/qw/domain/QwExternalContact.java
  85. 78 0
      fs-service/src/main/java/com/fs/qw/domain/QwGroupChatTransferLog.java
  86. 7 0
      fs-service/src/main/java/com/fs/qw/mapper/QwGroupChatMapper.java
  87. 18 0
      fs-service/src/main/java/com/fs/qw/mapper/QwGroupChatTransferLogMapper.java
  88. 25 0
      fs-service/src/main/java/com/fs/qw/param/ChatParam.java
  89. 5 0
      fs-service/src/main/java/com/fs/qw/param/QwGroupChatParam.java
  90. 15 0
      fs-service/src/main/java/com/fs/qw/param/QwGroupChatTransferLogParam.java
  91. 29 0
      fs-service/src/main/java/com/fs/qw/param/TransferChatParam.java
  92. 7 0
      fs-service/src/main/java/com/fs/qw/result/QwFsServerBindResult.java
  93. 21 0
      fs-service/src/main/java/com/fs/qw/result/ResultMessage.java
  94. 23 1
      fs-service/src/main/java/com/fs/qw/service/IQwGroupChatService.java
  95. 18 0
      fs-service/src/main/java/com/fs/qw/service/IQwGroupChatTransferLogService.java
  96. 7 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
  97. 27 6
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  98. 174 67
      fs-service/src/main/java/com/fs/qw/service/impl/QwGroupChatServiceImpl.java
  99. 29 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwGroupChatTransferLogServiceImpl.java
  100. 53 1
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java

+ 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

+ 12 - 1
fs-admin/src/main/java/com/fs/his/controller/FsIntegralGoodsController.java

@@ -8,6 +8,7 @@ import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.his.domain.FsIntegralGoods;
 import com.fs.his.domain.FsIntegralGoods;
 import com.fs.his.service.IFsIntegralGoodsService;
 import com.fs.his.service.IFsIntegralGoodsService;
+import com.fs.his.utils.RedisCacheUtil;
 import com.fs.his.vo.FsIntegralGoodsListVO;
 import com.fs.his.vo.FsIntegralGoodsListVO;
 import com.fs.his.vo.FsStoreProductExcelVO;
 import com.fs.his.vo.FsStoreProductExcelVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -29,7 +30,8 @@ public class FsIntegralGoodsController extends BaseController
 {
 {
     @Autowired
     @Autowired
     private IFsIntegralGoodsService fsIntegralGoodsService;
     private IFsIntegralGoodsService fsIntegralGoodsService;
-
+    @Autowired
+    RedisCacheUtil redisCacheUtil;
     /**
     /**
      * 查询积分商品列表
      * 查询积分商品列表
      */
      */
@@ -70,6 +72,8 @@ public class FsIntegralGoodsController extends BaseController
     @PostMapping("/importData")
     @PostMapping("/importData")
     public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
     public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
     {
     {
+        redisCacheUtil.delRedisKey("getIntegralGoodsList");
+        redisCacheUtil.delRedisKey("getIntegralGoodsById");
         ExcelUtil<FsIntegralGoods> util = new ExcelUtil<>(FsIntegralGoods.class);
         ExcelUtil<FsIntegralGoods> util = new ExcelUtil<>(FsIntegralGoods.class);
         List<FsIntegralGoods> list = util.importExcel(file.getInputStream());
         List<FsIntegralGoods> list = util.importExcel(file.getInputStream());
         String message = fsIntegralGoodsService.importIntegralGoodsService(list);
         String message = fsIntegralGoodsService.importIntegralGoodsService(list);
@@ -92,6 +96,8 @@ public class FsIntegralGoodsController extends BaseController
     @PostMapping
     @PostMapping
     public AjaxResult add(@RequestBody FsIntegralGoods fsIntegralGoods)
     public AjaxResult add(@RequestBody FsIntegralGoods fsIntegralGoods)
     {
     {
+        redisCacheUtil.delRedisKey("getIntegralGoodsList");
+        redisCacheUtil.delRedisKey("getIntegralGoodsById");
         return toAjax(fsIntegralGoodsService.insertFsIntegralGoods(fsIntegralGoods));
         return toAjax(fsIntegralGoodsService.insertFsIntegralGoods(fsIntegralGoods));
     }
     }
 
 
@@ -103,6 +109,9 @@ public class FsIntegralGoodsController extends BaseController
     @PutMapping
     @PutMapping
     public AjaxResult edit(@RequestBody FsIntegralGoods fsIntegralGoods)
     public AjaxResult edit(@RequestBody FsIntegralGoods fsIntegralGoods)
     {
     {
+
+        redisCacheUtil.delRedisKey("getIntegralGoodsList");
+        redisCacheUtil.delRedisKey("getIntegralGoodsById");
         return toAjax(fsIntegralGoodsService.updateFsIntegralGoods(fsIntegralGoods));
         return toAjax(fsIntegralGoodsService.updateFsIntegralGoods(fsIntegralGoods));
     }
     }
 
 
@@ -114,6 +123,8 @@ public class FsIntegralGoodsController extends BaseController
 	@DeleteMapping("/{goodsIds}")
 	@DeleteMapping("/{goodsIds}")
     public AjaxResult remove(@PathVariable Long[] goodsIds)
     public AjaxResult remove(@PathVariable Long[] goodsIds)
     {
     {
+        redisCacheUtil.delRedisKey("getIntegralGoodsList");
+        redisCacheUtil.delRedisKey("getIntegralGoodsById");
         return toAjax(fsIntegralGoodsService.deleteFsIntegralGoodsByGoodsIds(goodsIds));
         return toAjax(fsIntegralGoodsService.deleteFsIntegralGoodsByGoodsIds(goodsIds));
     }
     }
 }
 }

+ 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);
     }
     }
 }
 }

+ 68 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwGroupChatTransferController.java

@@ -0,0 +1,68 @@
+package com.fs.company.controller.qw;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.company.domain.CompanyUser;
+import com.fs.framework.security.SecurityUtils;
+import com.fs.qw.param.QwGroupChatParam;
+import com.fs.qw.param.TransferChatParam;
+import com.fs.qw.result.ResultMessage;
+import com.fs.qw.service.IQwGroupChatService;
+import com.fs.qw.vo.QwGroupChatTransferVO;
+import lombok.AllArgsConstructor;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@RestController
+@RequestMapping("/qw/groupChatTransfer")
+@AllArgsConstructor
+public class QwGroupChatTransferController extends BaseController {
+
+    private final IQwGroupChatService qwGroupChatService;
+
+    @PreAuthorize("@ss.hasPermi('qw:groupChatTransfer:listOnJob')")
+    @GetMapping("/listOnJob")
+    public TableDataInfo listOnJob(QwGroupChatParam qwGroupChat) {
+        startPage();
+        qwGroupChat.setStatusList(Arrays.asList("0", "3"));
+        List<QwGroupChatTransferVO> list = qwGroupChatService.selectQwGroupChatTransferList(qwGroupChat);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('qw:groupChatTransfer:transferOnJob')")
+    @PostMapping("/transferOnJob")
+    public AjaxResult transferOnJob(@RequestBody @Valid TransferChatParam param) {
+        if (param.getChatIds().size() > 100) {
+            return AjaxResult.error("单批次最大转移数为100个");
+        }
+        CompanyUser user = SecurityUtils.getLoginUser().getUser();
+        ResultMessage message = qwGroupChatService.processTransfer(param, user, false);
+        return AjaxResult.success(message.buildResultMessage());
+    }
+
+    @PreAuthorize("@ss.hasPermi('qw:groupChatTransfer:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwGroupChatParam qwGroupChat) {
+        startPage();
+        qwGroupChat.setStatusList(Collections.singletonList("1"));
+        List<QwGroupChatTransferVO> list = qwGroupChatService.selectQwGroupChatTransferList(qwGroupChat);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('qw:groupChatTransfer:transfer')")
+    @PostMapping("/transfer")
+    public AjaxResult transfer(@RequestBody @Valid TransferChatParam param) {
+        if (param.getChatIds().size() > 100) {
+            return AjaxResult.error("单批次最大转移数为100个");
+        }
+        CompanyUser user = SecurityUtils.getLoginUser().getUser();
+        ResultMessage message = qwGroupChatService.processTransfer(param, user, true);
+        return AjaxResult.success(message.buildResultMessage());
+    }
+}

+ 28 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwGroupChatTransferLogController.java

@@ -0,0 +1,28 @@
+package com.fs.company.controller.qw;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.qw.param.QwGroupChatTransferLogParam;
+import com.fs.qw.service.IQwGroupChatTransferLogService;
+import com.fs.qw.vo.QwGroupChatTransferLogVO;
+import lombok.AllArgsConstructor;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/qw/groupChatTransferLog")
+@AllArgsConstructor
+public class QwGroupChatTransferLogController extends BaseController {
+
+    private final IQwGroupChatTransferLogService groupChatTransferLogService;
+
+    @PreAuthorize("@ss.hasPermi('qw:groupChatTransferLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwGroupChatTransferLogParam param) {
+        startPage();
+        List<QwGroupChatTransferLogVO> list = groupChatTransferLogService.selectQwGroupChatTransferVOList(param);
+        return getDataTable(list);
+    }
+}

+ 9 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java

@@ -756,4 +756,13 @@ public class QwUserController extends BaseController
        }
        }
         return R.ok();
         return R.ok();
     }
     }
+
+    /**
+     * 重启云主机
+     * @return
+     */
+    @PutMapping("/restartHost")
+    public R restartCloudHost(@RequestParam String serverIp) {
+        return qwUserService.restartCloudHost(serverIp);
+    }
 }
 }

+ 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

+ 15 - 0
fs-doctor-app/src/main/java/com/fs/app/controller/IndexController.java

@@ -1,6 +1,7 @@
 package com.fs.app.controller;
 package com.fs.app.controller;
 
 
 
 
+import cn.hutool.json.JSONUtil;
 import com.fs.app.annotation.Login;
 import com.fs.app.annotation.Login;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
@@ -8,12 +9,15 @@ import com.fs.common.core.domain.R;
 import com.fs.his.service.IFsDataService;
 import com.fs.his.service.IFsDataService;
 import com.fs.his.service.IFsInquiryOrderService;
 import com.fs.his.service.IFsInquiryOrderService;
 import com.fs.his.vo.ChartDataVO;
 import com.fs.his.vo.ChartDataVO;
+import com.fs.store.config.ConceptConfig;
+import com.fs.system.service.ISysConfigService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.bind.annotation.RestController;
+import springfox.documentation.annotations.Cacheable;
 
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequest;
 import java.util.HashMap;
 import java.util.HashMap;
@@ -29,6 +33,8 @@ public class IndexController extends AppBaseController {
 	private IFsInquiryOrderService inquiryOrderService;
 	private IFsInquiryOrderService inquiryOrderService;
 	@Autowired
 	@Autowired
 	IFsDataService iFsDataService;
 	IFsDataService iFsDataService;
+	@Autowired
+	private ISysConfigService configService;
 
 
 	@Login
 	@Login
 	@ApiOperation("获取首页数据")
 	@ApiOperation("获取首页数据")
@@ -58,5 +64,14 @@ public class IndexController extends AppBaseController {
 		return R.ok().put("data",todayInquiryCount);
 		return R.ok().put("data",todayInquiryCount);
 	}
 	}
 
 
+	@ApiOperation("企业理念")
+	@GetMapping("/getConcept")
+	@Cacheable("getConcept")
+	public R getConcept(HttpServletRequest request){
+		String json=configService.selectConfigByKey("store.concept");
+		ConceptConfig config = JSONUtil.toBean(json, ConceptConfig.class);
+		return R.ok().put("data",config);
+	}
+
 
 
 }
 }

+ 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

+ 1 - 1
fs-qwhook-sop/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java

@@ -37,7 +37,7 @@ import java.util.List;
 
 
 @Api("课程库相关接口")
 @Api("课程库相关接口")
 @RestController
 @RestController
-@RequestMapping("/apis/app/fs/course")
+@RequestMapping("/app/fs/course")
 @Slf4j
 @Slf4j
 public class FsUserCourseVideoController {
 public class FsUserCourseVideoController {
 
 

+ 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>

+ 7 - 0
fs-service/pom.xml

@@ -284,6 +284,13 @@
             <version>1.0</version>
             <version>1.0</version>
         </dependency>
         </dependency>
 
 
+        <!-- 移动云ECS SDK -->
+        <dependency>
+            <groupId>com.ecloud.sdk</groupId>
+            <artifactId>ecloud-sdk-ecs</artifactId>
+            <version>1.1.26</version>
+        </dependency>
+
     </dependencies>
     </dependencies>
 
 
 </project>
 </project>

+ 29 - 0
fs-service/src/main/java/com/cloud/host/CloudHostConfig.java

@@ -0,0 +1,29 @@
+package com.cloud.host;
+
+import com.ecloud.sdk.config.Config;
+import com.ecloud.sdk.ecs.v1.Client;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class CloudHostConfig {
+    /**
+     * 使用AK&SK初始化账号Client
+     *
+     * @param accessKey
+     * @param secretKey
+     * @param poolId
+     * @return Client
+     */
+    public static Client createClient(String accessKey, String secretKey, String poolId) {
+        Config config = new Config();
+        config.setAccessKey(accessKey);
+        config.setSecretKey(secretKey);
+        config.setPoolId(poolId);
+        // 默认连接超时时间为60秒
+        config.setConnectTimeout(60);
+        // 默认响应超时时间为120秒
+        config.setReadTimeout(120);
+        return new Client(config);
+    }
+
+}

+ 77 - 0
fs-service/src/main/java/com/cloud/host/PoolInfoEnum.java

@@ -0,0 +1,77 @@
+package com.cloud.host;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.stream.Stream;
+
+@Getter
+@AllArgsConstructor
+public enum PoolInfoEnum {
+
+    CENTRAL_NODE("中心节点", "CIDC-CORE-00"),
+    YUNNAN_KUNMING("云南-昆明", "CIDC-RP-04"),
+    HUADONG_SUZHOU("华东-苏州", "CIDC-RP-25"),
+    HUANAN_GUANGZHOU3("华南-广州3", "CIDC-RP-26"),
+    XINAN_CHENGDU("西南-成都", "CIDC-RP-27"),
+    HUAZHONG_ZHENGZHOU1("华中-郑州1", "CIDC-RP-28"),
+    HUABEI_BEIJING3("华北-北京3", "CIDC-RP-29"),
+    HUAZHONG_CHANGSHA2("华中-长沙2", "CIDC-RP-30"),
+    HUADONG_JINAN("华东-济南", "CIDC-RP-31"),
+    XIBEI_XIAN("西北-西安", "CIDC-RP-32"),
+    HUADONG_SHANGHAI1("华东-上海1", "CIDC-RP-33"),
+    XINAN_CHONGQING("西南-重庆", "CIDC-RP-34"),
+    HUADONG_HANGZHOU("华东-杭州", "CIDC-RP-35"),
+    TIANJIN_TIANJIN("天津-天津", "CIDC-RP-36"),
+    JILIN_CHANGCHUN("吉林-长春", "CIDC-RP-37"),
+    HUBEI_XIANGYANG("湖北-襄阳", "CIDC-RP-38"),
+    JIANGXI_NANCHANG("江西-南昌", "CIDC-RP-39"),
+    GANSU_LANZHOU("甘肃-兰州", "CIDC-RP-40"),
+    SHANXI_TAIYUAN("山西-太原", "CIDC-RP-41"),
+    LIAONING_SHENYANG("辽宁-沈阳", "CIDC-RP-42"),
+    YUNNAN_KUNMING2("云南-昆明2", "CIDC-RP-43"),
+    HEBEI_SHIJIAZHUANG("河北-石家庄", "CIDC-RP-44"),
+    FUJIAN_XIAMEN("福建-厦门", "CIDC-RP-45"),
+    GUANGXI_NANNING("广西-南宁", "CIDC-RP-46"),
+    ANHUI_HUAINAN("安徽-淮南", "CIDC-RP-47"),
+    HUABEI_HUHEHAOTE("华北-呼和浩特", "CIDC-RP-48"),
+    XINAN_GUIYANG("西南-贵阳", "CIDC-RP-49"),
+    HAINAN_HAIKOU("海南-海口", "CIDC-RP-53"),
+    XINJIANG_CHANGJI("新疆-昌吉", "CIDC-RP-54"),
+    HEILONGJIANG_HAERBIN("黑龙江-哈尔滨", "CIDC-RP-55"),
+    NINGXIA_ZHONGWEI("宁夏-中卫", "CIDC-RP-60"),
+    QINGHAI_HAIDONG("青海-海东", "CIDC-RP-61"),
+    XIZANG_LASA("西藏-拉萨", "CIDC-RP-62"),
+    GUANGXI_NANNING3("广西-南宁3", "CIDC-RP-65");
+
+    private String poolName;
+    private String poolId;
+
+    /**
+     * 通过poolId精准获取poolName
+     *
+     * @param poolId 池ID
+     * @return poolName,如果未找到返回null
+     */
+    public static String getPoolNameByPoolId(String poolId) {
+        return Stream.of(values())
+                .filter(pool -> pool.getPoolId().equals(poolId))
+                .map(PoolInfoEnum::getPoolName)
+                .findFirst()
+                .orElse(null);
+    }
+
+    /**
+     * 通过城市名称匹配获取poolId
+     *
+     * @param cityName 城市名称
+     * @return poolId,如果未找到返回null
+     */
+    public static String getPoolIdByCityName(String cityName) {
+        return Stream.of(values())
+                .filter(pool -> pool.getPoolName().contains(cityName))
+                .map(PoolInfoEnum::getPoolId)
+                .findFirst()
+                .orElse(null);
+    }
+}

+ 7 - 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);
@@ -351,6 +355,9 @@ public class AdHtmlClickLogServiceImpl extends ServiceImpl<AdHtmlClickLogMapper,
         if(type == 2){
         if(type == 2){
             return AD_LOG_KEY + vid + ":" + type + ":" + clickType.getIqiyiType();
             return AD_LOG_KEY + vid + ":" + type + ":" + clickType.getIqiyiType();
         }
         }
+        if(type == 3){
+            return AD_LOG_KEY + vid + ":" + type + ":" + clickType.getDyType();
+        }
         throw new BaseException("错误类型");
         throw new BaseException("错误类型");
     }
     }
 
 

+ 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);

+ 5 - 1
fs-service/src/main/java/com/fs/baidu/service/impl/BdApiServiceImpl.java

@@ -11,6 +11,7 @@ import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.DateUtils;
 import org.apache.commons.text.StringSubstitutor;
 import org.apache.commons.text.StringSubstitutor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import com.fs.baidu.mapper.BdApiMapper;
 import com.fs.baidu.mapper.BdApiMapper;
 import com.fs.baidu.domain.BdApi;
 import com.fs.baidu.domain.BdApi;
@@ -28,6 +29,9 @@ public class BdApiServiceImpl extends ServiceImpl<BdApiMapper, BdApi> implements
 
 
     private final String OAUTH_URL = "https://u.baidu.com/oauth/page/index?platformId=4960345965958561794&appId={appId}&scope={scope}&state={state}&callback={callback}";
     private final String OAUTH_URL = "https://u.baidu.com/oauth/page/index?platformId=4960345965958561794&appId={appId}&scope={scope}&state={state}&callback={callback}";
 
 
+    @Value("${baidu.back-domain}")
+    private String backDomain;
+
     /**
     /**
      * 查询百度信息
      * 查询百度信息
      * 
      * 
@@ -117,7 +121,7 @@ public class BdApiServiceImpl extends ServiceImpl<BdApiMapper, BdApi> implements
         params.put("appId", api.getAppId());
         params.put("appId", api.getAppId());
         params.put("scope", "65,66,67,68,69,70,1009333,1009334,71,72,1009335,73,74,75,1001788,1001789,1001455,1001790,1001791,1002161,1002829,1004606");
         params.put("scope", "65,66,67,68,69,70,1009333,1009334,71,72,1009335,73,74,75,1001788,1001789,1001455,1001790,1001791,1002161,1002829,1004606");
         params.put("state", id.toString());
         params.put("state", id.toString());
-        params.put("callback", "https://bd.runtzh.com/prod-api/baiduBack/callback");
+        params.put("callback", "http://" + backDomain +"/prod-api/baiduBack/callback");
         String filledUri = UriComponentsBuilder
         String filledUri = UriComponentsBuilder
                 .fromUriString(OAUTH_URL)
                 .fromUriString(OAUTH_URL)
                 .buildAndExpand(params)
                 .buildAndExpand(params)

+ 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();

+ 10 - 3
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();
 
 
         // 处理逻辑
         // 处理逻辑
@@ -927,8 +935,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         redPacketLog.setRemark("点播答题领取积分转");
         redPacketLog.setRemark("点播答题领取积分转");
         redPacketLog.setWatchLogId(log.getLogId() !=null ? log.getLogId() : null);
         redPacketLog.setWatchLogId(log.getLogId() !=null ? log.getLogId() : null);
         redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
         redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
-
-        return R.ok("奖励发放成功");
+        return R.ok("奖励发放成功").put("rewardType",config.getRewardType());
     }
     }
 
 
     @Override
     @Override

+ 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();

+ 6 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsIntegralGoodsServiceImpl.java

@@ -190,6 +190,12 @@ public class FsIntegralGoodsServiceImpl implements IFsIntegralGoodsService
         param.setMaxPoints(stageMaxPoints);
         param.setMaxPoints(stageMaxPoints);
         List<FsIntegralGoods> stageGoods = selectRandomStageGoods(param);
         List<FsIntegralGoods> stageGoods = selectRandomStageGoods(param);
 
 
+        if (stageGoods.size()<3){
+            param.setMinPoints(currentStage);
+            param.setMaxPoints(stageMaxPoints);
+            stageGoods = selectRandomStageGoods(param);
+        }
+
         // 5. 转换为奖励商品列表
         // 5. 转换为奖励商品列表
         List<FsCourseAnswerReward.RewardProduct> products = new ArrayList<>();
         List<FsCourseAnswerReward.RewardProduct> products = new ArrayList<>();
         int minRequiredPoints = stageMaxPoints; // 用于计算进度的最小积分要求
         int minRequiredPoints = stageMaxPoints; // 用于计算进度的最小积分要求

+ 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;
 }
 }

+ 78 - 0
fs-service/src/main/java/com/fs/qw/domain/QwGroupChatTransferLog.java

@@ -0,0 +1,78 @@
+package com.fs.qw.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("qw_group_chat_transfer_log")
+public class QwGroupChatTransferLog {
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /**
+     * 企微主体ID
+     */
+    private String corpId;
+    /**
+     * 转移类型 0:在职继承 1:离职继承
+     */
+    private Integer transferType;
+    /**
+     * 公司ID
+     */
+    private Long companyId;
+    /**
+     * 销售ID
+     */
+    private Long companyUserId;
+    /**
+     * 原归属群主ID
+     */
+    private String oldOwner;
+    /**
+     * 原归属销售ID
+     */
+    private Long oldCompanyUserId;
+    /**
+     * 原归属企微用户ID
+     */
+    private Long oldQwUserId;
+    /**
+     * 原归属企微用户名称
+     */
+    private String oldQwUserName;
+    /**
+     * 接替群主ID
+     */
+    private String newOwner;
+    /**
+     * 接替销售ID
+     */
+    private Long newCompanyUserId;
+    /**
+     * 接替企微用户ID
+     */
+    private Long newQwUserId;
+    /**
+     * 接替企微用户名称
+     */
+    private String newQwUserName;
+    /**
+     * 客户群ID
+     */
+    private String chatId;
+    /**
+     * 客户群名称
+     */
+    private String chatName;
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+}

+ 7 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwGroupChatMapper.java

@@ -3,6 +3,7 @@ package com.fs.qw.mapper;
 import com.fs.qw.domain.QwGroupChat;
 import com.fs.qw.domain.QwGroupChat;
 import com.fs.qw.param.QwGroupChatParam;
 import com.fs.qw.param.QwGroupChatParam;
 import com.fs.qw.vo.QwGroupChatOptionsVO;
 import com.fs.qw.vo.QwGroupChatOptionsVO;
+import com.fs.qw.vo.QwGroupChatTransferVO;
 import com.fs.qw.vo.QwGroupChatVO;
 import com.fs.qw.vo.QwGroupChatVO;
 import org.apache.ibatis.annotations.Delete;
 import org.apache.ibatis.annotations.Delete;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Param;
@@ -115,4 +116,10 @@ public interface QwGroupChatMapper
 
 
     List<QwGroupChat> selectQwGroupChatByChatIds(@Param("ids") String[] ids);
     List<QwGroupChat> selectQwGroupChatByChatIds(@Param("ids") String[] ids);
 
 
+    /**
+     * 查询客户群列表
+     * @param qwGroupChat   参数
+     * @return  list
+     */
+    List<QwGroupChatTransferVO> selectQwGroupChatTransferList(QwGroupChatParam qwGroupChat);
 }
 }

+ 18 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwGroupChatTransferLogMapper.java

@@ -0,0 +1,18 @@
+package com.fs.qw.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.qw.domain.QwGroupChatTransferLog;
+import com.fs.qw.param.QwGroupChatTransferLogParam;
+import com.fs.qw.vo.QwGroupChatTransferLogVO;
+
+import java.util.List;
+
+public interface QwGroupChatTransferLogMapper extends BaseMapper<QwGroupChatTransferLog> {
+
+    /**
+     * 查询客户群转移记录
+     * @param param 参数
+     * @return  list
+     */
+    List<QwGroupChatTransferLogVO> selectQwGroupChatTransferVOList(QwGroupChatTransferLogParam param);
+}

+ 25 - 0
fs-service/src/main/java/com/fs/qw/param/ChatParam.java

@@ -0,0 +1,25 @@
+package com.fs.qw.param;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@Data
+public class ChatParam {
+    /**
+     * 群聊ID
+     */
+    @NotBlank(message = "客户群不能为空")
+    private String chatId;
+    /**
+     * 群主企微ID
+     */
+    @NotNull(message = "群主企微账户不能为空")
+    private Long qwId;
+    /**
+     * 群主ID
+     */
+    @NotBlank(message = "群主ID不能为空")
+    private String owner;
+}

+ 5 - 0
fs-service/src/main/java/com/fs/qw/param/QwGroupChatParam.java

@@ -3,6 +3,8 @@ package com.fs.qw.param;
 import com.fs.common.annotation.Excel;
 import com.fs.common.annotation.Excel;
 import lombok.Data;
 import lombok.Data;
 
 
+import java.util.List;
+
 @Data
 @Data
 public class QwGroupChatParam {
 public class QwGroupChatParam {
     private static final long serialVersionUID = 1L;
     private static final long serialVersionUID = 1L;
@@ -45,4 +47,7 @@ public class QwGroupChatParam {
 
 
     private String qwUserList;
     private String qwUserList;
 
 
+    /** 状态集合 */
+    private List<String> statusList;
+
 }
 }

+ 15 - 0
fs-service/src/main/java/com/fs/qw/param/QwGroupChatTransferLogParam.java

@@ -0,0 +1,15 @@
+package com.fs.qw.param;
+
+import lombok.Data;
+
+@Data
+public class QwGroupChatTransferLogParam {
+    /**
+     * 企微主体ID
+     */
+    private String corpId;
+    /**
+     * 群名
+     */
+    private String name;
+}

+ 29 - 0
fs-service/src/main/java/com/fs/qw/param/TransferChatParam.java

@@ -0,0 +1,29 @@
+package com.fs.qw.param;
+
+import lombok.Data;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Data
+public class TransferChatParam {
+    /**
+     * 客户群ID集合
+     */
+    @Valid
+    @NotEmpty(message = "转移客户群不能为空")
+    private List<ChatParam> chatIds;
+    /**
+     * 接替企微用户
+     */
+    @NotNull(message = "接替企微用户不能为空")
+    private Long qwId;
+    /**
+     * 企微主体ID
+     */
+    @NotBlank(message = "企微主体不能为空")
+    private String corpId;
+}

+ 7 - 0
fs-service/src/main/java/com/fs/qw/result/QwFsServerBindResult.java

@@ -8,9 +8,16 @@ public class QwFsServerBindResult {
     public int code;
     public int code;
     public Data data;
     public Data data;
 
 
+    @lombok.Data
     public static class Data {
     public static class Data {
         public String ipAddress;
         public String ipAddress;
         public boolean flag;
         public boolean flag;
         public String msg;
         public String msg;
+
+        private String serverId; // 云主机id
+        private String city; //城市
+        private String yidongAccount; // 移动云账号
+        private String accesskey;
+        private String secretKey;
     }
     }
 }
 }

+ 21 - 0
fs-service/src/main/java/com/fs/qw/result/ResultMessage.java

@@ -0,0 +1,21 @@
+package com.fs.qw.result;
+
+
+public class ResultMessage {
+    private int successNum = 0;
+    private int failureNum = 0;
+    private final StringBuilder errorMsg = new StringBuilder();
+
+    public void addSuccess() {
+        successNum++;
+    }
+
+    public void addFailure(String title, String error) {
+        failureNum++;
+        errorMsg.append("<br/>").append(title).append(" 失败:").append(error);
+    }
+
+    public String buildResultMessage() {
+        return "成功" + successNum + " 条,失败" + failureNum + "条。" + errorMsg;
+    }
+}

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

@@ -1,11 +1,16 @@
 package com.fs.qw.service;
 package com.fs.qw.service;
 
 
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
+import com.fs.company.domain.CompanyUser;
 import com.fs.qw.domain.QwGroupChat;
 import com.fs.qw.domain.QwGroupChat;
 import com.fs.qw.param.QwGroupChatParam;
 import com.fs.qw.param.QwGroupChatParam;
+import com.fs.qw.param.TransferChatParam;
+import com.fs.qw.result.ResultMessage;
 import com.fs.qw.vo.QwGroupChatOptionsVO;
 import com.fs.qw.vo.QwGroupChatOptionsVO;
+import com.fs.qw.vo.QwGroupChatTransferVO;
 import com.fs.qw.vo.QwGroupChatVO;
 import com.fs.qw.vo.QwGroupChatVO;
 
 
+import javax.validation.Valid;
 import java.util.List;
 import java.util.List;
 
 
 /**
 /**
@@ -62,7 +67,24 @@ 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);
+
+    /**
+     * 查询客户群列表
+     * @param qwGroupChat 参数
+     * @return  list
+     */
+    List<QwGroupChatTransferVO> selectQwGroupChatTransferList(QwGroupChatParam qwGroupChat);
+
+    /**
+     * 分配客户群
+     *
+     * @param param         参数
+     * @param user          当前登录销售
+     * @param isResigned    是否在职人员客户群
+     * @return message
+     */
+    ResultMessage processTransfer(TransferChatParam param, CompanyUser user, boolean isResigned);
 }
 }

+ 18 - 0
fs-service/src/main/java/com/fs/qw/service/IQwGroupChatTransferLogService.java

@@ -0,0 +1,18 @@
+package com.fs.qw.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.qw.domain.QwGroupChatTransferLog;
+import com.fs.qw.param.QwGroupChatTransferLogParam;
+import com.fs.qw.vo.QwGroupChatTransferLogVO;
+
+import java.util.List;
+
+public interface IQwGroupChatTransferLogService extends IService<QwGroupChatTransferLog> {
+
+    /**
+     * 查询客户群转移记录
+     * @param param 参数
+     * @return list
+     */
+    List<QwGroupChatTransferLogVO> selectQwGroupChatTransferVOList(QwGroupChatTransferLogParam param);
+}

+ 7 - 0
fs-service/src/main/java/com/fs/qw/service/IQwUserService.java

@@ -164,4 +164,11 @@ public interface IQwUserService
     R delQwIpad(QwLoginHookParam loginParam);
     R delQwIpad(QwLoginHookParam loginParam);
 
 
     List<QwWorkTask> selectQwWorkTaskList(SelectQwWorkTaskListParam param);
     List<QwWorkTask> selectQwWorkTaskList(SelectQwWorkTaskListParam param);
+
+    /**
+     * 重启云主机
+     * @param IP 服务器ip
+     * @return
+     */
+    R restartCloudHost(String IP);
 }
 }

+ 27 - 6
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);
         }
         }
@@ -1614,6 +1616,11 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
             return JSON.parseObject(key, QwUser.class);
             return JSON.parseObject(key, QwUser.class);
         }
         }
         QwUser qwUser = qwUserMapper.selectQwUserByCorpIdAndUserId(corpId, userID);
         QwUser qwUser = qwUserMapper.selectQwUserByCorpIdAndUserId(corpId, userID);
+
+        if (qwUser==null){
+            return null;
+        }
+
         redisCache.setCacheObject("qwUserRd:"+qwUser.getCorpId()+":"+qwUser.getQwUserId() ,JSON.toJSONString(qwUser),1, TimeUnit.HOURS);
         redisCache.setCacheObject("qwUserRd:"+qwUser.getCorpId()+":"+qwUser.getQwUserId() ,JSON.toJSONString(qwUser),1, TimeUnit.HOURS);
         return qwUser;
         return qwUser;
     };
     };
@@ -1637,12 +1644,26 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         qwExternalContact.setExternalUserId(externalUserID); // 设置外部联系人ID
         qwExternalContact.setExternalUserId(externalUserID); // 设置外部联系人ID
         qwExternalContact.setCorpId(corpId);
         qwExternalContact.setCorpId(corpId);
         qwExternalContact.setCreateTime(new Date());
         qwExternalContact.setCreateTime(new Date());
-
+        if(StringUtils.isNotEmpty(state)){
+            try {
+                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){
+                logger.error("广告回调上传",e);
+            }
+        }
         try {
         try {
-            logger.info("上传mq");
-            rocketMQTemplate.syncSend("ad-upload", JSON.toJSONString(AdUploadVo.builder().state(state).type(AdUploadType.ADD_WX).build()));
+            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){
         }catch (Exception e){
-            logger.error("广告回调上传",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));
 
 

+ 174 - 67
fs-service/src/main/java/com/fs/qw/service/impl/QwGroupChatServiceImpl.java

@@ -1,20 +1,31 @@
 package com.fs.qw.service.impl;
 package com.fs.qw.service.impl;
 
 
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
+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.qw.domain.*;
+import com.fs.common.utils.StringUtils;
 import com.fs.company.service.ICompanyConfigService;
 import com.fs.company.service.ICompanyConfigService;
 import com.fs.course.service.IFsCourseLinkService;
 import com.fs.course.service.IFsCourseLinkService;
 import com.fs.qw.domain.QwCompany;
 import com.fs.qw.domain.QwCompany;
 import com.fs.qw.domain.QwGroupChat;
 import com.fs.qw.domain.QwGroupChat;
 import com.fs.qw.domain.QwGroupChatUser;
 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.QwGroupChatParam;
 import com.fs.qw.param.QwGroupChatParam;
+import com.fs.qw.param.TransferChatParam;
+import com.fs.qw.result.ResultMessage;
 import com.fs.qw.service.*;
 import com.fs.qw.service.*;
 import com.fs.qw.vo.QwGroupChatOptionsVO;
 import com.fs.qw.vo.QwGroupChatOptionsVO;
+import com.fs.qw.vo.QwGroupChatTransferVO;
 import com.fs.qw.vo.QwGroupChatVO;
 import com.fs.qw.vo.QwGroupChatVO;
 import com.fs.qwApi.Result.QwGroupChatDetailsResult;
 import com.fs.qwApi.Result.QwGroupChatDetailsResult;
 import com.fs.qwApi.Result.QwGroupChatListResult;
 import com.fs.qwApi.Result.QwGroupChatListResult;
+import com.fs.qwApi.domain.QwGroupChatTransferResult;
+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.QwSopMapper;
 import com.fs.sop.mapper.SopUserLogsInfoMapper;
 import com.fs.sop.mapper.SopUserLogsInfoMapper;
 import com.fs.sop.service.IQwSopLogsService;
 import com.fs.sop.service.IQwSopLogsService;
@@ -24,16 +35,19 @@ import com.fs.sop.service.ISopUserLogsService;
 import com.fs.sop.service.impl.QwSopLogsServiceImpl;
 import com.fs.sop.service.impl.QwSopLogsServiceImpl;
 import com.fs.sop.service.impl.QwSopServiceImpl;
 import com.fs.sop.service.impl.QwSopServiceImpl;
 import com.fs.voice.utils.StringUtil;
 import com.fs.voice.utils.StringUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 
 import java.text.SimpleDateFormat;
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.Date;
 import java.util.List;
 import java.util.List;
+import java.util.stream.Collectors;
 
 
 /**
 /**
  * 客户群详情Service业务层处理
  * 客户群详情Service业务层处理
@@ -44,80 +58,21 @@ import java.util.List;
 @Service
 @Service
 public class QwGroupChatServiceImpl implements IQwGroupChatService
 public class QwGroupChatServiceImpl implements IQwGroupChatService
 {
 {
-    private static final Logger log = LoggerFactory.getLogger(QwGroupChatServiceImpl.class);
     @Autowired
     @Autowired
     private QwGroupChatMapper qwGroupChatMapper;
     private QwGroupChatMapper qwGroupChatMapper;
-
     @Autowired
     @Autowired
     private QwGroupChatUserMapper qwGroupChatUserMapper;
     private QwGroupChatUserMapper qwGroupChatUserMapper;
-
-    @Autowired
-    private QwApiService qwApiService;
-
-    @Autowired
-    private ICompanyConfigService companyConfigService;
-
-    @Autowired
-    private IFsCourseLinkService iFsCourseLinkService;
-
-    @Autowired
-    IQwExternalContactService qwExternalContactService;
-
-    @Autowired
-    IQwCompanyService iQwCompanyService;
-
-    @Autowired
-    private ISopUserLogsInfoService sopUserLogsInfoService;
-
-    @Autowired
-    private IQwSopService iQwSopService;
-
-    @Autowired
-    private IQwSopLogsService iQwSopLogsService;
-
-    @Autowired
-    private IQwGroupMsgService iQwGroupMsgService;
-
-    @Autowired
-    private QwSopLogsServiceImpl qwSopLogsService;
-
-    @Autowired
-    private QwSopServiceImpl qwSopService;
-
     @Autowired
     @Autowired
-    QwAutoTagsMapper qwAutoTagsMapper;
+    private QwSopMapper sopMapper;
 
 
     @Autowired
     @Autowired
-    QwCompanyMapper qwCompanyMapper;
-
+    private QwApiService qwApiService;
     @Autowired
     @Autowired
-    private QwSopMapper qwSopMapper;
-
+    private QwCompanyMapper qwCompanyMapper;
     @Autowired
     @Autowired
     private QwUserMapper qwUserMapper;
     private QwUserMapper qwUserMapper;
-
-    @Autowired
-    private QwExternalContactMapper qwExternalContactMapper;
-    @Autowired
-    private ISopUserLogsService sopUserLogsService;
-
-    @Autowired
-    private ISopUserLogsInfoService iSopUserLogsInfoService;
-
-    @Autowired
-    private QwAppContactWayMapper  qwAppContactWayMapper;
-
-    @Autowired
-    private QwAppContactWayLogsMapper qwAppContactWayLogsMapper;
-
-    @Autowired
-    private IQwExternalErrRetryService errRetryService;
-
     @Autowired
     @Autowired
-    private SopUserLogsInfoMapper sopUserLogsInfoMapper;
-
-//    @Autowired
-//    private IQwGetJsapiTicketService qwGetJsapiTicketService;
+    private IQwGroupChatTransferLogService qwGroupChatTransferLogService;
 
 
     /**
     /**
      * 查询客户群详情
      * 查询客户群详情
@@ -416,6 +371,15 @@ public class QwGroupChatServiceImpl implements IQwGroupChatService
                     });
                     });
 
 
                 }
                 }
+                // 处理离职查询不到详情的情况
+                else {
+                    if (list.getStatus() == 1) {
+                        QwGroupChat qwGroupChatResigned = qwGroupChatMapper.selectQwGroupChatByChatId(list.getChat_id());
+                        qwGroupChatResigned.setUpdateTime(new Date());
+                        qwGroupChatResigned.setStatus(String.valueOf(list.getStatus()));
+                        qwGroupChatMapper.updateQwGroupChat(qwGroupChatResigned);
+                    }
+                }
             }
             }
 
 
             if (!StringUtil.strIsNullOrEmpty(qwGroupChatListResult.getNext_cursor())){
             if (!StringUtil.strIsNullOrEmpty(qwGroupChatListResult.getNext_cursor())){
@@ -431,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
@@ -440,4 +415,136 @@ public class QwGroupChatServiceImpl implements IQwGroupChatService
         return qwGroupChatMapper.selectQwGroupChatByChatIds(ids);
         return qwGroupChatMapper.selectQwGroupChatByChatIds(ids);
     }
     }
 
 
+    /**
+     * 查询客户群列表
+     * @param qwGroupChat 参数
+     * @return  list
+     */
+    @Override
+    public List<QwGroupChatTransferVO> selectQwGroupChatTransferList(QwGroupChatParam qwGroupChat) {
+        return qwGroupChatMapper.selectQwGroupChatTransferList(qwGroupChat);
+    }
+
+    /**
+     * 客户群转移
+     * @param param         参数
+     * @param user          当前销售
+     * @param isResigned    是否转移离职员工客户群
+     * @return  message
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public ResultMessage processTransfer(TransferChatParam param, CompanyUser user, boolean isResigned) {
+        ResultMessage resultMessage = new ResultMessage();
+
+        QwUser qwUser = qwUserMapper.selectQwUserById(param.getQwId());
+        if (Objects.isNull(qwUser)) {
+            throw new CustomException("接替群主不存在");
+        }
+
+        // 准备转移参数
+        QwGroupChatTransferParam transferParam = new QwGroupChatTransferParam();
+        transferParam.setChat_id_list(param.getChatIds().stream()
+                .map(ChatParam::getChatId)
+                .collect(Collectors.toList()));
+        transferParam.setNew_owner(qwUser.getQwUserId());
+
+        // 调用不同的API
+        QwGroupChatTransferResult result = isResigned ?
+                qwApiService.resignedGroupChatTransfer(transferParam, param.getCorpId()) :
+                qwApiService.groupChatTransfer(transferParam, param.getCorpId());
+
+        if (result.getErrcode() != 0) {
+            throw new CustomException(result.getErrmsg());
+        }
+
+        // 处理失败结果
+        result.getFailed_chat_list().forEach(failed -> resultMessage.addFailure(failed.getChat_id(), failed.getErrmsg()));
+
+        // 过滤掉转移失败的群聊
+        List<ChatParam> successChatParams = filterSuccessChats(param.getChatIds(), result.getFailed_chat_list());
+
+        // 处理成功的转移记录
+        processSuccessTransfers(successChatParams, user, qwUser, resultMessage, isResigned);
+
+        return resultMessage;
+    }
+
+    /**
+     * 过滤转移失败客户群
+     * @param chatParams    入参
+     * @param failedChats   失败列表
+     * @return  客户群
+     */
+    private List<ChatParam> filterSuccessChats(List<ChatParam> chatParams, List<QwGroupChatTransferResult.FailedChat> failedChats) {
+        Set<String> failedChatIds = failedChats.stream()
+                .map(QwGroupChatTransferResult.FailedChat::getChat_id)
+                .collect(Collectors.toSet());
+
+        return chatParams.stream()
+                .filter(c -> !failedChatIds.contains(c.getChatId()))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 处理转移成功客户群
+     *
+     * @param successChatParams 转移成功客户群
+     * @param user              当前销售账户
+     * @param newOwner          新群主
+     * @param resultMessage     消息
+     * @param isResigned        是否转移离职员工客户群
+     */
+    private void processSuccessTransfers(List<ChatParam> successChatParams, CompanyUser user, QwUser newOwner, ResultMessage resultMessage, boolean isResigned) {
+        List<QwGroupChatTransferLog> transferLogs = successChatParams.stream()
+                .map(chatParam -> createTransferLog(chatParam, user, newOwner, isResigned))
+                .collect(Collectors.toList());
+
+        if (!transferLogs.isEmpty()) {
+            qwGroupChatTransferLogService.saveBatch(transferLogs);
+            transferLogs.forEach(log -> resultMessage.addSuccess());
+        }
+    }
+
+    /**
+     * 保存转移记录修改群主
+     * @param chatParam  客户群
+     * @param user       当前销售
+     * @param newOwner   新群主
+     * @param isResigned 是否转移离职员工客户群
+     * @return  log
+     */
+    private QwGroupChatTransferLog createTransferLog(ChatParam chatParam, CompanyUser user, QwUser newOwner, boolean isResigned) {
+        QwGroupChat qwGroupChat = qwGroupChatMapper.selectQwGroupChatByChatId(chatParam.getChatId());
+        QwUser oldOwner = qwUserMapper.selectQwUserById(chatParam.getQwId());
+
+        // 更新群主信息
+        qwGroupChat.setOwner(newOwner.getQwUserId());
+        qwGroupChat.setUpdateTime(new Date());
+        if (isResigned) {
+            qwGroupChat.setStatus("3");
+        }
+        qwGroupChatMapper.updateQwGroupChat(qwGroupChat);
+
+        // 创建转移日志
+        QwGroupChatTransferLog transferLog = new QwGroupChatTransferLog();
+        transferLog.setChatName(qwGroupChat.getName());
+        transferLog.setCorpId(qwGroupChat.getCorpId());
+        transferLog.setTransferType(isResigned ? 1 : 0);
+        transferLog.setCompanyId(user.getCompanyId());
+        transferLog.setCompanyUserId(user.getUserId());
+        transferLog.setOldOwner(oldOwner.getQwUserId());
+        transferLog.setOldCompanyUserId(oldOwner.getCompanyUserId());
+        transferLog.setOldQwUserName(oldOwner.getQwUserName());
+        transferLog.setOldQwUserId(oldOwner.getId());
+        transferLog.setNewOwner(newOwner.getQwUserId());
+        transferLog.setNewCompanyUserId(newOwner.getCompanyUserId());
+        transferLog.setNewQwUserName(newOwner.getQwUserName());
+        transferLog.setNewQwUserId(newOwner.getId());
+        transferLog.setChatId(chatParam.getChatId());
+        transferLog.setCreateTime(LocalDateTime.now());
+
+        return transferLog;
+    }
+
 }
 }

+ 29 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwGroupChatTransferLogServiceImpl.java

@@ -0,0 +1,29 @@
+package com.fs.qw.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.qw.domain.QwGroupChatTransferLog;
+import com.fs.qw.mapper.QwGroupChatTransferLogMapper;
+import com.fs.qw.param.QwGroupChatTransferLogParam;
+import com.fs.qw.service.IQwGroupChatTransferLogService;
+import com.fs.qw.vo.QwGroupChatTransferLogVO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.List;
+
+@Service
+@Slf4j
+public class QwGroupChatTransferLogServiceImpl extends ServiceImpl<QwGroupChatTransferLogMapper, QwGroupChatTransferLog>
+        implements IQwGroupChatTransferLogService {
+
+    /**
+     * 查询客户群转移记录
+     * @param param 参数
+     * @return list
+     */
+    @Override
+    public List<QwGroupChatTransferLogVO> selectQwGroupChatTransferVOList(QwGroupChatTransferLogParam param) {
+        return baseMapper.selectQwGroupChatTransferVOList(param);
+    }
+}

+ 53 - 1
fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java

@@ -3,6 +3,12 @@ package com.fs.qw.service.impl;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.http.HttpRequest;
 import cn.hutool.http.HttpRequest;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSON;
+import com.cloud.host.CloudHostConfig;
+import com.cloud.host.PoolInfoEnum;
+import com.ecloud.sdk.ecs.v1.Client;
+import com.ecloud.sdk.ecs.v1.model.VmRebootPath;
+import com.ecloud.sdk.ecs.v1.model.VmRebootRequest;
+import com.ecloud.sdk.ecs.v1.model.VmRebootResponse;
 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.company.domain.CompanyUser;
 import com.fs.company.domain.CompanyUser;
@@ -34,7 +40,9 @@ import com.fs.qwApi.service.QwApiService;
 import com.fs.voice.utils.StringUtil;
 import com.fs.voice.utils.StringUtil;
 import com.fs.wxwork.dto.*;
 import com.fs.wxwork.dto.*;
 import com.fs.wxwork.service.WxWorkService;
 import com.fs.wxwork.service.WxWorkService;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.commons.lang3.tuple.Pair;
+import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.scheduling.annotation.EnableAsync;
@@ -62,7 +70,7 @@ import java.util.stream.Collectors;
 @EnableAsync
 @EnableAsync
 public class QwUserServiceImpl implements IQwUserService
 public class QwUserServiceImpl implements IQwUserService
 {
 {
-    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(QwUserServiceImpl.class);
+    private static final Logger logger = LoggerFactory.getLogger(QwUserServiceImpl.class);
     @Autowired
     @Autowired
     QwApiService qwApiService;
     QwApiService qwApiService;
     @Autowired
     @Autowired
@@ -1305,6 +1313,50 @@ public class QwUserServiceImpl implements IQwUserService
 
 
         return R.ok(status.toString());
         return R.ok(status.toString());
     }
     }
+
+    @Override
+    public R restartCloudHost(String IP) {
+        // 调用SDK必须要的参数
+        String serverId = "";
+        String poolId = "";
+        String accessKey= "";
+        String secretKey ="";
+        try {
+            String bodyData = HttpRequest.get("http://watch.ylrzcloud.com/prod-api/server/getServerId?ipAddress="+IP)
+                    .execute().body();
+            QwFsServerBindResult qwFsServerBindResult = JSON.parseObject(bodyData, QwFsServerBindResult.class);
+            if(qwFsServerBindResult.getData() != null){
+                QwFsServerBindResult.Data data = qwFsServerBindResult.getData();
+                serverId = data.getServerId();
+                poolId = PoolInfoEnum.getPoolIdByCityName(data.getCity() != null ? data.getCity().substring(0, data.getCity().length() - 1):"");
+                accessKey = data.getAccesskey();
+                secretKey = data.getSecretKey();
+            }
+        } catch (Exception e){
+            e.printStackTrace();
+            logger.error("获取主机信息异常,服务器IP:{}", IP);
+        }
+
+        if(StringUtils.isNotEmpty(serverId) && StringUtils.isNotEmpty(poolId) && StringUtils.isNotEmpty(accessKey) && StringUtils.isNotEmpty(secretKey)){
+            Client client = CloudHostConfig.createClient(accessKey, secretKey, poolId);
+            VmRebootRequest.Builder requestBuilder = VmRebootRequest.builder();
+            VmRebootPath vmRebootPath = VmRebootPath.builder().serverId(serverId).build();
+            requestBuilder.vmRebootPath(vmRebootPath);
+            VmRebootRequest request = requestBuilder.build();
+            VmRebootResponse result = client.vmReboot(request);
+            System.out.println(result);
+
+            if(!VmRebootResponse.StateEnum.OK.equals(result.getState())){
+                return R.error(501,"重启主机异常," + result.getErrorMessage()).put("errMsg", result);
+            }
+
+            return R.ok();
+        } else {
+            logger.error("重启云主机异常,获取主机信息异常,serverId:{},poolId:{},accessKey:{},secretKey:{}", serverId, poolId, accessKey, secretKey);
+            return R.error(400,"重启云主机异常,获取主机信息异常");
+        }
+    }
+
     /**
     /**
      *  获取文件名
      *  获取文件名
      */
      */

Некоторые файлы не были показаны из-за большого количества измененных файлов