Ver código fonte

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java

caoliqin 4 dias atrás
pai
commit
ccafab165a
100 arquivos alterados com 4423 adições e 105 exclusões
  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. 138 0
      fs-admin/src/main/java/com/fs/qw/controller/QwExternalContactController.java
  5. 41 0
      fs-admin/src/main/java/com/fs/qw/controller/QwTagGroupController.java
  6. 10 0
      fs-admin/src/main/java/com/fs/qw/controller/QwUserController.java
  7. 2 7
      fs-admin/src/main/resources/application.yml
  8. 0 1
      fs-ai-chat/src/main/resources/application.yml
  9. 0 2
      fs-common-api/src/main/resources/application.yml
  10. 0 2
      fs-company-app/src/main/resources/application.yml
  11. 2 2
      fs-company/src/main/java/com/fs/company/controller/qw/QwGroupChatController.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. 39 0
      fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java
  26. 1 1
      fs-qwhook-sop/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  27. 39 0
      fs-qwhook-sop/src/main/java/com/fs/app/controller/QwStatisticsController.java
  28. 69 0
      fs-qwhook-sop/src/main/java/com/fs/app/controller/QwUserController.java
  29. 92 0
      fs-qwhook-sop/src/main/java/com/fs/app/controller/QwWorkTaskController.java
  30. 0 5
      fs-qwhook-sop/src/main/resources/application.yml
  31. 132 0
      fs-qwhook/src/main/java/com/fs/app/controller/ApisCommonController.java
  32. 200 0
      fs-qwhook/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java
  33. 39 0
      fs-qwhook/src/main/java/com/fs/app/controller/ApisQwStatisticsController.java
  34. 101 0
      fs-qwhook/src/main/java/com/fs/app/controller/ApisQwUserController.java
  35. 95 0
      fs-qwhook/src/main/java/com/fs/app/controller/ApisQwWorkTaskController.java
  36. 40 3
      fs-qwhook/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  37. 39 0
      fs-qwhook/src/main/java/com/fs/app/controller/QwStatisticsController.java
  38. 69 0
      fs-qwhook/src/main/java/com/fs/app/controller/QwUserController.java
  39. 92 0
      fs-qwhook/src/main/java/com/fs/app/controller/QwWorkTaskController.java
  40. 0 5
      fs-qwhook/src/main/resources/application.yml
  41. 141 0
      fs-repeat-api/pom.xml
  42. 14 0
      fs-repeat-api/src/main/java/com/fs/FSServletInitializer.java
  43. 25 0
      fs-repeat-api/src/main/java/com/fs/FsRepeatApiApplication.java
  44. 12 0
      fs-repeat-api/src/main/java/com/fs/app/annotation/Login.java
  45. 15 0
      fs-repeat-api/src/main/java/com/fs/app/annotation/LoginUser.java
  46. 39 0
      fs-repeat-api/src/main/java/com/fs/app/controller/CommonController.java
  47. 51 0
      fs-repeat-api/src/main/java/com/fs/app/exception/FSException.java
  48. 81 0
      fs-repeat-api/src/main/java/com/fs/app/exception/FSExceptionHandler.java
  49. 55 0
      fs-repeat-api/src/main/java/com/fs/app/mq/RocketMQConsumerService.java
  50. 24 0
      fs-repeat-api/src/main/java/com/fs/app/task/Task.java
  51. 182 0
      fs-repeat-api/src/main/java/com/fs/framework/aspectj/DataScopeAspect.java
  52. 73 0
      fs-repeat-api/src/main/java/com/fs/framework/aspectj/DataSourceAspect.java
  53. 245 0
      fs-repeat-api/src/main/java/com/fs/framework/aspectj/LogAspect.java
  54. 117 0
      fs-repeat-api/src/main/java/com/fs/framework/aspectj/RateLimiterAspect.java
  55. 31 0
      fs-repeat-api/src/main/java/com/fs/framework/config/ApplicationConfig.java
  56. 85 0
      fs-repeat-api/src/main/java/com/fs/framework/config/CaptchaConfig.java
  57. 100 0
      fs-repeat-api/src/main/java/com/fs/framework/config/DataSourceConfig.java
  58. 123 0
      fs-repeat-api/src/main/java/com/fs/framework/config/DruidConfig.java
  59. 72 0
      fs-repeat-api/src/main/java/com/fs/framework/config/FastJson2JsonRedisSerializer.java
  60. 59 0
      fs-repeat-api/src/main/java/com/fs/framework/config/FilterConfig.java
  61. 76 0
      fs-repeat-api/src/main/java/com/fs/framework/config/KaptchaTextCreator.java
  62. 150 0
      fs-repeat-api/src/main/java/com/fs/framework/config/MyBatisConfig.java
  63. 121 0
      fs-repeat-api/src/main/java/com/fs/framework/config/RedisConfig.java
  64. 65 0
      fs-repeat-api/src/main/java/com/fs/framework/config/ResourcesConfig.java
  65. 50 0
      fs-repeat-api/src/main/java/com/fs/framework/config/SecurityConfig.java
  66. 33 0
      fs-repeat-api/src/main/java/com/fs/framework/config/ServerConfig.java
  67. 121 0
      fs-repeat-api/src/main/java/com/fs/framework/config/SwaggerConfig.java
  68. 63 0
      fs-repeat-api/src/main/java/com/fs/framework/config/ThreadPoolConfig.java
  69. 77 0
      fs-repeat-api/src/main/java/com/fs/framework/config/properties/DruidProperties.java
  70. 27 0
      fs-repeat-api/src/main/java/com/fs/framework/datasource/DynamicDataSource.java
  71. 45 0
      fs-repeat-api/src/main/java/com/fs/framework/datasource/DynamicDataSourceContextHolder.java
  72. 56 0
      fs-repeat-api/src/main/java/com/fs/framework/interceptor/RepeatSubmitInterceptor.java
  73. 126 0
      fs-repeat-api/src/main/java/com/fs/framework/interceptor/impl/SameUrlDataInterceptor.java
  74. 56 0
      fs-repeat-api/src/main/java/com/fs/framework/manager/AsyncManager.java
  75. 40 0
      fs-repeat-api/src/main/java/com/fs/framework/manager/ShutdownManager.java
  76. 103 0
      fs-repeat-api/src/main/java/com/fs/framework/manager/factory/AsyncFactory.java
  77. 1 0
      fs-repeat-api/src/main/resources/META-INF/spring-devtools.properties
  78. 13 0
      fs-repeat-api/src/main/resources/application.yml
  79. 2 0
      fs-repeat-api/src/main/resources/banner.txt
  80. 37 0
      fs-repeat-api/src/main/resources/i18n/messages.properties
  81. 93 0
      fs-repeat-api/src/main/resources/logback.xml
  82. 15 0
      fs-repeat-api/src/main/resources/mybatis/mybatis-config.xml
  83. 7 0
      fs-service/src/main/java/com/fs/ad/service/impl/AdHtmlClickLogServiceImpl.java
  84. 4 2
      fs-service/src/main/java/com/fs/baidu/api/ConvertData.java
  85. 1 0
      fs-service/src/main/java/com/fs/baidu/service/impl/BdAccountServiceImpl.java
  86. 5 1
      fs-service/src/main/java/com/fs/baidu/service/impl/BdApiServiceImpl.java
  87. 9 0
      fs-service/src/main/java/com/fs/company/domain/Company.java
  88. 6 2
      fs-service/src/main/java/com/fs/core/config/WxMaConfiguration.java
  89. 1 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseLink.java
  90. 2 1
      fs-service/src/main/java/com/fs/course/param/FsCourseLinkCreateParam.java
  91. 1 0
      fs-service/src/main/java/com/fs/course/param/FsUserCourseVideoAddKfUParam.java
  92. 10 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseLinkServiceImpl.java
  93. 51 8
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  94. 40 0
      fs-service/src/main/java/com/fs/his/domain/FsUserWx.java
  95. 1 1
      fs-service/src/main/java/com/fs/his/mapper/FsStoreOrderMapper.java
  96. 7 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserWxMapper.java
  97. 7 0
      fs-service/src/main/java/com/fs/his/service/IFsUserWxService.java
  98. 11 1
      fs-service/src/main/java/com/fs/his/service/impl/FsInquiryOrderServiceImpl.java
  99. 6 0
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralGoodsServiceImpl.java
  100. 13 0
      fs-service/src/main/java/com/fs/his/service/impl/FsUserWxServiceImpl.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
 @Service
 @AllArgsConstructor
