Переглянути джерело

Merge branch 'refs/heads/master' into 红德堂

ct 1 день тому
батько
коміт
f39dcf0c6b
100 змінених файлів з 3176 додано та 508 видалено
  1. 6 0
      fs-admin/src/main/java/com/fs/api/controller/IndexStatisticsController.java
  2. 13 0
      fs-admin/src/main/java/com/fs/course/controller/qw/QwFsCourseWatchLogController.java
  3. 6 6
      fs-admin/src/main/java/com/fs/his/controller/FsArticleController.java
  4. 5 3
      fs-admin/src/main/java/com/fs/his/controller/FsStoreOrderController.java
  5. 49 0
      fs-admin/src/main/java/com/fs/his/task/Task.java
  6. 5 2
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  7. 1 4
      fs-admin/src/main/java/com/fs/qw/controller/QwExternalContactController.java
  8. 9 0
      fs-admin/src/main/java/com/fs/qw/controller/QwPushCountController.java
  9. 103 0
      fs-admin/src/main/java/com/fs/qw/controller/QwUserComplainRecordController.java
  10. 11 0
      fs-admin/src/main/java/com/fs/qw/controller/QwUserController.java
  11. 2 2
      fs-admin/src/main/resources/application.yml
  12. 37 0
      fs-company-app/src/main/java/com/fs/app/controller/AppBaseController.java
  13. 214 6
      fs-company-app/src/main/java/com/fs/app/controller/CompanyUserController.java
  14. 28 32
      fs-company-app/src/main/java/com/fs/core/aspectj/DataScopeAspect.java
  15. 73 0
      fs-company-app/src/main/java/com/fs/core/aspectj/DataSourceAspect.java
  16. 244 0
      fs-company-app/src/main/java/com/fs/core/aspectj/LogAspect.java
  17. 94 0
      fs-company-app/src/main/java/com/fs/core/config/DataSourceConfig.java
  18. 123 123
      fs-company-app/src/main/java/com/fs/core/config/DruidConfig.java
  19. 3 3
      fs-company-app/src/main/java/com/fs/core/datasource/DynamicDataSource.java
  20. 4 5
      fs-company-app/src/main/java/com/fs/core/datasource/DynamicDataSourceContextHolder.java
  21. 56 0
      fs-company-app/src/main/java/com/fs/core/manager/AsyncManager.java
  22. 40 0
      fs-company-app/src/main/java/com/fs/core/manager/ShutdownManager.java
  23. 103 0
      fs-company-app/src/main/java/com/fs/core/manager/factory/AsyncFactory.java
  24. 1 1
      fs-company-app/src/main/resources/application.yml
  25. 27 19
      fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java
  26. 1 1
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseAnswerLogsController.java
  27. 1 1
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseRedPacketLogController.java
  28. 2 1
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseWatchLogController.java
  29. 4 2
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactTransferLogController.java
  30. 3 1
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  31. 35 2
      fs-qw-api/src/main/java/com/fs/app/controller/CommonController.java
  32. 46 0
      fs-qw-api/src/main/java/com/fs/app/controller/QwUserComplainRecordController.java
  33. 9 1
      fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java
  34. 53 53
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  35. 43 2
      fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisQwSopController.java
  36. 4 0
      fs-service/src/main/java/com/fs/company/param/CompanyUserQwParam.java
  37. 5 5
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  38. 8 1
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  39. 10 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoRedPackageMapper.java
  40. 5 3
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java
  41. 8 7
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  42. 0 1
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseComplaintRecordService.java
  43. 23 5
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  44. 1 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseComplaintRecordServiceImpl.java
  45. 4 2
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodServiceImpl.java
  46. 4 2
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseTrainingCampServiceImpl.java
  47. 3 0
      fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogListVO.java
  48. 42 0
      fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogStatisticsListByCompanyVO.java
  49. 22 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptPushTokenTotal.java
  50. 1 0
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  51. 2 2
      fs-service/src/main/java/com/fs/fastgptApi/util/EventLogUtils.java
  52. 14 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java
  53. 8 0
      fs-service/src/main/java/com/fs/his/service/impl/FsInquiryOrderServiceImpl.java
  54. 12 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java
  55. 3 5
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  56. 2 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsPrescribeScrm.java
  57. 10 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsUserScrm.java
  58. 1 4
      fs-service/src/main/java/com/fs/hisStore/enums/SysConfigEnum.java
  59. 2 6
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  60. 10 1
      fs-service/src/main/java/com/fs/qw/domain/QwExternalContactTransferLog.java
  61. 4 0
      fs-service/src/main/java/com/fs/qw/domain/QwUser.java
  62. 56 0
      fs-service/src/main/java/com/fs/qw/domain/QwUserComplainRecord.java
  63. 8 0
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  64. 5 2
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactTransferLogMapper.java
  65. 12 0
      fs-service/src/main/java/com/fs/qw/mapper/QwRestrictionPushRecordMapper.java
  66. 61 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserComplainRecordMapper.java
  67. 3 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  68. 1 1
      fs-service/src/main/java/com/fs/qw/mapper/QwUserVideoMapper.java
  69. 26 0
      fs-service/src/main/java/com/fs/qw/param/CourseQuizRedEnvelopeStatsParam.java
  70. 17 0
      fs-service/src/main/java/com/fs/qw/param/ExternalContactParam.java
  71. 3 0
      fs-service/src/main/java/com/fs/qw/param/QwAutoTagsRulesTags.java
  72. 37 0
      fs-service/src/main/java/com/fs/qw/param/QwSidebarStatsParam.java
  73. 33 0
      fs-service/src/main/java/com/fs/qw/param/QwUserComplaintRecordParam.java
  74. 0 1
      fs-service/src/main/java/com/fs/qw/param/SopMsgParam.java
  75. 7 0
      fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java
  76. 4 2
      fs-service/src/main/java/com/fs/qw/service/IQwExternalContactTransferLogService.java
  77. 3 0
      fs-service/src/main/java/com/fs/qw/service/IQwPushCountService.java
  78. 64 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserComplainRecordService.java
  79. 3 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
  80. 69 21
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  81. 134 21
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactTransferLogServiceImpl.java
  82. 11 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwPushCountServiceImpl.java
  83. 187 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserComplainRecordServiceImpl.java
  84. 5 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  85. 2 0
      fs-service/src/main/java/com/fs/qw/vo/QwUserVO.java
  86. 16 0
      fs-service/src/main/java/com/fs/qwApi/param/QwSendMsgParam.java
  87. 14 0
      fs-service/src/main/java/com/fs/sop/service/IQwSopService.java
  88. 125 3
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopServiceImpl.java
  89. 184 141
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  90. 2 0
      fs-service/src/main/java/com/fs/statis/dto/ConsumptionBalanceDataDTO.java
  91. 4 2
      fs-service/src/main/java/com/fs/statis/service/impl/StatisticsServiceImpl.java
  92. 15 0
      fs-service/src/main/java/com/fs/store/vo/h5/ExternalAnswerStatsVO.java
  93. 16 0
      fs-service/src/main/java/com/fs/store/vo/h5/ExternalRedPacketStatsVO.java
  94. 35 0
      fs-service/src/main/java/com/fs/store/vo/h5/ExternalUserStatsVO.java
  95. 15 0
      fs-service/src/main/java/com/fs/store/vo/h5/ExternalWatchStatsVO.java
  96. 22 0
      fs-service/src/main/java/com/fs/tulin/JsonSortUtils.java
  97. 49 0
      fs-service/src/main/java/com/fs/tulin/PharmacyEnum.java
  98. 83 0
      fs-service/src/main/java/com/fs/tulin/StudentInfo.java
  99. 182 0
      fs-service/src/main/java/com/fs/tulin/SyncStudentInfoService.java
  100. 1 0
      fs-service/src/main/resources/application-dev-jnlzjk.yml

+ 6 - 0
fs-admin/src/main/java/com/fs/api/controller/IndexStatisticsController.java

@@ -1,5 +1,6 @@
 package com.fs.api.controller;
 
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.entity.SysDept;
 import com.fs.common.core.redis.RedisCache;
@@ -168,6 +169,11 @@ public class IndexStatisticsController {
                 );
             }
         }
+        BigDecimal redPacketCompanyMoney = redisCache.getCacheObject("redpacket_money");
+        if(ObjectUtils.isNull(redPacketCompanyMoney)){
+            redPacketCompanyMoney = BigDecimal.ZERO;
+        }
+        consumptionBalanceDataDTO.setRunTianBalance(redPacketCompanyMoney);
 
         return R.ok().put("data", consumptionBalanceDataDTO);
     }

+ 13 - 0
fs-admin/src/main/java/com/fs/course/controller/qw/QwFsCourseWatchLogController.java

@@ -3,6 +3,7 @@ package com.fs.course.controller.qw;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.exception.CustomException;
@@ -16,6 +17,7 @@ import com.fs.course.param.PeriodStatisticCountParam;
 import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.course.vo.FsCourseOverVO;
 import com.fs.course.vo.FsCourseWatchLogListVO;
+import com.fs.course.vo.FsCourseWatchLogStatisticsListByCompanyVO;
 import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
 import com.fs.qw.param.QwWatchLogStatisticsListParam;
 import com.fs.qw.service.IQwWatchLogService;
@@ -78,6 +80,17 @@ public class QwFsCourseWatchLogController extends BaseController
         List<FsCourseWatchLogStatisticsListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogStatisticsListVO(param);
         return getDataTable(list);
     }
