Jelajahi Sumber

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

caoliqin 5 hari lalu
induk
melakukan
f62916a406

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

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

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

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

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

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

+ 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

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