-@RocketMQMessageListener(topic = "${rocketmq.consumer.topic}", consumerGroup = "${rocketmq.consumer.group}")
+@RocketMQMessageListener(topic = "ad-upload", consumerGroup = "${rocketmq.consumer.group}")
 public class RocketMQConsumerService implements RocketMQListener<String> {
 
     private final IAdHtmlClickLogService  adHtmlClickLogService;

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

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

+ 138 - 0
fs-admin/src/main/java/com/fs/qw/controller/QwExternalContactController.java

@@ -0,0 +1,138 @@
+package com.fs.qw.controller;
+
+import java.util.List;
+import java.util.Objects;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.fs.common.exception.ServiceException;
+import com.fs.qw.param.QwExternalContactParam;
+import com.fs.qw.param.QwTagSearchParam;
+import com.fs.qw.service.IQwTagService;
+import com.fs.qw.vo.QwExternalContactVO;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.service.IQwExternalContactService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 企业微信客户Controller
+ * 
+ * @author fs
+ * @date 2025-06-13
+ */
+@RestController
+@RequestMapping("/qw/externalContact")
+public class QwExternalContactController extends BaseController
+{
+    private IQwExternalContactService qwExternalContactService;
+    private IQwTagService iQwTagService;
+
+    QwExternalContactController(IQwExternalContactService qwExternalContactService,IQwTagService iQwTagService){
+        this.qwExternalContactService=qwExternalContactService;
+        this.iQwTagService=iQwTagService;
+    }
+
+
+    /**
+     * 查询企业微信客户列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwExternalContactParam qwExternalContact)
+    {
+        if(ObjectUtil.isEmpty(qwExternalContact.getCompanyId())){
+            throw new ServiceException("操作失败,请选择企业!");
+        }
+        startPage();
+        List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(qwExternalContact);
+        list.forEach(item->{
+
+            if (!Objects.equals(item.getTagIds(), "[]") && item.getTagIds()!=null) {
+                QwTagSearchParam param = new QwTagSearchParam();
+                Gson gson = new Gson();
+                List<String> tagIds = gson.fromJson(
+                        item.getTagIds(),
+                        new TypeToken<List<String>>() {
+                        }.getType()
+                );
+
+                param.setTagIds(tagIds);
+
+                item.setTagIdsName(iQwTagService.selectQwTagListByTagIds(param));
+            }
+        });
+
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出企业微信客户列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:export')")
+    @Log(title = "企业微信客户", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(QwExternalContact qwExternalContact)
+    {
+        List<QwExternalContact> list = qwExternalContactService.selectQwExternalContactList(qwExternalContact);
+        ExcelUtil<QwExternalContact> util = new ExcelUtil<QwExternalContact>(QwExternalContact.class);
+        return util.exportExcel(list, "企业微信客户数据");
+    }
+
+    /**
+     * 获取企业微信客户详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(qwExternalContactService.selectQwExternalContactById(id));
+    }
+
+    /**
+     * 新增企业微信客户
+     */
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:add')")
+    @Log(title = "企业微信客户", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody QwExternalContact qwExternalContact)
+    {
+        return toAjax(qwExternalContactService.insertQwExternalContact(qwExternalContact));
+    }
+
+    /**
+     * 修改企业微信客户
+     */
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:edit')")
+    @Log(title = "企业微信客户", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody QwExternalContact qwExternalContact)
+    {
+        return toAjax(qwExternalContactService.updateQwExternalContact(qwExternalContact));
+    }
+
+    /**
+     * 删除企业微信客户
+     */
+    @PreAuthorize("@ss.hasPermi('qw:externalContact:remove')")
+    @Log(title = "企业微信客户", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(qwExternalContactService.deleteQwExternalContactByIds(ids));
+    }
+}

+ 41 - 0
fs-admin/src/main/java/com/fs/qw/controller/QwTagGroupController.java

@@ -0,0 +1,41 @@
+package com.fs.qw.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.qw.domain.QwTagGroup;
+import com.fs.qw.service.IQwTagGroupService;
+import com.fs.qw.vo.QwTagGroupListVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 企微客户标签组Controller
+ *
+ * @author fs
+ * @date 2024-06-20
+ */
+@RestController
+@RequestMapping("/qw/tagGroup")
+public class QwTagGroupController extends BaseController
+{
+    @Autowired
+    private IQwTagGroupService qwTagGroupService;
+
+    @Autowired
+    private RedisTemplate redisTemplate;
+
+    /**
+    * 所有标签列表
+    */
+    @GetMapping("/allList")
+    public TableDataInfo allList(QwTagGroup qwTagGroup)
+    {
+        startPage();
+
+        List<QwTagGroupListVO> list = qwTagGroupService.selectQwTagGroupListVO(qwTagGroup);
+        return getDataTable(list);
+    }
+}

+ 10 - 0
fs-admin/src/main/java/com/fs/qw/controller/QwUserController.java

@@ -1,7 +1,9 @@
 package com.fs.qw.controller;
 
+import com.baomidou.mybatisplus.extension.api.R;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
+import com.fs.qw.param.QwFsUserParam;
 import com.fs.qw.service.IQwUserService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -24,4 +26,12 @@ public class QwUserController extends BaseController {
     public AjaxResult getQwUserAll(){
         return AjaxResult.success(qwUserService.getQwUserAll());
     }
+
+    /**
+     * 获取企微信息
+     * **/
+    @GetMapping("/getQwUserInfo")
+    public R getQwUserInfo(QwFsUserParam param){
+        return R.ok(qwUserService.getQwUserInfo(param));
+    }
 }

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

@@ -4,14 +4,9 @@ server:
 # Spring配置
 spring:
   profiles:
-#    active: dev
-#    include: common,config-myhk
+    active: dev
 #    active: druid-hdt
-#    include: common,config-druid-hdt
 #    active: druid-yzt
-#    include: common,config-druid-yzt
 #    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:
   profiles:
     active: dev
-    include: common,config-dev

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

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

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

@@ -6,6 +6,4 @@ server:
 spring:
   profiles:
 #    active: dev
-#    include: common,config-dev
     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);
     }
     @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);
     }
 }

+ 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.param.SopUserLogsVO;
 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.SopUserLogs;
 import com.fs.sop.mapper.QwSopMapper;