+
+    @GetMapping("/statisticsListByCompany")
+    public R statisticsListByCompany(FsCourseWatchLogStatisticsListParam param)
+    {
+        if (param.getSTime()==null||param.getETime()==null){
+            return R.ok().put("rows", new ArrayList<>());
+        }
+        param.setSendType(2); //企微
+        List<FsCourseWatchLogStatisticsListByCompanyVO> list = fsCourseWatchLogService.selectFsCourseWatchLogStatisticsListByCompanyVO(param);
+        return R.ok().put("rows", list);
+    }
     @GetMapping("/qwWatchLogStatisticsList")
     public TableDataInfo qwWatchLogStatisticsList(QwWatchLogStatisticsListParam param)
     {

+ 6 - 6
fs-admin/src/main/java/com/fs/his/controller/FsArticleController.java

@@ -42,7 +42,7 @@ public class FsArticleController extends BaseController
     /**
      * 查询文章列表
      */
-    @PreAuthorize("@ss.hasPermi('his:article:list')")
+    @PreAuthorize("@ss.hasPermi('his:healthArticle:list')")
     @GetMapping("/list")
     public TableDataInfo list(FsArticle fsArticle)
     {
@@ -54,7 +54,7 @@ public class FsArticleController extends BaseController
     /**
      * 导出文章列表
      */
-    @PreAuthorize("@ss.hasPermi('his:article:export')")
+    @PreAuthorize("@ss.hasPermi('his:healthArticle:export')")
     @Log(title = "文章", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
     public AjaxResult export(FsArticle fsArticle)
@@ -67,7 +67,7 @@ public class FsArticleController extends BaseController
     /**
      * 获取文章详细信息
      */
-    @PreAuthorize("@ss.hasPermi('his:article:query')")
+    @PreAuthorize("@ss.hasPermi('his:healthArticle:query')")
     @GetMapping(value = "/{articleId}")
     public AjaxResult getInfo(@PathVariable("articleId") Long articleId)
     {
@@ -77,7 +77,7 @@ public class FsArticleController extends BaseController
     /**
      * 新增文章
      */
-    @PreAuthorize("@ss.hasPermi('his:article:add')")
+    @PreAuthorize("@ss.hasPermi('his:healthArticle:add')")
     @Log(title = "文章", businessType = BusinessType.INSERT)
     @PostMapping
     public AjaxResult add(@RequestBody FsArticle fsArticle)
@@ -94,7 +94,7 @@ public class FsArticleController extends BaseController
     /**
      * 修改文章
      */
-    @PreAuthorize("@ss.hasPermi('his:article:edit')")
+    @PreAuthorize("@ss.hasPermi('his:healthArticle:edit')")
     @Log(title = "文章", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody FsArticle fsArticle)
@@ -110,7 +110,7 @@ public class FsArticleController extends BaseController
     /**
      * 删除文章
      */
-    @PreAuthorize("@ss.hasPermi('his:article:remove')")
+    @PreAuthorize("@ss.hasPermi('his:healthArticle:remove')")
     @Log(title = "文章", businessType = BusinessType.DELETE)
 	@DeleteMapping("/{articleIds}")
     public AjaxResult remove(@PathVariable Long[] articleIds)

+ 5 - 3
fs-admin/src/main/java/com/fs/his/controller/FsStoreOrderController.java

@@ -122,6 +122,9 @@ public class FsStoreOrderController extends BaseController
 
     @Autowired
     private IFsStoreOrderLogsService fsStoreOrderLogsService;
+
+    @Autowired
+    private IFsDfAccountService fsDfAccountService;
     /**
      * 查询订单列表
      */
@@ -956,10 +959,9 @@ public class FsStoreOrderController extends BaseController
     {
         List<String> list = new ArrayList<>();
         if (CloudHostUtils.hasCloudHostName("金牛明医","康年堂")){
-            List<DFConfigVo> erpAccounts = fsStoreOrderService.getErpAccount();
-            list = erpAccounts.stream().map(DFConfigVo::getLoginAccount).collect(Collectors.toList());
+                List<FsDfAccount> erpAccounts = fsDfAccountService.selectFsDfAccountList(null);
+                list = erpAccounts.stream().map(FsDfAccount::getLoginAccount).collect(Collectors.toList());
         }
-
         return R.ok().put("data", list);
     }
 }

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

@@ -28,6 +28,7 @@ import com.fs.erp.dto.ErpOrderResponse;
 import com.fs.erp.mapper.FsErpFinishPushMapper;
 import com.fs.erp.service.IErpOrderService;
 import com.fs.fastGpt.domain.FastGptEventTokenLog;
+import com.fs.fastGpt.domain.FastGptPushTokenTotal;
 import com.fs.fastGpt.domain.FastgptChatVoiceHomo;
 import com.fs.fastGpt.domain.FastgptEventLogTotal;
 import com.fs.fastGpt.mapper.FastGptChatSessionMapper;
@@ -35,6 +36,7 @@ import com.fs.fastGpt.mapper.FastgptChatVoiceHomoMapper;
 import com.fs.fastGpt.service.IFastgptEventLogTotalService;
 import com.fs.fastgptApi.util.AudioUtils;
 import com.fs.fastgptApi.vo.AudioVO;
+import com.fs.gtPush.mapper.PushLogMapper;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.config.StoreConfig;
 import com.fs.his.domain.FsInquiryOrder;
@@ -53,6 +55,7 @@ import com.fs.his.vo.FsSubOrderResultVO;
 import com.fs.im.dto.*;
 import com.fs.im.service.IImService;
 import com.fs.qw.domain.QwCompany;
+import com.fs.qw.mapper.QwRestrictionPushRecordMapper;
 import com.fs.qw.service.*;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.sop.domain.QwSopTempVoice;
@@ -179,8 +182,54 @@ public class Task {
     @Autowired
     private IQwSopTempVoiceService qwSopTempVoiceService;
 
+    @Autowired
+    private QwRestrictionPushRecordMapper qwRestrictionPushRecordMapper;
+
     public static final String SOP_TEMP_VOICE_KEY = "sop:tempVoice";
 
+    /**
+     * sop任务token消耗统计
+     */
+    public void sopPushTokenTotal() {
+        // 判断是否是凌晨 00:00 - 00:59
+        boolean isEarlyMorning = isEarlyMorning();
+
+        // 获取日期字符串(今天或昨天)
+        String dateTime;
+        if (isEarlyMorning) {
+            dateTime = DateUtils.addDateDays(-1); // 昨天
+        } else {
+            dateTime = DateUtils.getDate(); // 今天
+        }
+        log.info("开始执行sop任务token消耗统计");
+        try {
+            List<FastGptPushTokenTotal> fastGptPushTotalList = qwRestrictionPushRecordMapper.selectFastgptPushTokenTotal(dateTime);
+            if (fastGptPushTotalList != null && !fastGptPushTotalList.isEmpty()) {
+                for (FastGptPushTokenTotal fastGptPushTotal : fastGptPushTotalList) {
+                    // 获取统计数据
+                    Integer type = fastGptPushTotal.getType();
+                    Long count = 0L;
+                    if(type == 7){
+                        count = fastGptPushTotal.getCount() * 450;
+                    }else{
+                        count = fastGptPushTotal.getCount() * 150;
+                    }
+                    fastGptPushTotal.setCount(count);
+                    FastGptPushTokenTotal pushTotal = qwRestrictionPushRecordMapper.selectFastGptPushTokenTotalByInfo(fastGptPushTotal);
+                    if(pushTotal == null){
+                        qwRestrictionPushRecordMapper.insertPushTokenTotal(fastGptPushTotal);
+                    }else{
+                        fastGptPushTotal.setId(pushTotal.getId());
+                        qwRestrictionPushRecordMapper.updatePushTokenTotal(fastGptPushTotal);
+                    }
+                }
+            }
+            log.info("结束执行sop任务token消耗统计");
+        } catch (Exception e) {
+            log.error("执行sop任务token消耗统计异常", e);
+        }
+    }
+
     /**
      * 一键生成语音定时任务
      */

+ 5 - 2
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java

@@ -890,8 +890,11 @@ public class FsStoreOrderScrmController extends BaseController {
     @GetMapping("/getErpAccount")
     public R getErpAccount()
     {
-        List<FsDfAccount> erpAccounts = fsDfAccountService.selectFsDfAccountList(null);
-        List<String> list = erpAccounts.stream().map(FsDfAccount::getLoginAccount).collect(Collectors.toList());
+        List<String> list = new ArrayList<>();
+        if (CloudHostUtils.hasCloudHostName("金牛明医","康年堂")) {
+            List<FsDfAccount> erpAccounts = fsDfAccountService.selectFsDfAccountList(null);
+            list = erpAccounts.stream().map(FsDfAccount::getLoginAccount).collect(Collectors.toList());
+        }
         return R.ok().put("data", list);
     }
 

+ 1 - 4
fs-admin/src/main/java/com/fs/qw/controller/QwExternalContactController.java

@@ -57,11 +57,8 @@ public class QwExternalContactController extends BaseController
     @GetMapping("/list")
     public TableDataInfo list(QwExternalContactParam qwExternalContact)
     {
-        if(ObjectUtil.isEmpty(qwExternalContact.getCompanyId())){
-            throw new ServiceException("操作失败,请选择企业!");
-        }
         startPage();
-        List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(qwExternalContact);
+        List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVONewSys(qwExternalContact);
         list.forEach(item->{
 
             if (!Objects.equals(item.getTagIds(), "[]") && item.getTagIds()!=null) {

+ 9 - 0
fs-admin/src/main/java/com/fs/qw/controller/QwPushCountController.java

@@ -6,6 +6,7 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.fastGpt.domain.FastGptPushTokenTotal;
 import com.fs.qw.domain.QwPushCount;
 import com.fs.qw.service.IQwPushCountService;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -151,4 +152,12 @@ public class QwPushCountController extends BaseController {
     public AjaxResult remove(@PathVariable Long[] ids) {
         return toAjax(qwPushCountService.deleteQwPushCountByIds(ids));
     }
+
+    @PreAuthorize("@ss.hasPermi('qw:qwPushCount:tokenList')")
+    @GetMapping("/tokenList")
+    public TableDataInfo tokenList(FastGptPushTokenTotal pushTokenInfo) {
+        startPage();
+        List<FastGptPushTokenTotal> list = qwPushCountService.selectFastGptPushTokenTotalList(pushTokenInfo);
+        return getDataTable(list);
+    }
 }

+ 103 - 0
fs-admin/src/main/java/com/fs/qw/controller/QwUserComplainRecordController.java

@@ -0,0 +1,103 @@
+package com.fs.qw.controller;
+
+import java.util.List;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.qw.domain.QwUserComplainRecord;
+import com.fs.qw.service.IQwUserComplainRecordService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 企微员工投诉记录Controller
+ *
+ * @author fs
+ * @date 2025-10-22
+ */
+@RestController
+@RequestMapping("/qw/record")
+public class QwUserComplainRecordController extends BaseController
+{
+    @Autowired
+    private IQwUserComplainRecordService qwUserComplainRecordService;
+
+    /**
+     * 查询企微员工投诉记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:record:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwUserComplainRecord qwUserComplainRecord)
+    {
+        startPage();
+        List<QwUserComplainRecord> list = qwUserComplainRecordService.selectQwUserComplainRecordList(qwUserComplainRecord);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出企微员工投诉记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:record:export')")
+    @Log(title = "企微员工投诉记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(QwUserComplainRecord qwUserComplainRecord)
+    {
+        List<QwUserComplainRecord> list = qwUserComplainRecordService.selectQwUserComplainRecordList(qwUserComplainRecord);
+        ExcelUtil<QwUserComplainRecord> util = new ExcelUtil<QwUserComplainRecord>(QwUserComplainRecord.class);
+        return util.exportExcel(list, "企微员工投诉记录数据");
+    }
+
+    /**
+     * 获取企微员工投诉记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('qw:record:query')")
+    @GetMapping(value = "/{recordId}")
+    public AjaxResult getInfo(@PathVariable("recordId") Long recordId)
+    {
+        return AjaxResult.success(qwUserComplainRecordService.selectQwUserComplainRecordByRecordId(recordId));
+    }
+
+    /**
+     * 新增企微员工投诉记录
+     */
+    @PreAuthorize("@ss.hasPermi('qw:record:add')")
+    @Log(title = "企微员工投诉记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody QwUserComplainRecord qwUserComplainRecord)
+    {
+        return toAjax(qwUserComplainRecordService.insertQwUserComplainRecord(qwUserComplainRecord));
+    }
+
+    /**
+     * 修改企微员工投诉记录
+     */
+    @PreAuthorize("@ss.hasPermi('qw:record:edit')")
+    @Log(title = "企微员工投诉记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody QwUserComplainRecord qwUserComplainRecord)
+    {
+        return toAjax(qwUserComplainRecordService.updateQwUserComplainRecord(qwUserComplainRecord));
+    }
+
+    /**
+     * 删除企微员工投诉记录
+     */
+    @PreAuthorize("@ss.hasPermi('qw:record:remove')")
+    @Log(title = "企微员工投诉记录", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{recordIds}")
+    public AjaxResult remove(@PathVariable Long[] recordIds)
+    {
+        return toAjax(qwUserComplainRecordService.deleteQwUserComplainRecordByRecordIds(recordIds));
+    }
+}

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

@@ -5,11 +5,14 @@ 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 com.fs.qw.vo.QwOptionsVO;
 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 java.util.List;
+
 /**
  * 企微用户Controller
  *
@@ -34,4 +37,12 @@ public class QwUserController extends BaseController {
     public R getQwUserInfo(QwFsUserParam param){
         return R.ok().put("data",qwUserService.getQwUserInfo(param));
     }
+
+
+   @GetMapping("/getMyQwCompanyList")
+    public R getMyQwCompanyList()
+    {
+        List<QwOptionsVO> list = qwUserService.selectQwCompanyListOptionsVOBySys();
+        return  R.ok().put("data",list);
+    }
 }

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

@@ -4,10 +4,10 @@ server:
 # Spring配置
 spring:
   profiles:
-    active: dev
+#    active: dev
 #    active: druid-hdt
 #    active: druid-yzt
-#    active: druid-sxjz
+    active: druid-sxjz-test
 #    active: druid-sft
 #    active: druid-fby
 #    active: dev

+ 37 - 0
fs-company-app/src/main/java/com/fs/app/controller/AppBaseController.java

@@ -4,15 +4,24 @@ package com.fs.app.controller;
 import cn.hutool.core.util.ObjectUtil;
 import com.fs.app.exception.FSException;
 import com.fs.app.utils.JwtUtils;
+import com.fs.common.constant.HttpStatus;
+import com.fs.common.core.page.PageDomain;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.core.page.TableSupport;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.sql.SqlUtil;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsUserService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import io.jsonwebtoken.Claims;
 import org.springframework.beans.factory.annotation.Autowired;
 
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 
@@ -67,5 +76,33 @@ public class AppBaseController {
 		}
 		return user.getUserId();
 	}
+    /**
+     * 设置请求分页数据
+     */
+    protected void startPage()
+    {
+        PageDomain pageDomain = TableSupport.buildPageRequest();
+        Integer pageNum = pageDomain.getPageNum();
+        Integer pageSize = pageDomain.getPageSize();
+        if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize))
+        {
+            String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
+            Boolean reasonable = pageDomain.getReasonable();
+            PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
+        }
+    }
 
+    /**
+     * 响应请求分页数据
+     */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    protected TableDataInfo getDataTable(List<?> list)
+    {
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(HttpStatus.SUCCESS);
+        rspData.setMsg("查询成功");
+        rspData.setRows(list);
+        rspData.setTotal(new PageInfo(list).getTotal());
+        return rspData;
+    }
 }

+ 214 - 6
fs-company-app/src/main/java/com/fs/app/controller/CompanyUserController.java

@@ -2,6 +2,7 @@ package com.fs.app.controller;
 
 import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.app.annotation.Login;
@@ -11,19 +12,32 @@ import com.fs.app.vo.CompanySubUserVO;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.constant.UserConstants;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.ServiceException;
 import com.fs.common.utils.PatternUtils;
 import com.fs.common.utils.bean.BeanUtils;
 import com.fs.company.domain.*;
 import com.fs.company.mapper.CompanyRoleMapper;
+import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.company.param.companyUserAddPrintParam;
 import com.fs.company.service.*;
 import com.fs.company.vo.CompanyTagUserVO;
 import com.fs.company.vo.CompanyUserChangeApplyVO;
+import com.fs.config.ai.AiHostProper;
 import com.fs.core.security.SecurityUtils;
 import com.fs.course.service.IFsCourseRedPacketLogService;
 import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.course.service.IFsUserCompanyUserService;
+import com.fs.fastGpt.domain.FastgptChatVoiceHomo;
+import com.fs.fastGpt.mapper.FastgptChatVoiceHomoMapper;
+import com.fs.fastgptApi.util.AudioUtils;
+import com.fs.fastgptApi.vo.AudioVO;
 import com.fs.qw.dto.UserProjectDTO;
+import com.fs.sop.domain.QwSopTempVoice;
+import com.fs.sop.service.IQwSopTempVoiceService;
+import com.fs.system.oss.CloudStorageService;
+import com.fs.system.oss.OSSFactory;
 import com.fs.system.service.ISysDictDataService;
 import com.fs.system.vo.DictVO;
 import com.github.pagehelper.PageHelper;
@@ -34,10 +48,19 @@ import io.swagger.annotations.ApiParam;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections.CollectionUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.apache.ibatis.annotations.Param;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
 import javax.validation.Valid;
+import java.io.File;
+import java.io.FileInputStream;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.time.LocalDate;
@@ -59,12 +82,15 @@ public class CompanyUserController extends AppBaseController {
     private final ICompanyUserChangeApplyService companyUserChangeApplyService;
     private final CompanyRoleMapper companyRoleMapper;
     private final IAppService appService;
-    @Autowired
-    private ISysDictDataService dictDataService;
-    @Autowired
-    private IFsUserCompanyUserService fsUserCompanyUserService;
-    @Autowired
-    private ICompanyTagUserService companyTagUserService;
+    private final RedisCache redisCache;
+    private final CompanyUserMapper companyUserMapper;
+    private final IQwSopTempVoiceService voiceService;
+    private final AiHostProper aiHostProper;
+    private final ISysDictDataService dictDataService;
+    private final IFsUserCompanyUserService fsUserCompanyUserService;
+    private final ICompanyTagUserService companyTagUserService;
+    private final FastgptChatVoiceHomoMapper fastgptChatVoiceHomoMapper;
+    public static final String SOP_TEMP_VOICE_KEY = "sop:tempVoice";
 
     @Login
     @ApiOperation("查询用户列表")
@@ -394,4 +420,186 @@ public class CompanyUserController extends AppBaseController {
                 .collect(Collectors.toList());
         return R.ok().put("data",filteredDictVOS);
     }
+    /**
+     * 当只有模板文字text时,生成表中对应条的voice_url和user_voice_url
+     * @param id            qw_sop_temp_voice的id
+     * @return
+     */
+    @GetMapping("/companyUserVoice")
+    public R companyUserVoice(@RequestParam("id") Long id){
+        AudioVO audioVO = new AudioVO();
+        Long companyUserId = getCompanyUserId();
+        List<QwSopTempVoice> sopTempVoices = redisCache.getVoiceAllList(SOP_TEMP_VOICE_KEY + ":" + companyUserId);
+        if(sopTempVoices != null && !sopTempVoices.isEmpty()){
+            List<Long> collect = sopTempVoices.stream().map(QwSopTempVoice::getId).collect(Collectors.toList());
+            if (collect.contains(id)){
+                return R.ok().put("code",202).put("msg","该语音已进入转换,请完成后再试。");
+            }
+        }
+
+        if(companyUserId != null){
+            CompanyUser companyUser = companyUserMapper.selectCompanyUserByCompanyUserId(companyUserId);
+            if(companyUser != null && companyUser.getVoicePrintUrl() == null){
+                return R.ok().put("code",201).put("msg","账号未录制声纹,请录制后再试!");
+            }
+        }
+
+        QwSopTempVoice qwSopTempVoice = voiceService.selectQwSopTempVoiceById(id);
+        if(qwSopTempVoice != null && qwSopTempVoice.getCompanyUserId() != null){
+            List<FastgptChatVoiceHomo> homos = fastgptChatVoiceHomoMapper.selectFastgptChatVoiceHomoList(new FastgptChatVoiceHomo());
+            audioVO = AudioUtils.createUserUrlAndUrl(homos,qwSopTempVoice.getCompanyUserId(), qwSopTempVoice.getVoiceTxt().replace(" ",""));
+            if(audioVO != null && audioVO.getWavUrl() != null &&  audioVO.getUrl() != null){
+                qwSopTempVoice.setVoiceUrl(audioVO.getUrl());
+                qwSopTempVoice.setUserVoiceUrl(audioVO.getWavUrl());
+                qwSopTempVoice.setDuration(audioVO.getDuration());
+                qwSopTempVoice.setRecordType(1);
+                voiceService.updateQwSopTempVoice(qwSopTempVoice);
+            }
+        }
+        return R.ok().put("data", audioVO);
+    }
+    @Login
+    @ApiOperation("上传声纹")
+    @PostMapping("/addVoicePrintUrl")
+    public R addVoicePrintUrl(@RequestBody companyUserAddPrintParam param) throws Exception {
+        Long userId=getCompanyUserId();
+        if(userId==null){
+            return R.error(403,"用户失效");
+        }
+        CompanyUser companyUser = new CompanyUser();
+        companyUser.setUserId(userId);
+        companyUser.setVoicePrintUrl(param.getVoicePrintUrl());
+
+        //转换音频格式 mp3-wav
+        String s = AudioUtils.audioWAVFromUrl(param.getVoicePrintUrl());
+
+        //保存文件并且上传存储桶
+        System.out.println(s);
+        File file = new File(s);
+        FileInputStream fileInputStream = new FileInputStream(file);
+        CloudStorageService storage = OSSFactory.build();
+        String wavUrl = storage.uploadSuffix(fileInputStream, ".wav");
+
+        //更新销售员工声纹
+        companyUser.setVoicePrintUrl(wavUrl);
+        companyUserMapper.updateCompanyUser(companyUser);
+
+        try {
+            CloseableHttpClient httpClient = HttpClients.createDefault();
+            HttpPost httpPost = new HttpPost(aiHostProper.getCommonApi()+"/app/common/addCompanyAudio");
+            String json = "{\"url\":\""+wavUrl+"\",\"id\":\""+userId+"\"}";
+            StringEntity entity = new StringEntity(json);
+            httpPost.setEntity(entity);
+            httpPost.setHeader("Content-type", "application/json");
+            HttpResponse response = httpClient.execute(httpPost);
+
+            if (response.getStatusLine().getStatusCode() == 200) {
+                String responseBody = EntityUtils.toString(response.getEntity());
+                JSONObject jsonObject = JSON.parseObject(responseBody);
+                Integer code = (Integer)jsonObject.get("code");
+                if (code==200){
+                    voiceService.insertQwSopTempVoiceModel(userId);
+                    return R.ok();
+                }
+            } else {
+                return R.error();
+            }
+
+            httpClient.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return R.error();
+
+    }
+
+    /**
+     * 当只有user_voice_url时,生成表中对应条的voice_url
+     * @param userVoiceUrl  wav格式的语音文件
+     * @param id            qw_sop_temp_voice的id
+     * @return
+     */
+    @GetMapping("/companyUserVoiceNew")
+    public R companyUserVoiceNew( @RequestParam("id") Long id,@RequestParam("userVoiceUrl") String userVoiceUrl){
+
+        AudioVO audioVO = new AudioVO();
+        Long companyUserId = getCompanyUserId();
+        List<QwSopTempVoice> sopTempVoices = redisCache.getVoiceAllList(SOP_TEMP_VOICE_KEY + ":" + companyUserId);
+        if(sopTempVoices != null && !sopTempVoices.isEmpty()){
+            List<Long> collect = sopTempVoices.stream().map(QwSopTempVoice::getId).collect(Collectors.toList());
+            if (collect.contains(id)){
+                return R.ok().put("code",202).put("msg","该语音已进入转换,请完成后再试。");
+            }
+        }
+
+        QwSopTempVoice qwSopTempVoice = voiceService.selectQwSopTempVoiceByIdAndUserVoiceUrl(id);
+        if(qwSopTempVoice != null && qwSopTempVoice.getId() != null){
+            audioVO = AudioUtils.createVoiceUrl(qwSopTempVoice.getCompanyUserId(), userVoiceUrl);
+            if(audioVO != null && audioVO.getUrl() != null){
+                qwSopTempVoice.setVoiceUrl(audioVO.getUrl());
+                qwSopTempVoice.setUserVoiceUrl(userVoiceUrl);
+                qwSopTempVoice.setDuration(audioVO.getDuration());
+                qwSopTempVoice.setRecordType(1);
+                voiceService.updateQwSopTempVoice(qwSopTempVoice);
+            }
+        }
+        return R.ok().put("data", audioVO);
+    }
+    @GetMapping("/querySopVoiceList")
+    public TableDataInfo querySopVoiceList(@Param("recordType") Integer recordType){
+        startPage();
+        QwSopTempVoice sopTempVoice = new QwSopTempVoice();
+        sopTempVoice.setRecordType(recordType);
+        sopTempVoice.setCompanyUserId(getCompanyUserId());
+        List<QwSopTempVoice> sopTempVoices = voiceService.selectQwSopTempVoiceNewList(sopTempVoice);
+        return getDataTable(sopTempVoices);
+    }
+    @GetMapping("/query/{id}")
+    public R querySopVoiceById(@PathVariable("id") Long id){
+        QwSopTempVoice tempVoice = voiceService.selectQwSopTempVoiceById(id);
+        AudioVO audioVO = new AudioVO();
+        if(tempVoice != null){
+            audioVO.setId(tempVoice.getId());
+            audioVO.setVoiceTxt(tempVoice.getVoiceTxt());
+            audioVO.setUrl(tempVoice.getVoiceUrl());
+            audioVO.setWavUrl(tempVoice.getUserVoiceUrl());
+            audioVO.setDuration(tempVoice.getDuration());
+            audioVO.setRecordType(tempVoice.getRecordType());
+        }
+        return R.ok().put("data", audioVO);
+    }
+    /**
+     * 一键转换
+     * @return
+     */
+    @GetMapping("/createUserAllVoice")
+    public R createUserAllVoice(){
+        QwSopTempVoice sopTempVoice = new QwSopTempVoice();
+        sopTempVoice.setRecordType(0);
+        Long companyUserId = getCompanyUserId();
+
+
+        if(companyUserId != null){
+            CompanyUser companyUser = companyUserMapper.selectCompanyUserByCompanyUserId(companyUserId);
+            if(companyUser != null && companyUser.getVoicePrintUrl() == null){
+                return R.ok().put("code",201).put("msg","账号未录制声纹,请录制后再试!");
+            }
+        }
+
+        sopTempVoice.setCompanyUserId(companyUserId);
+        List<QwSopTempVoice> sopTempVoices = voiceService.selectQwSopTempVoiceNewList(sopTempVoice);
+        if(sopTempVoices != null && !sopTempVoices.isEmpty()){
+            List<Long> newCompanyUserId = redisCache.getVoiceAllList(SOP_TEMP_VOICE_KEY);
+            if(newCompanyUserId != null && newCompanyUserId.contains(companyUserId)){
+                return R.error().put("code",202).put("msg","语音还未转换完成,请完成后再添加!");
+            }else{
+                redisCache.setVoice(SOP_TEMP_VOICE_KEY,companyUserId);
+                sopTempVoices.forEach(m -> m.setVoiceTxt(m.getVoiceTxt().replace(" ","")));
+                redisCache.setVoiceList(SOP_TEMP_VOICE_KEY + ":" + companyUserId, sopTempVoices);
+                return R.ok().put("msg","语音已加入队列进行转换,请耐心等待!");
+            }
+        }
+        return null;
+    }
 }

+ 28 - 32
fs-company-app/src/main/java/com/fs/core/aspectj/DataScopeAspect.java

@@ -1,27 +1,20 @@
 package com.fs.core.aspectj;
 
-import com.fs.app.utils.JwtUtils;
 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 com.fs.common.utils.spring.SpringUtils;
-import com.fs.company.domain.CompanyRole;
-import com.fs.company.domain.CompanyUser;
-import com.fs.company.service.ICompanyUserService;
-import io.jsonwebtoken.Claims;
 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.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
-import org.springframework.web.context.request.RequestAttributes;
-import org.springframework.web.context.request.RequestContextHolder;
-import org.springframework.web.context.request.ServletRequestAttributes;
 
-import javax.servlet.http.HttpServletRequest;
 import java.lang.reflect.Method;
 
 /**
@@ -33,9 +26,6 @@ import java.lang.reflect.Method;
 @Component
 public class DataScopeAspect
 {
-    @Autowired
-    JwtUtils jwtUtils;
-
     /**
      * 全部数据权限
      */
@@ -75,6 +65,7 @@ public class DataScopeAspect
     @Before("dataScopePointCut()")
     public void doBefore(JoinPoint point) throws Throwable
     {
+        clearDataScope(point);
         handleDataScope(point);
     }
 
@@ -86,22 +77,15 @@ public class DataScopeAspect
         {
             return;
         }
-        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
-        ServletRequestAttributes sra = (ServletRequestAttributes)ra;
-        HttpServletRequest request = sra.getRequest();
-
-        String headValue = request.getHeader("APPToken");
-        Claims claims = jwtUtils.getClaimByToken(headValue);
-        Long userId =Long.parseLong( claims.getSubject().toString());
-
         // 获取当前的用户
-        CompanyUser user = SpringUtils.getBean(ICompanyUserService.class).selectCompanyUserById(userId);
-        if (StringUtils.isNotNull(user))
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (StringUtils.isNotNull(loginUser))
         {
+            SysUser currentUser = loginUser.getUser();
             // 如果是超级管理员,则不过滤数据
-            if (StringUtils.isNotNull(user) && !user.isAdmin())
+            if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
             {
-                dataScopeFilter(joinPoint, user, controllerDataScope.deptAlias(),
+                dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
                         controllerDataScope.userAlias());
             }
         }
@@ -114,11 +98,11 @@ public class DataScopeAspect
      * @param user 用户
      * @param userAlias 别名
      */
-    public static void dataScopeFilter(JoinPoint joinPoint, CompanyUser user, String deptAlias, String userAlias)
+    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
     {
         StringBuilder sqlString = new StringBuilder();
 
-        for (CompanyRole role : user.getRoles())
+        for (SysRole role : user.getRoles())
         {
             String dataScope = role.getDataScope();
             if (DATA_SCOPE_ALL.equals(dataScope))
@@ -129,7 +113,7 @@ public class DataScopeAspect
             else if (DATA_SCOPE_CUSTOM.equals(dataScope))
             {
                 sqlString.append(StringUtils.format(
-                        " OR {}.dept_id IN ( SELECT dept_id FROM company_role_dept WHERE role_id = {} ) ", deptAlias,
+                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
                         role.getRoleId()));
             }
             else if (DATA_SCOPE_DEPT.equals(dataScope))
@@ -139,7 +123,7 @@ public class DataScopeAspect
             else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
             {
                 sqlString.append(StringUtils.format(
-                        " OR {}.dept_id IN ( SELECT dept_id FROM company_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
+                        " 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))
@@ -151,8 +135,7 @@ public class DataScopeAspect
                 else
                 {
                     // 数据权限为仅本人且没有userAlias别名不查询任何数据
-//                    sqlString.append(" OR 1=0 ");
-                    sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
+                    sqlString.append(" OR 1=0 ");
                 }
             }
         }
@@ -183,4 +166,17 @@ public class DataScopeAspect
         }
         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-company-app/src/main/java/com/fs/core/aspectj/DataSourceAspect.java

@@ -0,0 +1,73 @@
+package com.fs.core.aspectj;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.utils.StringUtils;
+import com.fs.core.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);
+    }
+}

+ 244 - 0
fs-company-app/src/main/java/com/fs/core/aspectj/LogAspect.java

@@ -0,0 +1,244 @@
+package com.fs.core.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.core.manager.AsyncManager;
+import com.fs.core.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;
+    }
+}

+ 94 - 0
fs-company-app/src/main/java/com/fs/core/config/DataSourceConfig.java

@@ -0,0 +1,94 @@
+package com.fs.core.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.core.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.servlet.FilterConfig;
+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.sop.druid.master")
+    public DataSource sopDataSource() {
+        return new DruidDataSource();
+    }
+
+    @Bean
+    @ConfigurationProperties(prefix = "spring.datasource.mysql.druid.master")
+    public DataSource masterDataSource() {
+        return new DruidDataSource();
+    }
+
+
+
+    @Bean
+    @Primary
+    public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("sopDataSource") DataSource sopDataSource) {
+        Map<Object, Object> targetDataSources = new HashMap<>();
+        targetDataSources.put(DataSourceType.MASTER, masterDataSource);
+        targetDataSources.put(DataSourceType.SOP.name(), sopDataSource);
+        return new DynamicDataSource(masterDataSource, targetDataSources);
+    }
+
+    /**
+     * 去除监控页面底部的广告
+     */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Bean
+    @ConditionalOnProperty(name = "spring.datasource.mysql.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(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 - 123
fs-company-app/src/main/java/com/fs/core/config/DruidConfig.java

@@ -1,123 +1,123 @@
-package com.fs.core.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.core.config.properties.DruidProperties;
-import com.fs.core.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.mysql.druid.master")
-    public DataSource masterDataSource(DruidProperties druidProperties)
-    {
-        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
-        return druidProperties.dataSource(dataSource);
-    }
-
-    @Bean
-    @ConfigurationProperties("spring.datasource.mysql.druid.slave")
-    @ConditionalOnProperty(prefix = "spring.datasource.mysql.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.mysql.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;
-    }
-}
+//package com.fs.core.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.core.config.properties.DruidProperties;
+//import com.fs.core.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.mysql.druid.master")
+//    public DataSource masterDataSource(DruidProperties druidProperties)
+//    {
+//        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
+//        return druidProperties.dataSource(dataSource);
+//    }
+//
+//    @Bean
+//    @ConfigurationProperties("spring.datasource.mysql.druid.slave")
+//    @ConditionalOnProperty(prefix = "spring.datasource.mysql.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.mysql.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;
+//    }
+//}

+ 3 - 3
fs-company-app/src/main/java/com/fs/core/datasource/DynamicDataSource.java

@@ -7,8 +7,8 @@ import java.util.Map;
 
 /**
  * 动态数据源
- * 
- 
+ *
+
  */
 public class DynamicDataSource extends AbstractRoutingDataSource
 {
@@ -24,4 +24,4 @@ public class DynamicDataSource extends AbstractRoutingDataSource
     {
         return DynamicDataSourceContextHolder.getDataSourceType();
     }
-}
+}

+ 4 - 5
fs-company-app/src/main/java/com/fs/core/datasource/DynamicDataSourceContextHolder.java

@@ -5,11 +5,10 @@ import org.slf4j.LoggerFactory;
 
 /**
  * 数据源切换处理
- * 
- 
+ *
+
  */
-public class DynamicDataSourceContextHolder
-{
+public class DynamicDataSourceContextHolder {
     public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
 
     /**
@@ -23,7 +22,7 @@ public class DynamicDataSourceContextHolder
      */
     public static void setDataSourceType(String dsType)
     {
-        log.info("切换到{}数据源", dsType);
+//        log.info("切换到{}数据源", dsType);
         CONTEXT_HOLDER.set(dsType);
     }
 

+ 56 - 0
fs-company-app/src/main/java/com/fs/core/manager/AsyncManager.java

@@ -0,0 +1,56 @@
+package com.fs.core.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-company-app/src/main/java/com/fs/core/manager/ShutdownManager.java

@@ -0,0 +1,40 @@
+package com.fs.core.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-company-app/src/main/java/com/fs/core/manager/factory/AsyncFactory.java

@@ -0,0 +1,103 @@
+package com.fs.core.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 - 1
fs-company-app/src/main/resources/application.yml

@@ -6,4 +6,4 @@ server:
 spring:
   profiles:
 #    active: druid-fcky-test
-    active: dev
+    active: dev-jnlzjk

+ 27 - 19
fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java

@@ -57,6 +57,7 @@ import org.springframework.web.multipart.MultipartFile;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 
 
@@ -136,27 +137,34 @@ public class CompanyUserController extends BaseController {
     public TableDataInfo qwList(CompanyUserQwParam user) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         user.setCompanyId(loginUser.getCompany().getCompanyId());
-        startPage();
         List<CompanyUserQwListVO> list = companyUserService.selectCompanyUserQwListVO(user);
-        for (CompanyUserQwListVO companyUserQwListVO : list) {
-            CompanyUserDelayTime companyUserDelayTime = companyUserDelayTimeService.selectCompanyUserDelayTimeByCompanyUser(companyUserQwListVO.getCompanyId(), companyUserQwListVO.getUserId());
-            if (ObjectUtil.isEmpty(companyUserDelayTime)) {
-                String json = configService.selectConfigByKey("course.config");
-                CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-                companyUserQwListVO.setSendDelayTime(config.getSendDelayTime());
-            } else {
-                companyUserQwListVO.setSendDelayTime(companyUserDelayTime.getSendDelayTime());
-            }
-            //是否绑定
-            if(QwStatusEnum.BOUND.getCode() == companyUserQwListVO.getQwStatus()){
-                if(!companyUserQwListVO.getQwUserId().isEmpty()){
-                    Long[] ids = Arrays.stream(companyUserQwListVO.getQwUserId().split(","))
-                            .map(Long::parseLong)
-                            .toArray(Long[]::new);
-                    List<QwUserVO> qwUserVOS = qwUserService.selectQwUserVOByIds(ids);
-                    companyUserQwListVO.setQwUsers(qwUserVOS);
-                }
+        if (!list.isEmpty()){
+            String json = configService.selectConfigByKey("course.config");
+            CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+            Long sendDelayTime = config.getSendDelayTime();
+            List<CompletableFuture<Void>> futures = new ArrayList<>();
+            for (CompanyUserQwListVO companyUserQwListVO : list) {
+                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+                    CompanyUserDelayTime companyUserDelayTime = companyUserDelayTimeService.selectCompanyUserDelayTimeByCompanyUser(companyUserQwListVO.getCompanyId(), companyUserQwListVO.getUserId());
+                    if (ObjectUtil.isEmpty(companyUserDelayTime)) {
+                        companyUserQwListVO.setSendDelayTime(sendDelayTime);
+                    } else {
+                        companyUserQwListVO.setSendDelayTime(companyUserDelayTime.getSendDelayTime());
+                    }
+                    //是否绑定
+                    if(QwStatusEnum.BOUND.getCode() == companyUserQwListVO.getQwStatus()){
+                        if(!companyUserQwListVO.getQwUserId().isEmpty()){
+                            Long[] ids = Arrays.stream(companyUserQwListVO.getQwUserId().split(","))
+                                    .map(Long::parseLong)
+                                    .toArray(Long[]::new);
+                            List<QwUserVO> qwUserVOS = qwUserService.selectQwUserVOByIds(ids);
+                            companyUserQwListVO.setQwUsers(qwUserVOS);
+                        }
+                    }
+                });
+                futures.add(future);
             }
+            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
         }
         return getDataTable(list);
     }

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

@@ -82,7 +82,7 @@ public class FsCourseAnswerLogsController extends BaseController
 
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setCompanyId( loginUser.getCompany().getCompanyId());
-        param.setCompanyUserId(loginUser.getUser().getUserId());
+        //param.setCompanyUserId(loginUser.getUser().getUserId());
         if (param.getPhoneMk() != null && param.getPhoneMk() != "") {
             param.setPhone(param.getPhoneMk());
         }

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

@@ -89,7 +89,7 @@ public class FsCourseRedPacketLogController extends BaseController
         startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         fsCourseRedPacketLog.setCompanyId( loginUser.getCompany().getCompanyId());
-        fsCourseRedPacketLog.setCompanyUserId(loginUser.getUser().getUserId());
+        //fsCourseRedPacketLog.setCompanyUserId(loginUser.getUser().getUserId());
         if (fsCourseRedPacketLog.getPhoneMk() != null && fsCourseRedPacketLog.getPhoneMk() != "") {
             fsCourseRedPacketLog.setPhone(encryptPhone(fsCourseRedPacketLog.getPhoneMk()));
         }

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

@@ -1,5 +1,6 @@
 package com.fs.company.controller.course;
 
+import cn.hutool.core.util.ObjectUtil;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
@@ -119,7 +120,7 @@ public class FsCourseWatchLogController extends BaseController
         param.setCompanyId(loginUser.getCompany().getCompanyId());
 
 
-        if (param.getSendType()==1&& param.getPeriodETime()!=null && param.getPeriodSTime()!=null) {
+        if (ObjectUtil.isNotEmpty(param.getSendType()) &&param.getSendType()==1&& param.getPeriodETime()!=null && param.getPeriodSTime()!=null) {
             List<Long> periodIds = userCoursePeriodDaysService.selectFsUserCoursePeriodDaysByTime(param.getPeriodSTime(), param.getPeriodETime());
 
             if (!periodIds.isEmpty()){

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

@@ -116,8 +116,10 @@ public class QwExternalContactTransferLogController extends BaseController
     @GetMapping("/sync/{corpId}")
     public R syncTag(@PathVariable("corpId") String corpId)
     {
-
-        return qwExternalContactTransferLogService.syncQwExternalContactTransferLog(corpId);
+//        transferService.syncQwExternalContactTransferLog(corpId);
+//
+         qwExternalContactTransferLogService.syncQwExternalContactTransferLog(corpId);
+        return R.ok();
     }
     /**
      * 获取转接记录详细信息

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

@@ -459,8 +459,10 @@ public class QwUserController extends BaseController
         startPage();
 
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        qwUser.setCompanyId(loginUser.getCompany().getCompanyId());
 
+        if(ObjectUtil.notEqual(qwUser.getDisableCompanyId(),1)){
+            qwUser.setCompanyId(loginUser.getCompany().getCompanyId());
+        }
         List<QwUser> list = qwUserService.selectQwUserList(qwUser);
         return getDataTable(list);
     }

+ 35 - 2
fs-qw-api/src/main/java/com/fs/app/controller/CommonController.java

@@ -1,8 +1,21 @@
 package com.fs.app.controller;
 
+import com.fs.common.core.domain.R;
+import com.fs.common.exception.file.OssException;
+import com.fs.course.param.UserCourseComplaintRecordParam;
+import com.fs.course.service.IFsUserCourseComplaintTypeService;
+import com.fs.course.vo.FsUserCourseComplaintTypeListVO;
+import com.fs.qw.param.QwUserComplaintRecordParam;
+import com.fs.qw.service.IQwUserComplainRecordService;
+import com.fs.system.oss.CloudStorageService;
+import com.fs.system.oss.OSSFactory;
 import io.swagger.annotations.Api;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
 
 
 @Api("公共接口")
@@ -10,6 +23,26 @@ import org.springframework.web.bind.annotation.RestController;
 @RequestMapping(value="/app/common")
 public class CommonController {
 
+    @Autowired
+    private IQwUserComplainRecordService qwUserComplainRecordService;
+
+    @Autowired
+    private IFsUserCourseComplaintTypeService fsUserCourseComplaintTypeService;
+
+    @PostMapping("uploadOSS")
+    public R uploadOSS(@RequestParam("file") MultipartFile file) throws Exception
+    {
 
+        if (file.isEmpty())
+        {
+            throw new OssException("上传文件不能为空");
+        }
+        // 上传文件
+        String fileName = file.getOriginalFilename();
+        String suffix = fileName.substring(fileName.lastIndexOf("."));
+        CloudStorageService storage = OSSFactory.build();
+        String url = storage.uploadSuffix(file.getBytes(), suffix);
+        return R.ok().put("url",url);
+    }
 
 }

+ 46 - 0
fs-qw-api/src/main/java/com/fs/app/controller/QwUserComplainRecordController.java

@@ -0,0 +1,46 @@
+package com.fs.app.controller;
+
+import com.fs.common.core.domain.R;
+import com.fs.course.service.IFsUserCourseComplaintTypeService;
+import com.fs.course.vo.FsUserCourseComplaintTypeListVO;
+import com.fs.qw.param.QwUserComplaintRecordParam;
+import com.fs.qw.service.IQwUserComplainRecordService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Api("企微员工投诉接口")
+@RestController
+@RequestMapping(value="/app/record")
+public class QwUserComplainRecordController {
+
+
+    @Autowired
+    private IQwUserComplainRecordService qwUserComplainRecordService;
+
+    @Autowired
+    private IFsUserCourseComplaintTypeService fsUserCourseComplaintTypeService;
+
+
+    @ApiOperation("获取投诉类型")
+    @GetMapping("/getTypeTree")
+    public R getTypeTree() {
+        List<FsUserCourseComplaintTypeListVO> allComplaintTypeTree = fsUserCourseComplaintTypeService.getAllComplaintTypeTree();
+        return R.ok().put("data", allComplaintTypeTree);
+    }
+
+
+    @ApiOperation("提交企微员工反馈记录")
+    @PostMapping("/recordByQwUser")
+    public R submitByQwUser(@RequestBody QwUserComplaintRecordParam param) {
+        int i = qwUserComplainRecordService.submitRecordByQwUser(param);
+        if (i > 0) {
+            return R.ok("提交成功");
+        } else {
+            return R.error("提交失败");
+        }
+    }
+}

+ 9 - 1
fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java

@@ -20,6 +20,7 @@ import com.fs.qwApi.domain.QwResult;
 import com.fs.qwApi.param.QwEditUserTagParam;
 import com.fs.qwApi.param.QwOpenidByUserParams;
 import com.fs.qwApi.util.AesException;
+import com.fs.voice.utils.StringUtil;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 import com.tencent.wework.Finance;
@@ -43,6 +44,7 @@ import java.security.PrivateKey;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.text.SimpleDateFormat;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 
 @Service
 @Slf4j
@@ -200,7 +202,13 @@ public class QwDataCallbackService {
                             if(WelcomeCodeList.getLength() > 0) {
                                 WelcomeCode = WelcomeCodeList.item(0).getTextContent();
                             }
-                            qwExternalContactService.insertQwExternalContactByExternalUserId(root.getElementsByTagName("ExternalUserID").item(0).getTextContent(),root.getElementsByTagName("UserID").item(0).getTextContent(),null,corpId,State,WelcomeCode);
+
+                            String qwApiExternal=redisCache.getCacheObject("qwApiExternal:"+root.getElementsByTagName("UserID").item(0).getTextContent()+":"+corpId+":"+root.getElementsByTagName("ExternalUserID").item(0).getTextContent());
+                            if (StringUtil.strIsNullOrEmpty(qwApiExternal)){
+                                redisCache.setCacheObject("qwApiExternal:"+root.getElementsByTagName("UserID").item(0).getTextContent()+":"+corpId+":"+root.getElementsByTagName("ExternalUserID").item(0).getTextContent() ,"1",10, TimeUnit.MINUTES);
+                                qwExternalContactService.insertQwExternalContactByExternalUserId(root.getElementsByTagName("ExternalUserID").item(0).getTextContent(),root.getElementsByTagName("UserID").item(0).getTextContent(),null,corpId,State,WelcomeCode);
+
+                            }
                             break;
                         case "edit_external_contact":
                             qwExternalContactService.updateQwExternalContactByExternalUserId(root.getElementsByTagName("ExternalUserID").item(0).getTextContent(),root.getElementsByTagName("UserID").item(0).getTextContent(),corpId);

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

@@ -931,66 +931,66 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 //文字和短链一起
                 case "1":
                 case "3":
-                    if ("1".equals(setting.getIsBindUrl())) {
-                        String link;
-                        if (isGroupChat) {
-                            FsCourseLinkCreateParam createParam = new FsCourseLinkCreateParam();
-                            createParam.setCourseId(courseId);
-                            createParam.setVideoId(videoId);
-                            createParam.setCorpId(logVo.getCorpId());
-                            createParam.setCompanyUserId(Long.parseLong(companyUserId));
-                            createParam.setCompanyId(Long.parseLong(companyId));
-                            createParam.setChatId(logVo.getChatId());
-                            createParam.setQwUserId(Long.valueOf(qwUserId));
-                            createParam.setDays(setting.getExpiresDays());
-                            R createLink = courseLinkService.createRoomLinkUrl(createParam);
-                            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);
-                            link = generateShortLink(setting, logVo, sendTime, courseId, videoId,
-                                    qwUserId, companyUserId, companyId, externalId,isOfficial,sopLogs.getFsUserId());
-                        }
+//                    if ("1".equals(setting.getIsBindUrl())) {
+//                        String link;
+//                        if (isGroupChat) {
+//                            FsCourseLinkCreateParam createParam = new FsCourseLinkCreateParam();
+//                            createParam.setCourseId(courseId);
+//                            createParam.setVideoId(videoId);
+//                            createParam.setCorpId(logVo.getCorpId());
+//                            createParam.setCompanyUserId(Long.parseLong(companyUserId));
+//                            createParam.setCompanyId(Long.parseLong(companyId));
+//                            createParam.setChatId(logVo.getChatId());
+//                            createParam.setQwUserId(Long.valueOf(qwUserId));
+//                            createParam.setDays(setting.getExpiresDays());
+//                            R createLink = courseLinkService.createRoomLinkUrl(createParam);
+//                            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);
+//                            link = generateShortLink(setting, logVo, sendTime, courseId, videoId,
+//                                    qwUserId, companyUserId, companyId, externalId,isOfficial,sopLogs.getFsUserId());
+//                        }
 
-                        if (StringUtils.isNotEmpty(link)) {
-                            if ("3".equals(setting.getContentType())) {
-                                setting.setLinkUrl(link);
-                            } else {
-                                String currentValue = setting.getValue();
-                                if (currentValue == null) {
-                                    setting.setValue(link);
-                                } else {
-                                    setting.setValue(currentValue
-                                            .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(welcomeText) ? "" : welcomeText)
-                                            .replaceAll("#客户称呼#", contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus())|| "0".equals(contact.getStageStatus())?"同学":contact.getStageStatus())
-                                            + "\n" + link);
-                                }
-                            }
-                        } else {
-                            log.error("生成短链失败,跳过设置 URL。");
-                        }
+//                        if (StringUtils.isNotEmpty(link)) {
+//                            if ("3".equals(setting.getContentType())) {
+//                                setting.setLinkUrl(link);
+//                            } else {
+//                                String currentValue = setting.getValue();
+//                                if (currentValue == null) {
+//                                    setting.setValue(link);
+//                                } else {
+//                                    setting.setValue(currentValue
+//                                            .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(welcomeText) ? "" : welcomeText)
+//                                            .replaceAll("#客户称呼#", contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus())|| "0".equals(contact.getStageStatus())?"同学":contact.getStageStatus())
+//                                            + "\n" + link);
+//                                }
+//                            }
+//                        } else {
+//                            log.error("生成短链失败,跳过设置 URL。");
+//                        }
 
-                    } else {
+//                    } else {
                         if ("1".equals(setting.getContentType())) {
                             setting.setValue(setting.getValue()
                                     .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(welcomeText) ? "" : welcomeText)
                                     .replaceAll("#客户称呼#", contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus())|| "0".equals(contact.getStageStatus())?"同学":contact.getStageStatus()));
                         }
-                    }
+//                    }
                     break;
                 //小程序单独
                 case "4":

+ 43 - 2
fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisQwSopController.java

@@ -2,12 +2,17 @@ package com.fs.app.controller;
 
 import com.fs.app.params.SopLogsEditParam;
 import com.fs.common.BeanCopyUtils;
+import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.ResponseResult;
+import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.StringUtils;
+import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
 import com.fs.fastGpt.param.FastGptChatSessionParam;
 import com.fs.fastGpt.service.IFastGptChatSessionService;
 import com.fs.qw.domain.QwTagGroup;
-import com.fs.qw.param.SopMsgParam;
+import com.fs.qw.param.*;
 import com.fs.qw.param.sidebar.ExternalContactInfoParam;
 import com.fs.qw.param.sidebar.TagGroupListParam;
 import com.fs.qw.param.sidebar.TagGroupUpdateParam;
@@ -21,6 +26,9 @@ import com.fs.sop.domain.QwSopLogs;
 import com.fs.sop.params.GetQwSopLogsByJsApiParam;
 import com.fs.sop.params.SendSopParamDetailsC;
 import com.fs.sop.service.IQwSopLogsService;
+import com.fs.sop.service.IQwSopService;
+import com.fs.store.vo.h5.ExternalUserStatsVO;
+import com.fs.store.vo.h5.FsUserStatisticsVO;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.ApiOperation;
@@ -28,13 +36,14 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
 import java.text.SimpleDateFormat;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 
 
 @RestController
 @RequestMapping("/apis/app/qwSop")
-public class ApisQwSopController {
+public class ApisQwSopController  extends BaseController {
 
     @Autowired
     RedisCache redisCache;
@@ -49,6 +58,8 @@ public class ApisQwSopController {
     @Autowired
     private IQwTagGroupService qwTagGroupService;
 
+    @Autowired
+    private IQwSopService qwSopService;
     /**
      * 更新AI发送状态
      */
@@ -162,4 +173,34 @@ public class ApisQwSopController {
         return R.error();
     }
 
+    //员工看板 课程/答题/红包统计--侧边栏
+    @GetMapping("/boardCourseQuizRedEnvelopeStats")
+    @ApiOperation("员工看板 课程/答题/红包统计")
+    public ResponseResult<FsUserStatisticsVO> boardCourseQuizRedEnvelopeStats(CourseQuizRedEnvelopeStatsParam qwParam) {
+        if (StringUtils.isBlank(qwParam.getStartTime()) || StringUtils.isBlank(qwParam.getEndTime())) {
+            return ResponseResult.ok(new FsUserStatisticsVO());
+        }
+        FsUserStatisticsVO resultVo = qwSopService.boardCourseQuizRedEnvelopeStats(qwParam);
+        return ResponseResult.ok(resultVo);
+    }
+
+    //外部联系人答题/红包/看课统计--侧边栏
+    @GetMapping("/externalStatsList")
+    @ApiOperation("外部联系人答题/红包/看课统计")
+    public ResponseResult<ExternalUserStatsVO> externalStatsList(QwSidebarStatsParam qwParam) {
+        ExternalUserStatsVO vo = qwSopService.externalStatsList(qwParam);
+        return ResponseResult.ok(vo);
+    }
+
+    //外部联系人看课轨迹--侧边栏
+    @GetMapping("/externalWatchRecordStatsList")
+    @ApiOperation("用户看课轨迹")
+    public TableDataInfo externalWatchRecordStatsList(QwSidebarStatsParam qwParam) {
+        if (qwParam.getStartTime() == null || qwParam.getEndTime() == null) {
+            return getDataTable(Collections.emptyList());
+        }
+        List<FsCourseWatchLogStatisticsListVO> list = qwSopService.externalWatchRecordStatsList(qwParam);
+        return getDataTable(list);
+    }
+
 }

+ 4 - 0
fs-service/src/main/java/com/fs/company/param/CompanyUserQwParam.java

@@ -115,4 +115,8 @@ public class CompanyUserQwParam extends BaseEntity {
     private Long qwUserId;
 
     private Integer qwStatus;
+
+    private Integer pageNum;
+
+    private Integer pageSize;
 }

+ 5 - 5
fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java

@@ -52,6 +52,7 @@ import com.fs.system.service.ISysRoleService;
 import com.fs.system.service.ISysUserService;
 import com.fs.voice.utils.StringUtil;
 import com.fs.wxUser.domain.CompanyWxUser;
+import com.github.pagehelper.PageHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -670,14 +671,13 @@ public class CompanyUserServiceImpl implements ICompanyUserService
 //        if(!StringUtils.isEmpty(companyConfig.getConfigValue())){
 //            isAdminShow = Boolean.parseBoolean(companyConfig.getConfigValue());
 //        }
-        List<CompanyUserQwListVO> companyUserQwListVOS = companyUserMapper.selectCompanyUserQwListVO(user);
-        if(!isAdminShow){
+        if(isAdminShow){
             Company company = companyService.selectCompanyById(user.getCompanyId());
             Long userId = company.getUserId();
-            companyUserQwListVOS = companyUserQwListVOS.stream()
-                    .filter(vo -> !vo.getUserId().equals(userId))
-                    .collect(Collectors.toList());
+            user.setUserId(userId);
         }
+        PageHelper.startPage(user.getPageNum(), user.getPageSize());
+        List<CompanyUserQwListVO> companyUserQwListVOS = companyUserMapper.selectCompanyUserQwListVO(user);
         return companyUserQwListVOS;
     }
 

+ 8 - 1
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -6,12 +6,12 @@ import com.fs.course.dto.WatchLogDTO;
 import com.fs.course.param.*;
 import com.fs.course.vo.*;
 import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.param.QwSidebarStatsParam;
 import com.fs.sop.vo.QwRatingVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
 
-import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
@@ -528,4 +528,11 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
      * 查询训练营看课人数
      */
     Integer getUserCountByCampId(@Param("trainingCampId") Long trainingCampId);
+
+    List<FsCourseWatchLogStatisticsListByCompanyVO> selectFsCourseWatchLogStatisticsListByCompanyVO(FsCourseWatchLogStatisticsListParam param);
+
+    /**
+     * 看课统计
+     * */
+    List<FsCourseWatchLogStatisticsListVO> selectQwFsCourseWatchLogStatisticsListVO(QwSidebarStatsParam param);
 }

+ 10 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseVideoRedPackageMapper.java

@@ -5,6 +5,7 @@ import java.util.List;
 import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.domain.FsUserCourseVideoRedPackage;
 import com.fs.course.param.FsUserCourseVideoParam;
+import org.apache.ibatis.annotations.Delete;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
@@ -103,5 +104,14 @@ public interface FsUserCourseVideoRedPackageMapper
             "</script>")
     int updateBatchDelFlag(@Param("ids") Long [] ids, @Param("delFlag") Integer delFlag);
 
+    @Delete("<script>" +
+            "DELETE FROM fs_user_course_video_red_package WHERE video_id IN " +
+            "<foreach collection='ids' item='id' open='(' separator=',' close=')'>" +
+            "#{id}" +
+            "</foreach>" +
+            "</script>")
+    int deleteBatchByVideoIds(@Param("ids") Long[] ids);
+
+
     Integer selectRedPacketByCompanyCount(@Param("videoId") Long videoId,@Param("companyId") Long companyId, @Param("periodId") Long periodId);
 }

+ 5 - 3
fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java

@@ -37,10 +37,10 @@ public class FsCourseWatchLogListParam implements Serializable {
     private Integer sendType;
 
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    private Date eTime;
+    private String eTime;
 
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    private Date sTime;
+    private String sTime;
 
     @JsonFormat(pattern = "yyyy-MM-dd")
     private String upSTime;
@@ -86,7 +86,9 @@ public class FsCourseWatchLogListParam implements Serializable {
      */
     private String userType;
 
-    //是否是会员
+    /**
+     * 是否是会员
+     */
     private Integer isVip;
 
     /**

+ 8 - 7
fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java

@@ -3,15 +3,10 @@ package com.fs.course.service;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.param.*;
-import com.fs.course.vo.FsCourseOverVO;
-import com.fs.course.vo.FsCourseUserStatisticsListVO;
-import com.fs.course.vo.FsCourseWatchLogListVO;
-import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
-import com.fs.qw.param.QwWatchLogStatisticsListParam;
-import com.fs.qw.vo.QwWatchLogStatisticsListVO;
+import com.fs.course.vo.*;
+import com.fs.qw.param.QwSidebarStatsParam;
 
 import java.time.LocalDateTime;
-import java.time.LocalTime;
 import java.util.List;
 import java.util.Map;
 
@@ -92,6 +87,7 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
     void testFinishMsg();
 
     List<FsCourseWatchLogStatisticsListVO> selectFsCourseWatchLogStatisticsListVO(FsCourseWatchLogStatisticsListParam param);
+    List<FsCourseWatchLogStatisticsListByCompanyVO> selectFsCourseWatchLogStatisticsListByCompanyVO(FsCourseWatchLogStatisticsListParam param);
 
     void scheduleBatchUpdateToDatabase();
 
@@ -137,4 +133,9 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
     void scheduleBatchUpdateToDatabaseIsOpen();
 
     List<FsCourseWatchLogListVO> selectFsCourseWatchLogListVOexport(FsCourseWatchLogListParam param);
+
+    /**
+     * 看课统计
+     * */
+    List<FsCourseWatchLogStatisticsListVO> selectQwFsCourseWatchLogStatisticsListVO(QwSidebarStatsParam param);
 }

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

@@ -70,5 +70,4 @@ public interface IFsUserCourseComplaintRecordService extends IService<FsUserCour
      * @return int
      */
     int submitRecord(UserCourseComplaintRecordParam userCourseComplaintRecordParam);
-
 }

+ 23 - 5
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -1,5 +1,6 @@
 package com.fs.course.service.impl;
 
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.http.HttpRequest;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
@@ -28,25 +29,21 @@ import com.fs.his.config.FsSysConfig;
 import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsUserService;
 import com.fs.his.utils.ConfigUtil;
-import com.fs.his.vo.OptionsVO;
 import com.fs.qw.Bean.MsgBean;
 import com.fs.qw.cache.IQwExternalContactCacheService;
 import com.fs.qw.cache.IQwUserCacheService;
 import com.fs.qw.domain.QwExternalContact;
-import com.fs.qw.domain.QwExternalContactInfo;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.domain.QwWatchLog;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.mapper.QwWatchLogMapper;
+import com.fs.qw.param.QwSidebarStatsParam;
 import com.fs.qw.param.SendSopParamDetails;
-import com.fs.qw.param.SopUserLogsVO;
 import com.fs.qw.vo.QwSopCourseFinishTempSetting;
 import com.fs.qw.vo.QwSopTempSetting;
 import com.fs.sop.domain.QwSopLogs;
-import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.SopUserLogsMapper;
-import com.fs.sop.service.IQwSopLogsService;
 import com.fs.store.service.cache.IFsUserCacheService;
 import com.fs.store.service.cache.IFsUserCourseCacheService;
 import com.fs.system.service.ISysConfigService;
@@ -621,6 +618,17 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
 
     @Override
     public List<FsCourseWatchLogListVO> selectFsCourseWatchLogListVO(FsCourseWatchLogListParam param) {
+        // 待看课-未注册
+        if(ObjectUtil.equal(param.getLogType(),5)){
+            param.setLogType(3);
+            param.setIsVip(0);
+        }
+
+        // 待看课-已注册
+        if(ObjectUtil.equal(param.getLogType(),6)){
+            param.setLogType(3);
+            param.setIsVip(1);
+        }
 
         List<FsCourseWatchLogListVO> fsCourseWatchLogListVOS = fsCourseWatchLogMapper.selectFsCourseWatchLogListVO(param);
         List<FsUserCoursePeriod> fsUserCoursePeriods = userCoursePeriodService.selectFsUserCoursePeriodList(new FsUserCoursePeriod());
@@ -723,6 +731,11 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         return fsCourseWatchLogMapper.selectFsCourseWatchLogStatisticsListVO(param);
     }
 
+    @Override
+    public List<FsCourseWatchLogStatisticsListByCompanyVO> selectFsCourseWatchLogStatisticsListByCompanyVO(FsCourseWatchLogStatisticsListParam param) {
+        return fsCourseWatchLogMapper.selectFsCourseWatchLogStatisticsListByCompanyVO(param);
+    }
+
 
     public void sendSocket(String cmd,String message,String appKey){
         MsgBean msgBean=new MsgBean();
@@ -1223,4 +1236,9 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         return list;
     }
 
+    @Override
+    public List<FsCourseWatchLogStatisticsListVO> selectQwFsCourseWatchLogStatisticsListVO(QwSidebarStatsParam param) {
+        return fsCourseWatchLogMapper.selectQwFsCourseWatchLogStatisticsListVO(param);
+    }
+
 }

+ 1 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseComplaintRecordServiceImpl.java

@@ -135,4 +135,5 @@ public class FsUserCourseComplaintRecordServiceImpl extends ServiceImpl<FsUserCo
         fsUserCourseComplaintRecord.setCreateTime(DateUtils.getNowDate());
         return baseMapper.insertFsUserCourseComplaintRecord(fsUserCourseComplaintRecord);
     }
+
 }

+ 4 - 2
fs-service/src/main/java/com/fs/course/service/impl/FsUserCoursePeriodServiceImpl.java

@@ -171,8 +171,10 @@ public class FsUserCoursePeriodServiceImpl implements IFsUserCoursePeriodService
         List<Long> videoIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getVideoId).collect(Collectors.toList());
         if(!periodDayIds.isEmpty()){
             fsUserCoursePeriodDaysMapper.updateBatchDelFlag(periodDayIds.toArray(new Long[0]),1);
-            //删除红包记录
-            fsUserCourseVideoRedPackageMapper.updateBatchDelFlag(videoIds.toArray(new Long[0]),1);
+            //删除红包记录(修改状态)
+            //fsUserCourseVideoRedPackageMapper.updateBatchDelFlag(videoIds.toArray(new Long[0]),1);
+            //直接删除
+            fsUserCourseVideoRedPackageMapper.deleteBatchByVideoIds(videoIds.toArray(new Long[0]));
         }
         return flag;
     }

+ 4 - 2
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseTrainingCampServiceImpl.java

@@ -100,8 +100,10 @@ public class FsUserCourseTrainingCampServiceImpl extends ServiceImpl<FsUserCours
         List<Long> videoIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getVideoId).collect(Collectors.toList());
         if(!periodDayIds.isEmpty()){
             fsUserCoursePeriodDaysMapper.updateBatchDelFlag(periodDayIds.toArray(new Long[0]),1);
-            //删除红包记录
-            fsUserCourseVideoRedPackageMapper.updateBatchDelFlag(videoIds.toArray(new Long[0]),1);
+            //删除红包记录(修改状态)
+            //fsUserCourseVideoRedPackageMapper.updateBatchDelFlag(videoIds.toArray(new Long[0]),1);
+            //直接删除
+            fsUserCourseVideoRedPackageMapper.deleteBatchByVideoIds(videoIds.toArray(new Long[0]));
         }
     }
 

+ 3 - 0
fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogListVO.java

@@ -63,6 +63,9 @@ public class FsCourseWatchLogListVO extends BaseEntity
     @Excel(name = "记录类型" ,dictType = "sys_course_watch_log_type")
     private Integer logType;
 
+    @Excel(name = "奖励类型 1红包 2积分")
+    private Integer rewardType;
+
 //    @Excel(name = "企微外部联系人id")
     private String qwExternalContactId;
 

+ 42 - 0
fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogStatisticsListByCompanyVO.java

@@ -0,0 +1,42 @@
+package com.fs.course.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class FsCourseWatchLogStatisticsListByCompanyVO {
+
+
+    @Excel(name = "公司名称")
+    private String companyName;
+    private Integer project;
+    @Excel(name = "项目名称")
+    private String projectName;
+
+    private Long courseId;
+    @Excel(name = "课程名称")
+    private String courseName;
+
+    private Long videoId;
+    @Excel(name = "小节名称")
+    private String videoName;
+
+    @Excel(name = "待看课")
+    private String type1;
+    @Excel(name = "看课中")
+    private String type2;
+    @Excel(name = "已完课")
+    private String type3;
+    @Excel(name = "看课中断")
+    private String type4;
+    @Excel(name = "上线数")
+    private Long onLineNum;
+
+    private Long userId;
+    @Excel(name = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date createTime;
+}

+ 22 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastGptPushTokenTotal.java

@@ -0,0 +1,22 @@
+package com.fs.fastGpt.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+@Data
+public class FastGptPushTokenTotal extends BaseEntity {
+
+    private Long id;
+    private Integer type;
+    private Long qwUserId;
+    private Long companyId;
+    @Excel(name = "公司名称")
+    private String companyName;
+    @Excel(name = "token消耗数")
+    private Long count;
+    private Integer status;
+    @Excel(name = "生成时间")
+    private String statTime;
+
+}

+ 1 - 0
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -974,6 +974,7 @@ public class AiHookServiceImpl implements AiHookService {
         wxwUploadCdnLinkFileDTO.setFilename(data.getUrl());
         wxwUploadCdnLinkFileDTO.setUuid(uuid);
         WxWorkResponseDTO<WxwUploadCdnLinkFileRespDTO> dto = wxWorkService.uploadCdnLinkFile(wxwUploadCdnLinkFileDTO, serverId);
+        log.info("语音数据:{}", JSON.toJSONString(dto));
         WxwUploadCdnLinkFileRespDTO voice = dto.getData();
         WxwSendCDNVoiceMsgDTO wxwSendCDNVoiceMsgDTO = new WxwSendCDNVoiceMsgDTO();
         wxwSendCDNVoiceMsgDTO.setSend_userid(sendId);

+ 2 - 2
fs-service/src/main/java/com/fs/fastgptApi/util/EventLogUtils.java

@@ -80,7 +80,7 @@ public class EventLogUtils {
 
 
         EventLogQueue.addEventLog(fastGptEventLog); // 入队
-        fastGptChatMsgService.insertFastGptEventLog(fastGptEventLog);
+        //fastGptChatMsgService.insertFastGptEventLog(fastGptEventLog);
     }
 
     /**
@@ -104,7 +104,7 @@ public class EventLogUtils {
         fastGptEventTokenLog.setQwUserId(user.getId());
         fastGptEventTokenLog.setCreateTime(new Date());
 
-        //EventLogQueue.addEventTokenLog(fastGptEventTokenLog); // 入队
+        EventLogQueue.addEventTokenLog(fastGptEventTokenLog); // 入队
         //fastGptChatMsgService.insertFastGptEventTokenLog(fastGptEventTokenLog);
     }
 

+ 14 - 0
fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java

@@ -410,4 +410,18 @@ public interface FsUserMapper
     Map<String, Object> countUserStats(
             UserStatisticsCommonParam param);
 
+    /**
+     * 统计用户领取红包数据(红包个数、红包金额)
+     * */
+    ExternalRedPacketStatsVO countExternalRedPacketStats(UserStatisticsCommonParam param);
+
+    /**
+     * 统计用户答题数据(答题次数、正确次数)
+     * */
+    ExternalAnswerStatsVO countExternalAnswerStats(UserStatisticsCommonParam param);
+
+    /**
+     * 统计用户看课数据(课程观看次数、课程完播次数)
+     * */
+    ExternalWatchStatsVO countExternalWatchStats(UserStatisticsCommonParam param);
 }

+ 8 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsInquiryOrderServiceImpl.java

@@ -14,6 +14,7 @@ import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.CustomException;
 import com.fs.common.service.impl.SmsServiceImpl;
+import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.StringUtils;
@@ -791,6 +792,13 @@ public class FsInquiryOrderServiceImpl implements IFsInquiryOrderService
             map.setPayTime(new Date());
             map.setTradeNo(tradeNo);
             fsInquiryOrderMapper.updateFsInquiryOrder(map);
+            // 木易华康特殊处理(支付成功医生自动接单)
+            if (CloudHostUtils.hasCloudHostName("木易华康")&&order.getDoctorId()!=null) {
+                FsInquiryOrderReceiveParam fsInquiryOrderReceiveParam = new FsInquiryOrderReceiveParam();
+                fsInquiryOrderReceiveParam.setOrderId(order.getOrderId());
+                fsInquiryOrderReceiveParam.setDoctorId(order.getDoctorId());
+                receiveOrder(fsInquiryOrderReceiveParam);
+            }
             try {
                 FsUser fsUser = userMapper.selectFsUserByUserId(order.getUserId());
                 imService.addAccount(fsUser.getUserId(),fsUser.getAvatar(),fsUser.getNickName());

+ 12 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java

@@ -3897,6 +3897,18 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService {
                     failureMsg.append(msg).append("该状态不支持修改为待推送");
                     continue;
                 }
+                if ("-1".equals(vo.getStatus())) {
+                    failureNum++;
+                    String msg = "<br/>" + failureNum + "、订单编号 " + vo.getOrderCode() + " 导入失败:";
+                    failureMsg.append(msg).append("该状态不支持修改为退款中,需要手动申请退款");
+                    continue;
+                }
+                if ("-2".equals(vo.getStatus())) {
+                    failureNum++;
+                    String msg = "<br/>" + failureNum + "、订单编号 " + vo.getOrderCode() + " 导入失败:";
+                    failureMsg.append(msg).append("该状态不支持修改为退款中,需要审核完成退款");
+                    continue;
+                }
 
                 Integer status = o.getStatus();
 

+ 3 - 5
fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java

@@ -1446,15 +1446,13 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
         }
         byte[] file;
         try {
-            file = wxMaService.getQrcodeService().createWxaCodeUnlimitBytes(
-                    scene,
-                    "pages_user/user/pay",
-                    true,
+            file = wxMaService.getQrcodeService().createWxaCodeBytes(
+                    "pages_user/user/pay?companyId="+param.getCompanyId(),
                     "release",
                     430,
                     true,
                     null,
-                    false);
+                    true);
 
             // 上传图片到存储桶
             String suffix = ".png";

+ 2 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsPrescribeScrm.java

@@ -2,6 +2,7 @@ package com.fs.hisStore.domain;
 
 import java.util.Date;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
@@ -31,6 +32,7 @@ public class FsPrescribeScrm extends BaseEntity
 
     /** 订单ID */
     @Excel(name = "订单ID")
+    @TableField (exist = false)
     private Long orderId;
 
     /** 用户ID */

+ 10 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsUserScrm.java

@@ -135,6 +135,9 @@ public class FsUserScrm extends BaseEntity
     @Excel(name = "连续签到天数")
     private Long signNum;
 
+    /** 订单数 */
+    private Integer orderCount;
+
     private Integer integralStatus;
 
     private Integer isBuy;
@@ -806,4 +809,11 @@ public class FsUserScrm extends BaseEntity
         return this;
     }
 
+    public Integer getOrderCount() {
+        return orderCount;
+    }
+
+    public void setOrderCount(Integer orderCount) {
+        this.orderCount = orderCount;
+    }
 }

+ 1 - 4
fs-service/src/main/java/com/fs/hisStore/enums/SysConfigEnum.java

@@ -85,10 +85,7 @@ public enum SysConfigEnum {
     //红包流量,joinTime.switch.config
     JOIN_TIME_SWITCH_CONFIG("joinTime.switch.config", "红包流量"),
     //签到配置,store.sign
-    SIGN_CONFIG("store.sign", "签到配置"),
-    //商城支付配置,store.pay
-    STORE_PAY_CONFIG("store.pay", "商城支付配置");
-
+    SIGN_CONFIG("store.sign", "签到配置");
     private final String key;
     private final String name;
 

+ 2 - 6
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -2152,12 +2152,8 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             //将钱退还给用户
             List<FsStorePaymentScrm> payments = paymentService.selectFsStorePaymentByOrderId(order.getId());
             if (payments != null) {
-                String json;
-                if (CloudHostUtils.hasCloudHostName("康年堂")) {
-                    json = configService.selectConfigByKey("his.pay");
-                } else {
-                    json = configService.selectConfigByKey("store.pay");
-                }
+                String json = configService.selectConfigByKey("his.pay");
+
                 FsPayConfigScrm fsPayConfig = JSON.parseObject(json, FsPayConfigScrm.class);
                 for (FsStorePaymentScrm payment : payments) {
                     if (payment.getPayMode() == null || payment.getPayMode().equals("wx")) {

+ 10 - 1
fs-service/src/main/java/com/fs/qw/domain/QwExternalContactTransferLog.java

@@ -1,9 +1,14 @@
 package com.fs.qw.domain;
 
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
 import lombok.Data;
 
+import java.util.Date;
+
 /**
  * 转接记录对象 qw_external_contact_transfer_log
  *
@@ -11,7 +16,7 @@ import lombok.Data;
  * @date 2024-06-27
  */
 @Data
-public class QwExternalContactTransferLog extends BaseEntity
+public class QwExternalContactTransferLog
 {
     private static final long serialVersionUID = 1L;
 
@@ -46,6 +51,10 @@ public class QwExternalContactTransferLog extends BaseEntity
     @Excel(name = "状态")
     private Integer status;
 
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
     private String takeoverUserId;
     private String handoverUserId;
     private Long qwUserId;

+ 4 - 0
fs-service/src/main/java/com/fs/qw/domain/QwUser.java

@@ -1,6 +1,7 @@
 package com.fs.qw.domain;
 
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
 import lombok.Data;
@@ -109,4 +110,7 @@ public class QwUser extends BaseEntity
      */
     private String isAuto;
     private Integer videoGetStatus;
+
+    @TableField(exist = false)
+    private Integer disableCompanyId;
 }

+ 56 - 0
fs-service/src/main/java/com/fs/qw/domain/QwUserComplainRecord.java

@@ -0,0 +1,56 @@
+package com.fs.qw.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 企微员工投诉记录对象 qw_user_complain_record
+ *
+ * @author fs
+ * @date 2025-10-22
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class QwUserComplainRecord extends BaseEntity{
+
+    /** 投诉记录id */
+    @TableId(type = IdType.AUTO)
+    private Long recordId;
+
+    /** 用户手机号 */
+    @Excel(name = "用户手机号")
+    private String phone;
+
+    /** 投诉类型id */
+    @Excel(name = "投诉类型id")
+    private Long complaintTypeId;
+
+    /** 投诉内容 */
+    @Excel(name = "投诉内容")
+    private String complaintContent;
+
+    /** 投诉上传图片 */
+    @Excel(name = "投诉上传图片")
+    private String complaintUrl;
+
+    /** 企微员工id */
+    @Excel(name = "企微员工id")
+    private Long qwUserId;
+
+    /** 外部联系人id */
+    @Excel(name = "外部联系人id")
+    private Long extId;
+
+    /** fs_user_id */
+    @Excel(name = "fs_user_id")
+    private Long userId;
+
+    //位置
+    private String position;
+
+
+}

+ 8 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java

@@ -14,6 +14,7 @@ import com.fs.qw.result.QwExternalContactVo;
 import com.fs.qw.vo.*;
 import com.fs.qw.vo.newvo.ExternalContactListVO;
 import com.fs.qw.vo.newvo.ExternalContactNumVO;
+import com.fs.qw.vo.sidebar.ExternalContactQwUserVO;
 import com.fs.qwApi.param.QwExternalContactHParam;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
@@ -520,4 +521,11 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
     int batchUpdateUnionId(@Param("item") QwExternalContact item);
 
     void updateQwExternalContactStatusById(QwExternalContact qwExternalContact);
+
+    List<QwExternalContactVO> selectQwExternalContactListVONewSys(QwExternalContactParam qwExternalContact);
+
+    /**
+     * 根据qw_user_id+crop_id+external_user_id查询外部联系人信息
+     * */
+    QwExternalContact selectQwUserListVOByQwUserIdAndCorpIdAndExternalUserId(ExternalContactParam externalContactParam);
 }

+ 5 - 2
fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactTransferLogMapper.java

@@ -1,5 +1,7 @@
 package com.fs.qw.mapper;
 
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwExternalContactTransferLog;
 import com.fs.qw.param.QwExternalContactTransferLogParam;
 import com.fs.qw.vo.QwExternalContactTransferLogListVO;
@@ -16,7 +18,7 @@ import java.util.List;
  * @date 2024-06-27
  */
 @Repository
-public interface QwExternalContactTransferLogMapper
+public interface QwExternalContactTransferLogMapper extends BaseMapper<QwExternalContactTransferLog>
 {
     /**
      * 查询转接记录
@@ -94,10 +96,11 @@ public interface QwExternalContactTransferLogMapper
             "LEFT JOIN company_user cu on cu.user_id=qu.company_user_id " +
             "<where>  \n" +
             "            <if test=\"corpId != null  and corpId != ''\"> and l.corp_id = #{corpId}</if>\n" +
+            "            <if test=\"name != null  and name != ''\"> and c.name like concat( #{name}, '%')</if>\n" +
             "            <if test=\"companyId != null \"> and l.company_id = #{companyId}</if>\n" +
             "            <if test=\"companyUserId != null \"> and l.company_user_id = #{companyUserId}</if>\n" +
             "            <if test=\"externalUserId != null  and externalUserId != ''\"> and l.external_user_id = #{externalUserId}</if>\n" +
-            "            <if test=\"companyUserNickName != null  and companyUserNickName != ''\"> and qu.qw_user_name = #{companyUserNickName}</if>\n" +
+            "            <if test=\"companyUserNickName != null  and companyUserNickName != ''\"> and qu.qw_user_name like concat( #{companyUserNickName}, '%')</if>\n" +
             "            <if test=\"customerId != null \"> and l.customer_id = #{customerId}</if>\n" +
             "            <if test=\"externalContactId != null \"> and l.external_contact_id = #{externalContactId}</if>\n" +
             "            <if test=\"status != null  and status != ''\"> and l.status = #{status}</if>\n" +

+ 12 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwRestrictionPushRecordMapper.java

@@ -1,6 +1,7 @@
 package com.fs.qw.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.fastGpt.domain.FastGptPushTokenTotal;
 import com.fs.qw.domain.QwRestrictionPushRecord;
 import org.apache.ibatis.annotations.Delete;
 import org.apache.ibatis.annotations.Param;
@@ -45,5 +46,16 @@ public interface QwRestrictionPushRecordMapper extends BaseMapper<QwRestrictionP
             "</foreach>" +
             "</script>")
     public int deleteQwRestrictionPushRecordIds(@Param("ids") Long[] ids);
+
+    List<FastGptPushTokenTotal> selectFastgptPushTokenTotal(String dateTime);
+
+    FastGptPushTokenTotal selectFastGptPushTokenTotalByInfo(FastGptPushTokenTotal fastGptPushTotal);
+
+    void insertPushTokenTotal(FastGptPushTokenTotal fastGptPushTotal);
+
+    void updatePushTokenTotal(FastGptPushTokenTotal fastGptPushTotal);
+
+    List<FastGptPushTokenTotal> selectFastGptPushTokenTotalList(FastGptPushTokenTotal pushTokenInfo);
+
 }
 

+ 61 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwUserComplainRecordMapper.java

@@ -0,0 +1,61 @@
+package com.fs.qw.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.qw.domain.QwUserComplainRecord;
+
+/**
+ * 企微员工投诉记录Mapper接口
+ *
+ * @author fs
+ * @date 2025-10-22
+ */
+public interface QwUserComplainRecordMapper extends BaseMapper<QwUserComplainRecord>{
+    /**
+     * 查询企微员工投诉记录
+     *
+     * @param recordId 企微员工投诉记录主键
+     * @return 企微员工投诉记录
+     */
+    QwUserComplainRecord selectQwUserComplainRecordByRecordId(Long recordId);
+
+    /**
+     * 查询企微员工投诉记录列表
+     *
+     * @param qwUserComplainRecord 企微员工投诉记录
+     * @return 企微员工投诉记录集合
+     */
+    List<QwUserComplainRecord> selectQwUserComplainRecordList(QwUserComplainRecord qwUserComplainRecord);
+
+    /**
+     * 新增企微员工投诉记录
+     *
+     * @param qwUserComplainRecord 企微员工投诉记录
+     * @return 结果
+     */
+    int insertQwUserComplainRecord(QwUserComplainRecord qwUserComplainRecord);
+
+    /**
+     * 修改企微员工投诉记录
+     *
+     * @param qwUserComplainRecord 企微员工投诉记录
+     * @return 结果
+     */
+    int updateQwUserComplainRecord(QwUserComplainRecord qwUserComplainRecord);
+
+    /**
+     * 删除企微员工投诉记录
+     *
+     * @param recordId 企微员工投诉记录主键
+     * @return 结果
+     */
+    int deleteQwUserComplainRecordByRecordId(Long recordId);
+
+    /**
+     * 批量删除企微员工投诉记录
+     *
+     * @param recordIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteQwUserComplainRecordByRecordIds(Long[] recordIds);
+}

+ 3 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java

@@ -438,4 +438,7 @@ public interface QwUserMapper extends BaseMapper<QwUser>
      * 根据销售公司和企微ID查询企微用户
      */
     List<QwUserVO> selectQwUserListVOByCompanyIdAndCorpIdAndNickName(@Param("companyId") Long companyId, @Param("corpId") String corpId, @Param("nickName") String nickName);
+
+    List<QwOptionsVO> selectQwCompanyListOptionsVOBySys();
+
 }

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

@@ -36,7 +36,7 @@ public interface QwUserVideoMapper
             "select quv.* from qw_user_video quv LEFT JOIN qw_user qu on quv.app_key=qu.app_key "+
             "where qu.is_del=0 and qu.company_user_id is not null and qu.app_key is not null "+
             "            <if test=\"data.qwUserName != null  and data.qwUserName != ''\"> and qu.qw_user_name like concat('%', #{data.qwUserName}, '%') </if>\n" +
-            "            <if test=\"data.nickname != null  and data.nickname != ''\"> and quv.nickname like concat('%', #{data.nickname}, '%') </if> " +
+            "            <if test=\"data.nickname != null  and data.nickname != ''\"> and quv.nick_name like concat('%', #{data.nickname}, '%') </if> " +
             "            <if test=\"data.companyId != null \"> and qu.company_id = #{data.companyId}</if>\n" +
             "</script>"})
     public List<QwUserVideoVO> selectQwUserVideoListVO(@Param("data") QwUserVideoParam param);

+ 26 - 0
fs-service/src/main/java/com/fs/qw/param/CourseQuizRedEnvelopeStatsParam.java

@@ -0,0 +1,26 @@
+package com.fs.qw.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
+
+@Data
+public class CourseQuizRedEnvelopeStatsParam  implements Serializable {
+    @ApiModelProperty(value = "企业id", required = true)
+    @NotBlank(message = "corpId 不能为空")
+    private String corpId;
+
+    @ApiModelProperty(value = "用户id", required = true)
+    @NotBlank(message = "qwUserId 不能为空")
+    private String qwUserId;
+
+    @ApiModelProperty(value = "开始日期", required = true)
+    @NotBlank(message = "startTime 不能为空")
+    private String startTime;
+
+    @ApiModelProperty(value = "截止日期", required = true)
+    @NotBlank(message = "endTime 不能为空")
+    private String endTime;
+}

+ 17 - 0
fs-service/src/main/java/com/fs/qw/param/ExternalContactParam.java

@@ -0,0 +1,17 @@
+package com.fs.qw.param;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class ExternalContactParam  implements Serializable {
+    //企业id
+    private String corpId;
+    //登录用户id
+    private String userId;
+
+    //外部联系人id
+    private String externalUserId;
+
+}

+ 3 - 0
fs-service/src/main/java/com/fs/qw/param/QwAutoTagsRulesTags.java

@@ -2,6 +2,7 @@ package com.fs.qw.param;
 
 import lombok.Data;
 
+import java.time.LocalDate;
 import java.util.List;
 
 @Data
@@ -12,4 +13,6 @@ public class QwAutoTagsRulesTags {
     private String endTime;
     private String remarks;
     private Integer isDay;
+    private Integer dayOrWeek;
+    private List<LocalDate> days;
 }

+ 37 - 0
fs-service/src/main/java/com/fs/qw/param/QwSidebarStatsParam.java

@@ -0,0 +1,37 @@
+package com.fs.qw.param;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
+@Data
+@ApiModel
+public class QwSidebarStatsParam implements Serializable {
+    @NotBlank(message = "corpId 不能为空")
+    @ApiModelProperty(value = "企业id", required = true)
+    private String corpId;
+
+    @ApiModelProperty(value = "用户id", required = true)
+    @NotBlank(message = "qwUserId 不能为空")
+    private String qwUserId;
+
+    @ApiModelProperty(value = "外部联系人id", required = true)
+    @NotBlank(message = "externalUserId 不能为空")
+    private String externalUserId;
+
+    @ApiModelProperty(value = "页码,默认为1", required = false)
+    private Integer pageNum = 1;
+
+    @ApiModelProperty(value = "页大小,默认为10", required = false)
+    private Integer pageSize = 10;
+
+    private String startTime;
+
+    private String endTime;
+
+    //外部联系人主键Id
+    private Long qwExternalContactId;
+
+}

+ 33 - 0
fs-service/src/main/java/com/fs/qw/param/QwUserComplaintRecordParam.java

@@ -0,0 +1,33 @@
+package com.fs.qw.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class QwUserComplaintRecordParam {
+
+    @ApiModelProperty(value = "fsUserId")
+    private Long userId;
+
+    @ApiModelProperty(value = "投诉类型id")
+    private Long complaintTypeId;
+
+    @ApiModelProperty(value = "投诉内容")
+    private String complaintContent;
+
+    @ApiModelProperty(value = "投诉上传图片")
+    private String complaintUrl;
+
+    //企微员工主键ID
+    private Long qwUserId;
+
+    //外部联系人ID
+    private Long extId;
+
+    //用户手机号
+    private String phone;
+
+    //位置
+    private String position;
+
+}

+ 0 - 1
fs-service/src/main/java/com/fs/qw/param/SopMsgParam.java

@@ -17,7 +17,6 @@ public class SopMsgParam {
     private String qwUserid;
 
     /** 企微外部联系人id */
-
     @NotBlank(message = "externalUserId 不能为空")
     private String externalUserId;
 

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

@@ -16,8 +16,10 @@ import com.fs.qw.vo.*;
 import com.fs.qw.vo.newvo.ExternalContactListVO;
 import com.fs.qw.vo.newvo.ExternalContactPageVO;
 import com.fs.qw.vo.sidebar.ExternalContactInfoVO;
+import com.fs.qw.vo.sidebar.ExternalContactQwUserVO;
 import com.fs.qw.vo.sidebar.ExternalContactTagVO;
 import com.fs.qwApi.param.QwExternalContactHParam;
+import org.apache.ibatis.annotations.Param;
 import org.codehaus.jettison.json.JSONException;
 
 import java.io.IOException;
@@ -250,4 +252,9 @@ public interface IQwExternalContactService extends IService<QwExternalContact> {
     void updateQwExternalContactStatusById(QwExternalContact qwExternalContact);
 
     R getRepeat(RepeatParam param);
+    List<QwExternalContactVO> selectQwExternalContactListVONewSys(QwExternalContactParam qwExternalContact);
+    /**
+     * 根据qw_user_id+crop_id+external_user_id查询外部联系人信息
+     * */
+    QwExternalContact selectQwUserListVOByQwUserIdAndCorpIdAndExternalUserId(ExternalContactParam externalContactParam);
 }

+ 4 - 2
fs-service/src/main/java/com/fs/qw/service/IQwExternalContactTransferLogService.java

@@ -1,6 +1,8 @@
 package com.fs.qw.service;
 
+import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.common.core.domain.R;
+import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwExternalContactTransferLog;
 import com.fs.qw.param.QwExternalContactTransferLogParam;
 import com.fs.qw.vo.QwExternalContactTransferLogListVO;
@@ -13,7 +15,7 @@ import java.util.List;
  * @author fs
  * @date 2024-06-27
  */
-public interface IQwExternalContactTransferLogService
+public interface IQwExternalContactTransferLogService extends IService<QwExternalContactTransferLog>
 {
     /**
      * 查询转接记录
@@ -63,7 +65,7 @@ public interface IQwExternalContactTransferLogService
      */
     public int deleteQwExternalContactTransferLogById(Long id);
 
-    public R syncQwExternalContactTransferLog(String corpId);
+    public void syncQwExternalContactTransferLog(String corpId);
 
     List<QwExternalContactTransferLogListVO> selectQwExternalContactTransferLogListVO(QwExternalContactTransferLogParam qwExternalContactTransferLog);
 

+ 3 - 0
fs-service/src/main/java/com/fs/qw/service/IQwPushCountService.java

@@ -1,6 +1,7 @@
 package com.fs.qw.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.fastGpt.domain.FastGptPushTokenTotal;
 import com.fs.qw.domain.QwPushCount;
 
 import java.util.List;
@@ -65,4 +66,6 @@ public interface IQwPushCountService extends IService<QwPushCount>{
      * @return 结果
      */
     int deleteQwPushCountById(Long id);
+
+    List<FastGptPushTokenTotal> selectFastGptPushTokenTotalList(FastGptPushTokenTotal pushTokenInfo);
 }

+ 64 - 0
fs-service/src/main/java/com/fs/qw/service/IQwUserComplainRecordService.java

@@ -0,0 +1,64 @@
+package com.fs.qw.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.qw.domain.QwUserComplainRecord;
+import com.fs.qw.param.QwUserComplaintRecordParam;
+
+/**
+ * 企微员工投诉记录Service接口
+ *
+ * @author fs
+ * @date 2025-10-22
+ */
+public interface IQwUserComplainRecordService extends IService<QwUserComplainRecord>{
+    /**
+     * 查询企微员工投诉记录
+     *
+     * @param recordId 企微员工投诉记录主键
+     * @return 企微员工投诉记录
+     */
+    QwUserComplainRecord selectQwUserComplainRecordByRecordId(Long recordId);
+
+    /**
+     * 查询企微员工投诉记录列表
+     *
+     * @param qwUserComplainRecord 企微员工投诉记录
+     * @return 企微员工投诉记录集合
+     */
+    List<QwUserComplainRecord> selectQwUserComplainRecordList(QwUserComplainRecord qwUserComplainRecord);
+
+    /**
+     * 新增企微员工投诉记录
+     *
+     * @param qwUserComplainRecord 企微员工投诉记录
+     * @return 结果
+     */
+    int insertQwUserComplainRecord(QwUserComplainRecord qwUserComplainRecord);
+
+    /**
+     * 修改企微员工投诉记录
+     *
+     * @param qwUserComplainRecord 企微员工投诉记录
+     * @return 结果
+     */
+    int updateQwUserComplainRecord(QwUserComplainRecord qwUserComplainRecord);
+
+    /**
+     * 批量删除企微员工投诉记录
+     *
+     * @param recordIds 需要删除的企微员工投诉记录主键集合
+     * @return 结果
+     */
+    int deleteQwUserComplainRecordByRecordIds(Long[] recordIds);
+
+    /**
+     * 删除企微员工投诉记录信息
+     *
+     * @param recordId 企微员工投诉记录主键
+     * @return 结果
+     */
+    int deleteQwUserComplainRecordByRecordId(Long recordId);
+
+    int submitRecordByQwUser(QwUserComplaintRecordParam param);
+}

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

@@ -196,4 +196,7 @@ public interface IQwUserService
      * 根据销售公司和企微ID查询企微用户
      */
     List<QwUserVO> selectQwUserListByCompanyIdAndCorpIdAndNickName(Long companyId, String corpId, String nickName);
+
+    List<QwOptionsVO> selectQwCompanyListOptionsVOBySys();
+
 }

+ 69 - 21
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -67,6 +67,7 @@ import com.fs.system.service.ISysDictTypeService;
 import com.fs.voice.utils.StringUtil;
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -95,7 +96,7 @@ import java.util.stream.Stream;
  * @date 2024-06-20
  */
 @Service
-
+@Slf4j
 public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactMapper, QwExternalContact> implements IQwExternalContactService {
 
     private static final String miniappRealLink = "/pages_course/video.html?course=";
@@ -2210,12 +2211,12 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
     public void insertQwExternalContactByExternalUserId(String externalUserID, String userID, Long companyId, String corpId, String state, String welcomeCode) throws ParseException {
 
 
-        String qwApiExternal=redisCache.getCacheObject("qwApiExternal:"+userID+":"+corpId+":"+externalUserID);
-        if (!StringUtil.strIsNullOrEmpty(qwApiExternal)){
-            return;
-        }else {
-            redisCache.setCacheObject("qwApiExternal:"+userID+":"+corpId+":"+externalUserID ,"1",10, TimeUnit.MINUTES);
-        }
+//        String qwApiExternal=redisCache.getCacheObject("qwApiExternal:"+userID+":"+corpId+":"+externalUserID);
+//        if (!StringUtil.strIsNullOrEmpty(qwApiExternal)){
+//            return;
+//        }else {
+//            redisCache.setCacheObject("qwApiExternal:"+userID+":"+corpId+":"+externalUserID ,"1",10, TimeUnit.MINUTES);
+//        }
 
         // 获取当前日期(只包含年月日)
         LocalDate currentDate = LocalDate.now();
@@ -2567,20 +2568,25 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
                         qwAutoTagsLogs.setType(3L);
                         qwAutoTagsLogs.setQwUserid(qwUser.getId());
                         qwAutoTagsLogs.setExternalUserId(externalUserID);
-                            List<QwAutoTagsRulesTags> qwAutoTagsRulesTagsList = JSON.parseArray(qwAutoTags.getRulesTags(), QwAutoTagsRulesTags.class);
-                            // 获取今天的星期数
-                            DayOfWeek today = LocalDate.now().getDayOfWeek();
-                            int todayIndex = today.getValue(); // 1: Monday, 2: Tuesday, ..., 7: Sunday
-                            // 获取当前时间
-                            LocalTime now = LocalTime.now();
-                            // 遍历所有规则
-                            for (QwAutoTagsRulesTags rulesTags : qwAutoTagsRulesTagsList) {
-                                List<String> tagsItem = rulesTags.getTags();
-                                List<Integer> week = rulesTags.getWeek();
-                                String startTime = rulesTags.getStartTime();
-                                String endTime = rulesTags.getEndTime();
-                                String remarks = rulesTags.getRemarks();
-
+                        List<QwAutoTagsRulesTags> qwAutoTagsRulesTagsList = JSON.parseArray(qwAutoTags.getRulesTags(), QwAutoTagsRulesTags.class);
+                        LocalDate dateNow = LocalDate.now();
+                        // 获取今天的星期数
+                        DayOfWeek today = dateNow.getDayOfWeek();
+                        int todayIndex = today.getValue(); // 1: Monday, 2: Tuesday, ..., 7: Sunday
+                        // 获取当前时间
+                        LocalTime now = LocalTime.now();
+                        // 遍历所有规则
+                        for (QwAutoTagsRulesTags rulesTags : qwAutoTagsRulesTagsList) {
+                            List<String> tagsItem = rulesTags.getTags();
+                            List<Integer> week = rulesTags.getWeek();
+                            String startTime = rulesTags.getStartTime();
+                            String endTime = rulesTags.getEndTime();
+                            String remarks = rulesTags.getRemarks();
+                            Integer dayOrWeek = rulesTags.getDayOrWeek();
+                            boolean isWeek = dayOrWeek == null || dayOrWeek == 0;
+                            log.info("自动打标签规则:{}", JSON.toJSONString(rulesTags));
+                            if(isWeek){
+                                log.info("进入按星期打标签");
                                 // 检查今天是否在 week 集合中
                                 boolean isTodayInWeek = week.contains(todayIndex);
                                 // 转换时间字符串为 LocalTime
@@ -2591,6 +2597,37 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
                                 // 如果当前时间和日期匹配规则,将 tagsItem 添加到 combinedTagsSet 中
                                 if (isTodayInWeek && isNowInTimeRange) {
 
+                                    combinedTagsSet.addAll(tagsItem);
+                                    combinedTagsItem.addAll(tagsItem);
+                                    isMatch = true;
+                                    if (rulesTags.getIsDay()!=null&&rulesTags.getIsDay()==1){
+                                        String DayDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyMMdd"));
+                                        tagRemark=DayDate.substring(1);
+                                    }
+                                    //如果备注不为空
+                                    if (rulesTags.getIsDay()!=null&&rulesTags.getIsDay()==1){
+                                        String DayDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyMMdd"));
+                                        tagRemark=DayDate.substring(1);
+                                    }
+                                    //如果备注不为空
+                                    if (!StringUtil.strIsNullOrEmpty(remarks)){
+                                        tagRemark = (tagRemark==null?"":(tagRemark+ "-") ) +remarks;
+                                    }
+                                }
+                            }else if(rulesTags.getDays() != null && rulesTags.getDays().size() == 2){
+                                log.info("进入按日期打标签");
+                                LocalDate startDate = rulesTags.getDays().get(0);
+                                LocalDate endDate = rulesTags.getDays().get(1);
+                                boolean isInRange = (dateNow.isEqual(startDate) || dateNow.isAfter(startDate))
+                                        && (dateNow.isEqual(endDate) || dateNow.isBefore(endDate));
+                                log.info("是否满足日期start:{},end:{},now:{},boolean:{}", startDate, endDate, dateNow, isInRange);
+                                // 转换时间字符串为 LocalTime
+                                LocalTime start = LocalTime.parse(startTime);
+                                LocalTime end = LocalTime.parse(endTime.equals("24:00") ? "23:59:59" : endTime);
+                                // 检查当前时间是否在 startTime 和 endTime 之间
+                                boolean isNowInTimeRange = !now.isBefore(start) && !now.isAfter(end);
+                                if (isInRange && isNowInTimeRange) {
+
                                     combinedTagsSet.addAll(tagsItem);
                                     combinedTagsItem.addAll(tagsItem);
                                     isMatch = true;
@@ -2609,6 +2646,7 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
                                     }
                                 }
                             }
+                        }
                     }
 
                         // 如果有匹配的规则,使用 combinedTagsList
@@ -5856,6 +5894,16 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         return R.ok().put("data", qwUsers.stream().map(e -> QwUserVO.builder().qwUserId(e.getQwUserId()).qwUserName(e.getQwUserName()).companyName(companyMap.getOrDefault(e.getCorpId(), qwCompany).getCorpName()).build()));
     }
 
+    @Override
+    public List<QwExternalContactVO> selectQwExternalContactListVONewSys(QwExternalContactParam qwExternalContact) {
+        return qwExternalContactMapper.selectQwExternalContactListVONewSys(qwExternalContact);
+    }
+
+    @Override
+    public QwExternalContact selectQwUserListVOByQwUserIdAndCorpIdAndExternalUserId(ExternalContactParam externalContactParam) {
+        return qwExternalContactMapper.selectQwUserListVOByQwUserIdAndCorpIdAndExternalUserId(externalContactParam);
+    }
+
 
     public Boolean getSopAiChatByRedis(String qwUserId,String corpId,String externalUserId){
 

+ 134 - 21
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactTransferLogServiceImpl.java

@@ -1,5 +1,6 @@
 package com.fs.qw.service.impl;
 
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
 import com.fs.qw.domain.QwExternalContact;
@@ -7,15 +8,21 @@ import com.fs.qw.domain.QwExternalContactTransferLog;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwExternalContactTransferLogMapper;
 import com.fs.qw.param.QwExternalContactTransferLogParam;
+import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qw.service.IQwExternalContactTransferLogService;
 import com.fs.qw.vo.QwExternalContactTransferLogListVO;
 import com.fs.qwApi.domain.QwGetTransferResult;
 import com.fs.qwApi.param.QwGetTransferParam;
 import com.fs.qwApi.service.QwApiService;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * 转接记录Service业务层处理
@@ -23,8 +30,9 @@ import java.util.List;
  * @author fs
  * @date 2024-06-27
  */
+@Slf4j
 @Service
-public class QwExternalContactTransferLogServiceImpl implements IQwExternalContactTransferLogService
+public class QwExternalContactTransferLogServiceImpl extends ServiceImpl<QwExternalContactTransferLogMapper, QwExternalContactTransferLog> implements IQwExternalContactTransferLogService
 {
     @Autowired
     private QwExternalContactTransferLogMapper qwExternalContactTransferLogMapper;
@@ -33,6 +41,9 @@ public class QwExternalContactTransferLogServiceImpl implements IQwExternalConta
 
     @Autowired
     private QwExternalContactMapper qwExternalContactMapper;
+
+    @Autowired
+    private IQwExternalContactService iQwExternalContactService;
     /**
      * 查询转接记录
      *
@@ -107,37 +118,139 @@ public class QwExternalContactTransferLogServiceImpl implements IQwExternalConta
     }
 
     @Override
-    public R syncQwExternalContactTransferLog(String corpId) {
+    @Async
+    public void syncQwExternalContactTransferLog(String corpId) {
+
+        try {
         QwExternalContactTransferLog qwExternalContactTransferLog = new QwExternalContactTransferLog();
-//        qwExternalContactTransferLog.setCompanyId(companyId);
         qwExternalContactTransferLog.setStatus(2);
         List<QwExternalContactTransferLog> qwExternalContactTransferLogs = qwExternalContactTransferLogMapper.selectQwExternalContactTransferLogList(qwExternalContactTransferLog);
-        for (QwExternalContactTransferLog externalContactTransferLog : qwExternalContactTransferLogs) {
-            QwGetTransferParam getTransferParam = new QwGetTransferParam();
-            getTransferParam.setTakeover_userid(externalContactTransferLog.getTakeoverUserId());
-            getTransferParam.setHandover_userid(externalContactTransferLog.getHandoverUserId());
-            QwGetTransferResult transfer = qwApiService.getTransfer(getTransferParam, corpId);
-            List<QwGetTransferResult.Customer> customers = transfer.getCustomer();
-            for (QwGetTransferResult.Customer customer: customers) {
-                if (customer.getExternal_userid().equals(externalContactTransferLog.getExternalUserId())){
-                    if (customer.getStatus()!=2){
-                        externalContactTransferLog.setStatus(customer.getStatus());
-                        qwExternalContactTransferLogMapper.updateQwExternalContactTransferLog(externalContactTransferLog);
+
+        Map<String, List<QwExternalContactTransferLog>> groupedLogs = qwExternalContactTransferLogs.stream()
+                .collect(Collectors.groupingBy(log ->
+                        log.getCorpId() + "/-/" + log.getTakeoverUserId() + "/-/" + log.getHandoverUserId()
+                ));
+
+                for (Map.Entry<String, List<QwExternalContactTransferLog>> entry : groupedLogs.entrySet()) {
+
+
+            try {
+                // 拆分key
+                String[] keyParts = entry.getKey().split("/-/");
+                String keyPartsCorpId = keyParts[0];
+                String keyPartsTakeoverUserId = keyParts[1];
+                String keyPartsHandoverUserId = keyParts[2];
+
+                List<QwExternalContactTransferLog> transferLogListEntry = entry.getValue();
+
+
+                QwGetTransferParam getTransferParam = new QwGetTransferParam();
+                getTransferParam.setTakeover_userid(keyPartsTakeoverUserId);
+                getTransferParam.setHandover_userid(keyPartsHandoverUserId);
+
+
+                // 第一次请求游标为空
+                String cursor = null;
+                List<QwGetTransferResult.Customer> allCustomers = new ArrayList<>();
+
+                int page = 0;
+                do {
+                    page++;
+
+                    // 设置游标(第一次请求可能不需要设置或设为空)
+                    if (cursor != null) {
+                        getTransferParam.setCursor(cursor);
+                    }
+
+                    // 调用接口
+                    QwGetTransferResult transfer = qwApiService.getTransfer(getTransferParam, keyPartsCorpId);
+
+                    // 检查接口返回是否成功
+                    if (transfer.getErrcode() != 0) {
+                        log.error("获取转移记录失败: errcode={}, errmsg={}, takeover_userid={}, handover_userid={}",
+                                transfer.getErrcode(), transfer.getErrmsg(),
+                                keyPartsTakeoverUserId, keyPartsHandoverUserId);
+                        break;
+                    }
+
+                    // 获取当前页数据
+                    List<QwGetTransferResult.Customer> customers = transfer.getCustomer();
+                    if (customers != null && !customers.isEmpty()) {
+                        allCustomers.addAll(customers);
+                        log.info("第 {} 页获取到 {} 条转移记录", page, customers.size());
+                    } else {
+                        log.info("第 {} 页没有数据", page);
+                        // 如果没有数据,也退出循环
+                        break;
+                    }
+
+                    // 更新游标,准备下一次请求
+                    cursor = transfer.getNext_cursor();
+
+                    // 添加短暂延时,避免请求过快
+                    try {
+                        Thread.sleep(200);
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                        break;
+                    }
+
+                    // 如果next_cursor为空或没有更多数据,则退出循环
+                } while (cursor != null && !cursor.isEmpty() && !"0".equals(cursor));
+
+                List<QwExternalContactTransferLog> transferLogList=new ArrayList<>();
+
+                List<QwExternalContact> contactList = new ArrayList<>();
+
+
+                Map<String, QwGetTransferResult.Customer> allCustomersMap = allCustomers.stream()
+                        .collect(Collectors.toMap(
+                                QwGetTransferResult.Customer::getExternal_userid,
+                                customer -> customer
+                        ));
+
+                for (QwExternalContactTransferLog transferLog : transferLogListEntry) {
+
+                    QwGetTransferResult.Customer customer = allCustomersMap.get(transferLog.getExternalUserId());
+
+                    if (customer != null && customer.getStatus() != 2) {
+                        transferLog.setStatus(customer.getStatus());
+                        transferLogList.add(transferLog);
+
                         QwExternalContact qwExternalContact = new QwExternalContact();
-                        qwExternalContact.setId(externalContactTransferLog.getExternalContactId());
+                        qwExternalContact.setId(transferLog.getExternalContactId());
                         qwExternalContact.setTransferStatus(customer.getStatus());
-                        if (customer.getStatus()==1){
-                            qwExternalContact.setUserId(externalContactTransferLog.getTakeoverUserId());
-                            qwExternalContact.setQwUserId(externalContactTransferLog.getQwUserId());
-                            qwExternalContact.setCompanyUserId(externalContactTransferLog.getCompanyUserId());
+
+                        if (customer.getStatus() == 1) {
+                            qwExternalContact.setUserId(transferLog.getTakeoverUserId());
+                            qwExternalContact.setQwUserId(transferLog.getQwUserId());
+                            qwExternalContact.setCompanyUserId(transferLog.getCompanyUserId());
                         }
-                        qwExternalContactMapper.updateQwExternalContact(qwExternalContact);
+                        contactList.add(qwExternalContact);
                     }
                 }
+
+                if (!transferLogList.isEmpty()){
+                    log.info("更新{}条外部联系人转移日志",transferLogList.size());
+                    super.updateBatchById(transferLogList,300);
+                }
+                if (!contactList.isEmpty()){
+                    log.info("更新{}条外部联系人",contactList.size());
+                    iQwExternalContactService.updateBatchById(contactList,300);
+                }
+            }catch (Exception e){
+                log.error("查询转接记录失败:"+entry);
             }
 
+
         }
-        return null;
+
+                log.info("转接记录同步成功");
+            } catch (Exception e){
+            e.printStackTrace();
+            log.info("转接记录同步失败");
+        }
+
     }
 
     @Override

+ 11 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwPushCountServiceImpl.java

@@ -1,9 +1,12 @@
 package com.fs.qw.service.impl;
 
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.fastGpt.domain.FastGptPushTokenTotal;
 import com.fs.qw.domain.QwPushCount;
 import com.fs.qw.mapper.QwPushCountMapper;
+import com.fs.qw.mapper.QwRestrictionPushRecordMapper;
 import com.fs.qw.service.IQwPushCountService;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
@@ -17,6 +20,9 @@ import java.util.List;
 @Service
 public class QwPushCountServiceImpl extends ServiceImpl<QwPushCountMapper, QwPushCount> implements IQwPushCountService {
 
+    @Autowired
+    private QwRestrictionPushRecordMapper pushRecordMapper;
+
     @Override
     public List<QwPushCount> selectQwPushCountLists() {
         return baseMapper.selectQwPushCountLists();
@@ -103,4 +109,9 @@ public class QwPushCountServiceImpl extends ServiceImpl<QwPushCountMapper, QwPus
     {
         return baseMapper.deleteQwPushCountById(id);
     }
+
+    @Override
+    public List<FastGptPushTokenTotal> selectFastGptPushTokenTotalList(FastGptPushTokenTotal pushTokenInfo) {
+        return pushRecordMapper.selectFastGptPushTokenTotalList(pushTokenInfo);
+    }
 }

+ 187 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwUserComplainRecordServiceImpl.java

@@ -0,0 +1,187 @@
+package com.fs.qw.service.impl;
+
+import java.util.List;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.course.domain.FsUserCourseComplaintRecord;
+import com.fs.qw.domain.QwCompany;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwCompanyMapper;
+import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.param.QwUserComplaintRecordParam;
+import com.fs.qwApi.Result.QwSendMsgResult;
+import com.fs.qwApi.param.QwSendMsgParam;
+import com.fs.qwApi.service.QwApiService;
+import org.checkerframework.checker.units.qual.A;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.qw.mapper.QwUserComplainRecordMapper;
+import com.fs.qw.domain.QwUserComplainRecord;
+import com.fs.qw.service.IQwUserComplainRecordService;
+
+/**
+ * 企微员工投诉记录Service业务层处理
+ *
+ * @author fs
+ * @date 2025-10-22
+ */
+@Service
+public class QwUserComplainRecordServiceImpl extends ServiceImpl<QwUserComplainRecordMapper, QwUserComplainRecord> implements IQwUserComplainRecordService {
+
+
+    private static final Logger log = LoggerFactory.getLogger(QwUserComplainRecordServiceImpl.class);
+    @Autowired
+    private QwUserMapper qwUserMapper;
+
+    @Autowired
+    private QwApiService qwApiService;
+
+    @Autowired
+    private QwCompanyMapper qwCompanyMapper;
+
+    /**
+     * 查询企微员工投诉记录
+     *
+     * @param recordId 企微员工投诉记录主键
+     * @return 企微员工投诉记录
+     */
+    @Override
+    public QwUserComplainRecord selectQwUserComplainRecordByRecordId(Long recordId)
+    {
+        return baseMapper.selectQwUserComplainRecordByRecordId(recordId);
+    }
+
+    /**
+     * 查询企微员工投诉记录列表
+     *
+     * @param qwUserComplainRecord 企微员工投诉记录
+     * @return 企微员工投诉记录
+     */
+    @Override
+    public List<QwUserComplainRecord> selectQwUserComplainRecordList(QwUserComplainRecord qwUserComplainRecord)
+    {
+        return baseMapper.selectQwUserComplainRecordList(qwUserComplainRecord);
+    }
+
+    /**
+     * 新增企微员工投诉记录
+     *
+     * @param qwUserComplainRecord 企微员工投诉记录
+     * @return 结果
+     */
+    @Override
+    public int insertQwUserComplainRecord(QwUserComplainRecord qwUserComplainRecord)
+    {
+        qwUserComplainRecord.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertQwUserComplainRecord(qwUserComplainRecord);
+    }
+
+    /**
+     * 修改企微员工投诉记录
+     *
+     * @param qwUserComplainRecord 企微员工投诉记录
+     * @return 结果
+     */
+    @Override
+    public int updateQwUserComplainRecord(QwUserComplainRecord qwUserComplainRecord)
+    {
+        return baseMapper.updateQwUserComplainRecord(qwUserComplainRecord);
+    }
+
+    /**
+     * 批量删除企微员工投诉记录
+     *
+     * @param recordIds 需要删除的企微员工投诉记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteQwUserComplainRecordByRecordIds(Long[] recordIds)
+    {
+        return baseMapper.deleteQwUserComplainRecordByRecordIds(recordIds);
+    }
+
+    /**
+     * 删除企微员工投诉记录信息
+     *
+     * @param recordId 企微员工投诉记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteQwUserComplainRecordByRecordId(Long recordId)
+    {
+        return baseMapper.deleteQwUserComplainRecordByRecordId(recordId);
+    }
+
+    @Override
+    public int submitRecordByQwUser(QwUserComplaintRecordParam param) {
+        QwUserComplainRecord qwUserComplainRecord = new QwUserComplainRecord();
+        BeanUtils.copyProperties(param, qwUserComplainRecord);
+        qwUserComplainRecord.setCreateTime(DateUtils.getNowDate());
+        String description = createDescription(qwUserComplainRecord);
+        //抛出发送消息出现的异常
+        try {
+            log.info("发送应用提醒:{},{}", param.getQwUserId(),description);
+            sendQwMsg(param.getQwUserId(), description);
+        }catch (Exception e){
+
+        }
+        return baseMapper.insertQwUserComplainRecord(qwUserComplainRecord);
+
+    }
+
+
+
+
+    /**
+     * 给企业微信的应用发送消息
+     */
+    private void sendQwMsg(Long qwUserId ,String msg){
+        //查询企微用户
+        QwUser qwUser = qwUserMapper.selectQwUserById(qwUserId);
+        //查询主体信息
+        QwCompany qwCompany = qwCompanyMapper.selectQwCompanyByCorpId(qwUser.getCorpId());
+        //应用消息组装
+        QwSendMsgParam sendMsgParam = new QwSendMsgParam();
+        sendMsgParam.setAgentid(Integer.parseInt(qwCompany.getServerAgentId().trim()));
+        sendMsgParam.setTouser(qwUser.getQwUserId());
+        sendMsgParam.setMsgtype("textcard");
+        //组合文本卡片消息
+        QwSendMsgParam.TextCard textCard = new QwSendMsgParam.TextCard();
+        textCard.setTitle("工单提醒");
+        textCard.setDescription(msg);
+        textCard.setUrl("https://www.baidu.com/");
+        textCard.setBtntxt("详情");
+        sendMsgParam.setTextcard(textCard);
+        log.info("发送投诉信息请求体:{}",sendMsgParam);
+        //发送消息
+        QwSendMsgResult result = qwApiService.sendMsg(sendMsgParam, qwCompany.getCorpId());
+        log.info("发送投诉应用消息返回:{}",result);
+        if (result.getErrcode()!=0 && !result.getErrmsg().equals("ok")) {
+            log.error("发送投诉应用消息返回失败:{}",result);
+        }
+    }
+
+    /**
+     * 创建描述信息
+     * @param record
+     * @return
+     */
+    private String createDescription(QwUserComplainRecord record){
+        String content = "点击下方'详情'查看投诉内容";
+        int picCount = 0;
+        String complaintUrl = record.getComplaintUrl();
+        if (complaintUrl != null && !complaintUrl.trim().isEmpty()) {
+            picCount = complaintUrl.split(",").length;
+        }
+//        String position = record.getPosition();
+        String position = "中国境内";
+
+//        String recordDescription = "<div class=\\\"gray\\\">内容</div><div class=\\\"normal\\\">"+content+"</div><br><div class=\\\"gray\\\">图片</div><div class=\\\"normal\\\">"+picCount+"张</div><br><div class=\\\"gray\\\">位置</div><div class=\\\"normal\\\">"+position+"</div><br><div class=\\\"gray\\\">分类</div><div class=\\\"normal\\\">"+record.getComplaintContent()+"</div><br>";
+
+        String recordDescription = "<div class=\\\"gray\\\">2016年9月26日</div> <div class=\\\"normal\\\">恭喜你抽中iPhone 7一台,领奖码:xxxx</div><div class=\\\"highlight\\\">请于2016年10月10日前联系行政同事领取</div>";
+        return recordDescription;
+    }
+}

+ 5 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java

@@ -1546,6 +1546,11 @@ public class QwUserServiceImpl implements IQwUserService
         return qwUserMapper.selectQwUserListVOByCompanyIdAndCorpIdAndNickName(companyId, corpId, nickName);
     }
 
+    @Override
+    public List<QwOptionsVO> selectQwCompanyListOptionsVOBySys() {
+        return qwUserMapper.selectQwCompanyListOptionsVOBySys();
+    }
+
 
     /**
      * 构建查询条件

+ 2 - 0
fs-service/src/main/java/com/fs/qw/vo/QwUserVO.java

@@ -94,4 +94,6 @@ public class QwUserVO {
     private String isAuto;
 
     private Long doctorId;
+
+    private Integer videoGetStatus;
 }

+ 16 - 0
fs-service/src/main/java/com/fs/qwApi/param/QwSendMsgParam.java

@@ -11,6 +11,7 @@ public class QwSendMsgParam {
     private int agentid; // 企业应用的id,整型。企业内部开发,可在应用的设置页面查看;第三方服务商,可通过接口获取企业授权信息获取该参数值。
     private Text text; // 消息内容,最长不超过2048个字节,超过将截断(支持id转译)。
     private Markdown markdown;
+    private TextCard textcard;
     private int safe; // 表示是否是保密消息,0表示可对外分享,1表示不能分享且内容显示水印,默认为0。
     private int enable_id_trans; // 表示是否开启id转译,0表示否,1表示是,默认0。
     private int enable_duplicate_check; // 表示是否开启重复消息检查,0表示否,1表示是,默认0。
@@ -26,4 +27,19 @@ public class QwSendMsgParam {
         private String content;
     }
 
+    /**
+     * 文本卡片消息类型
+     */
+    @Data
+    public static class TextCard {
+        //标题,不超过128个字符,超过会自动截断(支持id转译)
+        private String title;
+        //描述,不超过512个字符,超过会自动截断(支持id转译)
+        private String description;
+        //点击后跳转的链接。最长2048字节,请确保包含了协议头(http/https)
+        private String url;
+        //按钮文字。 默认为“详情”, 不超过4个文字,超过自动截断。
+        private String btntxt;
+    }
+
 }

+ 14 - 0
fs-service/src/main/java/com/fs/sop/service/IQwSopService.java

@@ -1,13 +1,18 @@
 package com.fs.sop.service;
 
 import com.fs.common.core.domain.R;
+import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
 import com.fs.qw.domain.QwSopUpdateStatus;
+import com.fs.qw.param.CourseQuizRedEnvelopeStatsParam;
+import com.fs.qw.param.QwSidebarStatsParam;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.params.GetSOPTaskDataParam;
 import com.fs.sop.params.QwSopAutoTime;
 import com.fs.sop.params.QwSopEditQwUserParam;
 import com.fs.sop.vo.QwSopTask;
 import com.fs.sop.vo.SopVoiceListVo;
+import com.fs.store.vo.h5.ExternalUserStatsVO;
+import com.fs.store.vo.h5.FsUserStatisticsVO;
 
 import java.io.IOException;
 import java.util.List;
@@ -100,4 +105,13 @@ public interface IQwSopService
     List<QwSop> selectAllQwSopInfo(QwSop qwSop);
 
     int deleteQwSopLogsBySopIds(String[] ids);
+
+    //员工看板 课程/答题/红包统计--侧边栏
+    FsUserStatisticsVO boardCourseQuizRedEnvelopeStats(CourseQuizRedEnvelopeStatsParam qwParam);
+
+    //红包/看课统计--侧边栏
+    ExternalUserStatsVO externalStatsList(QwSidebarStatsParam qwParam);
+
+    //看课足迹--侧边栏
+    List<FsCourseWatchLogStatisticsListVO> externalWatchRecordStatsList (QwSidebarStatsParam qwParam);
 }

+ 125 - 3
fs-service/src/main/java/com/fs/sop/service/impl/QwSopServiceImpl.java

@@ -13,13 +13,18 @@ import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.company.vo.CompanyQwUserByIdsVo;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.course.service.IFsCourseLinkService;
+import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
+import com.fs.his.mapper.FsUserMapper;
+import com.fs.his.service.IFsUserService;
+import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwSopUpdateStatus;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwUserMapper;
-import com.fs.qw.param.QwAutoSopTimeParam;
+import com.fs.qw.param.*;
 import com.fs.qw.result.QwFilterSopCustomersResult;
+import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qw.service.impl.AsyncChatSopService;
-import com.fs.qw.service.impl.AsyncSopService;
 import com.fs.qw.service.impl.AsyncSopTestService;
 import com.fs.qw.service.impl.AsyncWxSopService;
 import com.fs.qw.vo.QwSopRuleTimeVO;
@@ -32,9 +37,12 @@ import com.fs.sop.service.*;
 import com.fs.sop.vo.QwSopTask;
 import com.fs.sop.vo.SopVoiceListVo;
 import com.fs.sop.vo.VoiceVo;
+import com.fs.store.param.h5.UserStatisticsCommonParam;
+import com.fs.store.vo.h5.*;
 import com.fs.voice.utils.StringUtil;
 import com.fs.wxUser.mapper.CompanyWxUserMapper;
 import com.fs.wxUser.param.CompanyWxUserSopParam;
+import com.github.pagehelper.PageHelper;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.slf4j.Logger;
@@ -122,6 +130,17 @@ public class QwSopServiceImpl implements IQwSopService
     @Autowired
     private RocketMQTemplate rocketMQTemplate;
 
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
+    @Autowired
+    private IFsUserService fsUserService;
+
+    @Autowired
+    private IFsCourseWatchLogService fsCourseWatchLogService;
+
+    @Autowired
+    private FsUserMapper fsUserMapper;
     /**
      * 查询企微sop
      *
@@ -1100,7 +1119,6 @@ public class QwSopServiceImpl implements IQwSopService
         }
         return qwSopLogsMapper.delete(new QueryWrapper<QwSopLogs>().in("sop_id", Arrays.asList(ids)));
     }
-
     /**
      *新增员工执行SOP
      */
@@ -1253,4 +1271,108 @@ public class QwSopServiceImpl implements IQwSopService
     }
 
 
+    @Override
+    public FsUserStatisticsVO boardCourseQuizRedEnvelopeStats(CourseQuizRedEnvelopeStatsParam qwParam) {
+        String startTime = qwParam.getStartTime();
+        String endTime = qwParam.getEndTime();
+        //1:获取当前登录企微用户信息
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(qwParam.getCorpId().trim(),qwParam.getQwUserId().trim());
+        if (qwUser==null||qwUser.getCompanyUserId()==null){
+            return new FsUserStatisticsVO();
+        }
+        Long userId = qwUser.getCompanyUserId();
+        UserStatisticsCommonParam param = new UserStatisticsCommonParam();
+        param.setUserId(userId).setStartTime(startTime).setEndTime(endTime);
+        //2:获取当前用户统计数据
+        FsUserStatisticsVO vo = fsUserService.userStatistics(param);
+        DateTimeFormatter dfm = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        LocalDate today = LocalDate.now();
+        LocalDateTime startDateTime = LocalDateTime.parse(startTime, dfm);
+        LocalDateTime endDateTime = LocalDateTime.parse(endTime, dfm);
+        if (startDateTime.toLocalDate().equals(today) && endDateTime.toLocalDate().equals(today)) {
+            String yesterday = LocalDate.now().minusDays(1).toString();
+            UserStatisticsCommonParam paramYes = new UserStatisticsCommonParam();
+            paramYes.setUserId(userId).setStartTime(yesterday + " 00:00:00").setEndTime(yesterday + " 23:59:59");
+            FsUserStatisticsVO fsUserStatisticsVO = fsUserService.userStatistics(paramYes);
+            vo.setYesterdayVO(fsUserStatisticsVO);
+        } else {
+            vo.setYesterdayVO(new FsUserStatisticsVO());
+        }
+        return vo;
+    }
+
+
+    @Override
+    public ExternalUserStatsVO externalStatsList(QwSidebarStatsParam qwParam) {
+        ExternalUserStatsVO resultVo=new ExternalUserStatsVO();
+        //外部联系人入参对象
+        QwExternalContact externalContact = getExternalContact(
+                qwParam.getQwUserId(),
+                qwParam.getCorpId(),
+                qwParam.getExternalUserId()
+        );
+        if (externalContact == null) return new ExternalUserStatsVO();
+        Long fsUserId = externalContact.getFsUserId();
+        if (fsUserId == null || fsUserId <= 0) {
+            log.info("外部联系人没有绑定fs用户:{}",externalContact.getExternalUserId());
+            return resultVo;
+        }
+        Long companyUserId = externalContact.getCompanyUserId();
+        if (companyUserId == null || companyUserId <= 0){
+            log.info("外部联系人没有绑定销售人员:{}",externalContact.getExternalUserId());
+            return resultVo;
+        }
+        UserStatisticsCommonParam queryParam = new UserStatisticsCommonParam();
+        queryParam.setUserId(fsUserId).setCompanyUserId(String.valueOf(companyUserId)).setStartTime(qwParam.getStartTime()).setEndTime(qwParam.getEndTime());
+        //获取用户领取红包数据(红包个数、红包金额)
+        ExternalRedPacketStatsVO redPacketStatsVo = fsUserMapper.countExternalRedPacketStats(queryParam);
+        resultVo.setRedPacketNum(redPacketStatsVo.getRedPacketNum());
+        resultVo.setRedPacketAmount(redPacketStatsVo.getRedPacketAmount());
+        //获取用户答题数据(答题次数、正确次数)
+        ExternalAnswerStatsVO externalAnswerStatsVO = fsUserMapper.countExternalAnswerStats(queryParam);
+        resultVo.setAnswerNum(externalAnswerStatsVO.getAnswerNum());
+        resultVo.setAnswerRightNum(externalAnswerStatsVO.getAnswerRightNum());
+        //获取用户看课数据(观看次数、完播次数)
+        ExternalWatchStatsVO externalWatchStatsVO = fsUserMapper.countExternalWatchStats(queryParam);
+        resultVo.setCourseWatchNum(externalWatchStatsVO.getCourseWatchNum());
+        resultVo.setCourseCompleteNum(externalWatchStatsVO.getCourseCompleteNum());
+        return resultVo;
+    }
+
+    @Override
+    public List<FsCourseWatchLogStatisticsListVO> externalWatchRecordStatsList(QwSidebarStatsParam qwParam) {
+        QwExternalContact externalContact = getExternalContact(
+                qwParam.getQwUserId(),
+                qwParam.getCorpId(),
+                qwParam.getExternalUserId()
+        );
+        if (externalContact == null) return Collections.emptyList();
+        //根据外部联系人主键id获取用户的课程观看记录
+        qwParam.setQwExternalContactId(externalContact.getId());
+        PageHelper.startPage(qwParam.getPageNum(), qwParam.getPageSize());
+        List<FsCourseWatchLogStatisticsListVO> list = fsCourseWatchLogService.selectQwFsCourseWatchLogStatisticsListVO(qwParam);
+        if (CollectionUtils.isEmpty(list)){
+            return Collections.emptyList();
+        }
+        return list;
+    }
+
+    /**
+     *  获取外部联系人信息
+     * */
+    private QwExternalContact getExternalContact(String qwUserId, String corpId, String externalUserId) {
+        ExternalContactParam param = new ExternalContactParam();
+        param.setUserId(qwUserId);
+        param.setCorpId(corpId);
+        param.setExternalUserId(externalUserId);
+
+        QwExternalContact contact = qwExternalContactService.selectQwUserListVOByQwUserIdAndCorpIdAndExternalUserId(param);
+
+        if (contact == null) {
+            log.info("未查询到关联的外部联系人信息: userId={}, corpId={}, externalUserId={}",
+                    qwUserId, corpId, externalUserId);
+        }
+        return contact;
+    }
+
 }

+ 184 - 141
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -482,6 +482,15 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
             return R.error().put("msg","企业编号为空,不能创建一键群发");
         }
 
+
+        // 查询公司关联小程序数据
+        List<CompanyMiniapp> miniList = companyMiniappService.list(new QueryWrapper<CompanyMiniapp>().orderByAsc("sort_num"));
+
+        Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap = miniList.stream().collect(Collectors.groupingBy(CompanyMiniapp::getCompanyId, Collectors.groupingBy(CompanyMiniapp::getType)));
+        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(param.getCorpId());
+        if (qwCompany == null ) {
+            return  R.error().put("msg","企业不存在,请联系管理员");
+        }
         List<QwSopLogs> sopLogsList;
         if(param.getFilterMode() != null && param.getFilterMode() == 2 && param.getChatIds() != null && param.getChatIds().length > 0){
             List<QwGroupChat> groupList = qwGroupChatMapper.selectQwGroupChatByChatIds(param.getChatIds());
@@ -570,40 +579,45 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             //文字和短链一起
                             case "1":
                             case "3":
-                                if ("1".equals(st.getIsBindUrl())) {
-                                    String qwUserId = qwUser.getQwUserId();
-                                    String companyId = qwUser.getCompanyId().toString();
-                                    Long externalUserId = vo.getId();
-//                                    addWatchLogIfNeeded(param.getSopId(), param.getVideoId(), param.getCourseId(), null, qwUserId, companyUserId, companyId, externalUserId, param.getStartTime(), createTime);
-                                    FsCourseLinkCreateParam createParam = new FsCourseLinkCreateParam();
-                                    createParam.setCourseId(param.getCourseId().longValue());
-                                    createParam.setVideoId(param.getVideoId().longValue());
-                                    createParam.setCorpId(qwSop.getCorpId());
-                                    createParam.setCompanyUserId(Long.parseLong(companyUserId));
-                                    createParam.setCompanyId(Long.parseLong(companyId));
-                                    createParam.setChatId(groupUser.getChatId());
-                                    createParam.setQwUserId(qwUser.getId());
-                                    createParam.setDays(st.getExpiresDays());
-                                    R createLink = courseLinkService.createRoomLinkUrl(createParam);
-                                    if (createLink.get("code").equals(500)) {
-                                        throw new BaseException("链接生成失败!");
-                                    }
-                                    String sortLink = (String) createLink.get("url");
-
-                                    if (StringUtils.isNotEmpty(sortLink)) {
-                                        if ("3".equals(st.getContentType())) {
-                                            st.setLinkUrl(sortLink);
-                                        } else {
-                                            String currentValue = st.getValue();
-                                            if (currentValue == null) {
-                                                st.setValue(sortLink);
-                                            } else {
-                                                st.setValue(currentValue.replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText()) + "\n" + sortLink);
-                                            }
-                                        }
-                                    } else {
-                                        log.warn("生成短链失败,跳过设置 URL。");
-                                    }
+//                                if ("1".equals(st.getIsBindUrl())) {
+//                                    String qwUserId = qwUser.getQwUserId();
+//                                    String companyId = qwUser.getCompanyId().toString();
+//                                    Long externalUserId = vo.getId();
+////                                    addWatchLogIfNeeded(param.getSopId(), param.getVideoId(), param.getCourseId(), null, qwUserId, companyUserId, companyId, externalUserId, param.getStartTime(), createTime);
+//                                    FsCourseLinkCreateParam createParam = new FsCourseLinkCreateParam();
+//                                    createParam.setCourseId(param.getCourseId().longValue());
+//                                    createParam.setVideoId(param.getVideoId().longValue());
+//                                    createParam.setCorpId(qwSop.getCorpId());
+//                                    createParam.setCompanyUserId(Long.parseLong(companyUserId));
+//                                    createParam.setCompanyId(Long.parseLong(companyId));
+//                                    createParam.setChatId(groupUser.getChatId());
+//                                    createParam.setQwUserId(qwUser.getId());
+//                                    createParam.setDays(st.getExpiresDays());
+//                                    R createLink = courseLinkService.createRoomLinkUrl(createParam);
+//                                    if (createLink.get("code").equals(500)) {
+//                                        throw new BaseException("链接生成失败!");
+//                                    }
+//                                    String sortLink = (String) createLink.get("url");
+//
+//                                    if (StringUtils.isNotEmpty(sortLink)) {
+//                                        if ("3".equals(st.getContentType())) {
+//                                            st.setLinkUrl(sortLink);
+//                                        } else {
+//                                            String currentValue = st.getValue();
+//                                            if (currentValue == null) {
+//                                                st.setValue(sortLink);
+//                                            } else {
+//                                                st.setValue(currentValue.replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText()) + "\n" + sortLink);
+//                                            }
+//                                        }
+//                                    } else {
+//                                        log.warn("生成短链失败,跳过设置 URL。");
+//                                    }
+//                                }
+
+                                if ("1".equals(st.getContentType())) {
+                                    st.setValue(st.getValue()
+                                            .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText()));
                                 }
                                 break;
                             //小程序单独
@@ -611,11 +625,31 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                 String linkByMiniApp = createLinkByMiniApp(st, param.getCorpId(), new Date(), param.getCourseId(), param.getVideoId(),
                                         qwUser.getId(), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(), null, config, qwGroupChat.getChatId());
 
-                                if (StringUtil.strIsNullOrEmpty(config.getMiniprogramAppid())) {
-                                    log.error("配置中无小程序id,采用默认的");
-                                    st.setMiniprogramAppid("wxc84c6f789ba7f176");
+                                String miniAppId = null;
+
+                                int listIndex = 1;
+                                if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                                    Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(qwUser.getCompanyId());
+                                    if (integerListMap != null) {
+                                        List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);
+
+                                        if (miniapps != null && !miniapps.isEmpty()) {
+                                            CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                            if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                                miniAppId = companyMiniapp.getAppId();
+                                            }
+                                        }
+                                    }
+                                }
+
+                                if (StringUtil.strIsNullOrEmpty(miniAppId) && !StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
+                                    miniAppId = qwCompany.getMiniAppId();
+                                }
+                                st.setMiniType(listIndex);
+                                if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
+                                    st.setMiniprogramAppid(miniAppId);
                                 } else {
-                                    st.setMiniprogramAppid(config.getMiniprogramAppid());
+                                    log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
                                 }
 
                                 st.setMiniprogramPage(linkByMiniApp);
@@ -677,50 +711,73 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             //文字和短链一起
                             case "1":
                             case "3":
-                                if ("1".equals(st.getIsBindUrl())) {
-                                    FsCourseLinkCreateParam createParam = new FsCourseLinkCreateParam();
-                                    createParam.setCourseId(param.getCourseId().longValue());
-                                    createParam.setVideoId(param.getVideoId().longValue());
-                                    createParam.setCorpId(groupChat.getCorpId());
-                                    createParam.setCompanyUserId(qwUser.getCompanyUserId());
-                                    createParam.setCompanyId(qwUser.getCompanyId());
-                                    createParam.setChatId(groupChat.getChatId());
-                                    createParam.setQwUserId(qwUser.getId());
-                                    createParam.setDays(st.getExpiresDays());
-                                    R createLink = courseLinkService.createRoomLinkUrl(createParam);
-                                    if (createLink.get("code").equals(500)) {
-                                        throw new BaseException("链接生成失败!");
-                                    }
-                                    String link = (String) createLink.get("url");
-                                    if (StringUtils.isNotEmpty(link)) {
-                                        if ("3".equals(st.getContentType())) {
-                                            st.setLinkUrl(link);
-                                        } else {
-                                            String currentValue = st.getValue();
-                                            if (currentValue == null) {
-                                                st.setValue(link);
-                                            } else {
-                                                st.setValue(currentValue
-                                                        .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText())
-                                                        + "\n" + link);
-                                            }
-                                        }
-                                    } else {
-                                        log.error("生成短链失败,跳过设置 URL。");
-                                    }
+//                                if ("1".equals(st.getIsBindUrl())) {
+//                                    FsCourseLinkCreateParam createParam = new FsCourseLinkCreateParam();
+//                                    createParam.setCourseId(param.getCourseId().longValue());
+//                                    createParam.setVideoId(param.getVideoId().longValue());
+//                                    createParam.setCorpId(groupChat.getCorpId());
+//                                    createParam.setCompanyUserId(qwUser.getCompanyUserId());
+//                                    createParam.setCompanyId(qwUser.getCompanyId());
+//                                    createParam.setChatId(groupChat.getChatId());
+//                                    createParam.setQwUserId(qwUser.getId());
+//                                    createParam.setDays(st.getExpiresDays());
+//                                    R createLink = courseLinkService.createRoomLinkUrl(createParam);
+//                                    if (createLink.get("code").equals(500)) {
+//                                        throw new BaseException("链接生成失败!");
+//                                    }
+//                                    String link = (String) createLink.get("url");
+//                                    if (StringUtils.isNotEmpty(link)) {
+//                                        if ("3".equals(st.getContentType())) {
+//                                            st.setLinkUrl(link);
+//                                        } else {
+//                                            String currentValue = st.getValue();
+//                                            if (currentValue == null) {
+//                                                st.setValue(link);
+//                                            } else {
+//                                                st.setValue(currentValue
+//                                                        .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText())
+//                                                        + "\n" + link);
+//                                            }
+//                                        }
+//                                    } else {
+//                                        log.error("生成短链失败,跳过设置 URL。");
+//                                    }
+//                                }
+                                if ("1".equals(st.getContentType())) {
+                                    st.setValue(st.getValue()
+                                            .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText()));
                                 }
-
                                 break;
                             //小程序单独
                             case "4":
                                 String linkByMiniApp = createLinkByMiniApp(st, param.getCorpId(), new Date(), param.getCourseId(), param.getVideoId(),
                                         qwUser.getId(), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(), null, config, groupChat.getChatId());
 
-                                if (StringUtil.strIsNullOrEmpty(config.getMiniprogramAppid())) {
-                                    log.error("配置中无小程序id,采用默认的");
-                                    st.setMiniprogramAppid("wxc84c6f789ba7f176");
+                                String miniAppId = null;
+
+                                int listIndex = 1;
+                                if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                                    Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(qwUser.getCompanyId());
+                                    if (integerListMap != null) {
+                                        List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);
+
+                                        if (miniapps != null && !miniapps.isEmpty()) {
+                                            CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                            if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                                miniAppId = companyMiniapp.getAppId();
+                                            }
+                                        }
+                                    }
+                                }
+
+                                if (StringUtil.strIsNullOrEmpty(miniAppId) && !StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
+                                    miniAppId = qwCompany.getMiniAppId();
+                                }
+                                st.setMiniType(listIndex);
+                                if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
+                                    st.setMiniprogramAppid(miniAppId);
                                 } else {
-                                    st.setMiniprogramAppid(config.getMiniprogramAppid());
+                                    log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
                                 }
 
                                 st.setMiniprogramPage(linkByMiniApp);
@@ -743,14 +800,6 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                 }).collect(Collectors.toList());
             }
         }else{
-
-
-            // 查询公司关联小程序数据
-            List<CompanyMiniapp> miniList = companyMiniappService.list(new QueryWrapper<CompanyMiniapp>().orderByAsc("sort_num"));
-
-            Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap = miniList.stream().collect(Collectors.groupingBy(CompanyMiniapp::getCompanyId, Collectors.groupingBy(CompanyMiniapp::getType)));
-
-
             sopLogsList = new ArrayList<>();
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
             List<SopUserLogsInfo> sopUserLogsInfos = sopUserLogsInfoMapper.selectSopUserLogsInfoByIds(param.getIds());
@@ -783,12 +832,6 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 //                domainName = config.getRealLinkDomainName();
 //            }
 
-            QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(param.getCorpId());
-
-            if (qwCompany == null ) {
-                return  R.error().put("msg","企业不存在,请联系管理员");
-            }
-
 
             String finalDomainName = "domainName";
 
@@ -833,37 +876,37 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                         //文字和短链一起
                         case "1":
                         case "3":
-                            if ("1".equals(st.getIsBindUrl())) {
-
-                                addWatchLogIfNeeded(param.getSopId(), param.getVideoId(), param.getCourseId(),item.getFsUserId(), qwUserId, companyUserId, companyId, item.getExternalId(),param.getStartTime(),createTime );
-
-                                String sortLink = generateShortLink(st, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
-                                        Long.valueOf(qwUserId), companyUserId, companyId, finalDomainName,item.getExternalId(),config);
-
-                                if (StringUtils.isNotEmpty(sortLink)) {
-                                    if ("3".equals(st.getContentType())) {
-                                        st.setLinkUrl(sortLink);
-                                    } else {
-                                        String currentValue = st.getValue();
-                                        if (currentValue == null) {
-                                            st.setValue(sortLink);
-                                        } else {
-                                            st.setValue(currentValue
-                                                    .replaceAll("#销售称呼#",StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText())?"":qwUser.getWelcomeText())
-                                                    .replaceAll("#客户称呼#",StringUtil.strIsNullOrEmpty(contact.getStageStatus())|| "0".equals(contact.getStageStatus())?"同学":contact.getStageStatus())
-                                                    + "\n" + sortLink);
-                                        }
-                                    }
-                                } else {
-                                    log.warn("生成短链失败,跳过设置 URL。");
-                                }
-                            }else {
+//                            if ("1".equals(st.getIsBindUrl())) {
+//
+//                                addWatchLogIfNeeded(param.getSopId(), param.getVideoId(), param.getCourseId(),item.getFsUserId(), qwUserId, companyUserId, companyId, item.getExternalId(),param.getStartTime(),createTime );
+//
+//                                String sortLink = generateShortLink(st, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
+//                                        Long.valueOf(qwUserId), companyUserId, companyId, finalDomainName,item.getExternalId(),config);
+//
+//                                if (StringUtils.isNotEmpty(sortLink)) {
+//                                    if ("3".equals(st.getContentType())) {
+//                                        st.setLinkUrl(sortLink);
+//                                    } else {
+//                                        String currentValue = st.getValue();
+//                                        if (currentValue == null) {
+//                                            st.setValue(sortLink);
+//                                        } else {
+//                                            st.setValue(currentValue
+//                                                    .replaceAll("#销售称呼#",StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText())?"":qwUser.getWelcomeText())
+//                                                    .replaceAll("#客户称呼#",StringUtil.strIsNullOrEmpty(contact.getStageStatus())|| "0".equals(contact.getStageStatus())?"同学":contact.getStageStatus())
+//                                                    + "\n" + sortLink);
+//                                        }
+//                                    }
+//                                } else {
+//                                    log.warn("生成短链失败,跳过设置 URL。");
+//                                }
+//                            }else {
                                 if ("1".equals(st.getContentType())) {
                                     st.setValue(st.getValue()
                                             .replaceAll("#销售称呼#",StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText())?"":qwUser.getWelcomeText())
                                             .replaceAll("#客户称呼#",StringUtil.strIsNullOrEmpty(contact.getStageStatus()) || "0".equals(contact.getStageStatus())?"同学":contact.getStageStatus()));
                                 }
-                            }
+//                            }
 
                             break;
                         //小程序单独
@@ -1265,38 +1308,38 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                 //文字和短链一起
                 case "1":
                 case "3":
-                    if ("1".equals(st.getIsBindUrl())) {
-
-                        addWatchLogIfNeeded(item.getSopId(), param.getVideoId(), param.getCourseId(),item.getFsUserId(), String.valueOf(qwUser.getId()), companyUserId,
-                                companyId, item.getExternalId(),param.getStartTime(),dataTime );
-
-                        String sortLink = generateShortLink(st, param.getCorpId(), dataTime, param.getCourseId(), param.getVideoId(),
-                                qwUser.getId(), companyUserId, companyId, domainName,item.getExternalId(),config);
-
-                        if (StringUtils.isNotEmpty(sortLink)) {
-                            if ("3".equals(st.getContentType())) {
-                                st.setLinkUrl(sortLink);
-                            } else {
-                                String currentValue = st.getValue();
-                                if (currentValue == null) {
-                                    st.setValue(sortLink);
-                                } else {
-                                    st.setValue(currentValue
-                                            .replaceAll("#销售称呼#",StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText())?"":qwUser.getWelcomeText())
-                                            .replaceAll("#客户称呼#",StringUtil.strIsNullOrEmpty(contact.getStageStatus())|| "0".equals(contact.getStageStatus())?"同学":contact.getStageStatus())
-                                            + "\n" + sortLink);
-                                }
-                            }
-                        } else {
-                            log.warn("生成短链失败,跳过设置 URL。");
-                        }
-                    }else {
+//                    if ("1".equals(st.getIsBindUrl())) {
+//
+//                        addWatchLogIfNeeded(item.getSopId(), param.getVideoId(), param.getCourseId(),item.getFsUserId(), String.valueOf(qwUser.getId()), companyUserId,
+//                                companyId, item.getExternalId(),param.getStartTime(),dataTime );
+//
+//                        String sortLink = generateShortLink(st, param.getCorpId(), dataTime, param.getCourseId(), param.getVideoId(),
+//                                qwUser.getId(), companyUserId, companyId, domainName,item.getExternalId(),config);
+//
+//                        if (StringUtils.isNotEmpty(sortLink)) {
+//                            if ("3".equals(st.getContentType())) {
+//                                st.setLinkUrl(sortLink);
+//                            } else {
+//                                String currentValue = st.getValue();
+//                                if (currentValue == null) {
+//                                    st.setValue(sortLink);
+//                                } else {
+//                                    st.setValue(currentValue
+//                                            .replaceAll("#销售称呼#",StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText())?"":qwUser.getWelcomeText())
+//                                            .replaceAll("#客户称呼#",StringUtil.strIsNullOrEmpty(contact.getStageStatus())|| "0".equals(contact.getStageStatus())?"同学":contact.getStageStatus())
+//                                            + "\n" + sortLink);
+//                                }
+//                            }
+//                        } else {
+//                            log.warn("生成短链失败,跳过设置 URL。");
+//                        }
+//                    }else {
                         if ("1".equals(st.getContentType())) {
                             st.setValue(st.getValue()
                                     .replaceAll("#销售称呼#",StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText())?"":qwUser.getWelcomeText())
                                     .replaceAll("#客户称呼#",StringUtil.strIsNullOrEmpty(contact.getStageStatus()) || "0".equals(contact.getStageStatus())?"同学":contact.getStageStatus()));
                         }
-                    }
+//                    }
 
                     break;
                 //小程序单独

+ 2 - 0
fs-service/src/main/java/com/fs/statis/dto/ConsumptionBalanceDataDTO.java

@@ -15,6 +15,8 @@ public class ConsumptionBalanceDataDTO implements Serializable {
      * 数值表示,单位可能是分或其他约定单位。
      */
     private BigDecimal balance;
+
+    private BigDecimal runTianBalance;
     /**
      * 今日消费金额。
      * 当天的总消费数额。

+ 4 - 2
fs-service/src/main/java/com/fs/statis/service/impl/StatisticsServiceImpl.java

@@ -698,8 +698,10 @@ public class StatisticsServiceImpl implements IStatisticsService {
         dealerAggregatedDTO.setRecvTodayNum(dayPaymentCount);
         dealerAggregatedDTO.setGoodsTotalNum(productCount);
         dealerAggregatedDTO.setTodayGoodsNum(dayProductCount);
-        dealerAggregatedDTO.setPadTotalNum(padInfo.getTotalCount());
-        dealerAggregatedDTO.setPadUsedNum(padInfo.getCount());
+        if (padInfo != null){
+            dealerAggregatedDTO.setPadTotalNum(padInfo.getTotalCount());
+            dealerAggregatedDTO.setPadUsedNum(padInfo.getCount());
+        }
         return dealerAggregatedDTO;
     }
 

+ 15 - 0
fs-service/src/main/java/com/fs/store/vo/h5/ExternalAnswerStatsVO.java

@@ -0,0 +1,15 @@
+package com.fs.store.vo.h5;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel
+public class ExternalAnswerStatsVO {
+    @ApiModelProperty(value = "答题次数")
+    private int answerNum;
+
+    @ApiModelProperty(value = "正确次数")
+    private int answerRightNum;
+}

+ 16 - 0
fs-service/src/main/java/com/fs/store/vo/h5/ExternalRedPacketStatsVO.java

@@ -0,0 +1,16 @@
+package com.fs.store.vo.h5;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+@Data
+@ApiModel
+public class ExternalRedPacketStatsVO {
+    @ApiModelProperty(value = "答题红包数")
+    private int redPacketNum;
+
+    @ApiModelProperty(value = "答题红包金额")
+    private BigDecimal redPacketAmount = BigDecimal.ZERO;
+}

+ 35 - 0
fs-service/src/main/java/com/fs/store/vo/h5/ExternalUserStatsVO.java

@@ -0,0 +1,35 @@
+package com.fs.store.vo.h5;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+/*
+*   侧边栏外部联系人统计数据 (课程/答题/红包等数据)
+* */
+@Data
+@ApiModel
+public class ExternalUserStatsVO {
+
+    @ApiModelProperty(value = "课程统计-观看次数")
+    private int courseWatchNum;
+
+    @ApiModelProperty(value = "课程统计-完播次数")
+    private int courseCompleteNum;
+
+
+    @ApiModelProperty(value = "答题次数")
+    private int answerNum;
+
+    @ApiModelProperty(value = "正确次数")
+    private int answerRightNum;
+
+    @ApiModelProperty(value = "答题红包数")
+    private int redPacketNum;
+
+    @ApiModelProperty(value = "答题红包金额")
+    private BigDecimal redPacketAmount = BigDecimal.ZERO;
+
+
+}

+ 15 - 0
fs-service/src/main/java/com/fs/store/vo/h5/ExternalWatchStatsVO.java

@@ -0,0 +1,15 @@
+package com.fs.store.vo.h5;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel
+public class ExternalWatchStatsVO {
+    @ApiModelProperty(value = "看课统计-观看次数")
+    private int courseWatchNum;
+
+    @ApiModelProperty(value = "看课统计-完播次数")
+    private int courseCompleteNum;
+}

+ 22 - 0
fs-service/src/main/java/com/fs/tulin/JsonSortUtils.java

@@ -0,0 +1,22 @@
+package com.fs.tulin;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/10/22 上午9:59
+ */
+public class JsonSortUtils {
+
+    private static final ObjectMapper mapper = new ObjectMapper();
+
+    public static String toSortedJson(Object obj) {
+        try {
+            return mapper.writeValueAsString(obj); // 自动应用@JsonPropertyOrder
+        } catch (Exception e) {
+            throw new RuntimeException("JSON序列化失败", e);
+        }
+    }
+
+}

+ 49 - 0
fs-service/src/main/java/com/fs/tulin/PharmacyEnum.java

@@ -0,0 +1,49 @@
+package com.fs.tulin;
+
+/**
+ * @description: 这个后面要放到数据库去方便动态添加
+ * @author: Guos
+ * @time: 2025/10/22 上午9:41
+ */
+public enum PharmacyEnum {
+
+
+    YJKQS("药济康強昇专卖店"	,"ab0017b8-4961-4230-877e-e86fc1e96b3b", "prod"),
+
+    YJKJS("药济康匀善专卖店"	,"cbcdec50-ea88-448f-810d-3f2f1b809915", "prod"),
+
+    YJKYSY("药济康益寿缘专卖店","a8392536-7554-4e0a-9d12-20f856c94c55", "prod"),
+
+    YJKKM("药济康凯蒙专卖店"	,"517d85c4-e4d4-4a36-af7f-57097b59c650", "prod"),
+
+    YJKKMEB("药济康凯蒙二部专卖店","48e79523-3563-4252-a4a7-fdbcf4af4f34", "prod"),
+
+    YJKSST("药济康盛世堂专卖店","69cdae19-fbe6-4976-b9c2-611f9414440a", "prod"),
+
+    BDHD("保定弘德专卖店"	,"611edbf5-1e75-4f48-8171-533d60d50019", "prod"),
+
+    DEVCS("测试店铺"	,"26ee29ce-35aa-46c8-9650-3fe46014690c", "dev");
+
+    private String pharmacyName;
+
+    private String pharmacyId;
+
+    private String useEnvironment;
+
+    PharmacyEnum(String pharmacyName, String pharmacyId, String useEnvironment){
+        this.pharmacyName = pharmacyName;
+        this.pharmacyId = pharmacyId;
+        this.useEnvironment = useEnvironment;
+    }
+    public String getPharmacyId() {
+        return this.pharmacyId;
+    }
+
+    public String getPharmacyName() {
+        return this.pharmacyName;
+    }
+
+    public String getUseEnvironment() {
+        return this.useEnvironment;
+    }
+}

+ 83 - 0
fs-service/src/main/java/com/fs/tulin/StudentInfo.java

@@ -0,0 +1,83 @@
+package com.fs.tulin;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/10/22 上午9:15
+ */
+@Setter
+@Getter
+@Builder
+@ToString
+@JsonPropertyOrder(alphabetic = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class StudentInfo {
+
+     /**
+      * 必填 union_id
+      * 小程序union_id
+      */
+     private String union_id;
+
+     /**
+      * 必填 open_id
+      * 小程序open_id
+      */
+     private String open_id;
+
+     /**
+      * 必填 appid
+      * 微信小程序appid
+      */
+     private String appid;
+
+     /**
+      * 必填 nickname
+      * 昵称
+      */
+     private String nickname;
+
+     /**
+      * 必填 column_name
+      * 栏目名称
+      */
+     private String column_name;
+
+     /**
+      * 必填 pharmacy_id
+      * 药店ID
+      */
+     private String pharmacy_id;
+
+     /**
+      * 必填 service_phone
+      * 客服手机号
+      */
+     private String service_phone;
+
+     /**
+      * 可选 period_name
+      * 学员所属期名称(可选)
+      */
+     private String period_name;
+
+     /**
+      * 可选 name
+      * 学员名称(可选)
+      */
+     private String name;
+
+     /**
+      * 可选 avatar
+      * 学员头像 (可选)
+      */
+     private String avatar;
+
+}

+ 182 - 0
fs-service/src/main/java/com/fs/tulin/SyncStudentInfoService.java

@@ -0,0 +1,182 @@
+package com.fs.tulin;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.http.HttpUtil;
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+import java.util.logging.Logger;
+
+/**
+ * @description:
+ * @author: Guos
+ * @time: 2025/10/21 下午3:40
+ */
+public class SyncStudentInfoService {
+
+
+    private static final Logger logger = Logger.getLogger(SyncStudentInfoService.class.getName());
+    /**
+     * 密钥
+     */
+    private static final String SECRET_KEY = "xysync_dLsSaheCzK7RU9gd";
+
+    /**
+     * 测试药店ID
+     */
+    private static final String PHARMACY_ID = "26ee29ce-35aa-46c8-9650-3fe46014690c";
+
+    /**
+     * 测试企业ID
+     */
+    private static final String CORP_ID = "PNBnO6MajM0tAd8t";
+
+
+    public static void main(String[] args) throws JsonProcessingException {
+//第一种方式
+//        Map<String, Object> requestBody = new HashMap<>();
+//        requestBody.put("corp_id", "PNBnO6MajM0tAd8t");
+//        List<Map<String, Object>> dataList = new ArrayList<>();
+//        Map<String, Object> dataItem = new HashMap<>();
+//        dataItem.put("union_id", "oLH_O648dcNgoZdQT4CN2Qft8i7k");
+//        dataItem.put("open_id", "ozzk-19AxQC5D3X9Wqpb-I3_Y3EQ");
+//        dataItem.put("appid", "wx6688e6b9b6fb8700");
+//        dataItem.put("nickname", "@冰阔落");
+//        dataItem.put("column_name", "测试栏目0921");
+//        dataItem.put("pharmacy_id", "26ee29ce-35aa-46c8-9650-3fe46014690c");
+//        dataItem.put("service_phone", "13760648472");
+//        dataList.add(dataItem);
+//        String sort = sortRequestBody(dataItem);
+//        requestBody.put("data", dataList);
+//      // 生成签名
+//        long timestamp = generateTimestamp();
+//        String sign = generateSign("PNBnO6MajM0tAd8t", sort, timestamp);
+//        System.out.println("时间戳: " + timestamp);
+//        System.out.println("签名: " + sign);
+//        // 构建完整的请求URL          "https://api.xiangyue.life/api/v1/mp/sync/student
+//        String url = String.format("https://api.xiangyue.life/api/v1/mp/sync/student?sign=%s&t=%d", sign, timestamp);
+//        HttpRequest httpRequest = HttpUtil.createPost(url);
+//        httpRequest.header("Content-Type", "application/json");
+//        System.out.println("url: " + url);
+//        System.out.println("排序后的"+sortRequestBody(requestBody));
+//        httpRequest.body(sortRequestBody(requestBody));
+//        HttpResponse execute = httpRequest.execute();
+//        String body = execute.body();
+//        System.out.println("响应结果: " + body);
+//第二种方式
+//        StudentInfo student = StudentInfo.builder()
+//                .union_id("oLH_O648dcNgoZdQT4CN2Qft8i7k")
+//                .open_id("ozzk-19AxQC5D3X9Wqpb-I3_Y3EQ")
+//                .appid("wx6688e6b9b6fb8700")
+//                .nickname("@冰阔落")
+//                .column_name("测试栏目0921")
+//                .pharmacy_id(PHARMACY_ID).service_phone("13760648472").build();
+//        //排序
+//        String sortedJson = JsonSortUtils.toSortedJson(student);
+//        long timestamp = generateTimestamp();
+//        String sign = generateSign(CORP_ID, sortedJson, timestamp);
+//        String url = String.format("https://api.xiangyue.life/api/v1/mp/sync/student?sign=%s&t=%d", sign, timestamp);
+//        Map<String, Object> request = new HashMap<>();
+//        request.put("corp_id", CORP_ID);
+//        request.put("data", Collections.singletonList(student));
+//        String jsonBody = mapper.writeValueAsString(request);
+//        System.out.println("正确JSON:");
+//        System.out.println(jsonBody);
+//        sendHttpPost(url, jsonBody);
+
+        StudentInfo student = StudentInfo.builder()
+                .union_id("oLH_O648dcNgoZdQT4CN2Qft8i7k")
+                .open_id("ozzk-19AxQC5D3X9Wqpb-I3_Y3EQ")
+                .appid("wx6688e6b9b6fb8700")
+                .nickname("@冰阔落")
+                .column_name("测试栏目0921")
+                .pharmacy_id(PHARMACY_ID).service_phone("13760648472").build();
+        String result = send(CORP_ID, student);
+    }
+
+    /**
+     * @param corpId 企业ID
+     * @param studentInfo 学生信息对象
+     * @return 请求结果
+     * @throws JsonProcessingException
+     */
+    public static String send(String corpId, StudentInfo studentInfo) throws JsonProcessingException {
+        String sortedJson = JsonSortUtils.toSortedJson(studentInfo);
+        long timestamp = generateTimestamp();
+        String sign = generateSign(corpId, sortedJson, timestamp);
+        String url = String.format("https://api.xiangyue.life/api/v1/mp/sync/student?sign=%s&t=%d", sign, timestamp);
+        Map<String, Object> request = new HashMap<>();
+        request.put("corp_id", corpId);
+        request.put("data", Collections.singletonList(studentInfo));
+        String jsonBody = JsonSortUtils.toSortedJson(request);
+        return sendHttpPost(url, jsonBody);
+    }
+
+    /**
+     * 发送HTTP POST请求
+     * @param url 请求URL
+     * @param jsonBody 请求体JSON字符串
+     * @return 响应结果
+     */
+    private static String sendHttpPost(String url, String jsonBody) {
+        logger.info("云联学生信息同步接口");
+        logger.info("请求URL:"+ url);
+        logger.info("请求Body:"+ jsonBody);
+        HttpRequest httpRequest = HttpUtil.createPost(url);
+        httpRequest.header("Content-Type", "application/json");
+        httpRequest.body(jsonBody);
+        HttpResponse execute = httpRequest.execute();
+        String result = execute.body();
+        logger.info("请求结果:"+ result);
+        return result;
+    }
+
+    /**
+     * 生成签名,在这里去组装成数组的样子,然后进行MD5加密
+     * @param corpId 企业ID
+     * @param sortStr 请求体对象
+     * @param timestamp 时间戳(秒级)
+     * @return 32位小写MD5签名
+     */
+    private static String generateSign(String corpId, Object sortStr, long timestamp) {
+        try {
+            String signString = SECRET_KEY + corpId + "["+sortStr+"]" + timestamp;
+            return generateMD5(signString);
+        } catch (Exception e) {
+            throw new RuntimeException("生成签名失败", e);
+        }
+    }
+
+    /**
+     * 生成MD5哈希值
+     */
+    private static String generateMD5(String input) {
+        try {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            byte[] hash = md.digest(input.getBytes());
+            // 转换为16进制小写字符串
+            StringBuilder hexString = new StringBuilder();
+            for (byte b : hash) {
+                String hex = Integer.toHexString(0xff & b);
+                if (hex.length() == 1) {
+                    hexString.append('0');
+                }
+                hexString.append(hex);
+            }
+            return hexString.toString();
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("MD5算法不可用", e);
+        }
+    }
+
+    /**
+     * 生成当前时间戳(秒级)
+     */
+    private static long generateTimestamp() {
+        return System.currentTimeMillis() / 1000;
+    }
+
+}

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

@@ -149,5 +149,6 @@ rocketmq:
 openIM:
     secret: openIM123
     userID: imAdmin
+    url: https://im.muyi88.com/api
 #是否为新商户,新商户不走mpOpenId
 isNewWxMerchant: true

Деякі файли не було показано, через те що забагато файлів було змінено