Browse Source

Merge branch 'refs/heads/master' into 转接增加清空标签配置

# Conflicts:
#	fs-admin/src/main/java/com/fs/qw/controller/QwUserController.java
ct 15 hours ago
parent
commit
e77b065480
81 changed files with 2652 additions and 259 deletions
  1. 6 0
      fs-admin/src/main/java/com/fs/api/controller/IndexStatisticsController.java
  2. 6 6
      fs-admin/src/main/java/com/fs/his/controller/FsArticleController.java
  3. 5 3
      fs-admin/src/main/java/com/fs/his/controller/FsStoreOrderController.java
  4. 49 0
      fs-admin/src/main/java/com/fs/his/task/Task.java
  5. 5 2
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreOrderScrmController.java
  6. 1 4
      fs-admin/src/main/java/com/fs/qw/controller/QwExternalContactController.java
  7. 9 0
      fs-admin/src/main/java/com/fs/qw/controller/QwPushCountController.java
  8. 103 0
      fs-admin/src/main/java/com/fs/qw/controller/QwUserComplainRecordController.java
  9. 2 2
      fs-admin/src/main/java/com/fs/qw/controller/QwUserController.java
  10. 37 0
      fs-company-app/src/main/java/com/fs/app/controller/AppBaseController.java
  11. 214 6
      fs-company-app/src/main/java/com/fs/app/controller/CompanyUserController.java
  12. 28 32
      fs-company-app/src/main/java/com/fs/core/aspectj/DataScopeAspect.java
  13. 73 0
      fs-company-app/src/main/java/com/fs/core/aspectj/DataSourceAspect.java
  14. 244 0
      fs-company-app/src/main/java/com/fs/core/aspectj/LogAspect.java
  15. 94 0
      fs-company-app/src/main/java/com/fs/core/config/DataSourceConfig.java
  16. 123 123
      fs-company-app/src/main/java/com/fs/core/config/DruidConfig.java
  17. 3 3
      fs-company-app/src/main/java/com/fs/core/datasource/DynamicDataSource.java
  18. 4 5
      fs-company-app/src/main/java/com/fs/core/datasource/DynamicDataSourceContextHolder.java
  19. 56 0
      fs-company-app/src/main/java/com/fs/core/manager/AsyncManager.java
  20. 40 0
      fs-company-app/src/main/java/com/fs/core/manager/ShutdownManager.java
  21. 103 0
      fs-company-app/src/main/java/com/fs/core/manager/factory/AsyncFactory.java
  22. 1 1
      fs-company-app/src/main/resources/application.yml
  23. 3 1
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  24. 35 2
      fs-qw-api/src/main/java/com/fs/app/controller/CommonController.java
  25. 46 0
      fs-qw-api/src/main/java/com/fs/app/controller/QwUserComplainRecordController.java
  26. 9 1
      fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java
  27. 43 2
      fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisQwSopController.java
  28. 6 1
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  29. 2 2
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java
  30. 6 3
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  31. 0 1
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseComplaintRecordService.java
  32. 18 5
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  33. 1 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseComplaintRecordServiceImpl.java
  34. 3 0
      fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogListVO.java
  35. 22 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptPushTokenTotal.java
  36. 1 0
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  37. 2 2
      fs-service/src/main/java/com/fs/fastgptApi/util/EventLogUtils.java
  38. 14 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java
  39. 12 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java
  40. 3 5
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  41. 2 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsPrescribeScrm.java
  42. 10 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsUserScrm.java
  43. 4 0
      fs-service/src/main/java/com/fs/qw/domain/QwUser.java
  44. 56 0
      fs-service/src/main/java/com/fs/qw/domain/QwUserComplainRecord.java
  45. 8 0
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  46. 2 1
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactTransferLogMapper.java
  47. 12 0
      fs-service/src/main/java/com/fs/qw/mapper/QwRestrictionPushRecordMapper.java
  48. 61 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserComplainRecordMapper.java
  49. 3 0
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  50. 26 0
      fs-service/src/main/java/com/fs/qw/param/CourseQuizRedEnvelopeStatsParam.java
  51. 17 0
      fs-service/src/main/java/com/fs/qw/param/ExternalContactParam.java
  52. 37 0
      fs-service/src/main/java/com/fs/qw/param/QwSidebarStatsParam.java
  53. 33 0
      fs-service/src/main/java/com/fs/qw/param/QwUserComplaintRecordParam.java
  54. 0 1
      fs-service/src/main/java/com/fs/qw/param/SopMsgParam.java
  55. 7 0
      fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java
  56. 3 0
      fs-service/src/main/java/com/fs/qw/service/IQwPushCountService.java
  57. 64 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserComplainRecordService.java
  58. 3 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
  59. 16 6
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  60. 11 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwPushCountServiceImpl.java
  61. 187 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserComplainRecordServiceImpl.java
  62. 5 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  63. 16 0
      fs-service/src/main/java/com/fs/qwApi/param/QwSendMsgParam.java
  64. 14 0
      fs-service/src/main/java/com/fs/sop/service/IQwSopService.java
  65. 125 3
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopServiceImpl.java
  66. 57 22
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  67. 2 0
      fs-service/src/main/java/com/fs/statis/dto/ConsumptionBalanceDataDTO.java
  68. 15 0
      fs-service/src/main/java/com/fs/store/vo/h5/ExternalAnswerStatsVO.java
  69. 16 0
      fs-service/src/main/java/com/fs/store/vo/h5/ExternalRedPacketStatsVO.java
  70. 35 0
      fs-service/src/main/java/com/fs/store/vo/h5/ExternalUserStatsVO.java
  71. 15 0
      fs-service/src/main/java/com/fs/store/vo/h5/ExternalWatchStatsVO.java
  72. 1 1
      fs-service/src/main/resources/application-druid-cqtyt.yml
  73. 2 0
      fs-service/src/main/resources/application-druid-qdtst-test.yml
  74. 38 3
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  75. 4 5
      fs-service/src/main/resources/mapper/his/FsStoreOrderMapper.xml
  76. 44 0
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  77. 4 4
      fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml
  78. 130 0
      fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml
  79. 38 1
      fs-service/src/main/resources/mapper/qw/QwRestrictionPushRecordMapper.xml
  80. 94 0
      fs-service/src/main/resources/mapper/qw/QwUserComplainRecordMapper.xml
  81. 3 0
      fs-service/src/main/resources/mapper/qw/QwUserMapper.xml

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

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

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

@@ -44,10 +44,10 @@ public class QwUserController extends BaseController {
         return R.ok().put("data",qwUserService.getQwUserInfo(param));
     }
 
-    @GetMapping("/getMyQwCompanyList")
+   @GetMapping("/getMyQwCompanyList")
     public R getMyQwCompanyList()
     {
-        List<QwExternalContactTransferCompanyAudit> list = auditService.selectQwExternalContactTransferCompanyAuditLists();
+        List<QwOptionsVO> list = qwUserService.selectQwCompanyListOptionsVOBySys();
         return  R.ok().put("data",list);
     }
 }

+ 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

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

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

+ 6 - 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;
@@ -530,4 +530,9 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
     Integer getUserCountByCampId(@Param("trainingCampId") Long trainingCampId);
 
     List<FsCourseWatchLogStatisticsListByCompanyVO> selectFsCourseWatchLogStatisticsListByCompanyVO(FsCourseWatchLogStatisticsListParam param);
+
+    /**
+     * 看课统计
+     * */
+    List<FsCourseWatchLogStatisticsListVO> selectQwFsCourseWatchLogStatisticsListVO(QwSidebarStatsParam param);
 }

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

+ 6 - 3
fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java

@@ -4,11 +4,9 @@ import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.param.*;
 import com.fs.course.vo.*;
-import com.fs.qw.param.QwWatchLogStatisticsListParam;
-import com.fs.qw.vo.QwWatchLogStatisticsListVO;
+import com.fs.qw.param.QwSidebarStatsParam;
 
 import java.time.LocalDateTime;
-import java.time.LocalTime;
 import java.util.List;
 import java.util.Map;
 
@@ -135,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);
-
 }

+ 18 - 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());
@@ -1228,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);
     }
+
 }

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

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

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

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

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

@@ -96,10 +96,11 @@ public interface QwExternalContactTransferLogMapper extends BaseMapper<QwExterna
             "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();
+
 }

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

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

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

+ 16 - 6
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -2213,12 +2213,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();
@@ -5924,6 +5924,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){
 

+ 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();
+    }
+
 
     /**
      * 构建查询条件

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

+ 57 - 22
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());
@@ -616,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);
@@ -724,11 +753,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, 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);
@@ -751,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());
@@ -791,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";
 

+ 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;
     /**
      * 今日消费金额。
      * 当天的总消费数额。

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

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

@@ -5,7 +5,7 @@ spring:
     # redis 配置
     redis:
         # 地址
-        host: r-2zexagt5g4z7arviu5.redis.rds.aliyuncs.com
+        host: r-2zevrvnmu1iu1e6ye3pd.redis.rds.aliyuncs.com
         # 端口,默认为6379
         port: 6379
         # 密码

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

@@ -166,3 +166,5 @@ token:
 openIM:
     secret:
     userID:
+#是否为新商户,新商户不走mpOpenId
+isNewWxMerchant: false

+ 38 - 3
fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml

@@ -60,7 +60,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         l.log_type,SEC_TO_TIME(l.duration) as duration,c.company_name,l.camp_period_time,l.finish_time,
         cu.nick_name as company_user_name ,l.send_type,l.create_time,l.update_time,l.last_heartbeat_time,
         qu.qw_user_name,qec.name as external_user_name,c.company_id,u.avatar as fsAvatar,u.nick_name as fsNickName,qec.create_time as qec_create_time,
-        u.is_vip isVip
+        u.is_vip isVip,l.reward_type
          from fs_course_watch_log l
          left join fs_user_course_video v on v.video_id = l.video_id
          left join fs_user_course uc on uc.course_id = l.course_id
@@ -122,10 +122,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 and DATE(qec.create_time) &lt;= DATE(#{maps.qecETime})
             </if>
             <if test= 'maps.sTime != null '>
-                and DATE(l.create_time) &gt;= DATE(#{maps.sTime})
+                and l.create_time &gt;= #{maps.sTime}
             </if>
             <if test='maps.eTime != null '>
-                and DATE(l.create_time) &lt;= DATE(#{maps.eTime})
+                and l.create_time &lt;= #{maps.eTime}
             </if>
             <if test= 'maps.scheduleStartTime != null '>
                 and DATE(l.camp_period_time) &gt;= DATE(#{maps.scheduleStartTime})
@@ -915,4 +915,39 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         comp.company_id,
             DATE (o.create_time)
     </select>
+    <select id="selectQwFsCourseWatchLogStatisticsListVO"
+            resultType="com.fs.course.vo.FsCourseWatchLogStatisticsListVO" parameterType="com.fs.qw.param.QwSidebarStatsParam">
+        SELECT
+        o.project,
+        o.course_id AS courseId,
+        uc.course_name AS courseName,
+        o.video_id AS videoId,
+        v.title AS videoName,
+        CASE WHEN o.log_type = 1 THEN 1 END AS type1,
+        CASE WHEN o.log_type = 2 THEN 1 END AS type2,
+        CASE WHEN o.log_type = 3 THEN 1 END AS type3,
+        CASE WHEN o.log_type = 4 THEN 1 END AS type4,
+        o.qw_user_id,
+        o.user_id AS userId,
+        o.company_user_id AS companyUserId,
+        o.company_id AS companyId,
+        o.create_time AS createTime
+        FROM
+        fs_course_watch_log o
+        LEFT JOIN fs_user_course_video v ON v.video_id = o.video_id
+        LEFT JOIN fs_user_course uc ON uc.course_id = v.course_id
+        WHERE o.qw_external_contact_id=#{qwExternalContactId}
+        <if test="sendType != null">
+            AND send_type = #{sendType}
+        </if>
+        <if test="startTime != null">
+            AND DATE(o.create_time) &gt;= DATE(#{startTime})
+        </if>
+
+        <if test="endTime != null">
+            AND DATE(o.create_time) &lt;= DATE(#{endTime})
+        </if>
+        o.create_time DESC
+    </select>
+
 </mapper>

+ 4 - 5
fs-service/src/main/resources/mapper/his/FsStoreOrderMapper.xml

@@ -2120,17 +2120,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
         FROM fs_store_order
         WHERE is_del = 0 AND status > 1
-        <if test="startTime != null">
+        <if test="startTime != null and startTime !=''">
             AND create_time &gt;= #{startTime}
         </if>
-        <if test="endTime != null">
+        <if test="endTime != null and endTime != ''">
             AND create_time &lt;= #{endTime}
         </if>
-
-        <if test="companyId != null">
+        <if test="companyId != null and companyId !=''">
             AND company_id = #{companyId}
         </if>
-        <if test="companyUserId != null">
+        <if test="companyUserId != null and companyUserId !=''">
             AND company_user_id = #{companyUserId}
         </if>
     </select>

+ 44 - 0
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -2136,4 +2136,48 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </if>
     </select>
 
+    <select id="countExternalRedPacketStats" resultType="com.fs.store.vo.h5.ExternalRedPacketStatsVO">
+        SELECT
+            COUNT(red.log_id) AS redPacketNum,
+            COALESCE(SUM(red.amount), 0) AS redPacketAmount
+        FROM fs_course_red_packet_log log
+        WHERE log.status = 1
+          AND log.user_id = #{userId} AND log.company_user_id = #{companyUserId}
+        <if test="startTime != null  and startTime != ''">
+            AND log.create_time &gt;= #{startTime}
+        </if>
+        <if test="endTime != null  and endTime != ''">
+            AND log.create_time &lt;= #{endTime}
+        </if>
+    </select>
+
+    <select id="countExternalAnswerStats" resultType="com.fs.store.vo.h5.ExternalAnswerStatsVO">
+        SELECT
+        COUNT(*) AS answerNum,
+        SUM(CASE WHEN is_right = 1 THEN 1 ELSE 0 END) AS answerRightNum
+        FROM fs_course_answer_logs log
+        WHERE log.user_id = #{userId} AND log.company_user_id = #{companyUserId}
+        <if test="startTime != null  and startTime != ''">
+            AND log.create_time &gt;= #{startTime}
+        </if>
+        <if test="endTime != null  and endTime != ''">
+            AND log.create_time &lt;= #{endTime}
+
+        </if>
+    </select>
+
+    <select id="countExternalWatchStats" resultType="com.fs.store.vo.h5.ExternalWatchStatsVO">
+        SELECT
+        COUNT(CASE WHEN log_type != 3 THEN 1 END) AS courseWatchNum,
+        COUNT(CASE WHEN log_type = 2 THEN 1 END) AS courseCompleteNum
+        FROM fs_course_watch_log log
+        WHERE log.user_id = #{userId} AND log.company_user_id = #{companyUserId}
+        <if test="startTime != null  and startTime != ''">
+            AND log.create_time &gt;= #{startTime}
+        </if>
+        <if test="endTime != null  and endTime != ''">
+            AND log.create_time &lt;= #{endTime}
+        </if>
+    </select>
+
 </mapper>

+ 4 - 4
fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml

@@ -1877,16 +1877,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         SUM(CASE WHEN pay_type IN ('2', '3') THEN COALESCE(pay_money, 0) ELSE 0 END) AS codAmount
         FROM fs_store_order_scrm
         WHERE is_del = 0 AND status > 0
-        <if test="startTime != null">
+        <if test="startTime != null and startTime !=''">
             AND create_time &gt;= #{startTime}
         </if>
-        <if test="endTime != null">
+        <if test="endTime != null and endTime != ''">
             AND create_time &lt;= #{endTime}
         </if>
-        <if test="companyId != null">
+        <if test="companyId != null and companyId !=''">
             AND company_id = #{companyId}
         </if>
-        <if test="companyUserId != null">
+        <if test="companyUserId != null and companyUserId !=''">
             AND company_user_id = #{companyUserId}
         </if>
     </select>

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

@@ -654,6 +654,136 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <include refid="selectQwExternalContactVo"/>
         where fs_user_id = #{userId} and company_user_id = #{companyUserId}
     </select>
+    <select id="selectQwExternalContactListVONewSys" resultType="com.fs.qw.vo.QwExternalContactVO">
+            select ec.*, qu.qw_user_name, qd.dept_name as departmentName
+            from qw_external_contact ec
+            left join qw_user qu on ec.user_id = qu.qw_user_id and qu.corp_id = ec.corp_id
+            left join qw_dept qd on qd.dept_id = qu.department and qd.corp_id = qu.corp_id
+            left join company_user cu on ec.company_user_id = cu.user_id
+            <where>
+                <if test="userId != null and userId != ''">
+                    and ec.user_id like concat(#{userId}, '%')
+                </if>
+                <if test="qwUserName != null and qwUserName != ''">
+                    and qu.qw_user_name like concat(#{qwUserName}, '%')
+                </if>
+                <if test="externalUserId != null and externalUserId != ''">
+                    and ec.external_user_id = #{externalUserId}
+                </if>
+                <if test="wayId != null and wayId != ''">
+                    and SUBSTRING_INDEX(ec.state, ':', -1) = #{wayId}
+                </if>
+                <if test="name != null and name != ''">
+                    and ec.name like concat(#{name}, '%')
+                </if>
+                <if test="type != null">
+                    and ec.type = #{type}
+                </if>
+                <if test="gender != null">
+                    and ec.gender = #{gender}
+                </if>
+                <if test="description != null and description != ''">
+                    and ec.description = #{description}
+                </if>
 
+                <if test="tagIds != null and tagIds.size() != 0">
+                    and (
+                    <foreach collection="tagIds" item="item" index="index" separator=" AND ">
+                        find_in_set(#{item}, REGEXP_REPLACE(ec.tag_ids, '[\"\\[\\]]', ''))
+                    </foreach>
+                    )
+                </if>
 
+                <if test="remarkMobiles != null and remarkMobiles != ''">
+                    and ec.remark_mobiles like concat(#{remarkMobiles}, '%')
+                </if>
+                <if test="remark != null and remark != ''">
+                    and ec.remark like concat('%', #{remark}, '%')
+                </if>
+                <if test="remarkCorpName != null and remarkCorpName != ''">
+                    and ec.remark_corp_name like concat('%', #{remarkCorpName}, '%')
+                </if>
+                <if test="addWay != null">
+                    and ec.add_way = #{addWay}
+                </if>
+                <if test="operUserid != null and operUserid != ''">
+                    and ec.oper_userid = #{operUserid}
+                </if>
+                <if test="corpId != null and corpId != ''">
+                    and ec.corp_id = #{corpId}
+                </if>
+                <!--<if test="companyId != null">
+                    and qu.company_id = #{companyId}
+                </if>
+                <if test="companyUserId != null">
+                    and ec.company_user_id = #{companyUserId}
+                </if>-->
+                <if test="customerId != null">
+                    and ec.customer_id = #{customerId}
+                </if>
+                <if test="status != null">
+                    and ec.status = #{status}
+                </if>
+                <if test="stageStatus != null">
+                    and ec.stage_status = #{stageStatus}
+                </if>
+                <if test="transferStatus != null">
+                    and ec.transfer_status = #{transferStatus}
+                </if>
+                <if test="qwUserId != null">
+                    and ec.qw_user_id = #{qwUserId}
+                </if>
+                <if test="level != null">
+                    and ec.level = #{level}
+                </if>
+                <if test="levelType != null">
+                    and ec.level_type = #{levelType}
+                </if>
+                <if test="isBind == 'isBind'">
+                    and ec.customer_id is not null
+                </if>
+                <if test="isBind == 'noBind'">
+                    and ec.customer_id is null
+                </if>
+                <if test="isBindMini == 'isBindMini'">
+                    and ec.fs_user_id is not null
+                </if>
+                <if test="isBindMini == 'noBindMini'">
+                    and ec.fs_user_id is null
+                </if>
+                <if test="lossTime != null">
+                    and DATE(ec.loss_time) = DATE(#{lossTime})
+                </if>
+                <if test="createTime != null">
+                    and DATE(ec.create_time) = DATE(#{createTime})
+                </if>
+                <if test="delTime != null">
+                    and DATE(ec.del_time) = DATE(#{delTime})
+                </if>
+                <if test="sTime != null">
+                    and DATE(ec.create_time) &gt;= DATE(#{sTime})
+                </if>
+                <if test="eTime != null">
+                    and DATE(ec.create_time) &lt;= DATE(#{eTime})
+                </if>
+                <if test="companyUserName != null and companyUserName != ''">
+                    and cu.user_name = #{companyUserName}
+                </if>
+                <if test="cuDeptIdList != null and !cuDeptIdList.isEmpty() and userType != '00'">
+                    AND cu.dept_id IN
+                    <foreach collection="cuDeptIdList" item="item" open="(" separator="," close=")">
+                        #{item}
+                    </foreach>
+                </if>
+                <if test="companyUser != null">
+                    and (cu.nick_name like concat('%', #{companyUser}, '%') or cu.phonenumber = #{companyUser})
+                </if>
+            </where>
+            order by ec.create_time desc, ec.id desc
+    </select>
+
+    <select id="selectQwUserListVOByQwUserIdAndCorpIdAndExternalUserId" resultMap="QwExternalContactResult">
+        <include refid="selectQwExternalContactVo"/>
+        where user_id = #{userId} and corp_id = #{corpId} and external_user_id = #{externalUserId}
+    </select>
 </mapper>

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

@@ -3,5 +3,42 @@
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.fs.qw.mapper.QwRestrictionPushRecordMapper">
-    
+    <insert id="insertPushTokenTotal">
+        insert into fastgpt_push_token_total(type,qw_user_id,company_id,status,stat_time,count)
+        values(#{type},#{qwUserId},#{companyId},#{status},#{statTime},#{count})
+    </insert>
+    <update id="updatePushTokenTotal">
+        update fastgpt_push_token_total
+        set count=#{count}
+        where id=#{id}
+    </update>
+
+    <select id="selectFastgptPushTokenTotal" resultType="com.fs.fastGpt.domain.FastGptPushTokenTotal">
+        select id,`type`,qw_user_id as qwUserId,company_id as companyId,status,date_format(create_time,'%Y-%m-%d') as statTime,count(id) count
+        from qw_restriction_push_record
+        where  create_time like concat(#{createTime}, '%') and qw_user_id is not null and status = 1
+        GROUP BY `type`,company_id,qw_user_id
+        ORDER BY `type`,company_id,qw_user_id
+    </select>
+
+    <select id="selectFastGptPushTokenTotalByInfo" resultType="com.fs.fastGpt.domain.FastGptPushTokenTotal">
+        select id,type,qw_user_id as qwUserId,company_id as companyId,status,stat_time statTime,count
+        from fastgpt_push_token_total
+        where  type=#{type} and qw_user_id=#{qwUserId} and company_id=#{companyId} and status=#{status} and stat_time=#{statTime}
+    </select>
+    <select id="selectFastGptPushTokenTotalList" resultType="com.fs.fastGpt.domain.FastGptPushTokenTotal">
+        select ft.id,ft.type,ft.qw_user_id as qwUserId,ft.company_id as companyId,com.company_name as companyName,ft.status,ft.stat_time statTime,sum(ft.count) count
+        from fastgpt_push_token_total ft left join company com on ft.company_id= com.company_id
+        <where>
+            <if test="type != null" >and `ft.type` = #{type} </if>
+            <if test="qwUserId != null" >and ft.qw_user_id= #{qwUserId} </if>
+            <if test="companyId != null" >and ft.company_id= #{companyId} </if>
+            <if test="status != null" >and ft.status= #{status} </if>
+            <if test="beginTime != null and endTime != null" >
+                    AND date_format(ft.stat_time,'%Y-%m-%d') &gt;= #{beginTime}
+                    AND date_format(ft.stat_time,'%Y-%m-%d') &lt;= #{endTime}
+            </if>
+        </where>
+        group by ft.company_id,ft.stat_time
+    </select>
 </mapper>

+ 94 - 0
fs-service/src/main/resources/mapper/qw/QwUserComplainRecordMapper.xml

@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.qw.mapper.QwUserComplainRecordMapper">
+
+    <resultMap type="QwUserComplainRecord" id="QwUserComplainRecordResult">
+        <result property="recordId"    column="record_id"    />
+        <result property="phone"    column="phone"    />
+        <result property="complaintTypeId"    column="complaint_type_id"    />
+        <result property="complaintContent"    column="complaint_content"    />
+        <result property="complaintUrl"    column="complaint_url"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="qwUserId"    column="qw_user_id"    />
+        <result property="extId"    column="ext_id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="position"    column="position"    />
+    </resultMap>
+
+    <sql id="selectQwUserComplainRecordVo">
+        select record_id,position, phone, complaint_type_id, complaint_content, complaint_url, create_time, qw_user_id, ext_id, user_id from qw_user_complain_record
+    </sql>
+
+    <select id="selectQwUserComplainRecordList" parameterType="QwUserComplainRecord" resultMap="QwUserComplainRecordResult">
+        <include refid="selectQwUserComplainRecordVo"/>
+        <where>
+            <if test="phone != null  and phone != ''"> and phone = #{phone}</if>
+            <if test="complaintTypeId != null "> and complaint_type_id = #{complaintTypeId}</if>
+            <if test="complaintContent != null  and complaintContent != ''"> and complaint_content = #{complaintContent}</if>
+            <if test="complaintUrl != null  and complaintUrl != ''"> and complaint_url = #{complaintUrl}</if>
+            <if test="qwUserId != null "> and qw_user_id = #{qwUserId}</if>
+            <if test="extId != null "> and ext_id = #{extId}</if>
+            <if test="userId != null "> and user_id = #{userId}</if>
+        </where>
+    </select>
+
+    <select id="selectQwUserComplainRecordByRecordId" parameterType="Long" resultMap="QwUserComplainRecordResult">
+        <include refid="selectQwUserComplainRecordVo"/>
+        where record_id = #{recordId}
+    </select>
+
+    <insert id="insertQwUserComplainRecord" parameterType="QwUserComplainRecord" useGeneratedKeys="true" keyProperty="recordId">
+        insert into qw_user_complain_record
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="phone != null">phone,</if>
+            <if test="complaintTypeId != null">complaint_type_id,</if>
+            <if test="complaintContent != null">complaint_content,</if>
+            <if test="complaintUrl != null">complaint_url,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="qwUserId != null">qw_user_id,</if>
+            <if test="extId != null">ext_id,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="position != null">position,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="phone != null">#{phone},</if>
+            <if test="complaintTypeId != null">#{complaintTypeId},</if>
+            <if test="complaintContent != null">#{complaintContent},</if>
+            <if test="complaintUrl != null">#{complaintUrl},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="qwUserId != null">#{qwUserId},</if>
+            <if test="extId != null">#{extId},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="position != null">#{position},</if>
+        </trim>
+    </insert>
+
+    <update id="updateQwUserComplainRecord" parameterType="QwUserComplainRecord">
+        update qw_user_complain_record
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="phone != null">phone = #{phone},</if>
+            <if test="complaintTypeId != null">complaint_type_id = #{complaintTypeId},</if>
+            <if test="complaintContent != null">complaint_content = #{complaintContent},</if>
+            <if test="complaintUrl != null">complaint_url = #{complaintUrl},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="qwUserId != null">qw_user_id = #{qwUserId},</if>
+            <if test="extId != null">ext_id = #{extId},</if>
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="position != null">user_id = #{position},</if>
+        </trim>
+        where record_id = #{recordId}
+    </update>
+
+    <delete id="deleteQwUserComplainRecordByRecordId" parameterType="Long">
+        delete from qw_user_complain_record where record_id = #{recordId}
+    </delete>
+
+    <delete id="deleteQwUserComplainRecordByRecordIds" parameterType="String">
+        delete from qw_user_complain_record where record_id in
+        <foreach item="recordId" collection="array" open="(" separator="," close=")">
+            #{recordId}
+        </foreach>
+    </delete>
+</mapper>

+ 3 - 0
fs-service/src/main/resources/mapper/qw/QwUserMapper.xml

@@ -268,6 +268,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             (qw_user_id = #{query.qwUserId} AND corp_id = #{query.corpId})
         </foreach>
     </select>
+    <select id="selectQwCompanyListOptionsVOBySys" resultType="com.fs.qw.vo.QwOptionsVO">
+        select corp_id as dictValue,corp_name as dictLabel from qw_company
+    </select>
 
     <select id="selectQwUserListVOByCompanyIdAndCorpIdAndNickName" resultType="com.fs.qw.vo.QwUserVO">
         select