@@ -117,4 +119,18 @@ public class SopUserLogsController extends BaseController
     {
         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:
   profiles:
-#    active: dev
-#    include: common,config-dev
+    active: dev
 #    active: druid-jzzx
-#    include: common,config-druid-jzzx
 #    active: druid-hdt
-#    include: common,config-druid-hdt
 #    active: druid-sxjz
-#    include: common,config-druid-sxjz
 #    active: druid-yzt
-#    include: common,config-druid-yzt
 #    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;
 
 
+import cn.hutool.json.JSONUtil;
 import com.fs.app.annotation.Login;
 import com.fs.common.core.domain.AjaxResult;
 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.IFsInquiryOrderService;
 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.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
+import springfox.documentation.annotations.Cacheable;
 
 import javax.servlet.http.HttpServletRequest;
 import java.util.HashMap;
@@ -29,6 +33,8 @@ public class IndexController extends AppBaseController {
 	private IFsInquiryOrderService inquiryOrderService;
 	@Autowired
 	IFsDataService iFsDataService;
+	@Autowired
+	private ISysConfigService configService;
 
 	@Login
 	@ApiOperation("获取首页数据")
@@ -58,5 +64,14 @@ public class IndexController extends AppBaseController {
 		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:
   profiles:
 #    active: dev
-#    include: common,config-dev
     active: druid-jnmy
-    include: common,config-druid-jnmy

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

@@ -5,5 +5,4 @@ server:
 # Spring配置
 spring:
   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:
   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:
   profiles:
     active: dev
-    include: common,config-dev
 #    active: druid-jzzx
-#    include: common,config-druid-jzzx
 #    active: druid-hdt
-#    include: common,config-druid-
 #    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.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.PubFun;
 import com.fs.company.service.ICompanyConfigService;
 import com.fs.qw.domain.*;
 import com.fs.qw.mapper.QwCompanyMapper;
@@ -270,6 +271,13 @@ public class QwDataCallbackService {
                             qwGroupChatUser.setCorpId(corpId);
                             //成员入群
                             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();
@@ -318,8 +326,12 @@ public class QwDataCallbackService {
                                 for (int i = 0; i < items.getLength(); i++) {
 
                                     String userid = items.item(i).getTextContent();
+                                    QwGroupChatDetailsResult.Member member = memberMap.get(userid);
                                     qwGroupChatUser.setChatId(chatId);
                                     qwGroupChatUser.setUserId(userid);
+                                    qwGroupChatUser.setType(member.getType() + "");
+                                    qwGroupChatUser.setName(member.getName());
+                                    qwGroupChatUser.setUnionid(member.getUnionid());
                                     qwGroupChatUser.setJoinScene(joinScene);
                                     qwGroupChatUser.setJoinTime(formattedDate);
 

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

@@ -5,9 +5,7 @@ server:
 # Spring配置
 spring:
   profiles:
-#    active: dev
-#    include: common,config-dev
+    active: dev
 #    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.JSONArray;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.app.taskService.SopLogsTaskService;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.base.BaseException;
-import com.fs.common.utils.BatchUtils;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.CompanyUser;
@@ -705,19 +703,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             ruleTimeVO.setType(2);
             if (content.getIndex() == 0) {
                 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,
-                        type, qwUserId, companyUserId, companyId, groupChat.getChatId(), welcomeText, qwUserName, null, true, miniAppId);
+                        type, qwUserId, companyUserId, companyId, groupChat.getChatId(), welcomeText, qwUserName, null, true, miniAppId, groupChat);
             } else {
                 if(groupChat.getChatUserList() != null && !groupChat.getChatUserList().isEmpty()){
                     groupChat.getChatUserList().forEach(user -> {
@@ -725,7 +712,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                         ruleTimeVO.setRemark("客户群催课");
                         QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, user.getUserId(), user.getName(), null, isOfficial, null);
                         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();
                     QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, contactId.getExternalContactId(), externalUserName, fsUserId, isOfficial, contactId.getExternalId());
                     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) {
                     log.error("处理 externalContactId {} 时发生异常: {}", contactId, e.getMessage(), e);
                 }
@@ -826,15 +813,15 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     private void handleLogBasedOnType(QwSopLogs sopLogs, QwSopTempSetting.Content content,
                                       SopUserLogsVo logVo, Date sendTime, Long courseId,
                                       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) {
             case 1:
                 handleNormalMessage(sopLogs, content,companyUserId);
                 break;
             case 2:
                 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;
             case 3:
                 handleOrderMessage(sopLogs, content);
@@ -866,8 +853,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     private void handleCourseMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content,
                                      SopUserLogsVo logVo, Date sendTime, Long courseId,
                                      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
         QwSopTempSetting.Content clonedContent = deepCopyContent(content);
         if (clonedContent == null) {
@@ -908,6 +895,18 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                             if (createLink.get("code").equals(500)) {
                                 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");
                         } else {
                             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:
   profiles:
 #    active: dev
-#    include: common,config-dev
 #    active: druid-hcl
-#    include: common,config-druid-hcl
 #    active: druid-sxjz
-#    include: common,config-druid-sxjz
 #    active: druid-hdt
-#    include: common,config-druid-hdt
     active: druid-myhk
-    include: common,config-myhk

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

@@ -6,5 +6,4 @@ server:
 # Spring配置
 spring:
   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:
   profiles:
 #    active: dev
-#    include: common,config-dev
 #    active: druid-sxjz
-#    include: common,config-druid-sxjz
     active: druid-hdt
-    include: common,config-druid-hdt

+ 39 - 0
fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java

@@ -1,10 +1,15 @@
 package com.fs.app.controller;
 
+import com.alibaba.fastjson.JSONObject;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.R;
+import com.fs.course.domain.FsUserCourse;
+import com.fs.course.param.FsCourseLinkCreateParam;
 import com.fs.course.param.FsCourseLinkMiniParam;
+import com.fs.course.param.FsCourseLinkRoomParam;
 import com.fs.course.param.newfs.UserCourseVideoPageParam;
+import com.fs.course.service.IFsCourseLinkService;
 import com.fs.course.service.IFsUserCoursePeriodService;
 import com.fs.course.service.IFsUserCourseService;
 import com.fs.course.service.IFsUserCourseVideoService;
@@ -44,6 +49,9 @@ public class ApisFsUserCourseVideoController extends BaseController {
     @Autowired
     private IQwExternalContactService qwExternalContactService;
 
+    @Autowired
+    private IFsCourseLinkService courseLinkService;
+
 
 
     @PostMapping("/pageList")
@@ -158,4 +166,35 @@ public class ApisFsUserCourseVideoController extends BaseController {
         return fsUserCourseVideoService.createCartLink(param);
     }
 
+    @GetMapping("/createRoomLink")
+    @ApiOperation("创建发群链接")
+    public R createRoomLink(FsCourseLinkRoomParam param) {
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(),param.getQwUserId().trim());
+        if (qwUser==null||qwUser.getCompanyId()==null){
+            return R.error("无权限");
+        }
+        FsCourseLinkCreateParam createParam = new FsCourseLinkCreateParam();
+        createParam.setCourseId(param.getCourseId());
+        createParam.setVideoId(param.getVideoId());
+        createParam.setCorpId(param.getCorpId());
+        createParam.setCompanyUserId(qwUser.getCompanyUserId());
+        createParam.setCompanyId(qwUser.getCompanyId());
+        createParam.setQwUserId(qwUser.getId().toString());
+        String linkUrl;
+        R createLink = courseLinkService.createRoomLinkUrl(createParam);
+        if (createLink.get("code").equals(500)){
+            return R.error("链接生成失败!");
+        }
+        linkUrl = (String) createLink.get("url");
+
+        FsUserCourse course = fsUserCourseService.selectFsUserCourseByCourseId(param.getCourseId());
+
+        JSONObject news = new JSONObject(true); // true 表示保持字段顺序
+        news.put("link", linkUrl);
+        news.put("title", course.getCourseName());
+        news.put("desc", param.getTitle());
+        news.put("imgUrl", course.getImgUrl());
+        return R.ok().put("news",news);
+    }
+
 }

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

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

+ 39 - 0
fs-qwhook-sop/src/main/java/com/fs/app/controller/QwStatisticsController.java

@@ -0,0 +1,39 @@
+package com.fs.app.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.common.exception.CustomException;
+import com.fs.statistics.dto.WatchCourseStatisticsDTO;
+import com.fs.statistics.param.WatchCourseStatisticsParam;
+import com.fs.statistics.service.IStatisticsService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 企微统计数据
+ */
+@RestController
+@RequestMapping("/app/qw/statistics")
+public class QwStatisticsController extends BaseController {
+    @Autowired
+    private IStatisticsService statisticsService;
+
+
+//    @Login
+    @PostMapping("/course/watch")
+    @ApiOperation("会员看课详情")
+    public R queryCourseWatchStatistics(@RequestBody WatchCourseStatisticsParam param) {
+        if(param.getQwExternalContactId() == null) {
+            throw new CustomException("外部联系人id为空!");
+        }
+
+        WatchCourseStatisticsDTO watchCourseStatisticsDTO = statisticsService.queryWatchCourse(param);
+
+        return R.ok().put("data",watchCourseStatisticsDTO);
+    }
+
+}

+ 69 - 0
fs-qwhook-sop/src/main/java/com/fs/app/controller/QwUserController.java

@@ -0,0 +1,69 @@
+package com.fs.app.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.common.exception.CustomException;
+import com.fs.qw.domain.QwExternalContactInfo;
+import com.fs.qw.param.ExternalContactDetailsParam;
+import com.fs.qw.service.IQwExternalContactInfoService;
+import com.fs.qw.service.IQwExternalContactService;
+import com.fs.qw.vo.ExternalContactDetailsVO;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Api(tags = "企微会员相关接口")
+@RestController
+@RequestMapping("/app/qw/user")
+public class QwUserController extends BaseController {
+
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
+    @Autowired
+    private IQwExternalContactInfoService qwExternalContactInfoService;
+
+    @GetMapping("/details")
+    @ApiOperation("会员看课详情")
+    public R getUserDetails(@ApiParam(value = "外部联系人id", required = true) @RequestParam Long contactId,
+                            @ApiParam(value = "时间tab,不传表示查询全部,分别是:今天、昨天、前天、近七天", required = true) String dateTag) {
+        ExternalContactDetailsParam param = new ExternalContactDetailsParam();
+        param.setUserId(getUserId());
+        param.setContactId(contactId);
+        param.setDateTag(dateTag);
+        ExternalContactDetailsVO userDetails = qwExternalContactService.getUserDetails(param);
+        Map<String, Object> map = new HashMap<>();
+        map.put("userDetails", userDetails);
+        return R.ok(map);
+    }
+
+
+    @GetMapping("/getQwUserInfo")
+    @ApiOperation("获取企微用户信息")
+    public R getQwUserInfo(@RequestParam(value = "qwExternalContactId",required = false) Long qwExternalContactId){
+        if(qwExternalContactId == null) {
+            throw new CustomException("企微外部联系人id不能为空!");
+        }
+
+        QwExternalContactInfo qwExternalContactInfo = qwExternalContactInfoService.selectQwExternalContactInfoByExternalContactId(qwExternalContactId);
+        return R.ok().put("data",qwExternalContactService.selectQwExternalContactById(qwExternalContactId)).put("moreInfo",qwExternalContactInfo);
+    }
+
+    @PostMapping("/updateQwUserInfo")
+    @ApiOperation("更新企微用户信息")
+    public R updateQwUserInfo(@RequestBody QwExternalContactInfo qwExternalContactInfo){
+        if(qwExternalContactInfo.getExternalContactId() == null) {
+            throw new CustomException("企微外部联系人id不能为空!");
+        }
+        qwExternalContactInfoService.updateQwExternalContactInfoByExternalContactId(qwExternalContactInfo);
+        return R.ok();
+    }
+
+}

+ 92 - 0
fs-qwhook-sop/src/main/java/com/fs/app/controller/QwWorkTaskController.java

@@ -0,0 +1,92 @@
+package com.fs.app.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.company.service.ICompanyUserService;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.domain.QwWorkTask;
+import com.fs.qw.param.SelectQwWorkTaskListParam;
+import com.fs.qw.service.IQwExternalContactService;
+import com.fs.qw.service.IQwUserService;
+import com.fs.qw.service.IQwWorkTaskService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Slf4j
+@Api(tags = "企微任务看板接口")
+@RestController
+@RequestMapping("/app/qw/workTask")
+@AllArgsConstructor
+public class QwWorkTaskController extends BaseController {
+
+    private final IQwWorkTaskService qwWorkTaskService;
+    private final IQwUserService qwUserService;
+
+    @Autowired
+    ICompanyUserService companyUserService;
+
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
+    @PostMapping("/list")
+    @ApiOperation("企微任务看板列表")
+    public R selectQwWorkTaskList(@RequestBody SelectQwWorkTaskListParam param) {
+
+        log.info("企微任务看板列表: {}",param);
+
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(), param.getQwUserId().trim());
+
+        if (qwUser == null || qwUser.getCompanyId() == null) {
+            return R.error("员工未绑定 销售公司 或 未获取到员工信息,请重试!");
+        }
+
+        param.setUserId(qwUser.getId());
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<QwWorkTask> list = qwUserService.selectQwWorkTaskList(param);
+
+        for (QwWorkTask qwWorkTask : list) {
+            List<Integer> logs = fsCourseWatchLogMapper.selectFsCourseWatchLog7DayByExtId(qwWorkTask.getExtId());
+            qwWorkTask.setLogs(logs);
+        }
+
+        PageInfo<QwWorkTask> pageInfo = new PageInfo<>(list);
+        return R.ok().put("data",pageInfo);
+    }
+
+    /**
+     * 处理催课
+     */
+    @GetMapping("/updateWorkTaskStatus/{id}")
+    public R updateWorkTaskStatus(@PathVariable("id") Long id) {
+
+        try {
+            QwWorkTask qwWorkTask=new QwWorkTask();
+            qwWorkTask.setId(id);
+            qwWorkTask.setStatus(1);
+            int i = qwWorkTaskService.updateQwWorkTask(qwWorkTask);
+            if (i>0) {
+                return R.ok();
+            }else {
+                return R.error("更新失败,请重试!");
+            }
+        }catch (Exception e){
+            return R.error("更新失败,请重试!");
+        }
+    }
+
+
+
+}

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

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

+ 132 - 0
fs-qwhook/src/main/java/com/fs/app/controller/ApisCommonController.java

@@ -0,0 +1,132 @@
+package com.fs.app.controller;
+
+
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.course.mapper.FsUserCourseVideoMapper;
+import com.fs.fastGpt.mapper.FastgptChatVoiceHomoMapper;
+import com.fs.his.domain.FsAppVersion;
+import com.fs.his.service.IFsAppVersionService;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwCompanyMapper;
+import com.fs.qw.mapper.QwExternalContactCrmMapper;
+import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.param.QwConfigSignatureParam;
+import com.fs.qw.service.IQwJsApiService;
+import com.fs.qw.service.IQwUserService;
+import com.fs.qw.service.IQwUserVideoService;
+import com.fs.qw.vo.QwHookAuthVO;
+import com.fs.qwApi.param.QwExternalContactHParam;
+import com.fs.qwApi.service.QwApiService;
+import com.fs.qwHookApi.param.QwHookSendMsgParam;
+import com.fs.qwHookApi.service.QwHookApiService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+
+@Api("公共接口")
+@RestController
+@RequestMapping(value="/apis/app/common")
+@Slf4j
+public class ApisCommonController {
+
+    @Autowired
+    private QwHookApiService qwHookApiService;
+
+    @Autowired
+    private IQwUserService qwUserService;
+
+    @Autowired
+    QwApiService qwApiService;
+    @Autowired
+    QwCompanyMapper qwCompanyMapper;
+
+    @Autowired
+    FsCourseWatchLogMapper fsCourseWatchLogMapper;
+    @Autowired
+    FsUserCourseVideoMapper fsUserCourseVideoMapper;
+    @Autowired
+    FsCourseWatchLogMapper   watchLogMapper;
+    @Autowired
+    IQwJsApiService qwGetJsapiTicketService;
+
+    @Autowired
+    QwUserMapper qwUserMapper;
+    @Autowired
+    FastgptChatVoiceHomoMapper fastgptChatVoiceHomoMapper;
+
+    @Autowired
+    QwExternalContactCrmMapper qwExternalContactCrmMapper;
+    @Autowired
+    private IFsAppVersionService appVersionService;
+    @Autowired
+    RedisCache redisCache;
+
+
+    @Autowired
+    private IQwUserVideoService qwUserVideoService;
+
+
+    @PostMapping("/qwHookSendMsg")
+    public R qwHookSendMsg(@RequestBody QwHookSendMsgParam param ) {
+        param.setClientId(2);
+        return qwHookApiService.sendMsg(param);
+    }
+
+    @GetMapping("/qwHookAuth")
+    public R qwHookAuth(@RequestParam(value = "key", required = false) String key) {
+        QwHookAuthVO qwHookAuthVO = qwUserService.selectQwUserByAppKeyAuth(key);
+        if(qwHookAuthVO!=null){
+            return R.ok().put("data",qwHookAuthVO);
+        }
+        else {
+            return R.error("查询到相关成员信息");
+        }
+    }
+
+    @GetMapping("/qwHookCheck")
+    public R qwHookCheckCorpId(@RequestParam(value = "key", required = false) String key,
+                               @RequestParam(value = "qwHookId", required = false) String qwHookId,
+                               @RequestParam(value = "corpId", required = false) String corpId) {
+        QwUser user=qwUserService.selectQwUserByAppKey(key);
+        if(user.getCorpId().equals(corpId)){
+            if(user.getQwHookId().equals(qwHookId)){
+                return R.ok();
+            }
+            else{
+                return R.error("此帐号绑定的企业微信未授权");
+            }
+        }
+        else{
+           return R.error("此帐号绑定的企业微信未授权");
+        }
+    }
+
+    //获取企业微信签名
+    @PostMapping("/getConfigSignature")
+    public R getConfigSignature(@RequestBody QwConfigSignatureParam qwConfigSignatureParam) throws Exception {
+        return qwGetJsapiTicketService.getQwJsapiTicket(qwConfigSignatureParam);
+    }
+
+    //根据userid和外部联系人id获取到客户详情
+    @PostMapping("/getQwExternalContactDetails")
+    public R getQwExternalContactDetails(@RequestBody QwExternalContactHParam param){
+        return qwGetJsapiTicketService.getQwExternalContactDetails(param);
+    }
+
+    @ApiOperation("获取最新版本")
+    @GetMapping("/getNewAppVersion")
+    public R getNewAppVersion(@RequestParam("appType")Integer appType)
+    {
+        FsAppVersion version=appVersionService.getNewVersion(appType,3);
+        return R.ok().put("data",version);
+    }
+
+
+
+
+}

+ 200 - 0
fs-qwhook/src/main/java/com/fs/app/controller/ApisFsUserCourseVideoController.java

@@ -0,0 +1,200 @@
+package com.fs.app.controller;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fs.common.annotation.RepeatSubmit;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.course.domain.FsUserCourse;
+import com.fs.course.param.FsCourseLinkCreateParam;
+import com.fs.course.param.FsCourseLinkMiniParam;
+import com.fs.course.param.FsCourseLinkRoomParam;
+import com.fs.course.param.FsCourseListBySidebarParam;
+import com.fs.course.param.newfs.UserCourseVideoPageParam;
+import com.fs.course.service.IFsCourseLinkService;
+import com.fs.course.service.IFsUserCoursePeriodService;
+import com.fs.course.service.IFsUserCourseService;
+import com.fs.course.service.IFsUserCourseVideoService;
+import com.fs.course.vo.FsCourseListBySidebarVO;
+import com.fs.course.vo.FsCourseVideoListBySidebarVO;
+import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.service.IQwExternalContactService;
+import com.fs.voice.utils.StringUtil;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+
+@Api("课程库相关接口")
+@RestController
+@RequestMapping("/apis/app/fs/course")
+@Slf4j
+public class ApisFsUserCourseVideoController extends BaseController {
+
+    @Autowired
+    private IFsUserCourseVideoService fsUserCourseVideoService;
+
+    @Autowired
+    private IFsUserCourseService fsUserCourseService;
+
+    @Autowired
+    private IFsUserCoursePeriodService fsUserCoursePeriodService;
+
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
+    @Autowired
+    private IFsCourseLinkService courseLinkService;
+
+
+
+    @PostMapping("/pageList")
+    @ApiOperation("课程分页列表")
+    public R list(@RequestBody UserCourseVideoPageParam param) {
+
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(), param.getQwUserId().trim());
+
+        if (qwUser == null || qwUser.getCompanyId() == null) {
+            return R.error("员工未绑定 销售公司 或 未获取到员工信息,请重试!");
+        }
+        param.setCompanyId(qwUser.getCompanyId());
+
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsUserCourseVideoPageListVO> list = fsUserCourseVideoService.pageListCourseVideo(param);
+        PageInfo<FsUserCourseVideoPageListVO> pageInfo = new PageInfo<>(list);
+        return R.ok().put("data",pageInfo);
+    }
+
+
+    @ApiOperation("课程视频详情")
+    @GetMapping(value = "/videoDetails")
+    public R getVideoDetails(Long videoId) {
+        return R.ok().put("data",fsUserCourseVideoService.getVideoDetails(videoId));
+    }
+
+
+    @PostMapping("/getFsCourseListBySidebar")
+    @ApiOperation("获取视频课程下拉列表 侧边栏")
+    public R getFsCourseListBySidebar(@RequestBody FsCourseListBySidebarParam param) {
+
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(),param.getQwUserId().trim());
+
+        if (qwUser == null || qwUser.getCompanyId() == null) {
+            return R.error("员工未绑定 销售公司 或 未获取到员工信息,请重试!");
+        }
+        param.setCompanyId(qwUser.getCompanyId());
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsCourseListBySidebarVO> fsCourseListBySidebar = fsUserCourseService.getFsCourseListBySidebar(param);
+        PageInfo<FsCourseListBySidebarVO> result = new PageInfo<>(fsCourseListBySidebar);
+        return R.ok().put("data", result);
+    }
+
+    @PostMapping("/getFsCourseVideoListBySidebar")
+    @ApiOperation("获取视频课程的课节下拉列表 侧边栏")
+    public R getFsCourseVideoListBySidebar(@RequestBody FsCourseListBySidebarParam param) {
+
+        if (param.getCourseId()==null){
+            return R.error("课程id不能为空");
+        }
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsCourseVideoListBySidebarVO> videoListBySidebar = fsUserCourseVideoService.getFsCourseVideoListBySidebar(param);
+        PageInfo<FsCourseVideoListBySidebarVO> result = new PageInfo<>(videoListBySidebar);
+        return R.ok().put("data", result);
+    }
+
+    /**
+    * 创建 发客户小程序
+    */
+
+    @RepeatSubmit
+    @PostMapping("/createMiniLink")
+    public R createMiniLink(@RequestBody FsCourseLinkMiniParam param) {
+
+        if (param.getCourseId()==null){
+            return R.error("课程id不能为空");
+        }
+        if (param.getVideoId()==null){
+            return R.error("视频id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getQwUserId())){
+            return R.error("用户id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getCorpId())){
+            return R.error("企业id不能为空");
+        }
+
+        if (param.getExternalUserId()==null){
+            return R.error("客户id不能为空");
+        }
+
+        return fsUserCourseVideoService.createMiniLink(param);
+    }
+
+    /**
+    * 创建发卡片
+    */
+    @RepeatSubmit
+    @PostMapping("/createCartLink")
+    public R createCartLink(@RequestBody  FsCourseLinkMiniParam param) {
+
+        if (param.getCourseId()==null){
+            return R.error("课程id不能为空");
+        }
+        if (param.getVideoId()==null){
+            return R.error("视频id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getQwUserId())){
+            return R.error("用户id不能为空");
+        }
+        if (StringUtil.strIsNullOrEmpty(param.getCorpId())){
+            return R.error("企业id不能为空");
+        }
+
+        if (param.getExternalUserId()==null){
+            return R.error("客户id不能为空");
+        }
+
+        return fsUserCourseVideoService.createCartLink(param);
+    }
+
+    @GetMapping("/createRoomLink")
+    @ApiOperation("创建发群链接")
+    public R createRoomLink(FsCourseLinkRoomParam param) {
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(),param.getQwUserId().trim());
+        if (qwUser==null||qwUser.getCompanyId()==null){
+            return R.error("无权限");
+        }
+        FsCourseLinkCreateParam createParam = new FsCourseLinkCreateParam();
+        createParam.setCourseId(param.getCourseId());
+        createParam.setVideoId(param.getVideoId());
+        createParam.setCorpId(param.getCorpId());
+        createParam.setCompanyUserId(qwUser.getCompanyUserId());
+        createParam.setCompanyId(qwUser.getCompanyId());
+        createParam.setQwUserId(qwUser.getId().toString());
+        String linkUrl;
+        R createLink = courseLinkService.createRoomLinkUrl(createParam);
+        if (createLink.get("code").equals(500)){
+            return R.error("链接生成失败!");
+        }
+        linkUrl = (String) createLink.get("url");
+
+        FsUserCourse course = fsUserCourseService.selectFsUserCourseByCourseId(param.getCourseId());
+
+        JSONObject news = new JSONObject(true); // true 表示保持字段顺序
+        news.put("link", linkUrl);
+        news.put("title", course.getCourseName());
+        news.put("desc", param.getTitle());
+        news.put("imgUrl", course.getImgUrl());
+        return R.ok().put("news",news);
+    }
+
+}

+ 39 - 0
fs-qwhook/src/main/java/com/fs/app/controller/ApisQwStatisticsController.java

@@ -0,0 +1,39 @@
+package com.fs.app.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.common.exception.CustomException;
+import com.fs.statistics.dto.WatchCourseStatisticsDTO;
+import com.fs.statistics.param.WatchCourseStatisticsParam;
+import com.fs.statistics.service.IStatisticsService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 企微统计数据
+ */
+@RestController
+@RequestMapping("/apis/app/qw/statistics")
+public class ApisQwStatisticsController extends BaseController {
+    @Autowired
+    private IStatisticsService statisticsService;
+
+
+//    @Login
+    @PostMapping("/course/watch")
+    @ApiOperation("会员看课详情")
+    public R queryCourseWatchStatistics(@RequestBody WatchCourseStatisticsParam param) {
+        if(param.getQwExternalContactId() == null) {
+            throw new CustomException("外部联系人id为空!");
+        }
+
+        WatchCourseStatisticsDTO watchCourseStatisticsDTO = statisticsService.queryWatchCourse(param);
+
+        return R.ok().put("data",watchCourseStatisticsDTO);
+    }
+
+}

+ 101 - 0
fs-qwhook/src/main/java/com/fs/app/controller/ApisQwUserController.java

@@ -0,0 +1,101 @@
+package com.fs.app.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.common.exception.CustomException;
+import com.fs.course.param.FsCourseListBySidebarParam;
+import com.fs.qw.domain.QwExternalContactInfo;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.param.ExternalContactDetailsParam;
+import com.fs.qw.service.IQwExternalContactInfoService;
+import com.fs.qw.service.IQwExternalContactService;
+import com.fs.qw.vo.ExternalContactDetailsVO;
+import com.fs.qw.vo.QwExternalListByHeavyVO;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Api(tags = "企微会员相关接口")
+@RestController
+@RequestMapping("/apis/app/qw/user")
+public class ApisQwUserController extends BaseController {
+
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
+    @Autowired
+    private IQwExternalContactInfoService qwExternalContactInfoService;
+
+    @GetMapping("/details")
+    @ApiOperation("会员看课详情")
+    public R getUserDetails(@ApiParam(value = "外部联系人id", required = true) @RequestParam Long contactId,
+                            @ApiParam(value = "时间tab,不传表示查询全部,分别是:今天、昨天、前天、近七天", required = true) String dateTag) {
+        ExternalContactDetailsParam param = new ExternalContactDetailsParam();
+        param.setUserId(getUserId());
+        param.setContactId(contactId);
+        param.setDateTag(dateTag);
+        ExternalContactDetailsVO userDetails = qwExternalContactService.getUserDetails(param);
+        Map<String, Object> map = new HashMap<>();
+        map.put("userDetails", userDetails);
+        return R.ok(map);
+    }
+
+
+    @GetMapping("/getQwUserInfo")
+    @ApiOperation("获取企微用户信息")
+    public R getQwUserInfo(@RequestParam(value = "qwExternalContactId",required = false) Long qwExternalContactId){
+        if(qwExternalContactId == null) {
+            throw new CustomException("企微外部联系人id不能为空!");
+        }
+//        QwExternalContact qwExternalContact = qwExternalContactService.selectQwExternalContactById(qwExternalContactId);
+        QwExternalContactInfo contactInfo = qwExternalContactInfoService.selectQwExternalContactInfoByExternalContactId(qwExternalContactId);
+        if (contactInfo==null){
+
+            contactInfo = new QwExternalContactInfo();
+            contactInfo.setExternalContactId(qwExternalContactId);
+            qwExternalContactInfoService.insertQwExternalContactInfo(contactInfo);
+
+        }
+
+//        return R.ok().put("data",qwExternalContact).put("moreInfo",contactInfo);
+        return R.ok().put("moreInfo",contactInfo);
+    }
+
+    @PostMapping("/updateQwUserInfo")
+    @ApiOperation("更新企微用户信息")
+    public R updateQwUserInfo(@RequestBody QwExternalContactInfo qwExternalContactInfo){
+        if(qwExternalContactInfo.getExternalContactId() == null) {
+            throw new CustomException("企微外部联系人id不能为空!");
+        }
+        qwExternalContactInfoService.updateQwExternalContactInfoByExternalContactId(qwExternalContactInfo);
+        return R.ok();
+    }
+
+    /**
+    * 获取客户是否加了其他的销售(重粉)
+    */
+    @PostMapping("/getQwExternalListByHeavy")
+    @ApiOperation("获取客户是否加了其他的销售")
+    public R getQwExternalListByHeavy(@RequestBody FsCourseListBySidebarParam param){
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(),param.getQwUserId().trim());
+
+        if (qwUser == null) {
+            return R.error("未查询到企业微信账号信息!请重试");
+        }
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<QwExternalListByHeavyVO> qwExternalListByHeavy = qwExternalContactService.getQwExternalListByHeavy(param);
+        PageInfo<QwExternalListByHeavyVO> result = new PageInfo<>(qwExternalListByHeavy);
+        return R.ok().put("data", result);
+    }
+}

+ 95 - 0
fs-qwhook/src/main/java/com/fs/app/controller/ApisQwWorkTaskController.java

@@ -0,0 +1,95 @@
+package com.fs.app.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.company.service.ICompanyUserService;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.domain.QwWorkTask;
+import com.fs.qw.param.SelectQwWorkTaskListParam;
+import com.fs.qw.service.IQwExternalContactService;
+import com.fs.qw.service.IQwUserService;
+import com.fs.qw.service.IQwWorkTaskService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Slf4j
+@Api(tags = "企微任务看板接口")
+@RestController
+@RequestMapping("/apis/app/qw/workTask")
+@AllArgsConstructor
+public class ApisQwWorkTaskController extends BaseController {
+
+    @Autowired
+    private  IQwWorkTaskService qwWorkTaskService;
+    @Autowired
+    private  IQwUserService qwUserService;
+
+    @Autowired
+    ICompanyUserService companyUserService;
+
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
+
+//    @Login
+    @PostMapping("/list")
+    @ApiOperation("企微任务看板列表")
+    public R selectQwWorkTaskList(@RequestBody SelectQwWorkTaskListParam param) {
+
+        log.info("企微任务看板列表: {}",param);
+
+
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(), param.getQwUserId().trim());
+
+        if (qwUser == null || qwUser.getCompanyId() == null) {
+            return R.error("员工未绑定 销售公司 或 未获取到员工信息,请重试!");
+        }
+
+        param.setUserId(qwUser.getId());
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<QwWorkTask> list = qwUserService.selectQwWorkTaskList(param);
+        for (QwWorkTask qwWorkTask : list) {
+            List<Integer> logs = fsCourseWatchLogMapper.selectFsCourseWatchLog7DayByExtId(qwWorkTask.getExtId());
+            qwWorkTask.setLogs(logs);
+        }
+
+        PageInfo<QwWorkTask> pageInfo = new PageInfo<>(list);
+        return R.ok().put("data",pageInfo);
+    }
+
+    /**
+    * 处理催课
+    */
+    @GetMapping("/updateWorkTaskStatus/{id}")
+    public R updateWorkTaskStatus(@PathVariable("id") Long id) {
+
+        try {
+            QwWorkTask qwWorkTask=new QwWorkTask();
+            qwWorkTask.setId(id);
+            qwWorkTask.setStatus(1);
+            int i = qwWorkTaskService.updateQwWorkTask(qwWorkTask);
+            if (i>0) {
+                return R.ok();
+            }else {
+                return R.error("更新失败,请重试!");
+            }
+        }catch (Exception e){
+            return R.error("更新失败,请重试!");
+        }
+    }
+
+
+}

+ 40 - 3
fs-qwhook/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java

@@ -6,16 +6,20 @@ import com.fs.common.core.domain.ResponseResult;
 import com.fs.course.domain.FsUserCourse;
 import com.fs.course.param.FsCourseLinkCreateParam;
 import com.fs.course.param.FsCourseLinkRoomParam;
+import com.fs.course.param.FsCourseListBySidebarParam;
 import com.fs.course.param.newfs.FsUserCourseListParam;
 import com.fs.course.param.newfs.UserCourseVideoPageParam;
 import com.fs.course.service.IFsCourseLinkService;
 import com.fs.course.service.IFsUserCourseService;
 import com.fs.course.service.IFsUserCourseVideoService;
+import com.fs.course.vo.FsCourseListBySidebarVO;
+import com.fs.course.vo.FsCourseVideoListBySidebarVO;
 import com.fs.course.vo.newfs.FsUserCourseListVO;
 import com.fs.course.vo.newfs.FsUserCourseVideoDetailsVO;
 import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
 import com.fs.course.vo.newfs.FsUserVideoListVO;
 import com.fs.qw.domain.QwUser;
+import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qw.service.IQwUserService;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
@@ -23,9 +27,7 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
@@ -45,6 +47,9 @@ public class FsUserCourseVideoController {
     @Autowired
     private IQwUserService qwUserService;
 
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
     @GetMapping("/pageList")
     @ApiOperation("课程分页列表")
     public ResponseResult<PageInfo<FsUserCourseVideoPageListVO>> list(UserCourseVideoPageParam param) {
@@ -93,6 +98,38 @@ public class FsUserCourseVideoController {
         return ResponseResult.ok(result);
     }
 
+
+    @PostMapping("/getFsCourseListBySidebar")
+    @ApiOperation("获取视频课程下拉列表 侧边栏")
+    public R getFsCourseListBySidebar(@RequestBody FsCourseListBySidebarParam param) {
+
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(),param.getQwUserId().trim());
+
+        if (qwUser == null || qwUser.getCompanyId() == null) {
+            return R.error("员工未绑定 销售公司 或 未获取到员工信息,请重试!");
+        }
+        param.setCompanyId(qwUser.getCompanyId());
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsCourseListBySidebarVO> fsCourseListBySidebar = fsUserCourseService.getFsCourseListBySidebar(param);
+        PageInfo<FsCourseListBySidebarVO> result = new PageInfo<>(fsCourseListBySidebar);
+        return R.ok().put("data", result);
+    }
+
+    @PostMapping("/getFsCourseVideoListBySidebar")
+    @ApiOperation("获取视频课程的课节下拉列表 侧边栏")
+    public R getFsCourseVideoListBySidebar(@RequestBody FsCourseListBySidebarParam param) {
+
+        if (param.getCourseId()==null){
+            return R.error("课程id不能为空");
+        }
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<FsCourseVideoListBySidebarVO> videoListBySidebar = fsUserCourseVideoService.getFsCourseVideoListBySidebar(param);
+        PageInfo<FsCourseVideoListBySidebarVO> result = new PageInfo<>(videoListBySidebar);
+        return R.ok().put("data", result);
+    }
+
     @Autowired
     private IFsCourseLinkService courseLinkService;
 

+ 39 - 0
fs-qwhook/src/main/java/com/fs/app/controller/QwStatisticsController.java

@@ -0,0 +1,39 @@
+package com.fs.app.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.common.exception.CustomException;
+import com.fs.statistics.dto.WatchCourseStatisticsDTO;
+import com.fs.statistics.param.WatchCourseStatisticsParam;
+import com.fs.statistics.service.IStatisticsService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 企微统计数据
+ */
+@RestController
+@RequestMapping("/app/qw/statistics")
+public class QwStatisticsController extends BaseController {
+    @Autowired
+    private IStatisticsService statisticsService;
+
+
+//    @Login
+    @PostMapping("/course/watch")
+    @ApiOperation("会员看课详情")
+    public R queryCourseWatchStatistics(@RequestBody WatchCourseStatisticsParam param) {
+        if(param.getQwExternalContactId() == null) {
+            throw new CustomException("外部联系人id为空!");
+        }
+
+        WatchCourseStatisticsDTO watchCourseStatisticsDTO = statisticsService.queryWatchCourse(param);
+
+        return R.ok().put("data",watchCourseStatisticsDTO);
+    }
+
+}

+ 69 - 0
fs-qwhook/src/main/java/com/fs/app/controller/QwUserController.java

@@ -0,0 +1,69 @@
+package com.fs.app.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.common.exception.CustomException;
+import com.fs.qw.domain.QwExternalContactInfo;
+import com.fs.qw.param.ExternalContactDetailsParam;
+import com.fs.qw.service.IQwExternalContactInfoService;
+import com.fs.qw.service.IQwExternalContactService;
+import com.fs.qw.vo.ExternalContactDetailsVO;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Api(tags = "企微会员相关接口")
+@RestController
+@RequestMapping("/app/qw/user")
+public class QwUserController extends BaseController {
+
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
+    @Autowired
+    private IQwExternalContactInfoService qwExternalContactInfoService;
+
+    @GetMapping("/details")
+    @ApiOperation("会员看课详情")
+    public R getUserDetails(@ApiParam(value = "外部联系人id", required = true) @RequestParam Long contactId,
+                            @ApiParam(value = "时间tab,不传表示查询全部,分别是:今天、昨天、前天、近七天", required = true) String dateTag) {
+        ExternalContactDetailsParam param = new ExternalContactDetailsParam();
+        param.setUserId(getUserId());
+        param.setContactId(contactId);
+        param.setDateTag(dateTag);
+        ExternalContactDetailsVO userDetails = qwExternalContactService.getUserDetails(param);
+        Map<String, Object> map = new HashMap<>();
+        map.put("userDetails", userDetails);
+        return R.ok(map);
+    }
+
+
+    @GetMapping("/getQwUserInfo")
+    @ApiOperation("获取企微用户信息")
+    public R getQwUserInfo(@RequestParam(value = "qwExternalContactId",required = false) Long qwExternalContactId){
+        if(qwExternalContactId == null) {
+            throw new CustomException("企微外部联系人id不能为空!");
+        }
+
+        QwExternalContactInfo qwExternalContactInfo = qwExternalContactInfoService.selectQwExternalContactInfoByExternalContactId(qwExternalContactId);
+        return R.ok().put("data",qwExternalContactService.selectQwExternalContactById(qwExternalContactId)).put("moreInfo",qwExternalContactInfo);
+    }
+
+    @PostMapping("/updateQwUserInfo")
+    @ApiOperation("更新企微用户信息")
+    public R updateQwUserInfo(@RequestBody QwExternalContactInfo qwExternalContactInfo){
+        if(qwExternalContactInfo.getExternalContactId() == null) {
+            throw new CustomException("企微外部联系人id不能为空!");
+        }
+        qwExternalContactInfoService.updateQwExternalContactInfoByExternalContactId(qwExternalContactInfo);
+        return R.ok();
+    }
+
+}

+ 92 - 0
fs-qwhook/src/main/java/com/fs/app/controller/QwWorkTaskController.java

@@ -0,0 +1,92 @@
+package com.fs.app.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.R;
+import com.fs.company.service.ICompanyUserService;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.domain.QwWorkTask;
+import com.fs.qw.param.SelectQwWorkTaskListParam;
+import com.fs.qw.service.IQwExternalContactService;
+import com.fs.qw.service.IQwUserService;
+import com.fs.qw.service.IQwWorkTaskService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Slf4j
+@Api(tags = "企微任务看板接口")
+@RestController
+@RequestMapping("/app/qw/workTask")
+@AllArgsConstructor
+public class QwWorkTaskController extends BaseController {
+
+    private final IQwWorkTaskService qwWorkTaskService;
+    private final IQwUserService qwUserService;
+
+    @Autowired
+    ICompanyUserService companyUserService;
+
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
+    @PostMapping("/list")
+    @ApiOperation("企微任务看板列表")
+    public R selectQwWorkTaskList(@RequestBody SelectQwWorkTaskListParam param) {
+
+        log.info("企微任务看板列表: {}",param);
+
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(param.getCorpId().trim(), param.getQwUserId().trim());
+
+        if (qwUser == null || qwUser.getCompanyId() == null) {
+            return R.error("员工未绑定 销售公司 或 未获取到员工信息,请重试!");
+        }
+
+        param.setUserId(qwUser.getId());
+
+        PageHelper.startPage(param.getPageNum(), param.getPageSize());
+        List<QwWorkTask> list = qwUserService.selectQwWorkTaskList(param);
+
+        for (QwWorkTask qwWorkTask : list) {
+            List<Integer> logs = fsCourseWatchLogMapper.selectFsCourseWatchLog7DayByExtId(qwWorkTask.getExtId());
+            qwWorkTask.setLogs(logs);
+        }
+
+        PageInfo<QwWorkTask> pageInfo = new PageInfo<>(list);
+        return R.ok().put("data",pageInfo);
+    }
+
+    /**
+     * 处理催课
+     */
+    @GetMapping("/updateWorkTaskStatus/{id}")
+    public R updateWorkTaskStatus(@PathVariable("id") Long id) {
+
+        try {
+            QwWorkTask qwWorkTask=new QwWorkTask();
+            qwWorkTask.setId(id);
+            qwWorkTask.setStatus(1);
+            int i = qwWorkTaskService.updateQwWorkTask(qwWorkTask);
+            if (i>0) {
+                return R.ok();
+            }else {
+                return R.error("更新失败,请重试!");
+            }
+        }catch (Exception e){
+            return R.error("更新失败,请重试!");
+        }
+    }
+
+
+
+}

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

@@ -7,12 +7,7 @@ server:
 spring:
   profiles:
 #    active: dev
-#    include: common,config-dev
 #    active: druid-yzt
-#    include: common,config-druid-yzt
 #    active: druid-hdt
-#    include: common,config-druid-hdt
 #    active: druid-sxjz
-#    include: common,config-druid-sxjz
     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/src/main/java/com/fs/ad/service/impl/AdHtmlClickLogServiceImpl.java

@@ -162,12 +162,16 @@ public class AdHtmlClickLogServiceImpl extends ServiceImpl<AdHtmlClickLogMapper,
                 switch (type){
                     case 0: // 百度
                         uploadBaiDu(one, clickType);
+                        break;
                     case 1:// 优酷
                         uploadYouKu(one, clickType);
+                        break;
                     case 2:// 爱奇艺
                         uploadIqiyi(one, clickType);
+                        break;
                     case 3:// 巨量(抖音)
                         uploadDy(one, clickType);
+                        break;
                 }
                 one.setUploadType(uploadType);
                 saveLog(key, one, type);
@@ -351,6 +355,9 @@ public class AdHtmlClickLogServiceImpl extends ServiceImpl<AdHtmlClickLogMapper,
         if(type == 2){
             return AD_LOG_KEY + vid + ":" + type + ":" + clickType.getIqiyiType();
         }
+        if(type == 3){
+            return AD_LOG_KEY + vid + ":" + type + ":" + clickType.getDyType();
+        }
         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.JsonParser;
 import com.google.gson.reflect.TypeToken;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.http.HttpResponse;
 import org.apache.http.HttpStatus;
 import org.apache.http.client.methods.HttpPost;
@@ -23,6 +24,7 @@ import java.util.List;
 /**
  * API 回传数据Demo
  */
+@Slf4j
 public class ConvertData {
 
     private static final Integer RETRY_TIMES = 3;
@@ -44,7 +46,7 @@ public class ConvertData {
                 new Gson().toJsonTree(conversionTypeList, new TypeToken<List<ConversionType>>() {}.getType()).getAsJsonArray());
         // 发送的完整请求数据
         // do some log
-        System.out.println("req data: " + data);
+        log.info("req data: {}", data);
         // 向百度发送数据
         return sendWithRetry(data.toString());
 
@@ -70,7 +72,7 @@ public class ConvertData {
                     JsonObject returnData = JsonParser.parseString(res).getAsJsonObject();
                     // 打印返回结果
                     // 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();
                     // 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
     public List<BdAccount> selectBdAccountList(BdAccount bdAccount){
         List<BdAccount> list = baseMapper.selectBdAccountList(bdAccount);
+        if(list.isEmpty()) return Collections.emptyList();
         List<Long> longs = PubFun.listToNewList(list, BdAccount::getAccountId);
         List<BdPlan> planList = bdPlanService.list(new QueryWrapper<BdPlan>().in("account_id", longs));
         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 org.apache.commons.text.StringSubstitutor;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import com.fs.baidu.mapper.BdApiMapper;
 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}";
 
+    @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("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("callback", "https://bd.runtzh.com/prod-api/baiduBack/callback");
+        params.put("callback", "http://" + backDomain +"/prod-api/baiduBack/callback");
         String filledUri = UriComponentsBuilder
                 .fromUriString(OAUTH_URL)
                 .buildAndExpand(params)

+ 9 - 0
fs-service/src/main/java/com/fs/company/domain/Company.java

@@ -102,6 +102,15 @@ public class Company extends BaseEntity
     @TableField(exist = false)
     private List<String> rules;
 
+    /**
+     * 点播配置-公众号appId
+     */
+    private String courseMaAppId;
+    /**
+     * 点播配置-小程序appId
+     */
+    private String courseMiniAppId;
+
 //    public String getDoctorIds() {
 //        return doctorIds;
 //    }

+ 6 - 2
fs-service/src/main/java/com/fs/core/config/WxMaConfiguration.java

@@ -13,6 +13,7 @@ import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import jodd.util.StringUtil;
 import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.error.WxRuntimeException;
@@ -41,17 +42,20 @@ public class WxMaConfiguration {
     public WxMaConfiguration(SysConfigMapper sysConfigMapper) {
         SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("his.config");
         String configValue = sysConfig.getConfigValue();
+        //下单小程序加载
         WxMaConfig.Config config = JSON.parseObject(configValue, WxMaConfig.Config.class);
         WxMaConfig wx = new WxMaConfig();
         List<WxMaConfig.Config> c = new ArrayList<>();
-
-        c.add(config);
+        if (StringUtil.isNotEmpty(config.getAppid())){
+            c.add(config);
+        }
 //        SysConfig sysConfig2 = sysConfigMapper.selectConfigByConfigKey("store.config");
 //        if (sysConfig2!=null&&sysConfig2.getConfigValue()!=null&&sysConfig2.getConfigValue()!="") {
 //            String configValue2 = sysConfig2.getConfigValue();
 //            WxMaConfig.Config config2 = JSON.parseObject(configValue2, WxMaConfig.Config.class);
 //            c.add(config2);
 //        }
+        //看课小程序加载
         SysConfig sysConfig3 = sysConfigMapper.selectConfigByConfigKey("courseMa.config");
         List<CourseMaConfig> courseMaConfigs = JSON.parseArray(sysConfig3.getConfigValue(), CourseMaConfig.class);
         if (courseMaConfigs!=null&& !courseMaConfigs.isEmpty()){

+ 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 isRoom;//是否发群
+    private String chatId;//是否发群
 
 //    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 Long qwUserIdLong;
     private String qwUserId;
 
     private String corpId;
 
     private Long companyId;
+    private String chatId;
 
     private Long companyUserId;
 
@@ -27,7 +29,6 @@ public class FsCourseLinkCreateParam {
     private Integer sendType;
 
     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 String link;
 
     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.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
@@ -202,6 +203,15 @@ public class FsCourseLinkServiceImpl implements IFsCourseLinkService
 
     @Override
     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");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
         FsCourseLink link = new FsCourseLink();

+ 51 - 8
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -1,18 +1,23 @@
 package com.fs.course.service.impl;
 
-import cn.hutool.core.util.NumberUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.common.BeanCopyUtils;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.CustomException;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.date.DateUtil;
+import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyUser;
+import com.fs.company.mapper.CompanyMapper;
 import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.*;
@@ -29,11 +34,13 @@ import com.fs.course.vo.FsUserCourseVideoVO;
 import com.fs.course.vo.newfs.*;
 import com.fs.his.domain.FsUser;
 import com.fs.his.domain.FsUserIntegralLogs;
+import com.fs.his.domain.FsUserWx;
 import com.fs.his.mapper.FsUserIntegralLogsMapper;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.param.WxSendRedPacketParam;
 import com.fs.his.service.IFsStorePaymentService;
 import com.fs.his.service.IFsUserService;
+import com.fs.his.service.IFsUserWxService;
 import com.fs.his.vo.OptionsVO;
 import com.fs.qw.domain.QwCompany;
 import com.fs.qw.domain.QwExternalContact;
@@ -45,6 +52,7 @@ import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qwApi.Result.QwAddContactWayResult;
 import com.fs.qwApi.param.QwAddContactWayParam;
 import com.fs.qwApi.service.QwApiService;
+import com.fs.repeat.vo.RepeatUploadVo;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.SopUserLogsInfoMapper;
 import com.fs.sop.service.ISopUserLogsInfoService;
@@ -52,6 +60,7 @@ import com.fs.system.service.ISysConfigService;
 import com.fs.voice.utils.StringUtil;
 import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
@@ -62,7 +71,6 @@ import org.springframework.transaction.annotation.Transactional;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.time.LocalDateTime;
-import java.time.LocalTime;
 import java.time.ZoneId;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
@@ -93,6 +101,8 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     @Autowired
     private IFsUserService fsUserService;
     @Autowired
+    private RocketMQTemplate rocketMQTemplate;
+    @Autowired
     private FsUserCourseStudyLogMapper courseStudyLogMapper;
 
     @Autowired
@@ -168,6 +178,10 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
     private IQwExternalContactService qwExternalContactService;
     @Autowired
     private IQwCompanyService iQwCompanyService;
+    @Autowired
+    private CompanyMapper companyMapper;
+    @Autowired
+    private IFsUserWxService fsUserWxService;
 
 
     /**
@@ -384,7 +398,11 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
         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>";
-
+        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();
 
         // 处理逻辑
@@ -833,11 +851,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
         // 准备发送红包参数
         WxSendRedPacketParam packetParam = new WxSendRedPacketParam();
-        packetParam.setOpenId(user.getMpOpenId());
-        // 来源是小程序切换openId
-        if (param.getSource() == 2) {
-            packetParam.setOpenId(user.getCourseMaOpenId());
-        }
+        packetParam.setOpenId(getOpenId(user.getUserId(), param.getCompanyId(), param.getSource()));
         packetParam.setAmount(amount);
         packetParam.setSource(param.getSource());
         packetParam.setRedPacketMode(config.getRedPacketMode());
@@ -882,6 +896,35 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         }
     }
 
+    /**
+     * 获取用户openId
+     *
+     * @param userId    用户ID
+     * @param companyId 公司ID
+     * @param source    来源 1公众号 2小程序
+     * @return openId
+     */
+    private String getOpenId(Long userId, Long companyId, Integer source) {
+        Company company = companyMapper.selectCompanyById(companyId);
+        String appId = source == 1 ? company.getCourseMaAppId() : company.getCourseMiniAppId();
+
+        // 公司配置为空时获取默认配置
+        if (StringUtils.isBlank(appId)) {
+            String json = configService.selectConfigByKey("course.config");
+            CourseConfig config = JSON.parseObject(json, CourseConfig.class);
+            appId = source == 1 ? config.getMpAppId() : config.getMiniprogramAppid();
+        }
+
+        // 查询openId
+        Wrapper<FsUserWx> queryWrapper = Wrappers.<FsUserWx>lambdaQuery().eq(FsUserWx::getFsUserId, userId).eq(FsUserWx::getAppId, appId);
+        FsUserWx fsUserWx = fsUserWxService.getOne(queryWrapper);
+        if (Objects.isNull(fsUserWx)) {
+            throw new CustomException("获取openId失败");
+        }
+
+        return fsUserWx.getOpenId();
+    }
+
     /**
      * 发放积分奖励
      *

+ 40 - 0
fs-service/src/main/java/com/fs/his/domain/FsUserWx.java

@@ -0,0 +1,40 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("fs_user_wx")
+public class FsUserWx {
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    /**
+     * 用户ID
+     */
+    private Long fsUserId;
+    /**
+     * 小程序/公众号appId
+     */
+    private String appId;
+    /**
+     * 微信unionId
+     */
+    private String unionId;
+    /**
+     * 微信openId
+     */
+    private String openId;
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+    /**
+     * 修改时间
+     */
+    private LocalDateTime updateTime;
+}

+ 1 - 1
fs-service/src/main/java/com/fs/his/mapper/FsStoreOrderMapper.java

@@ -114,7 +114,7 @@ public interface FsStoreOrderMapper
             "<if test = 'maps.companyId != null  and   maps.companyId != \"-1\" '> " + "and so.company_id =#{maps.companyId} " + "</if>" +
             "<if test = ' maps.companyId == \"-1\" '> and so.company_id is null </if>" +
             "<if test = 'maps.deliveryStatus != null    '> and so.delivery_status =#{maps.deliveryStatus} </if>" +
-            "<if test = 'maps.customerId != null    '> and so.customer_id =#{customerId} </if>" +
+            "<if test = 'maps.customerId != null    '> and so.customer_id =#{maps.customerId} </if>" +
             "<if test = 'maps.deliveryPayStatus != null    '> and so.delivery_pay_status =#{maps.deliveryPayStatus} </if>" +
             "<if test = 'maps.tuiMoneyStatus != null    '> and so.tui_money_status =#{maps.tuiMoneyStatus} </if>" +
             "<if test = 'maps.deptId != null    '>   AND (so.dept_id = #{maps.deptId} OR so.dept_id IN ( SELECT t.dept_id FROM company_dept t WHERE find_in_set(#{maps.deptId}, ancestors) )) </if>" +

+ 7 - 0
fs-service/src/main/java/com/fs/his/mapper/FsUserWxMapper.java

@@ -0,0 +1,7 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsUserWx;
+
+public interface FsUserWxMapper extends BaseMapper<FsUserWx> {
+}

+ 7 - 0
fs-service/src/main/java/com/fs/his/service/IFsUserWxService.java

@@ -0,0 +1,7 @@
+package com.fs.his.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.his.domain.FsUserWx;
+
+public interface IFsUserWxService extends IService<FsUserWx> {
+}

+ 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.service.IImService;
 import com.fs.jpush.service.JpushService;
+import com.fs.repeat.vo.RepeatUploadVo;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
 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.google.gson.Gson;
 import lombok.Synchronized;
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
@@ -98,6 +100,8 @@ public class FsInquiryOrderServiceImpl implements IFsInquiryOrderService
     @Autowired
     private WxPayProperties wxPayProperties;
     @Autowired
+    private RocketMQTemplate rocketMQTemplate;
+    @Autowired
     private TzBankService tzBankService;
     @Autowired
     private ApplicationEventPublisher publisher;
@@ -719,7 +723,7 @@ public class FsInquiryOrderServiceImpl implements IFsInquiryOrderService
     @Transactional
     public R payConfirm(String orderSn,String payCode, String tradeNo,String payType,Integer type) {
         try {
-            FsInquiryOrder order=null;
+            FsInquiryOrder order;
             if(type.equals(1)){
                 FsStorePayment storePayment = fsStorePaymentMapper.selectFsStorePaymentByPaymentCode(payCode);
                 if (storePayment!=null){
@@ -744,15 +748,20 @@ public class FsInquiryOrderServiceImpl implements IFsInquiryOrderService
                         }
                         fsStorePaymentMapper.updateFsStorePayment(paymentMap);
                         order=fsInquiryOrderMapper.selectFsInquiryOrderByOrderId(Long.parseLong(storePayment.getBusinessId()));
+                    } else {
+                        order = null;
                     }
                 }
                 else{
+                    order = null;
 
                     return R.error("支付单号不存在");
                 }
             }
             else if(type.equals(2)){
                 order=fsInquiryOrderMapper.selectFsInquiryOrderByOrderSn(orderSn);
+            } else {
+                order = null;
             }
             if(!order.getStatus().equals(FsInquiryOrderStatusEnum.STATUS_1.getValue())){
                 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()));
                 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();
         }catch (Exception e){
             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);
         List<FsIntegralGoods> stageGoods = selectRandomStageGoods(param);
 
+        if (stageGoods.size()<3){
+            param.setMinPoints(currentStage);
+            param.setMaxPoints(stageMaxPoints);
+            stageGoods = selectRandomStageGoods(param);
+        }
+
         // 5. 转换为奖励商品列表
         List<FsCourseAnswerReward.RewardProduct> products = new ArrayList<>();
         int minRequiredPoints = stageMaxPoints; // 用于计算进度的最小积分要求

+ 13 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsUserWxServiceImpl.java

@@ -0,0 +1,13 @@
+package com.fs.his.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.his.domain.FsUserWx;
+import com.fs.his.mapper.FsUserWxMapper;
+import com.fs.his.service.IFsUserWxService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+@Service
+@Slf4j
+public class FsUserWxServiceImpl extends ServiceImpl<FsUserWxMapper, FsUserWx> implements IFsUserWxService {
+}

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff