瀏覽代碼

Merge remote-tracking branch 'origin/master'

三七 4 天之前
父節點
當前提交
91d0afbbbc
共有 25 個文件被更改,包括 580 次插入169 次删除
  1. 16 0
      fs-admin/src/main/java/com/fs/course/task/VideoTask.java
  2. 26 2
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  3. 9 16
      fs-company/src/main/java/com/fs/company/controller/qw/QwFriendWelcomeController.java
  4. 7 2
      fs-framework/src/main/java/com/fs/framework/config/DataSourceConfig.java
  5. 114 88
      fs-qw-task/src/main/java/com/fs/app/task/qwTask.java
  6. 121 36
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  7. 12 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseTrafficLog.java
  8. 10 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseTrafficLogMapper.java
  9. 10 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  10. 5 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseTrafficLogService.java
  11. 68 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseTrafficLogServiceImpl.java
  12. 47 21
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  13. 6 0
      fs-service/src/main/java/com/fs/qw/domain/QwFriendWelcome.java
  14. 5 0
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  15. 7 0
      fs-service/src/main/java/com/fs/qw/mapper/QwFriendWelcomeMapper.java
  16. 1 1
      fs-service/src/main/java/com/fs/qw/mapper/QwUserMapper.java
  17. 6 0
      fs-service/src/main/java/com/fs/qw/param/QwExternalContactParam.java
  18. 3 0
      fs-service/src/main/java/com/fs/qw/param/QwFriendWelcomeParam.java
  19. 1 0
      fs-service/src/main/java/com/fs/qw/param/ResignedTransferParam.java
  20. 1 0
      fs-service/src/main/java/com/fs/qw/param/TransferParam.java
  21. 7 0
      fs-service/src/main/java/com/fs/qw/service/IQwFriendWelcomeService.java
  22. 10 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwFriendWelcomeServiceImpl.java
  23. 2 2
      fs-service/src/main/resources/application-config-myhk.yml
  24. 49 1
      fs-service/src/main/resources/mapper/course/FsCourseTrafficLogMapper.xml
  25. 37 0
      fs-service/src/main/resources/mapper/qw/QwFriendWelcomeMapper.xml

+ 16 - 0
fs-admin/src/main/java/com/fs/course/task/VideoTask.java

@@ -5,6 +5,7 @@ import com.fs.common.core.redis.RedisCache;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.*;
 import com.fs.course.mapper.*;
+import com.fs.course.service.IFsCourseTrafficLogService;
 import com.fs.course.service.IFsUserVideoCommentService;
 import com.fs.his.domain.FsUser;
 import com.fs.his.mapper.FsUserMapper;
@@ -18,6 +19,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
 import java.time.LocalDate;
@@ -55,6 +57,9 @@ public class VideoTask {
     @Autowired
     private FsCourseRedPacketLogMapper redPacketLogMapper;
 
+    @Autowired
+    private IFsCourseTrafficLogService iFsCourseTrafficLogService;
+
     public void autoNotShowVideo() {
         List<Long> list = videoMapper.selectNotAuditVideo();
         for (Long videoId : list){
@@ -238,4 +243,15 @@ public class VideoTask {
 
         }
     }
+
+    /**
+     * 存储课程流量日志
+     * @throws Exception
+     */
+//    @Scheduled(fixedRate = 1000)
+    public void saveCourseTrafficLog() throws Exception
+    {
+        iFsCourseTrafficLogService.saveCourseTrafficLog();
+    }
+
 }

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

@@ -1,5 +1,6 @@
 package com.fs.company.controller.qw;
 
+import cn.hutool.core.util.ObjectUtil;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
@@ -30,6 +31,7 @@ import com.github.pagehelper.PageHelper;
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 import io.swagger.annotations.ApiOperation;
+import org.apache.commons.collections.CollectionUtils;
 import org.codehaus.jettison.json.JSONException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -39,6 +41,7 @@ import java.io.IOException;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
+import java.util.stream.Collectors;
 
 import static com.fs.his.utils.PhoneUtil.decryptAutoPhoneMk;
 import static com.fs.his.utils.PhoneUtil.encryptPhone;
@@ -233,7 +236,18 @@ public class QwExternalContactController extends BaseController
     @PutMapping("/resignedTransfer")
     public R resignedTransfer(@RequestBody ResignedTransferParam param)
     {
-
+        if (ObjectUtil.isNotEmpty(param.getQwUserName())){
+            QwExternalContactParam qwExternalContact =new QwExternalContactParam();
+            qwExternalContact.setQwUserName(param.getQwUserName());
+
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            qwExternalContact.setCompanyId(loginUser.getCompany().getCompanyId());
+            List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(qwExternalContact);
+            if (!CollectionUtils.isEmpty(list)){
+                List<Long> ids = list.stream().map(QwExternalContactVO::getId).collect(Collectors.toList());
+                param.setIds(ids);
+            }
+        }
 
         return qwExternalContactService.resignedTransfer(param);
     }
@@ -246,7 +260,17 @@ public class QwExternalContactController extends BaseController
     @PutMapping("/transfer")
     public R transfer(@RequestBody TransferParam param)
     {
-
+        if (ObjectUtil.isNotEmpty(param.getQwUserName())){
+            QwExternalContactParam qwExternalContact =new QwExternalContactParam();
+            qwExternalContact.setQwUserName(param.getQwUserName());
+            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+            qwExternalContact.setCompanyId(loginUser.getCompany().getCompanyId());
+            List<QwExternalContactVO> list = qwExternalContactService.selectQwExternalContactListVO(qwExternalContact);
+            if (!CollectionUtils.isEmpty(list)){
+                List<Long> ids = list.stream().map(QwExternalContactVO::getId).collect(Collectors.toList());
+                param.setIds(ids);
+            }
+        }
         return qwExternalContactService.transfer(param);
     }
     /**

+ 9 - 16
fs-company/src/main/java/com/fs/company/controller/qw/QwFriendWelcomeController.java

@@ -1,5 +1,6 @@
 package com.fs.company.controller.qw;
 
+import com.fs.common.annotation.DataScope;
 import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
@@ -8,6 +9,7 @@ import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.CompanyUser;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
 import com.fs.qw.domain.QwFriendWelcome;
@@ -49,24 +51,13 @@ public class QwFriendWelcomeController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('qw:friendWelcome:list')")
     @GetMapping("/list")
+    @DataScope(deptAlias = "w", userAlias = "w")
     public TableDataInfo list(QwFriendWelcomeParam qwFriendWelcomeParam)
     {
-        startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         qwFriendWelcomeParam.setCompanyId(loginUser.getCompany().getCompanyId());
-        if(qwFriendWelcomeParam.getType() != null){
-            if(qwFriendWelcomeParam.getType() == 1){
-                List<QwOptionsVO> qwOptionsVOS = qwUserService.selectQwUserListOptionsVOByCompanyUserId(loginUser.getUser().getUserId());
-                qwFriendWelcomeParam.setQwUserIds(qwOptionsVOS.stream().map(QwOptionsVO::getDictValue).collect(Collectors.joining(",")));
-                if(StringUtils.isEmpty(qwFriendWelcomeParam.getQwUserIds())){
-                    return getDataTable(Collections.EMPTY_LIST);
-                }
-            }
-            if(qwFriendWelcomeParam.getType() == 2){
-
-            }
-        }
-        List<QwFriendWelcome> list = qwFriendWelcomeService.selectQwFriendWelcomeListVO(qwFriendWelcomeParam);
+        startPage();
+        List<QwFriendWelcome> list = qwFriendWelcomeService.selectQwFriendWelcomeList(qwFriendWelcomeParam);
         return getDataTable(list);
     }
 
@@ -119,8 +110,10 @@ public class QwFriendWelcomeController extends BaseController
     public R add(@RequestBody QwFriendWelcomeParam qwFriendWelcomeParam)
     {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        Long companyId = loginUser.getCompany().getCompanyId();
-        qwFriendWelcomeParam.setCompanyId(companyId);
+        CompanyUser companyUser = loginUser.getUser();
+        qwFriendWelcomeParam.setCompanyId(companyUser.getCompanyId());
+        qwFriendWelcomeParam.setCompanyUserId(companyUser.getUserId());
+        qwFriendWelcomeParam.setDeptId(companyUser.getDeptId());
         qwFriendWelcomeParam.setCreateTime(new Date());
         qwFriendWelcomeParam.setUpdateTime(new Date());
         return qwFriendWelcomeService.insertQwFriendWelcomeVO(qwFriendWelcomeParam);

+ 7 - 2
fs-framework/src/main/java/com/fs/framework/config/DataSourceConfig.java

@@ -33,13 +33,18 @@ public class DataSourceConfig {
         return new DruidDataSource();
     }
 
-
+    @Bean
+    @ConfigurationProperties(prefix = "spring.datasource.clickhouse")
+    public DataSource clickhouseDataSource() {
+        return new DruidDataSource();
+    }
 
     @Bean
     @Primary
-    public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("sopDataSource") DataSource sopDataSource) {
+    public DynamicDataSource dataSource(@Qualifier("clickhouseDataSource") DataSource clickhouseDataSource,@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("sopDataSource") DataSource sopDataSource) {
         Map<Object, Object> targetDataSources = new HashMap<>();
         targetDataSources.put(DataSourceType.SOP.name(), sopDataSource);
+        targetDataSources.put(DataSourceType.CLICKHOUSE.name(), clickhouseDataSource);
         return new DynamicDataSource(masterDataSource, targetDataSources);
     }
 

+ 114 - 88
fs-qw-task/src/main/java/com/fs/app/task/qwTask.java

@@ -1,6 +1,5 @@
 package com.fs.app.task;
 
-
 import com.fs.app.taskService.QwExternalContactRatingService;
 import com.fs.app.taskService.SopLogsChatTaskService;
 import com.fs.app.taskService.SopLogsTaskService;
@@ -26,6 +25,13 @@ import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.List;
 
+/**
+ * 企业微信SOP定时任务管理类
+ * 负责处理各种定时任务,包括SOP规则检查、消息发送、数据清理等
+ * 
+ * @author 系统
+ * @version 1.0
+ */
 @Component
 @Slf4j
 public class qwTask {
@@ -44,9 +50,10 @@ public class qwTask {
 
     @Autowired
     private ISopUserLogsService sopUserLogsService;
-
+    
     @Autowired
     private SopLogsTaskService sopLogsTaskService;
+    
     @Autowired
     private SopWxLogsService sopWxLogsService;
 
@@ -64,143 +71,149 @@ public class qwTask {
 
     @Autowired
     private QwSopLogsMapper qwSopLogsMapper;
+    
     @Autowired
     private IQwSopTagService qwSopTagService;
-    /**
-    * 定时任务 将 qw_sop任务 符合条件的录入到sop_user_Logs(clickHouse)
-    */
 
+    /**
+     * 定时任务:检查SOP规则时间
+     * 执行时间:每天凌晨 1:10:00
+     * 功能:将符合条件的qw_sop任务录入到sop_user_Logs(clickHouse)
+     */
     @Scheduled(cron = "0 10 1 * * ?")
-    public void qwCheckSopRuleTime()
-    {
+    public void qwCheckSopRuleTime() {
         qwSopService.checkSopRuleTime();
     }
 
+    /**
+     * 定时任务:添加标签
+     * 执行时间:每20分钟执行一次
+     * 功能:自动为符合条件的记录添加标签
+     */
     @Scheduled(cron = "0 0/20 * * * ?")
-    public void addTag(){
+    public void addTag() {
         qwSopTagService.addTag();
     }
+
     /**
-     * 根据营期生成sopLogs待发记录
-     * @throws Exception
+     * 定时任务:根据营期生成sopLogs待发记录
+     * 执行时间:每小时的第5分钟执行
+     * 功能:根据营期时间生成需要发送的SOP日志记录
+     * 
+     * @throws Exception 执行异常
      */
     @Scheduled(cron = "0 5 * * * ?") // 每小时的第5分钟触发
     @Async
     public void selectSopUserLogsListByTime() throws Exception {
-        // 获取当前时间
+        // 获取当前时间,精确到小时
         LocalDateTime currentTime = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0);
-        // 打印日志,确认时间
+        // 打印日志,确认任务执行时间
         log.info("任务实际执行时间: {}", currentTime);
 
-        // 调用服务方法
+        // 调用服务方法处理SOP用户日志
         sopLogsTaskService.selectSopUserLogsListByTime(currentTime);
     }
+
+    /**
+     * 定时任务:微信SOP处理
+     * 执行时间:每小时的第5分钟执行
+     * 功能:处理微信相关的SOP日志
+     * 
+     * @throws Exception 执行异常
+     */
     @Scheduled(cron = "0 5 * * * ?") // 每小时的第5分钟触发
     public void wxSop() throws Exception {
-        // 获取当前时间
+        // 获取当前时间,精确到小时
         LocalDateTime currentTime = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0);
-        // 打印日志,确认时间
+        // 打印日志,确认任务执行时间
         log.info("任务实际执行时间: {}", currentTime);
 
-        // 调用服务方法
+        // 调用服务方法处理微信SOP日志
         sopWxLogsService.wxSopLogsByTime(currentTime);
     }
 
     /**
-     * 定时任务 将 clickHouse的sopUserLogsChat(营期表)按每1分钟的巡回
+     * 定时任务:处理聊天SOP用户日志
+     * 执行时间:已注释,原为每分钟的第5秒执行
+     * 功能:将clickHouse的sopUserLogsChat(营期表)按每分钟巡回处理
+     * 
+     * @throws Exception 执行异常
      */
 //    @Scheduled(cron = "5 0/1 * * * ?")
     public void selectChatSopUserLogsListByTime() throws Exception {
-
+        // 获取当前时间,精确到分钟
         LocalDateTime today = LocalDateTime.now().withSecond(0).withNano(0);
 
+        // 创建AI聊天SOP日志
         sopLogsTaskChatService.createAiChatSopLogs(today);
     }
 
-//    /**
-//    * 定时 发送 通过调用 企业微信接口 发送的 SOP 群发消息
-//    */
-//    @Scheduled(cron = "0 15 0 * * ?")
-//    public void SendQwApiSopLogTimer(){
-//        log.info("zyp \n【企微官方接口群发开始】");
-////        qwSopLogsService.checkQwSopLogs();
-//        LocalDate localDate = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0).toLocalDate();
-//        String date = localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
-//
-//        qwSopLogsService.createCorpMassSending(date);
-//    }
-//
-//    /**
-//    * 定时获取 通过调用 企业微信接口 发送的 SOP 客户群发消息 的反馈结果
-//    */
-//    @Scheduled(cron = "0 0 8 * * ?")
-//    public void GetQwApiSopLogResultTimer(){
-//        qwSopLogsService.qwSopLogsResult();
-//    }
-
     /**
-     * 定时 发送 通过调用 企业微信接口 发送的 SOP 群发消息(新版-安装营期发)
+     * 定时任务:发送企业微信SOP群发消息(新版-按营期发送)
+     * 执行时间:每天凌晨 0:20:00
+     * 功能:通过调用企业微信接口发送SOP群发消息
      */
     @Scheduled(cron = "0 20 0 * * ?")
-    public void SendQwApiSopLogTimerNew(){
-
+    public void SendQwApiSopLogTimerNew() {
         log.info("zyp \n【企微官方接口群发开始】");
-//        qwSopLogsService.checkQwSopLogs();
+        
+        // 获取当前日期
         LocalDate localDate = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0).toLocalDate();
         String date = localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
 
+        // 根据用户日志创建企业群发
         qwSopLogsService.createCorpMassSendingByUserLogs(date);
     }
 
-
     /**
-     * 定时获取 通过调用 企业微信接口 发送的 SOP 客户群发消息 的反馈结果(新版-安装营期发)
+     * 定时任务:获取企业微信SOP群发消息反馈结果(新版-按营期发送)
+     * 执行时间:每天上午 8:00:00
+     * 功能:获取通过企业微信接口发送的SOP客户群发消息的反馈结果
      */
     @Scheduled(cron = "0 0 8 * * ?")
-    public void GetQwApiSopLogResultTimerNew(){
+    public void GetQwApiSopLogResultTimerNew() {
         qwSopLogsService.qwSopLogsResultNew();
     }
 
     /**
-    * 定时群发API接口的 客户/群 群发
-    */
+     * 定时任务:群发API接口的客户/群群发
+     * 执行时间:每10分钟执行一次
+     * 功能:定时处理群发消息任务
+     */
     @Scheduled(cron = "0 0/10 * * * ?")
-    public void sendQwGroupMsgTask(){
+    public void sendQwGroupMsgTask() {
         qwGroupMsgService.qwGroupMsgTask();
     }
+
     /**
-    * 定时发送转换消息
-    */
+     * 定时任务:发送转换消息
+     * 执行时间:每天上午 8:00:00
+     * 功能:根据SOP规则发送转换消息
+     */
     @Scheduled(cron = "0 0 8 * * ?")
-//    @Scheduled(cron = "0/10 * * * * ?")
-    public void sendQwBySop(){
+//    @Scheduled(cron = "0/10 * * * * ?") // 测试用:每10秒执行一次
+    public void sendQwBySop() {
         sopUserLogsService.sendQwBySop();
     }
 
-
     /**
-    * 企业微信自动打标签/备注 没打上的 补偿机制
-    */
+     * 定时任务:企业微信自动打标签/备注补偿机制
+     * 执行时间:每3分钟执行一次
+     * 功能:对没有成功打标签或备注的记录进行补偿处理
+     */
     @Scheduled(cron = "0 0/3 * * * ?")
-    public void qwExternalErrRetryTimer(){
+    public void qwExternalErrRetryTimer() {
         log.info("补偿机制开始");
         errRetryService.qwExternalErrRetryTimer();
     }
 
-
     /**
-     * 同步完企微客户,然后对加微的数据信息筛选并上传给百度进行投流优化
-     */
-//    @Scheduled(cron = "0 0 6 * * ?")
-//    public void bdUpload(){
-//        qwWorkUserService.uploadBd();
-//    }
-
-    /**
-     * 补发过期完课消息
+     * 定时任务:补发过期完课消息
+     * 执行时间:每小时的第0分钟执行
+     * 功能:补发已过期但未发送的完课消息
      */
     @Scheduled(cron = "0 0 * * * ?")  // 每小时的第0分钟0秒执行
-    public void updateQwSopLogsByCancel(){
+    public void updateQwSopLogsByCancel() {
         log.info("补发过期完课消息 - 定时任务开始");
         try {
             sopLogsTaskService.updateSopLogsByCancel();
@@ -211,16 +224,18 @@ public class qwTask {
     }
 
     /**
-    * 批量处理sop待发送记录中已过期得消息每8分钟执行一次
-    */
+     * 定时任务:批量处理SOP待发送记录中已过期的消息
+     * 执行时间:每8分钟执行一次
+     * 功能:批量更新已过期的SOP待发送记录
+     */
     @Scheduled(cron = "0 0/8 * * * ?")
-    public void batchProcessingExpiredMessages(){
-
+    public void batchProcessingExpiredMessages() {
         log.info("批量处理sop待发送记录中已过期的消息");
         try {
-            // Step 1: 批量更新已过期的记录
+            // 步骤1:批量获取已过期的记录
             List<QwSopLogsDoSendListTVO> expireded = iQwSopLogsService.expiredMessagesByQwSopLogs();
             if (!expireded.isEmpty()) {
+                // 步骤2:批量处理并插入记录
                 processAndInsertQwSopLogs(expireded);
             }
             log.info("处理已过期 - 定时任务成功完成");
@@ -229,19 +244,23 @@ public class qwTask {
         }
     }
 
-
-    // 定义一个方法来批量处理插入逻辑,支持每 500 条数据一次的批量插入
+    /**
+     * 批量处理插入逻辑,支持每500条数据一次的批量插入
+     * 
+     * @param logsByJsApiNotExtId 需要处理的日志列表
+     */
     private void processAndInsertQwSopLogs(List<QwSopLogsDoSendListTVO> logsByJsApiNotExtId) {
         // 定义批量插入的大小
         int batchSize = 500;
 
-        // 循环处理外部用户 ID,每次处理批量大小的子集
+        // 循环处理外部用户ID,每次处理批量大小的子集
         for (int i = 0; i < logsByJsApiNotExtId.size(); i += batchSize) {
-
+            // 计算当前批次的结束索引
             int endIndex = Math.min(i + batchSize, logsByJsApiNotExtId.size());
-            List<QwSopLogsDoSendListTVO> batchList = logsByJsApiNotExtId.subList(i, endIndex);  // 获取当前批次的子集
+            // 获取当前批次的子集
+            List<QwSopLogsDoSendListTVO> batchList = logsByJsApiNotExtId.subList(i, endIndex);
 
-            // 直接使用批次数据进行批量更新,不需要额外的 List
+            // 直接使用批次数据进行批量更新
             try {
                 qwSopLogsMapper.batchUpdateQwSopLogsBySendTime(batchList);
             } catch (Exception e) {
@@ -252,35 +271,42 @@ public class qwTask {
     }
 
     /**
-    * 定时清除 2天以前的sop任务记录 每天删除
-    */
+     * 定时任务:清除2天以前的SOP任务记录
+     * 执行时间:每天凌晨 0:10:00
+     * 功能:清理历史数据,保持数据库性能
+     */
     @Scheduled(cron = "0 10 0 * * ?")
     public void deleteQwSopLogsByDate() {
-
         qwSopLogsMapper.deleteQwSopLogsByDate();
-
     }
 
     /**
-    * 定时处理 营期异常的数据 每3小时
-    */
-
+     * 定时任务:处理营期异常的数据
+     * 执行时间:每3小时的第30分钟执行
+     * 功能:修复营期相关的异常数据
+     */
     @Scheduled(cron = "0 30 0/3 * * ? ")
     public void processRepairQwSopLogsTimer() {
         sopUserLogsService.repairSopUserLogsTimer();
     }
 
     /**
-    * 凌晨3点30开始 客户评级
-    */
+     * 定时任务:客户评级处理
+     * 执行时间:每天凌晨 3:45:00
+     * 功能:对SOP营期用户进行分级评级
+     * 备注:异步执行,避免阻塞其他任务
+     */
     @Scheduled(cron = "0 45 3 * * ?")
     @Async
     public void processQwSopExternalContactRatingTimer() {
+        // 记录任务开始时间
         long startTimeMillis = System.currentTimeMillis();
         log.info("====== 开始选择和处理 sop营期-用户分级 ======");
 
+        // 执行用户分级评级
         qwExternalContactRatingService.ratingUserLogs();
 
+        // 计算并记录任务执行耗时
         long endTimeMillis = System.currentTimeMillis();
         log.info("====== sop营期-用户分级处理完成,耗时 {} 毫秒 ======", (endTimeMillis - startTimeMillis));
     }

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

@@ -1638,47 +1638,138 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
     @Override
     public void createCourseFinishMsg() {
-        // 查询所有需要处理的完课记录
-        List<FsCourseWatchLog> finishLogs = fsCourseWatchLogMapper.selectFsCourseWatchLogFinish();
-        if (finishLogs.isEmpty()) {
-            log.info("没有需要处理的完课记录");
-            return;
+        long startTime = System.currentTimeMillis();
+        log.info("创建完课消息 - 定时任务开始 {}", startTime);
+
+        // 线程池配置
+        int threadPoolSize = 4;
+        ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);
+
+        // 用于收集所有处理结果的队列
+        BlockingQueue<List<FsCourseWatchLog>> batchQueue = new LinkedBlockingQueue<>();
+
+        try {
+            // 查询当天日期范围
+            LocalDate today = LocalDate.now();
+            Date startDate = Date.from(today.atStartOfDay(ZoneId.systemDefault()).toInstant());
+            Date endDate = Date.from(today.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
+
+            // 启动生产者线程 - 流式分批查询数据
+            executorService.submit(() -> {
+                try {
+                    int batchSize = 1000;
+                    long  maxId = 0;
+                    boolean hasMore = true;
+
+                    while (hasMore) {
+                        // 查询当前批次数据
+                        List<FsCourseWatchLog> batch = fsCourseWatchLogMapper.selectFsCourseWatchLogFinishBatchByDate(
+                                startDate, endDate, maxId, batchSize);
+
+                        if (!batch.isEmpty()) {
+                            // 将批次放入队列
+                            batchQueue.put(batch);
+                            // 更新maxId为当前批次的最后一个ID
+                            maxId = batch.get(batch.size() - 1).getLogId();
+                            log.debug("已生产批次数据,最后logId: {}, 数量: {}", maxId, batch.size());
+                        }
+
+                        if (batch.size() < batchSize) {
+                            hasMore = false;
+                            batchQueue.put(Collections.emptyList());// 结束标志
+                            log.info("数据生产完成,最后logId: {}", maxId);
+                        }
+                    }
+                } catch (Exception e) {
+                    log.error("生产数据时出错", e);
+                    try {
+                        batchQueue.put(Collections.emptyList()); // 确保消费者能退出
+                    } catch (InterruptedException ie) {
+                        Thread.currentThread().interrupt();
+                    }
+                }
+            });
+
+            // 消费者线程处理数据
+            List<Future<?>> futures = new ArrayList<>();
+            for (int i = 0; i < threadPoolSize; i++) {
+                futures.add(executorService.submit(() -> {
+                    try {
+                        while (true) {
+                            List<FsCourseWatchLog> batch = batchQueue.take();
+
+                            // 空列表表示处理结束
+                            if (batch.isEmpty()) {
+                                batchQueue.put(Collections.emptyList()); // 传递给其他消费者
+                                break;
+                            }
+                            log.info("开始处理批次数据");
+                            processBatch(batch); // 处理批次数据
+                        }
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                        log.error("处理数据时被中断", e);
+                    } catch (Exception e) {
+                        log.error("处理数据时出错", e);
+                    }
+                }));
+            }
+
+            // 等待所有任务完成
+            for (Future<?> future : futures) {
+                try {
+                    future.get();
+                } catch (InterruptedException | ExecutionException e) {
+                    log.error("等待任务完成时出错", e);
+                    Thread.currentThread().interrupt();
+                }
+            }
+
+            log.info("所有批次处理完成,总耗时: {}ms", System.currentTimeMillis() - startTime);
+
+        } finally {
+            executorService.shutdown();
+            try {
+                if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
+                    executorService.shutdownNow();
+                }
+            } catch (InterruptedException e) {
+                executorService.shutdownNow();
+                Thread.currentThread().interrupt();
+            }
         }
+    }
 
-        // 用于批量更新的 finishLog 列表
+    // 处理单个批次的方法
+    private void processBatch(List<FsCourseWatchLog> batch) {
         List<FsCourseWatchLog> finishLogsToUpdate = new ArrayList<>();
-        // 用于批量插入的 sopLogs 列表
         List<QwSopLogs> sopLogsToInsert = new ArrayList<>();
-
-        for (FsCourseWatchLog finishLog : finishLogs) {
+        log.info("开始执行处理批次方法-数量:{}",batch.size());
+        for (FsCourseWatchLog finishLog : batch) {
             try {
                 // 查询外部联系人信息
                 QwExternalContact externalContact = qwExternalContactMapper.selectQwExternalContactById(finishLog.getQwExternalContactId());
                 if (externalContact == null) {
-                    log.error("外部联系人不存在: " + finishLog.getQwExternalContactId());
+                    log.error("外部联系人不存在: {}", finishLog.getQwExternalContactId());
                     continue;
                 }
 
                 // 查询完课模板信息
-//                FsCourseFinishTemp finishTemp = fsCourseFinishTempMapper.selectFsCourseFinishTempByCompanyUserId(finishLog.getCompanyUserId(), finishLog.getVideoId());
-                long startTimeMillis = System.currentTimeMillis();
-                log.info("====== 开始查询模板,{}",finishLog);
                 FsCourseFinishTemp finishTemp = fsCourseFinishTempMapper.selectFsCourseFinishTempByCompanyId(finishLog.getCompanyUserId(),finishLog.getCompanyId(), finishLog.getVideoId());
 
-                long endTimeMillis = System.currentTimeMillis();
-                log.info("====== 模板查询完成,耗时 {} 毫秒 ======", (endTimeMillis - startTimeMillis));
                 // 设置 finishLog 为已发送状态,并加入批量更新列表
                 finishLog.setSendFinishMsg(1);
                 finishLogsToUpdate.add(finishLog);
 
                 if (finishTemp == null) {
-//                    log.error("完课模板不存在: " + finishLog.getCompanyUserId() + ", " + finishLog.getVideoId());
+//                    log.error("完课模板不存在: " + finishLog.getQwUserId() + ", " + finishLog.getVideoId());
                     continue;
                 }
 
                 // 构建 sopLogs 对象
                 QwSopLogs sopLogs = buildSopLogs(finishLog, externalContact, finishTemp);
                 if (sopLogs == null) {
+                    log.error("生成完课发送记录为空-:{}", finishLog.getQwExternalContactId());
                     continue;
                 }
 
@@ -1686,38 +1777,32 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 if (isValidExternalContact(externalContact)) {
                     sopLogsToInsert.add(sopLogs);
                 } else {
-                    log.info("完课消息-客户信息有误,不生成完课消息: " + finishLog.getQwExternalContactId());
+                    log.info("完课消息-客户信息有误,不生成完课消息: {}", finishLog.getQwExternalContactId());
                 }
             } catch (Exception e) {
-                log.error("处理完课记录失败: " + finishLog.getLogId(), e);
+                log.error("处理完课记录失败: {}", finishLog.getLogId(), e);
             }
         }
 
-        // 分批更新 finishLog(每批 100 条)
+        // 批量更新和插入
         if (!finishLogsToUpdate.isEmpty()) {
-            List<List<FsCourseWatchLog>> updateBatches = BatchUtils.splitList(finishLogsToUpdate, 100);
-            for (List<FsCourseWatchLog> batch : updateBatches) {
-                try {
-                    fsCourseWatchLogMapper.batchUpdateWatchLogSendMsg(batch);
-                    log.info("批量更新 finishLog 成功,更新数量: " + batch.size());
-                } catch (Exception e) {
-                    log.error("批量更新 finishLog 失败", e);
-                }
+            try {
+                fsCourseWatchLogMapper.batchUpdateWatchLogSendMsg(finishLogsToUpdate);
+                log.info("批量更新 finishLog 成功,数量: {}", finishLogsToUpdate.size());
+            } catch (Exception e) {
+                log.error("批量更新 finishLog 失败", e);
             }
         }
 
-        // 分批插入 sopLogs(每批 500 条)
         if (!sopLogsToInsert.isEmpty()) {
-            List<List<QwSopLogs>> insertBatches = BatchUtils.splitList(sopLogsToInsert, 500);
-            for (List<QwSopLogs> batch : insertBatches) {
-                try {
-                    qwSopLogsService.batchInsertQwSopLogs(batch);
-                    log.info("批量插入 sopLogs 成功,插入数量: " + batch.size());
-                } catch (Exception e) {
-                    log.error("批量插入 sopLogs 失败", e);
-                }
+            try {
+                qwSopLogsService.batchInsertQwSopLogs(sopLogsToInsert);
+                log.info("批量插入 sopLogs 成功,数量: {}", sopLogsToInsert.size());
+            } catch (Exception e) {
+                log.error("批量插入 sopLogs 失败", e);
             }
         }
+        log.info("结束处理批次方法-数量:{}",batch.size());
     }
 
     /**

+ 12 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseTrafficLog.java

@@ -56,6 +56,18 @@ public class FsCourseTrafficLog extends BaseEntity
     @Excel(name = "课程id")
     private Long courseId;
 
+    /**
+     * 项目
+     */
+    private Long project;
+
+    /**
+     * 营期id
+     */
+    private Long periodId;
+
+
+
 //    @JsonFormat(pattern = "yyyy-MM-dd")
 //    private Date time;
 

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

@@ -1,11 +1,15 @@
 package com.fs.course.mapper;
 
 import java.util.List;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.course.domain.FsCourseTrafficLog;
 import com.fs.course.param.FsCourseTrafficLogParam;
 import com.fs.course.vo.FsCourseTrafficLogListVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
+import org.springframework.stereotype.Repository;
 
 /**
  * 短链课程流量记录Mapper接口
@@ -13,6 +17,7 @@ import org.apache.ibatis.annotations.Select;
  * @author fs
  * @date 2024-10-31
  */
+@Repository
 public interface FsCourseTrafficLogMapper
 {
     /**
@@ -97,5 +102,10 @@ public interface FsCourseTrafficLogMapper
     Long getYesterdayTrafficLog();
 
     Long getMonthTrafficLog();
+    @Select("select count(1) from fs_course_traffic_log l WHERE l.create_time < DATE_SUB(NOW(), INTERVAL 2 DAY)")
+    Integer saveCourseTrafficLogByTwoDaysLaterCount();
 
+    List<FsCourseTrafficLog> selectCourseTrafficLogByTwoDaysLater(@Param("offset")Integer offset,@Param("limit")Integer limit);
+    @DataSource(DataSourceType.CLICKHOUSE)
+    void insertCourseTrafficLogByTwoDaysLaterBatch(@Param("list") List<FsCourseTrafficLog> redPacketLogs);
 }

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

@@ -377,4 +377,14 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
             ") dd ON ds.report_date = dd.log_date\n" +
             "ORDER BY ds.report_date ASC")
     List<WatchLogDTO> selectFsCourseWatchLog30DayByExtId(@Param("extId") Long extId);
+
+    @Select("SELECT * FROM fs_course_watch_log " +
+            "WHERE log_type = 2 AND send_finish_msg = 0 " +
+            "AND finish_time >= #{startDate} AND finish_time < #{endDate} and log_id > #{maxId} order by log_id asc  " +
+            "LIMIT #{limit}")
+    List<FsCourseWatchLog> selectFsCourseWatchLogFinishBatchByDate(
+            @Param("startDate") Date startDate,
+            @Param("endDate") Date endDate,
+            @Param("maxId") long  maxId,
+            @Param("limit") int limit);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseTrafficLogService.java

@@ -63,4 +63,9 @@ public interface IFsCourseTrafficLogService
 
 
     List<FsCourseTrafficLogListVO> selectTrafficByCompany(FsCourseTrafficLogParam param);
+
+    /**
+     * 存储课程流量日志
+     */
+    void saveCourseTrafficLog();
 }

+ 68 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseTrafficLogServiceImpl.java

@@ -1,10 +1,13 @@
 package com.fs.course.service.impl;
 
+import java.text.SimpleDateFormat;
 import java.util.Collections;
+import java.util.Date;
 import java.util.List;
 import com.fs.common.utils.DateUtils;
 import com.fs.course.param.FsCourseTrafficLogParam;
 import com.fs.course.vo.FsCourseTrafficLogListVO;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.course.mapper.FsCourseTrafficLogMapper;
@@ -18,6 +21,7 @@ import com.fs.course.service.IFsCourseTrafficLogService;
  * @date 2024-10-31
  */
 @Service
+@Slf4j
 public class FsCourseTrafficLogServiceImpl implements IFsCourseTrafficLogService
 {
     @Autowired
@@ -100,4 +104,68 @@ public class FsCourseTrafficLogServiceImpl implements IFsCourseTrafficLogService
     public List<FsCourseTrafficLogListVO> selectTrafficByCompany(FsCourseTrafficLogParam param) {
         return fsCourseTrafficLogMapper.selectTrafficByCompany(param);
     }
+
+
+    @Override
+    public void saveCourseTrafficLog() {
+        Integer count = fsCourseTrafficLogMapper.saveCourseTrafficLogByTwoDaysLaterCount();
+        Integer limit = 1000;
+        log.info("总数: {}", count);
+
+        // Start timing
+        long startTime = System.currentTimeMillis();
+        log.info("开始处理,时间: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
+
+        // 计算需要查询的次数
+        int totalPages = (count + limit - 1) / limit; // 向上取整
+        for (int page = 0; page < totalPages; page++) {
+            int offset = page * limit;
+
+            log.info("开始查询");
+            List<FsCourseTrafficLog> redPacketLogs = fsCourseTrafficLogMapper.selectCourseTrafficLogByTwoDaysLater(0,limit);
+            log.info("查询结果: {}", redPacketLogs);
+            // 处理当前批次的1000条数据
+            if (redPacketLogs==null ||redPacketLogs.isEmpty()) {
+                log.info("没有数据");
+                continue;
+            }
+            fsCourseTrafficLogMapper.insertCourseTrafficLogByTwoDaysLaterBatch(redPacketLogs);
+            Long[] delArray = redPacketLogs.stream()
+                    .map(FsCourseTrafficLog::getLogId)
+                    .toArray(Long[]::new);
+            fsCourseTrafficLogMapper.deleteFsCourseTrafficLogByLogIds(delArray);
+
+            // 打印进度
+            log.info("已处理: {}/{} ({:.2f}%)",
+                    Math.min(offset + limit, count),
+                    count,
+                    (double)(offset + limit) / count * 100);
+
+            // Calculate estimated remaining time
+            long currentTime = System.currentTimeMillis();
+            long elapsedTime = currentTime - startTime;
+            double timePerBatch = (double)elapsedTime / (page + 1);
+            long estimatedRemaining = (long)(timePerBatch * (totalPages - page - 1));
+
+            log.info("当前批次耗时: {:.2f}s, 预计剩余时间: {}",
+                    timePerBatch / 1000,
+                    formatDuration(estimatedRemaining));
+        }
+
+        // End timing
+        long endTime = System.currentTimeMillis();
+        log.info("处理完成,时间: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
+        log.info("总耗时: {}", formatDuration(endTime - startTime));
+    }
+
+    private static String formatDuration(long millis) {
+        long seconds = millis / 1000;
+        long minutes = seconds / 60;
+        long hours = minutes / 60;
+
+        return String.format("%02d:%02d:%02d",
+                hours,
+                minutes % 60,
+                seconds % 60);
+    }
 }

+ 47 - 21
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -465,13 +465,22 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         List<FsCourseWatchLog> logs = new ArrayList<>();
         for (String key : keys) {
             //取key中数据
-            String[] parts = key.split(":");
-            Long qwUserId = Long.parseLong(parts[3]);
-            Long externalId = Long.parseLong(parts[4]);
-            Long videoId = Long.parseLong(parts[5]);
+            Long qwUserId=null;
+            Long videoId=null;
+            Long externalId=null;
+            try {
+                String[] parts = key.split(":");
+                qwUserId = Long.parseLong(parts[3]);
+                externalId = Long.parseLong(parts[4]);
+                videoId = Long.parseLong(parts[5]);
+            }catch (Exception e){
+                log.error("key中id为null:{}", key);
+                continue;
+            }
             String durationStr = redisCache.getCacheObject(key);
-            if(durationStr==null){
-                log.error("key中数据为null:{}",key);
+            if (com.fs.common.utils.StringUtils.isEmpty(durationStr)) {
+                redisCache.deleteObject(key);
+                log.error("key中数据为null:{}", key);
                 continue;  // 如果 Redis 中没有记录,跳过
             }
             Long duration = Long.valueOf(durationStr);
@@ -510,6 +519,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
             //集合中增加
             logs.add(watchLog);
         }
+
         batchUpdateFsCourseWatchLog(logs,100);
     }
 
@@ -522,13 +532,23 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         List<FsCourseWatchLog> logs = new ArrayList<>();
         for (String key : keys) {
             FsCourseWatchLog watchLog = new FsCourseWatchLog();
-            String[] parts = key.split(":");
-            Long qwUserId = Long.parseLong(parts[3]);
-            Long externalId = Long.parseLong(parts[4]);
-            Long videoId = Long.parseLong(parts[5]);
+            //取key中数据
+            Long qwUserId=null;
+            Long videoId=null;
+            Long externalId=null;
+            try {
+                String[] parts = key.split(":");
+                qwUserId = Long.parseLong(parts[3]);
+                externalId = Long.parseLong(parts[4]);
+                videoId = Long.parseLong(parts[5]);
+            }catch (Exception e){
+                log.error("key中id为null:{}", key);
+                continue;
+            }
             // 获取最后心跳时间
             String lastHeartbeatStr = redisCache.getCacheObject(key);
-            if (lastHeartbeatStr == null) {
+            if (com.fs.common.utils.StringUtils.isEmpty(lastHeartbeatStr)) {
+                redisCache.deleteObject(key);
                 continue; // 如果 Redis 中没有记录,跳过
             }
             LocalDateTime lastHeartbeatTime = LocalDateTime.parse(lastHeartbeatStr);
@@ -691,6 +711,10 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
 
 
     public void batchUpdateFsCourseWatchLog(List<FsCourseWatchLog> logs, int batchSize) {
+        // 记录开始时间
+        long startTime = System.currentTimeMillis();
+        log.info("开始批量更新日志,总日志数量: {}", logs == null ? 0 : logs.size());
+
         if (logs == null || logs.isEmpty()) {
             log.info("待更新的日志列表为空,无需处理");
             return;
@@ -703,18 +727,20 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         for (int i = 0; i < logs.size(); i += batchSize) {
             int end = Math.min(i + batchSize, logs.size());
             List<FsCourseWatchLog> batchList = logs.subList(i, end);
-
-            // 记录当前批次的数量
-            log.info("正在更新第 {} 批日志,数量: {}", (i / batchSize) + 1, batchList.size());
-
             // 执行批量更新
-            fsCourseWatchLogMapper.batchUpdateWatchLog(batchList);
-
-            // 记录当前批次更新完成
-            log.info("第 {} 批日志更新完成,数量: {}", (i / batchSize) + 1, batchList.size());
+            try {
+                fsCourseWatchLogMapper.batchUpdateWatchLog(batchList);
+            } catch (Exception e) {
+                log.error("第 {} 批日志更新失败:{}",(i / batchSize) + 1,e.getMessage(),e);
+                throw new RuntimeException(e);
+            }
         }
 
-        // 记录全部更新完成
-        log.info("所有日志更新完成,总日志数量: {}", logs.size());
+        // 计算总耗时
+        long totalCost = System.currentTimeMillis() - startTime;
+        log.info("所有日志更新完成,总数量: {}, 总耗时: {}ms, 平均速度: {}/s",
+                logs.size(),
+                totalCost,
+                totalCost > 0 ? String.format("%.2f", logs.size() * 1000.0 / totalCost) : "∞");
     }
 }

+ 6 - 0
fs-service/src/main/java/com/fs/qw/domain/QwFriendWelcome.java

@@ -49,6 +49,12 @@ public class QwFriendWelcome extends BaseEntity
     @Excel(name = "公司id")
     private Long companyId;
 
+    /** 销售ID */
+    private Long userId;
+
+    /** 部门ID */
+    private Long deptId;
+
     /** 创建时间 */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date createTime;

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

@@ -213,6 +213,7 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
             "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>  \n" +
             "            <if test=\"userId != null  and userId != ''\"> and ec.user_id   like concat( #{userId}, '%') </if>\n" +
             "            <if test=\"qwUserName != null  and qwUserName != ''\"> and qu.qw_user_name   like concat( #{qwUserName}, '%') </if>\n" +
@@ -250,6 +251,10 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
             "            <if test=\"delTime != null \"> and DATE(ec.del_time) = DATE(#{delTime})</if>\n" +
             "            <if test=\"sTime != null \">  and DATE(ec.create_time) &gt;= DATE(#{sTime})</if>\n" +
             "            <if test=\"eTime != null \">  and DATE(ec.create_time) &lt;= DATE(#{eTime})</if>\n" +
+
+            "<if test ='companyUser!=null'> " +
+                "and (cu.nick_name like concat('%', #{companyUser}, '%') or cu.phonenumber= #{companyUser})"+
+            "</if> " +
             "        </where>"+
             "order by ec.id desc"+
             "</script>"})

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

@@ -103,4 +103,11 @@ public interface QwFriendWelcomeMapper
      * @return 结果
      */
     public int deleteQwFriendWelcomeByIds(Long[] ids);
+
+    /**
+     * 查询欢迎语列表
+     * @param qwFriendWelcomeParam 参数
+     * @return  list
+     */
+    List<QwFriendWelcome> selectQwFriendWelcomeList(QwFriendWelcomeParam qwFriendWelcomeParam);
 }

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

@@ -190,7 +190,7 @@ public interface QwUserMapper extends BaseMapper<QwUser>
             "where qu.is_del=0 and qu.company_user_id is not null"+
             "            <if test=\"qwUserId != null  and qwUserId != ''\"> and qu.qw_user_id = #{qwUserId}</if>\n" +
             "            <if test=\"companyId != null \"> and qu.company_id = #{companyId}</if>\n" +
-            "            <if test=\"nickName != null  and nickName != ''\"> and cu.nick_name like concat( #{nickName}, '%') </if>\n" +
+            "            <if test=\"nickName != null  and nickName != ''\"> and cu.nick_name like concat('%', #{nickName}, '%') </if>\n" +
             "            <if test=\"qwUserName != null  and qwUserName != ''\"> and qu.qw_user_uame like concat( #{qwUserName}, '%') </if>\n" +
             "            <if test=\"companyUserId != null \"> and qu.company_user_id = #{companyUserId}</if>\n" +
             "            <if test=\"corpId != null \"> and qu.corp_id = #{corpId}</if>\n" +

+ 6 - 0
fs-service/src/main/java/com/fs/qw/param/QwExternalContactParam.java

@@ -27,6 +27,12 @@ public class QwExternalContactParam {
 
     private Long companyUserId;
 
+    /**
+     * 销售员工信息(昵称,手机号)
+     */
+    private String companyUser;
+
+
     private Long customerId;
 
     /** 名称 */

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

@@ -58,4 +58,7 @@ public class QwFriendWelcomeParam extends BaseEntity {
     @Excel(name = "公司员工id")
     private Long companyUserId;
 
+    /** 部门ID */
+    private Long deptId;
+
 }

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

@@ -9,4 +9,5 @@ public class ResignedTransferParam {
     List<Long> ids;
     Long  userId;
     String corpId;
+    String  qwUserName;
 }

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

@@ -8,6 +8,7 @@ import java.util.List;
 public class TransferParam {
     List<Long> ids;
     Long  userId;
+    String  qwUserName;
     String corpId;
     String content;
 }

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

@@ -64,4 +64,11 @@ public interface IQwFriendWelcomeService
      * @return 结果
      */
     public int deleteQwFriendWelcomeById(Long id);
+
+    /**
+     * 查询欢迎语列表
+     * @param qwFriendWelcomeParam 参数
+     * @return list
+     */
+    List<QwFriendWelcome> selectQwFriendWelcomeList(QwFriendWelcomeParam qwFriendWelcomeParam);
 }

+ 10 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwFriendWelcomeServiceImpl.java

@@ -383,6 +383,16 @@ public class QwFriendWelcomeServiceImpl implements IQwFriendWelcomeService {
         return qwFriendWelcomeMapper.deleteQwFriendWelcomeById(id);
     }
 
+    /**
+     * 查询欢迎语列表
+     * @param qwFriendWelcomeParam 参数
+     * @return list
+     */
+    @Override
+    public List<QwFriendWelcome> selectQwFriendWelcomeList(QwFriendWelcomeParam qwFriendWelcomeParam) {
+        return qwFriendWelcomeMapper.selectQwFriendWelcomeList(qwFriendWelcomeParam);
+    }
+
     /**
      * url转临时文件filr
      */

+ 2 - 2
fs-service/src/main/resources/application-config-myhk.yml

@@ -47,7 +47,7 @@ watch:
   password3: v9xsKuqn_$d2y
 
 fs :
-  commonApi: http://172.16.0.16:8010
+  commonApi: http://172.27.0.7:8010
 #  commonApi: http://127.0.0.1:8010
   h5CommonApi: http://119.29.195.254:8010
 nuonuo:
@@ -69,7 +69,7 @@ cloud_host:
 headerImg:
   imgUrl: https://fs-1346741853.cos.ap-chengdu.myqcloud.com/fs/20250323/6189704f2e134b84ad9c9e7c9999f103.jpg
 ipad:
-  ipadUrl: http://qwipad.muyikp.com
+  ipadUrl: http://qwipad.muyi88.com
 
 wx_miniapp_temp:
   pay_order_temp_id: VXEvKaGNPFuJmhWK9O_QPrTZxe9umDCukq-maI8Vdek

+ 49 - 1
fs-service/src/main/resources/mapper/course/FsCourseTrafficLogMapper.xml

@@ -16,10 +16,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="companyId"    column="company_id"    />
         <result property="courseId"    column="course_id"    />
         <result property="uuId"    column="uu_id"    />
+        <result property="periodId"    column="period_id"    />
+        <result property="project"    column="project"    />
     </resultMap>
 
     <sql id="selectFsCourseTrafficLogVo">
-        select log_id, uu_id,user_id, video_id, create_time, qw_external_contact_id, internet_traffic, qw_user_id, company_user_id, company_id, course_id from fs_course_traffic_log
+        select log_id,
+               user_id,
+               video_id,
+               create_time,
+               qw_external_contact_id,
+               internet_traffic,
+               qw_user_id,
+               company_user_id,
+               company_id,
+               course_id,
+               uu_id,
+               project,
+               period_id from fs_course_traffic_log
     </sql>
 
     <select id="selectFsCourseTrafficLogList" parameterType="FsCourseTrafficLog" resultMap="FsCourseTrafficLogResult">
@@ -190,4 +204,38 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{logId}
         </foreach>
     </delete>
+
+
+
+    <select id="selectCourseTrafficLogByTwoDaysLater" resultMap="FsCourseTrafficLogResult">
+        <![CDATA[
+        SELECT
+            l.log_id,
+            l.user_id,
+            l.video_id,
+            l.create_time,
+            l.qw_external_contact_id,
+            l.internet_traffic,
+            l.qw_user_id,
+            l.company_user_id,
+            l.company_id,
+            l.course_id,
+            l.uu_id,
+            l.project, l.period_id
+        FROM
+            fs_course_traffic_log l
+        WHERE
+            l.create_time < DATE_SUB( NOW( ), INTERVAL 2 DAY )
+        ORDER BY l.log_id  limit #{offset},#{limit} ]]>
+    </select>
+
+
+    <insert id="insertCourseTrafficLogByTwoDaysLaterBatch" useGeneratedKeys="false">
+        insert into fs_course_traffic_log (
+        log_id, user_id, video_id, create_time, qw_external_contact_id, internet_traffic, qw_user_id, company_user_id, company_id, course_id, uu_id, project, period_id ) values
+        <foreach collection="list" item="item" separator=",">
+            (#{item.logId},#{item.userId},#{item.videoId},#{item.createTime},#{item.qwExternalContactId},#{item.internetTraffic},#{item.qwUserId}
+            ,#{item.companyUserId},#{item.companyId},#{item.courseId},#{item.uuId},#{item.project},#{item.periodId})
+        </foreach>
+    </insert>
 </mapper>

+ 37 - 0
fs-service/src/main/resources/mapper/qw/QwFriendWelcomeMapper.xml

@@ -41,6 +41,39 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where id = #{id}
     </select>
 
+    <select id="selectQwFriendWelcomeList" resultType="com.fs.qw.domain.QwFriendWelcome">
+        select
+            w.*
+        from qw_friend_welcome w
+        <where>
+            <if test="qwUserIds != null  and qwUserIds != ''">
+                and FIND_IN_SET(#{qwUserIds},REPLACE(REPLACE(REPLACE(qw_user_ids, '[', ''), ']', ''), ' ', '')) > 0
+            </if>
+            <if test="isSendMsg != null ">
+                and is_send_msg = #{isSendMsg}
+            </if>
+            <if test="welcomeText != null  and welcomeText != ''">
+                and welcome_text = #{welcomeText}
+            </if>
+            <if test="isDayparting != null ">
+                and is_dayparting = #{isDayparting}
+            </if>
+            <if test="companyId != null ">
+                and company_id = #{companyId}
+            </if>
+            <if test="createTime != null ">
+                and create_time = #{createTime}
+            </if>
+            <if test="corpId != null ">
+                and corp_id = #{corpId}
+            </if>
+            <if test="updateTime != null ">
+                and update_time = #{updateTime}
+            </if>
+            ${params.dataScope}
+        </where>
+    </select>
+
     <insert id="insertQwFriendWelcomeVO" parameterType="QwFriendWelcome" useGeneratedKeys="true" keyProperty="id">
         insert into qw_friend_welcome
         <trim prefix="(" suffix=")" suffixOverrides=",">
@@ -51,6 +84,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="isDayparting != null">is_dayparting,</if>
             <if test="daypartingItemlist != null">dayparting_ItemList,</if>
             <if test="companyId != null">company_id,</if>
+            <if test="companyUserId != null">user_id,</if>
+            <if test="deptId != null">dept_id,</if>
             <if test="createTime != null">create_time,</if>
             <if test="updateTime != null">update_time,</if>
             <if test="corpId != null">corp_id,</if>
@@ -63,6 +98,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="isDayparting != null">#{isDayparting},</if>
             <if test="daypartingItemlist != null">#{daypartingItemlist},</if>
             <if test="companyId != null">#{companyId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="deptId != null">#{deptId},</if>
             <if test="createTime != null">#{createTime},</if>
             <if test="updateTime != null">#{updateTime},</if>
             <if test="corpId != null">#{corpId},</if>