Browse Source

AB 主备小程序

三七 4 days ago
parent
commit
0fbbfb8b2c
21 changed files with 980 additions and 80 deletions
  1. 39 4
      fs-qw-task/src/main/java/com/fs/app/task/qwTask.java
  2. 10 0
      fs-qw-task/src/main/java/com/fs/app/taskService/QwExternalContactRatingMoreSevenDaysService.java
  3. 9 0
      fs-qw-task/src/main/java/com/fs/app/taskService/SopUserLogsInfoByIsDaysNotStudy.java
  4. 333 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/QwExternalContactRatingMoreSevenDaysServiceImpl.java
  5. 50 60
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  6. 262 0
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopUserLogsInfoByIsDaysNotStudyImpl.java
  7. 28 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  8. 4 0
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  9. 6 0
      fs-service/src/main/java/com/fs/sop/domain/SopUserLogsInfo.java
  10. 52 0
      fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsInfoMapper.java
  11. 7 0
      fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsMapper.java
  12. 1 0
      fs-service/src/main/java/com/fs/sop/params/QwRatingConfig.java
  13. 8 0
      fs-service/src/main/java/com/fs/sop/service/ISopUserLogsInfoService.java
  14. 3 0
      fs-service/src/main/java/com/fs/sop/service/ISopUserLogsService.java
  15. 84 13
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  16. 5 0
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsServiceImpl.java
  17. 1 1
      fs-service/src/main/java/com/fs/sop/vo/QwRatingVO.java
  18. 1 1
      fs-service/src/main/resources/application-config-druid-hcl.yml
  19. 24 0
      fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml
  20. 12 1
      fs-service/src/main/resources/mapper/sop/SopUserLogsInfoMapper.xml
  21. 41 0
      fs-service/src/main/resources/mapper/sop/SopUserLogsMapper.xml

+ 39 - 4
fs-qw-task/src/main/java/com/fs/app/task/qwTask.java

@@ -1,9 +1,6 @@
 package com.fs.app.task;
 package com.fs.app.task;
 
 
-import com.fs.app.taskService.QwExternalContactRatingService;
-import com.fs.app.taskService.SopLogsChatTaskService;
-import com.fs.app.taskService.SopLogsTaskService;
-import com.fs.app.taskService.SopWxLogsService;
+import com.fs.app.taskService.*;
 import com.fs.qw.service.IQwExternalErrRetryService;
 import com.fs.qw.service.IQwExternalErrRetryService;
 import com.fs.qw.service.IQwGroupMsgService;
 import com.fs.qw.service.IQwGroupMsgService;
 import com.fs.qw.service.IQwWorkUserService;
 import com.fs.qw.service.IQwWorkUserService;
@@ -75,6 +72,12 @@ public class qwTask {
     @Autowired
     @Autowired
     private IQwSopTagService qwSopTagService;
     private IQwSopTagService qwSopTagService;
 
 
+    @Autowired
+    private SopUserLogsInfoByIsDaysNotStudy logsInfoByIsDaysNotStudy;
+
+    @Autowired
+    private QwExternalContactRatingMoreSevenDaysService qwExternalContactRatingMoreSevenDaysService;
+
     /**
     /**
      * 定时任务:检查SOP规则时间
      * 定时任务:检查SOP规则时间
      * 执行时间:每天凌晨 1:10:00
      * 执行时间:每天凌晨 1:10:00
@@ -306,6 +309,22 @@ public class qwTask {
         sopUserLogsService.repairSopUserLogsTimer();
         sopUserLogsService.repairSopUserLogsTimer();
     }
     }
 
 
+
+    /**
+     * 凌晨 2点35开始,将营期小于7天中标记为 是否7天未看课的(E级) 客户的 但是看课了的恢复一下
+     */
+    @Scheduled(cron = "0 35 2 * * ?")
+    @Async
+    public void processSopUserLogsInfoByIsDaysNotStudy() {
+        long startTimeMillis = System.currentTimeMillis();
+        log.info("====== 开始选择和处理 是否7天未看课的(E级) 客户的 恢复一下 ======");
+
+        logsInfoByIsDaysNotStudy.restoreByIsDaysNotStudy();
+
+        long endTimeMillis = System.currentTimeMillis();
+        log.info("====== 用户E级恢复处理完成,耗时 {} 毫秒 ======", (endTimeMillis - startTimeMillis));
+    }
+
     /**
     /**
      * 定时任务:客户评级处理
      * 定时任务:客户评级处理
      * 执行时间:每天凌晨 3:45:00
      * 执行时间:每天凌晨 3:45:00
@@ -327,6 +346,22 @@ public class qwTask {
         log.info("====== sop营期-用户分级处理完成,耗时 {} 毫秒 ======", (endTimeMillis - startTimeMillis));
         log.info("====== sop营期-用户分级处理完成,耗时 {} 毫秒 ======", (endTimeMillis - startTimeMillis));
     }
     }
 
 
+    /**
+     * 凌晨4点35开始 客户超过7天没有看课的 标记E级
+     */
+    @Scheduled(cron = "0 30 3 * * ?")
+    @Async
+    public void processQwSopExternalContactRatingMoreSevenDaysTimer() {
+        long startTimeMillis = System.currentTimeMillis();
+        log.info("====== 开始选择和处理 sop营期-用户超7天的看课情况 ======");
+
+        qwExternalContactRatingMoreSevenDaysService.ratingMoreSevenDaysUserLogs();
+
+        long endTimeMillis = System.currentTimeMillis();
+        log.info("====== sop营期-用户超7天处理完成,耗时 {} 毫秒 ======", (endTimeMillis - startTimeMillis));
+    }
+
+
     /**
     /**
      * 更新掉所有前一天的所有待发送
      * 更新掉所有前一天的所有待发送
      */
      */

+ 10 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/QwExternalContactRatingMoreSevenDaysService.java

@@ -0,0 +1,10 @@
+package com.fs.app.taskService;
+
+import com.fs.common.core.domain.R;
+
+public interface QwExternalContactRatingMoreSevenDaysService {
+    /**
+     * Sop客户超7天评次
+     */
+    public R ratingMoreSevenDaysUserLogs();
+}

+ 9 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/SopUserLogsInfoByIsDaysNotStudy.java

@@ -0,0 +1,9 @@
+package com.fs.app.taskService;
+
+public interface SopUserLogsInfoByIsDaysNotStudy {
+
+    /**
+     * 将前7天营期中标记为 是否7天未看课的(E级) 客户的 恢复一下,突然有的恢复一下 (复刻版)
+     */
+    public void restoreByIsDaysNotStudy();
+}

+ 333 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/impl/QwExternalContactRatingMoreSevenDaysServiceImpl.java

@@ -0,0 +1,333 @@
+package com.fs.app.taskService.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.app.taskService.QwExternalContactRatingMoreSevenDaysService;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.sop.domain.SopUserLogs;
+import com.fs.sop.domain.SopUserLogsInfo;
+import com.fs.sop.mapper.SopUserLogsInfoMapper;
+import com.fs.sop.mapper.SopUserLogsMapper;
+import com.fs.sop.params.QwRatingConfig;
+import com.fs.sop.service.IQwSopTempDayService;
+import com.fs.sop.service.ISopUserLogsInfoService;
+import com.fs.sop.vo.QwRatingVO;
+import com.fs.system.service.ISysConfigService;
+import com.fs.voice.utils.StringUtil;
+import com.google.common.util.concurrent.AtomicDouble;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.*;
+import java.util.stream.Collectors;
+
+@Service
+@Slf4j
+public class QwExternalContactRatingMoreSevenDaysServiceImpl implements QwExternalContactRatingMoreSevenDaysService {
+
+
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @Autowired
+    private SopUserLogsMapper sopUserLogsMapper;
+
+    @Autowired
+    private IQwSopTempDayService qwSopTempDayService;
+
+    @Autowired
+    private SopUserLogsInfoMapper sopUserLogsInfoMapper;
+
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+
+    @Autowired
+    private ISopUserLogsInfoService iSopUserLogsInfoService;
+
+    @Autowired
+    private ExecutorService sopRatingExecutor;  // 自定义线程池
+
+    // 任务队列
+    private final BlockingQueue<SopUserLogs> taskQueue = new LinkedBlockingQueue<>(10000);
+
+    private volatile boolean running = true;
+    //批量更新队列
+    private final List<CompletableFuture<Void>> updateFutures = Collections.synchronizedList(new ArrayList<>());
+
+    private final Object configLock = new Object();
+
+    // 启动时初始化消费者线程
+    @PostConstruct
+    public void init() {
+
+        loadCourseConfig();
+
+        int consumerCount = Runtime.getRuntime().availableProcessors(); // 消费者线程数,默认 CPU 核心数
+        for (int i = 0; i < consumerCount; i++) {
+            sopRatingExecutor.submit(this::consumeTasks); // 提交消费者任务
+        }
+
+        log.info("初始化 {} 个消费者线程", consumerCount);
+    }
+
+    private  volatile QwRatingConfig qwRatingConfig;
+
+    private void loadCourseConfig() {
+        try {
+            String json = configService.selectConfigByKey("qwRating:config");
+            QwRatingConfig config = JSON.parseObject(json, QwRatingConfig.class);
+            if (!StringUtil.strIsNullOrEmpty(json) && config != null) {
+                qwRatingConfig = config;
+                log.info("Loaded qwRating.config successfully.");
+            } else {
+                log.error("Failed to load course.config from configService.");
+            }
+        } catch (Exception e) {
+            log.error("Exception while loading qwRating.config: {}", e.getMessage(), e);
+        }
+    }
+
+
+    @Override
+    public R ratingMoreSevenDaysUserLogs() {
+        // 分页加载并放入队列
+        int pageSize = 1000;
+        int offset = 0;
+        List<SopUserLogs> sopUserLogs;
+
+        // 获取缓存的配置
+        QwRatingConfig config;
+        synchronized(configLock) {
+            config = qwRatingConfig;
+        }
+
+        do {
+            sopUserLogs = sopUserLogsMapper.meetsTheRatingByUserInfoWithPaginationStudyDays(offset, pageSize,config.getNotStudyDays());
+            if (!sopUserLogs.isEmpty()) {
+                sopUserLogs.forEach(item -> {
+                    try {
+                        taskQueue.put(item); // 将任务放入队列
+                    } catch (InterruptedException e) {
+                        log.error("任务放入队列失败,sopId: {}", item.getSopId(), e);
+                        Thread.currentThread().interrupt();
+                    }
+                });
+                offset += pageSize;
+            }
+        } while (!sopUserLogs.isEmpty());
+
+
+        // 等待队列处理完成
+        CompletableFuture.runAsync(() -> {
+            while (!taskQueue.isEmpty()) {
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    log.error("等待队列处理时中断", e);
+                    Thread.currentThread().interrupt();
+                }
+            }
+        }).join(); // 等待任务完成
+
+        return R.ok();
+    }
+
+
+    private void consumeTasks() {
+
+        if (!running && taskQueue.isEmpty()) {
+            log.info("没有评级任务需要处理");
+            return; // 如果队列为空且没有正在运行的线程,则直接返回
+        }
+
+        while (running) {
+            try {
+                SopUserLogs item = taskQueue.poll(1, TimeUnit.SECONDS); // 等待 1 秒
+                if (item != null) {
+                    processSingleTask(item);
+                }
+            } catch (Exception e) {
+                log.error("消费者线程异常", e);
+            }
+        }
+    }
+
+    private void processSingleTask(SopUserLogs item) {
+
+        // 获取缓存的配置
+        QwRatingConfig config;
+        synchronized(configLock) {
+            config = qwRatingConfig;
+        }
+
+        List<SopUserLogsInfo> sopUserLogsInfosList = sopUserLogsInfoMapper
+                .selectSopUserLogsInfoListBySopId(item.getSopId(), item.getId());
+
+        if (sopUserLogsInfosList == null || sopUserLogsInfosList.isEmpty()) {
+            log.error("当前营期没有客户-sopId:{},营期id:{}", item.getSopId(), item.getId());
+            return;
+        }
+
+        List<QwExternalContact> batchQwExternalContact = sopUserLogsInfosList.stream()
+                .map(logsInfo -> processUserLog(logsInfo, config))
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        if (!batchQwExternalContact.isEmpty()) {
+            batchUpdateQwExternalContact(batchQwExternalContact);
+        }
+    }
+
+    private QwExternalContact processUserLog(SopUserLogsInfo logsInfo, QwRatingConfig config) {
+        try {
+            Long externalId = logsInfo.getExternalId();
+            if (externalId == null) {
+                return null;
+            }
+
+            List<QwRatingVO> ratingVOS = fsCourseWatchLogMapper
+                    .selectFsCourseWatchLogByExtIdRatingMoreStudyDays(externalId, config.getNotStudyDays());
+
+            if (ratingVOS == null || ratingVOS.isEmpty() || ratingVOS.size() < 6) {
+                log.info("没有记录或不满足条件不评级或看课记录小于6 不评级,externalId: {}", externalId);
+                return null;
+            }
+
+
+            //判断 7天的时长是否大于0
+            boolean scoreMoreStudyLevel = getScoreMoreStudyLevel(ratingVOS);
+
+            if (!scoreMoreStudyLevel) {
+                QwExternalContact externalContact = new QwExternalContact();
+                externalContact.setId(externalId);
+                externalContact.setLevel(5);
+                externalContact.setIsDaysNotStudy(1);
+                return externalContact;
+            }else {
+                QwExternalContact externalContact = new QwExternalContact();
+                externalContact.setId(externalId);
+                externalContact.setLevel(ratingVOS.get(0).getLevel());
+                externalContact.setIsDaysNotStudy(0);
+                return externalContact;
+            }
+
+
+        } catch (Exception e) {
+            log.error("计算用户积分异常,用户:{}", logsInfo, e);
+            return null;
+        }
+    }
+
+    private void batchUpdateQwExternalContact(List<QwExternalContact> notInExternalUseridList) {
+        int batchSize = 300;
+
+        for (int i = 0; i < notInExternalUseridList.size(); i += batchSize) {
+            int endIndex = Math.min(i + batchSize, notInExternalUseridList.size());
+            List<QwExternalContact> batchList = notInExternalUseridList.subList(i, endIndex);
+
+            int finalI = i;
+            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+                try {
+                    qwExternalContactMapper.batchUpdateQwExternalContactByMoreStudy(batchList);
+                    iSopUserLogsInfoService.batchUpdateSopUserLogsInfoByMoreStudy(batchList);
+                    log.info("成功更新看课7天数据,起始索引: {}, 数量: {}", finalI, batchList.size());
+                } catch (Exception e) {
+                    log.error("批量更新异常,批次起始索引: {}", finalI, e);
+                }
+
+            }, sopRatingExecutor);
+
+            updateFutures.add(future);
+        }
+    }
+
+    @PreDestroy
+    public void shutdown() {
+        running = false;  // 标记消费者停止
+        log.info("正在关闭线程池...");
+
+        // **等待任务队列处理完毕**
+        while (!taskQueue.isEmpty()) {
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                log.warn("等待任务队列处理完成时被中断", e);
+            }
+        }
+
+        // **确保所有 `batchUpdateQwExternalContact` 的任务完成**
+        log.info("等待所有批量更新任务完成...");
+        CompletableFuture.allOf(updateFutures.toArray(new CompletableFuture[0])).join();
+
+        // 关闭线程池
+        sopRatingExecutor.shutdown();
+        try {
+            if (!sopRatingExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
+                List<Runnable> pendingTasks = sopRatingExecutor.shutdownNow();
+                log.warn("强制关闭线程池,未完成任务数: {}", pendingTasks.size());
+            }
+        } catch (InterruptedException e) {
+            sopRatingExecutor.shutdownNow();
+            Thread.currentThread().interrupt();
+        }
+        log.info("线程池和消费者已完全关闭");
+    }
+
+
+    /**
+     * 每6小时更新一次
+     */
+    @Scheduled(cron = "0 50 0/6 * * ?")
+    public void refreshRatingConfig() {
+
+        synchronized(configLock) {
+            try {
+                String json = configService.selectConfigByKey("qwRating:config");
+                QwRatingConfig config = JSON.parseObject(json, QwRatingConfig.class);
+                if (!StringUtil.strIsNullOrEmpty(json) && config != null) {
+                    qwRatingConfig = config;
+                    log.info("LoadedTime qwRating.config successfully.");
+                } else {
+                    log.error("Failed to load course.config from configService.");
+                }
+            } catch (Exception e) {
+                log.error("Exception while refreshing course.config: {}", e.getMessage(), e);
+            }
+        }
+
+    }
+
+
+    //查 E级
+    public boolean getScoreMoreStudyLevel(List<QwRatingVO> qwRatingVOS) {
+
+        AtomicDouble watchCount= new AtomicDouble();
+
+        qwRatingVOS.forEach(vo -> {
+            watchCount.addAndGet(vo.getWatchDuration());
+        });
+
+        // 判断总 watchDuration 是否 > 0
+        return watchCount.get() > 0;
+    }
+
+}

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

@@ -8,7 +8,9 @@ import com.fs.common.core.domain.R;
 import com.fs.common.exception.base.BaseException;
 import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.StringUtils;
+import com.fs.company.domain.CompanyMiniapp;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyMiniappService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.*;
 import com.fs.course.domain.*;
@@ -150,7 +152,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     private IQwGroupChatService qwGroupChatService;
     private IQwGroupChatService qwGroupChatService;
     @Autowired
     @Autowired
     private IQwGroupChatUserService qwGroupChatUserService;
     private IQwGroupChatUserService qwGroupChatUserService;
-
+    @Autowired
+    private ICompanyMiniappService companyMiniappService;
     // Shutdown flags
     // Shutdown flags
     private volatile boolean running = true;
     private volatile boolean running = true;
     @Autowired
     @Autowired
@@ -304,44 +307,10 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         Map<String, List<SopUserLogsVo>> sopLogsGroupedById = sopUserLogsVos.stream()
         Map<String, List<SopUserLogsVo>> sopLogsGroupedById = sopUserLogsVos.stream()
                 .collect(Collectors.groupingBy(SopUserLogsVo::getSopId));
                 .collect(Collectors.groupingBy(SopUserLogsVo::getSopId));
 
 
+        // 查询公司关联小程序数据
+        List<CompanyMiniapp> miniList = companyMiniappService.list(new QueryWrapper<CompanyMiniapp>().orderByAsc("sort_num"));
 
 
-        // 查询销售二级域名
-//        Set<Long> ids = sopUserLogsVos.stream().map(s -> {
-//            String[] userKey = s.getUserId().split("\\|");
-//            if (userKey.length < 3) {
-//                return null;
-//            }
-//            return Long.parseLong(userKey[1]);
-//        }).filter(Objects::nonNull).collect(Collectors.toSet());
-//
-//        List<CompanyUser> companyUserList;
-//        if (ids.isEmpty()) {
-//            companyUserList = new ArrayList<>();
-//        } else {
-//            companyUserList = companyUserService.selectCompanyUserByIds(ids);
-//        }
-//
-//        Map<String, List<SopUserLogsVo>> sopLogsGroupedById = sopUserLogsVos.stream()
-//                .peek(s -> {
-//                    String[] userKey = s.getUserId().split("\\|");
-//                    if (userKey.length < 3) {
-//                        return;
-//                    }
-//
-//                    // 销售ID
-//                    Long companyUserId = Long.parseLong(userKey[1]);
-//                    CompanyUser companyUser = companyUserList.stream().filter(cu -> Objects.equals(cu.getUserId(), companyUserId)).findFirst().orElse(null);
-//                    if (Objects.nonNull(companyUser)) {
-//                        if (!StringUtil.strIsNullOrEmpty(companyUser.getDomain())) {
-//                            s.setDomain(companyUser.getDomain().trim());
-//                        } else {
-//                            s.setDomain(config.getRealLinkDomainName().trim());
-//                        }
-//                    } else {
-//                        s.setDomain(config.getRealLinkDomainName().trim());
-//                    }
-//                })
-//                .collect(Collectors.groupingBy(SopUserLogsVo::getSopId));
+        Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap = miniList.stream().collect(Collectors.groupingBy(CompanyMiniapp::getCompanyId, Collectors.groupingBy(CompanyMiniapp::getType)));
 
 
         log.info("共分组 {} 个 SOP ID 进行处理。", sopLogsGroupedById.size());
         log.info("共分组 {} 个 SOP ID 进行处理。", sopLogsGroupedById.size());
 
 
@@ -350,7 +319,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
         for (Map.Entry<String, List<SopUserLogsVo>> entry : sopLogsGroupedById.entrySet()) {
         for (Map.Entry<String, List<SopUserLogsVo>> entry : sopLogsGroupedById.entrySet()) {
             String sopId = entry.getKey();
             String sopId = entry.getKey();
             List<SopUserLogsVo> userLogsVos = entry.getValue();
             List<SopUserLogsVo> userLogsVos = entry.getValue();
-            processSopGroupAsync(sopId, userLogsVos, sopGroupLatch,currentTime, groupChatMap,config);
+            processSopGroupAsync(sopId, userLogsVos, sopGroupLatch,currentTime, groupChatMap,config,miniMap);
         }
         }
 
 
         // 等待所有 SOP 分组处理完成
         // 等待所有 SOP 分组处理完成
@@ -371,9 +340,9 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             backoff = @Backoff(delay = 2000)
             backoff = @Backoff(delay = 2000)
     )
     )
     public void processSopGroupAsync(String sopId, List<SopUserLogsVo> userLogsVos, CountDownLatch latch ,LocalDateTime currentTime,
     public void processSopGroupAsync(String sopId, List<SopUserLogsVo> userLogsVos, CountDownLatch latch ,LocalDateTime currentTime,
-                                     Map<String, QwGroupChat> groupChatMap,CourseConfig config) {
+                                     Map<String, QwGroupChat> groupChatMap,CourseConfig config,Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap) {
         try {
         try {
-            processSopGroup(sopId, userLogsVos,currentTime, groupChatMap, config);
+            processSopGroup(sopId, userLogsVos,currentTime, groupChatMap, config,miniMap);
         } catch (Exception e) {
         } catch (Exception e) {
             log.error("处理 SOP ID {} 时发生异常: {}", sopId, e.getMessage(), e);
             log.error("处理 SOP ID {} 时发生异常: {}", sopId, e.getMessage(), e);
         } finally {
         } finally {
@@ -383,7 +352,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
 
 
 
     private void processSopGroup(String sopId, List<SopUserLogsVo> userLogsVos,LocalDateTime currentTime, Map<String,
     private void processSopGroup(String sopId, List<SopUserLogsVo> userLogsVos,LocalDateTime currentTime, Map<String,
-            QwGroupChat> groupChatMap,CourseConfig config) throws Exception {
+            QwGroupChat> groupChatMap,CourseConfig config,Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap) throws Exception {
         QwSopRuleTimeVO ruleTimeVO = sopMapper.selectQwSopByClickHouseId(sopId);
         QwSopRuleTimeVO ruleTimeVO = sopMapper.selectQwSopByClickHouseId(sopId);
 
 
         if (ruleTimeVO == null) {
         if (ruleTimeVO == null) {
@@ -425,7 +394,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
 
         CountDownLatch userLogsLatch = new CountDownLatch(userLogsVos.size());
         CountDownLatch userLogsLatch = new CountDownLatch(userLogsVos.size());
         for (SopUserLogsVo logVo : userLogsVos) {
         for (SopUserLogsVo logVo : userLogsVos) {
-            processUserLogAsync(logVo, ruleTimeVO, rulesList, userLogsLatch, currentTime, groupChatMap,qwCompany.getMiniAppId(), config);
+            processUserLogAsync(logVo, ruleTimeVO, rulesList, userLogsLatch, currentTime, groupChatMap,qwCompany.getMiniAppId(), config,miniMap);
         }
         }
 
 
         // 等待所有用户日志处理完成
         // 等待所有用户日志处理完成
@@ -446,9 +415,9 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     )
     )
     public void processUserLogAsync(SopUserLogsVo logVo, QwSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings,
     public void processUserLogAsync(SopUserLogsVo logVo, QwSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings,
                                     CountDownLatch latch, LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap,
                                     CountDownLatch latch, LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap,
-                                    String miniAppId,CourseConfig config) {
+                                    String miniAppId,CourseConfig config,Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap) {
         try {
         try {
-            processUserLog(logVo, ruleTimeVO, tempSettings,currentTime, groupChatMap, miniAppId, config);
+            processUserLog(logVo, ruleTimeVO, tempSettings,currentTime, groupChatMap, miniAppId, config,miniMap);
         } catch (Exception e) {
         } catch (Exception e) {
             log.error("处理用户日志 {} 时发生异常: {}", logVo.getId(), e.getMessage(), e);
             log.error("处理用户日志 {} 时发生异常: {}", logVo.getId(), e.getMessage(), e);
         } finally {
         } finally {
@@ -458,7 +427,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
 
 
 
     private void processUserLog(SopUserLogsVo logVo, QwSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings,
     private void processUserLog(SopUserLogsVo logVo, QwSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings,
-                                LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap,String miniAppId,CourseConfig config) {
+                                LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap,String miniAppId,
+                                CourseConfig config,Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap) {
         try {
         try {
 
 
             LocalDate startDate = LocalDate.parse(logVo.getStartTime(), DATE_FORMATTER);
             LocalDate startDate = LocalDate.parse(logVo.getStartTime(), DATE_FORMATTER);
@@ -510,6 +480,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             String qwUserId = String.valueOf(qwUserByRedis.getId()).trim();
             String qwUserId = String.valueOf(qwUserByRedis.getId()).trim();
             String companyUserId = String.valueOf(qwUserByRedis.getCompanyUserId()).trim();
             String companyUserId = String.valueOf(qwUserByRedis.getCompanyUserId()).trim();
             String companyId = String.valueOf(qwUserByRedis.getCompanyId()).trim();
             String companyId = String.valueOf(qwUserByRedis.getCompanyId()).trim();
+            Integer sendMsgType = qwUserByRedis.getSendMsgType();
 
 
             if (StringUtil.strIsNullOrEmpty(companyUserId) || StringUtil.strIsNullOrEmpty(companyId) || "null".equals(companyUserId)) {
             if (StringUtil.strIsNullOrEmpty(companyUserId) || StringUtil.strIsNullOrEmpty(companyId) || "null".equals(companyUserId)) {
                 log.error("员工未绑定销售账号或公司,跳过处理:"+qwUserId);
                 log.error("员工未绑定销售账号或公司,跳过处理:"+qwUserId);
@@ -635,7 +606,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
 
                         insertSopUserLogs(sopUserLogsInfos, logVo, sendTime, ruleTimeVO, content, qwUserId,
                         insertSopUserLogs(sopUserLogsInfos, logVo, sendTime, ruleTimeVO, content, qwUserId,
                                 companyUserId, companyId, qwUserByRedis.getWelcomeText(),qwUserByRedis.getQwUserName(),
                                 companyUserId, companyId, qwUserByRedis.getWelcomeText(),qwUserByRedis.getQwUserName(),
-                                groupChatMap, miniAppId,config);
+                                groupChatMap, miniAppId,config,miniMap, sendMsgType);
 
 
                     }
                     }
                 } catch (Exception e) {
                 } catch (Exception e) {
@@ -678,7 +649,8 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     private void insertSopUserLogs(List<SopUserLogsInfo> sopUserLogsInfos, SopUserLogsVo logVo, Date sendTime,
     private void insertSopUserLogs(List<SopUserLogsInfo> sopUserLogsInfos, SopUserLogsVo logVo, Date sendTime,
                                    QwSopRuleTimeVO ruleTimeVO, QwSopTempSetting.Content content,
                                    QwSopRuleTimeVO ruleTimeVO, QwSopTempSetting.Content content,
                                    String qwUserId,String companyUserId,String companyId,String welcomeText,String qwUserName,
                                    String qwUserId,String companyUserId,String companyId,String welcomeText,String qwUserName,
-                                   Map<String, QwGroupChat> groupChatMap,String miniAppId,CourseConfig config) {
+                                   Map<String, QwGroupChat> groupChatMap,String miniAppId,CourseConfig config,
+                                   Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap, Integer sendMsgType) {
         String formattedSendTime = sendTime.toInstant()
         String formattedSendTime = sendTime.toInstant()
                 .atZone(ZoneId.systemDefault())
                 .atZone(ZoneId.systemDefault())
                 .format(DATE_TIME_FORMATTER);
                 .format(DATE_TIME_FORMATTER);
@@ -705,7 +677,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                 QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, groupChat.getChatId(), groupChat.getName(), null, isOfficial, null);
                 QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, groupChat.getChatId(), groupChat.getName(), null, isOfficial, null);
                 handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                 handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                         type, qwUserId, companyUserId, companyId, groupChat.getChatId(), welcomeText, qwUserName,
                         type, qwUserId, companyUserId, companyId, groupChat.getChatId(), welcomeText, qwUserName,
-                        null, true, miniAppId, groupChat,config);
+                        null, true, miniAppId, groupChat,config, miniMap, null, sendMsgType);
             } else {
             } else {
                 if(groupChat.getChatUserList() != null && !groupChat.getChatUserList().isEmpty()){
                 if(groupChat.getChatUserList() != null && !groupChat.getChatUserList().isEmpty()){
                     groupChat.getChatUserList().forEach(user -> {
                     groupChat.getChatUserList().forEach(user -> {
@@ -714,7 +686,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                         QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, user.getUserId(), user.getName(), null, isOfficial, null);
                         QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, user.getUserId(), user.getName(), null, isOfficial, null);
                         handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                         handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                                 type, qwUserId, companyUserId, companyId, user.getId().toString(), welcomeText, qwUserName,
                                 type, qwUserId, companyUserId, companyId, user.getId().toString(), welcomeText, qwUserName,
-                                null, false, miniAppId, groupChat,config);
+                                null, false, miniAppId, groupChat,config, miniMap, null, sendMsgType);
                     });
                     });
                 }
                 }
             }
             }
@@ -725,9 +697,11 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     String externalId = contactId.getExternalId().toString();
                     String externalId = contactId.getExternalId().toString();
                     String externalUserName = contactId.getExternalUserName();
                     String externalUserName = contactId.getExternalUserName();
                     Long fsUserId = contactId.getFsUserId();
                     Long fsUserId = contactId.getFsUserId();
+                    Integer grade = contactId.getGrade();
                     QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, contactId.getExternalContactId(), externalUserName, fsUserId, isOfficial, contactId.getExternalId());
                     QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, contactId.getExternalContactId(), externalUserName, fsUserId, isOfficial, contactId.getExternalId());
                     handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                     handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
-                            type, qwUserId, companyUserId, companyId, externalId, welcomeText, qwUserName, fsUserId, false, miniAppId, null,config);
+                            type, qwUserId, companyUserId, companyId, externalId, welcomeText, qwUserName, fsUserId, false, miniAppId,
+                            null,config, miniMap, grade, sendMsgType);
                 } catch (Exception e) {
                 } catch (Exception e) {
                     log.error("处理 externalContactId {} 时发生异常: {}", contactId, e.getMessage(), e);
                     log.error("处理 externalContactId {} 时发生异常: {}", contactId, e.getMessage(), e);
                 }
                 }
@@ -831,11 +805,12 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     }
     }
 
 
     private void handleLogBasedOnType(QwSopLogs sopLogs, QwSopTempSetting.Content content,
     private void handleLogBasedOnType(QwSopLogs sopLogs, QwSopTempSetting.Content content,
-                                      SopUserLogsVo logVo, Date sendTime, Long courseId,
-                                      Long videoId, int type, String qwUserId,
+                                      SopUserLogsVo logVo, Date sendTime, Long courseId, Long videoId, int type, String qwUserId,
                                       String companyUserId, String companyId, String externalId, String welcomeText,
                                       String companyUserId, String companyId, String externalId, String welcomeText,
                                       String qwUserName, Long fsUserId, boolean isGroupChat, String miniAppId,
                                       String qwUserName, Long fsUserId, boolean isGroupChat, String miniAppId,
-                                      QwGroupChat groupChat,CourseConfig config) {
+                                      QwGroupChat groupChat,CourseConfig config,
+                                      Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
+                                      Integer grade, Integer sendMsgType  ) {
         switch (type) {
         switch (type) {
             case 1:
             case 1:
                 handleNormalMessage(sopLogs, content,companyUserId);
                 handleNormalMessage(sopLogs, content,companyUserId);
@@ -843,7 +818,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
             case 2:
             case 2:
                 handleCourseMessage(sopLogs, content, logVo, sendTime, courseId, videoId,
                 handleCourseMessage(sopLogs, content, logVo, sendTime, courseId, videoId,
                         qwUserId, companyUserId, companyId, externalId, welcomeText,qwUserName, fsUserId,
                         qwUserId, companyUserId, companyId, externalId, welcomeText,qwUserName, fsUserId,
-                        isGroupChat, miniAppId, groupChat,config);
+                        isGroupChat, miniAppId, groupChat,config,miniMap, grade, sendMsgType);
                 break;
                 break;
             case 3:
             case 3:
                 handleOrderMessage(sopLogs, content);
                 handleOrderMessage(sopLogs, content);
@@ -873,10 +848,10 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     }
     }
 
 
     private void handleCourseMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content,
     private void handleCourseMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content,
-                                     SopUserLogsVo logVo, Date sendTime, Long courseId,
-                                     Long videoId, String qwUserId, String companyUserId,
+                                     SopUserLogsVo logVo, Date sendTime, Long courseId, Long videoId, String qwUserId, String companyUserId,
                                      String companyId, String externalId, String welcomeText, String qwUserName,
                                      String companyId, String externalId, String welcomeText, String qwUserName,
-                                     Long fsUserId, boolean isGroupChat, String miniAppId, QwGroupChat groupChat,CourseConfig config) {
+                                     Long fsUserId, boolean isGroupChat, String miniAppId, QwGroupChat groupChat,CourseConfig config,Map<Long,
+                                     Map<Integer, List<CompanyMiniapp>>> miniMap,Integer grade, Integer sendMsgType) {
         // 深拷贝 Content 对象,避免使用 JSON
         // 深拷贝 Content 对象,避免使用 JSON
         QwSopTempSetting.Content clonedContent = deepCopyContent(content);
         QwSopTempSetting.Content clonedContent = deepCopyContent(content);
         if (clonedContent == null) {
         if (clonedContent == null) {
@@ -968,10 +943,25 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                     String sortLink = createLinkByMiniApp(setting, logVo, sendTime, courseId, videoId,
                     String sortLink = createLinkByMiniApp(setting, logVo, sendTime, courseId, videoId,
                             qwUserId, companyUserId, companyId, externalId,isOfficial,sopLogs.getFsUserId());
                             qwUserId, companyUserId, companyId, externalId,isOfficial,sopLogs.getFsUserId());
 
 
-                    if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
+                    if (!miniMap.isEmpty() && sendMsgType==1) {
+                        Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
+                        if (integerListMap != null) {
+
+                            int effectiveGrade = (grade == null) ? 5 : grade;
+                            int listIndex = (effectiveGrade == 1 || effectiveGrade == 2) ? 0 : 1;
+                            List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);
+
+                            if (miniapps != null && !miniapps.isEmpty()) {
+                                CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                    setting.setMiniprogramAppid(companyMiniapp.getAppId());
+                                }
+                            }
+                        }
+                    }else if (!StringUtil.strIsNullOrEmpty(miniAppId)){
                         setting.setMiniprogramAppid(miniAppId);
                         setting.setMiniprogramAppid(miniAppId);
                     }else {
                     }else {
-                        log.error("公司的小程序id为空:采用了前端传的固定值"+sopLogs.getSopId());
+                        log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
                     }
                     }
 
 
                     setting.setMiniprogramPage(sortLink.replaceAll("^[\\s\\u2005]+", ""));
                     setting.setMiniprogramPage(sortLink.replaceAll("^[\\s\\u2005]+", ""));

+ 262 - 0
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopUserLogsInfoByIsDaysNotStudyImpl.java

@@ -0,0 +1,262 @@
+package com.fs.app.taskService.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.app.taskService.SopUserLogsInfoByIsDaysNotStudy;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.fs.sop.domain.SopUserLogs;
+import com.fs.sop.domain.SopUserLogsInfo;
+import com.fs.sop.params.QwRatingConfig;
+import com.fs.sop.service.ISopUserLogsInfoService;
+import com.fs.sop.service.ISopUserLogsService;
+import com.fs.system.service.ISysConfigService;
+import com.fs.voice.utils.StringUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.*;
+import java.util.stream.Collectors;
+
+@Service
+@Slf4j
+public class SopUserLogsInfoByIsDaysNotStudyImpl implements SopUserLogsInfoByIsDaysNotStudy {
+
+
+    @Autowired
+    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+
+    @Autowired
+    private ISopUserLogsInfoService iSopUserLogsInfoService;
+
+    @Autowired
+    private ISopUserLogsService iSopUserLogsService;
+
+    @Autowired
+    private ExecutorService sopRatingExecutor;  // 自定义线程池
+
+    // 任务队列
+    private final BlockingQueue<SopUserLogs> taskQueue = new LinkedBlockingQueue<>(10000);
+
+    private volatile boolean running = true;
+    //批量更新队列
+    private final List<CompletableFuture<Void>> updateFutures = Collections.synchronizedList(new ArrayList<>());
+
+    private final Object configLock = new Object();
+
+
+    private  volatile QwRatingConfig qwRatingConfig;
+
+    // 启动时初始化消费者线程
+    @PostConstruct
+    public void init() {
+
+        loadCourseConfig();
+
+        int consumerCount = Runtime.getRuntime().availableProcessors(); // 消费者线程数,默认 CPU 核心数
+        for (int i = 0; i < consumerCount; i++) {
+            sopRatingExecutor.submit(this::consumeTasks); // 提交消费者任务
+        }
+
+    }
+
+    private void loadCourseConfig() {
+        try {
+            String json = configService.selectConfigByKey("qwRating:config");
+            QwRatingConfig config = JSON.parseObject(json, QwRatingConfig.class);
+            if (!StringUtil.strIsNullOrEmpty(json) && config != null) {
+                qwRatingConfig = config;
+                log.info("Loaded qwRating.config successfully.");
+            } else {
+                log.error("Failed to load course.config from configService.");
+            }
+        } catch (Exception e) {
+            log.error("Exception while loading qwRating.config: {}", e.getMessage(), e);
+        }
+    }
+
+
+
+    @Override
+    public void restoreByIsDaysNotStudy() {
+
+        // 分页加载并放入队列
+        int pageSize = 1000;
+        int offset = 0;
+        List<SopUserLogs> sopUserLogs;
+
+        // 获取缓存的配置
+        QwRatingConfig config;
+        synchronized(configLock) {
+            config = qwRatingConfig;
+        }
+
+        do {
+            sopUserLogs = iSopUserLogsService.meetsTherestoreByIsDaysNotStudy(offset, pageSize,config.getNotStudyDays());
+            if (!sopUserLogs.isEmpty()) {
+                sopUserLogs.forEach(item -> {
+                    try {
+                        taskQueue.put(item); // 将任务放入队列
+                    } catch (InterruptedException e) {
+                        log.error("任务放入队列失败,sopId: {}", item.getSopId(), e);
+                        Thread.currentThread().interrupt();
+                    }
+                });
+                offset += pageSize;
+            }
+        } while (!sopUserLogs.isEmpty());
+
+
+        // 等待队列处理完成
+        CompletableFuture.runAsync(() -> {
+            while (!taskQueue.isEmpty()) {
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    log.error("等待队列处理时中断", e);
+                    Thread.currentThread().interrupt();
+                }
+            }
+        }).join(); // 等待任务完成
+
+    }
+
+    private void consumeTasks() {
+        if (!running && taskQueue.isEmpty()) {
+            log.info("没有评级任务需要处理");
+            return; // 如果队列为空且没有正在运行的线程,则直接返回
+        }
+
+        while (running) {
+            try {
+                SopUserLogs item = taskQueue.poll(1, TimeUnit.SECONDS); // 等待 1 秒
+                if (item != null) {
+                    processRestoreByIsDaysNotStudy(item);
+                }
+            } catch (Exception e) {
+                log.error("消费者线程异常", e);
+            }
+        }
+    }
+
+    private void processRestoreByIsDaysNotStudy(SopUserLogs item) {
+
+        // 获取缓存的配置
+        QwRatingConfig config;
+        synchronized(configLock) {
+            config = qwRatingConfig;
+        }
+
+        List<SopUserLogsInfo> infos = iSopUserLogsInfoService.selectRestoreByIsDaysNotStudy(
+                item.getSopId(), item.getId());
+
+        if (infos == null || infos.isEmpty()) {
+            log.error("当前营期没有E级客户-sopId:{},营期id:{}", item.getSopId(), item.getId());
+            return;
+        }
+
+        List<QwExternalContact> contacts = infos.stream()
+                .map(info -> processUserLog(info, config))
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        if (!contacts.isEmpty()) {
+            batchUpdateQwExternalContact(contacts);
+        }
+    }
+
+    private void batchUpdateQwExternalContact(List<QwExternalContact> contacts) {
+        // 9. 优化分批逻辑
+        int total = contacts.size();
+        for (int i = 0; i < total; i += 300) {
+            List<QwExternalContact> batch = contacts.subList(i, Math.min(i + 300, total));
+
+            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+                try {
+                    qwExternalContactMapper.batchUpdateQwExternalByIsDaysNotStudy(batch);
+                    iSopUserLogsInfoService.batchUpdateSopUserLogsInfoByIsDaysNotStudy(batch);
+                } catch (Exception e) {
+                    log.error("批量更新异常, 批次大小: {}", batch.size(), e);
+                }
+            }, sopRatingExecutor);
+
+            updateFutures.add(future);
+        }
+    }
+
+    @PreDestroy
+    public void shutdown() {
+        running = false;  // 标记消费者停止
+        log.info("正在关闭线程池...");
+
+        // **等待任务队列处理完毕**
+        while (!taskQueue.isEmpty()) {
+            try {
+                Thread.sleep(500);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                log.warn("等待任务队列处理完成时被中断", e);
+            }
+        }
+
+        // **确保所有  的任务完成**
+        log.info("等待所有批量更新任务完成...");
+        CompletableFuture.allOf(updateFutures.toArray(new CompletableFuture[0])).join();
+
+        // 关闭线程池
+        sopRatingExecutor.shutdown();
+        try {
+            if (!sopRatingExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
+                List<Runnable> pendingTasks = sopRatingExecutor.shutdownNow();
+                log.warn("强制关闭线程池,未完成任务数: {}", pendingTasks.size());
+            }
+        } catch (InterruptedException e) {
+            sopRatingExecutor.shutdownNow();
+            Thread.currentThread().interrupt();
+        }
+        log.info("线程池和消费者已完全关闭");
+    }
+
+    /**
+    * 只计算时长
+    */
+    private QwExternalContact processUserLog(SopUserLogsInfo logsInfo, QwRatingConfig config) {
+        try {
+
+            Long externalId = logsInfo.getExternalId();
+            if (externalId == null) {
+                return null;
+            }
+
+            Integer sumDuration = fsCourseWatchLogMapper.selectFsCourseWatchLogByByIsDaysNotStudy(externalId, config.getNotStudyDays());
+
+            if (sumDuration!=null && sumDuration>0) {
+                QwExternalContact externalContact = new QwExternalContact();
+                externalContact.setId(externalId);
+                externalContact.setIsDaysNotStudy(0);
+                return externalContact;
+            }
+
+            return null;
+
+        } catch (Exception e) {
+            log.error("计算用户积分异常,用户:{}", logsInfo, e);
+            return null;
+        }
+    }
+
+
+}

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

@@ -165,6 +165,34 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
             "\tAND wl.create_time < CURDATE() ")
             "\tAND wl.create_time < CURDATE() ")
     List<QwRatingVO> selectFsCourseWatchLogByExtIdRating(@Param("externalId") Long externalId, @Param("dayNum") Integer dayNum);
     List<QwRatingVO> selectFsCourseWatchLogByExtIdRating(@Param("externalId") Long externalId, @Param("dayNum") Integer dayNum);
 
 
+    @Select("SELECT\n" +
+            "\twl.duration AS watchDuration,\n" +
+            "\tcv.duration AS allDuration,\n" +
+            "\twl.finish_time,\n" +
+            "\twl.create_time," +
+            "\tec.`level` \n" +
+            "FROM\n" +
+            "\tfs_course_watch_log wl\n" +
+            "\tLEFT JOIN qw_external_contact ec ON wl.qw_external_contact_id = ec.id\n" +
+            "\tLEFT JOIN fs_user_course_video cv ON wl.video_id = cv.video_id \n" +
+            "WHERE\n" +
+            "\twl.send_type = 2 \n" +
+            "\tAND wl.qw_external_contact_id = #{externalId} \n" +
+            "\tAND wl.create_time >= DATE_SUB(CURDATE(), INTERVAL #{dayNum} DAY ) \n" +
+            "\tAND wl.create_time < CURDATE()")
+    List<QwRatingVO> selectFsCourseWatchLogByExtIdRatingMoreStudyDays(@Param("externalId") Long externalId, @Param("dayNum") Integer dayNum);
+
+    @Select("SELECT\n" +
+            "\tCOALESCE(SUM(wl.duration), 0) AS watchDuration\n" +
+            "FROM\n" +
+            "\tfs_course_watch_log wl\n" +
+            "WHERE\n" +
+            "\twl.send_type = 2 \n" +
+            "\tAND wl.qw_external_contact_id = #{externalId} \n" +
+            "\tAND wl.create_time >= DATE_SUB( CURDATE(), INTERVAL #{dayNum} DAY ) \n" +
+            "\tAND wl.create_time < CURDATE()")
+    Integer selectFsCourseWatchLogByByIsDaysNotStudy(@Param("externalId") Long externalId,@Param("dayNum") Integer dayNum);
+
     @Select("select l.*,v.title,c.course_name 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 c ON c.course_id = l.course_id WHERE l.qw_external_contact_id =#{ExtId} and l.qw_user_id=#{QwUserId} and DATE(l.create_time) =CURDATE() ORDER BY l.create_time  desc LIMIT 1  ")
     @Select("select l.*,v.title,c.course_name 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 c ON c.course_id = l.course_id WHERE l.qw_external_contact_id =#{ExtId} and l.qw_user_id=#{QwUserId} and DATE(l.create_time) =CURDATE() ORDER BY l.create_time  desc LIMIT 1  ")
     FsCourseWatchLogVO selectFsCourseWatchLogByExtIdAndQwUserId(@Param("ExtId")String ExtId,@Param("QwUserId")Long QwUserId);
     FsCourseWatchLogVO selectFsCourseWatchLogByExtIdAndQwUserId(@Param("ExtId")String ExtId,@Param("QwUserId")Long QwUserId);
 
 

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

@@ -78,6 +78,10 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
                                                   @Param("corpId") String corpId);
                                                   @Param("corpId") String corpId);
 
 
 
 
+    public int batchUpdateQwExternalContactByMoreStudy(List<QwExternalContact> qwExternalContact);
+    public int batchUpdateQwExternalByIsDaysNotStudy(List<QwExternalContact> qwExternalContact);
+
+
     @Select("SELECT * FROM qw_external_contact WHERE external_user_id = #{externalUserId}  AND corp_id=#{corpId} and user_id=#{qwUserId} limit 1")
     @Select("SELECT * FROM qw_external_contact WHERE external_user_id = #{externalUserId}  AND corp_id=#{corpId} and user_id=#{qwUserId} limit 1")
     public QwExternalContact selectQwExternalContactByExternalUserIdAndQwUserId(@Param("externalUserId") String externalUserId,@Param("corpId") String corpId,@Param("qwUserId") String qwUserId);
     public QwExternalContact selectQwExternalContactByExternalUserIdAndQwUserId(@Param("externalUserId") String externalUserId,@Param("corpId") String corpId,@Param("qwUserId") String qwUserId);
 
 

+ 6 - 0
fs-service/src/main/java/com/fs/sop/domain/SopUserLogsInfo.java

@@ -73,6 +73,12 @@ public class SopUserLogsInfo implements Serializable {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private String inComingETime;
     private String inComingETime;
 
 
+
+    /**
+     * 评级
+     */
+    private Integer grade;
+
     /**
     /**
      * 营期时间
      * 营期时间
      */
      */

+ 52 - 0
fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsInfoMapper.java

@@ -2,6 +2,7 @@ package com.fs.sop.mapper;
 
 
 import com.fs.common.annotation.DataSource;
 import com.fs.common.annotation.DataSource;
 import com.fs.common.enums.DataSourceType;
 import com.fs.common.enums.DataSourceType;
+import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.param.QwExtCourseSopWatchLog;
 import com.fs.qw.param.QwExtCourseSopWatchLog;
 import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.params.BatchSopUserLogsInfoParamU;
 import com.fs.sop.params.BatchSopUserLogsInfoParamU;
@@ -148,11 +149,62 @@ public interface SopUserLogsInfoMapper {
     @DataSource(DataSourceType.SOP)
     @DataSource(DataSourceType.SOP)
     List<SopUserLogsInfo> selectSopUserLogsInfoListBySopId(@Param("sopId") String sopId, @Param("userLogsId") String userLogsId);
     List<SopUserLogsInfo> selectSopUserLogsInfoListBySopId(@Param("sopId") String sopId, @Param("userLogsId") String userLogsId);
 
 
+    @DataSource(DataSourceType.SOP)
+    List<SopUserLogsInfo> selectRestoreByIsDaysNotStudy(@Param("sopId") String sopId, @Param("userLogsId") String userLogsId);
+
 
 
 
 
     @DataSource(DataSourceType.SOP)
     @DataSource(DataSourceType.SOP)
     void batchInsertSopUserLogsInfo(@Param("SopUserLogsInfo") List<SopUserLogsInfo> logsToInsert);
     void batchInsertSopUserLogsInfo(@Param("SopUserLogsInfo") List<SopUserLogsInfo> logsToInsert);
 
 
+
+    @DataSource(DataSourceType.SOP)
+    @Update("<script>" +
+            "UPDATE sop_user_logs_info " +
+            "SET is_days_not_study = CASE " +
+            "<foreach collection='contactList' item='item'> " +
+            "    WHEN external_id = #{item.id} THEN " +
+            "    CASE WHEN #{item.level} = 5 AND #{item.isDaysNotStudy}=1 THEN 1 ELSE 0 END " +
+            "</foreach> " +
+            "ELSE is_days_not_study " +
+            "END " +
+            "WHERE external_id IN " +
+            "<foreach collection='contactList' item='item' open='(' separator=',' close=')'> " +
+            "    #{item.id} " +
+            "</foreach>" +
+            "</script>")
+    void batchUpdateSopUserLogsInfoByMoreStudy(@Param("contactList") List<QwExternalContact> contactList);
+
+    @DataSource(DataSourceType.SOP)
+    @Update("<script>" +
+            "UPDATE sop_user_logs_info " +
+            "SET is_days_not_study = 0 " +
+            "WHERE external_id IN " +
+            "<foreach collection='contactList' item='item' open='(' separator=',' close=')'>" +
+            "    #{item.id} " +
+            "</foreach>" +
+            "</script>")
+    void batchUpdateSopUserLogsInfoByIsDaysNotStudy(@Param("contactList") List<QwExternalContact> contactList);
+
+    @DataSource(DataSourceType.SOP)
+    @Update("<script>" +
+            "UPDATE sop_user_logs_info " +
+            "SET grade = CASE external_id " +
+            "<foreach collection=\"contactList\" item=\"item\">" +
+            "    WHEN #{item.id} THEN #{item.level} " +
+            "</foreach>" +
+            "    ELSE grade " +
+            "END " +
+            "WHERE external_id IN " +
+            "<foreach collection='contactList' item='item' open='(' separator=',' close=')'>" +
+            "    #{item.id} " +
+            "</foreach>" +
+            "</script>")
+    void batchUpdateSopUserLogsInfoByLevel(@Param("contactList") List<QwExternalContact> contactList);
+
+
+
+
     @DataSource(DataSourceType.SOP)
     @DataSource(DataSourceType.SOP)
     void batchUpdateSopUserLogsInfoToTime(BatchSopUserLogsInfoParamU paramU);
     void batchUpdateSopUserLogsInfoToTime(BatchSopUserLogsInfoParamU paramU);
 
 

+ 7 - 0
fs-service/src/main/java/com/fs/sop/mapper/SopUserLogsMapper.java

@@ -64,6 +64,13 @@ public interface SopUserLogsMapper {
     @DataSource(DataSourceType.SOP)
     @DataSource(DataSourceType.SOP)
     public List<SopUserLogs> meetsTheRatingByUserInfoWithPagination(@Param("offset") int offset,@Param("pageSize") int pageSize);
     public List<SopUserLogs> meetsTheRatingByUserInfoWithPagination(@Param("offset") int offset,@Param("pageSize") int pageSize);
 
 
+    @DataSource(DataSourceType.SOP)
+    public List<SopUserLogs> meetsTheRatingByUserInfoWithPaginationStudyDays(@Param("offset") int offset,@Param("pageSize") int pageSize,@Param("notStudyDays") Integer notStudyDays);
+
+    @DataSource(DataSourceType.SOP)
+    public List<SopUserLogs> meetsTherestoreByIsDaysNotStudy(@Param("offset") int offset,@Param("pageSize") int pageSize,@Param("notStudyDays") Integer notStudyDays);
+
+
     @DataSource(DataSourceType.SOP)
     @DataSource(DataSourceType.SOP)
     public List<SopUserLogs> meetsTheRatingByUserInfoBySopId(@Param("sopId") String sopId);
     public List<SopUserLogs> meetsTheRatingByUserInfoBySopId(@Param("sopId") String sopId);
 
 

+ 1 - 0
fs-service/src/main/java/com/fs/sop/params/QwRatingConfig.java

@@ -5,6 +5,7 @@ import lombok.Data;
 @Data
 @Data
 public class QwRatingConfig {
 public class QwRatingConfig {
     private Integer levelDay;
     private Integer levelDay;
+    private Integer notStudyDays;
     private Integer aLevelMin;
     private Integer aLevelMin;
     private Integer aLevelMax;
     private Integer aLevelMax;
     private Integer bLevelMin;
     private Integer bLevelMin;

+ 8 - 0
fs-service/src/main/java/com/fs/sop/service/ISopUserLogsInfoService.java

@@ -1,6 +1,7 @@
 package com.fs.sop.service;
 package com.fs.sop.service;
 
 
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
+import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.param.QwExtCourseSopWatchLog;
 import com.fs.qw.param.QwExtCourseSopWatchLog;
 import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.params.BatchSopUserLogsInfoParam;
 import com.fs.sop.params.BatchSopUserLogsInfoParam;
@@ -54,6 +55,11 @@ public interface ISopUserLogsInfoService {
     List<SopUserLogsInfoVOE> selectSopUserLogsInfoListVO(SopUserLogsInfo info);
     List<SopUserLogsInfoVOE> selectSopUserLogsInfoListVO(SopUserLogsInfo info);
 
 
     void batchInsertSopUserLogsInfo(List<SopUserLogsInfo> logsToInsert);
     void batchInsertSopUserLogsInfo(List<SopUserLogsInfo> logsToInsert);
+
+    void batchUpdateSopUserLogsInfoByMoreStudy(List<QwExternalContact> contactList);
+    void batchUpdateSopUserLogsInfoByIsDaysNotStudy(List<QwExternalContact> contactList);
+    void batchUpdateSopUserLogsInfoByLevel(List<QwExternalContact> contactList);
+
     void insertSopUserLogsInfo(SopUserLogsInfo logsToInsert);
     void insertSopUserLogsInfo(SopUserLogsInfo logsToInsert);
     /**
     /**
      * 查询sopUserLogsInfo
      * 查询sopUserLogsInfo
@@ -74,5 +80,7 @@ public interface ISopUserLogsInfoService {
     public R sendUserLogsInfoMsg(SendUserLogsInfoMsgParam param);
     public R sendUserLogsInfoMsg(SendUserLogsInfoMsgParam param);
     public R sendUserLogsInfoMsgType(SendUserLogsInfoMsgParam param);
     public R sendUserLogsInfoMsgType(SendUserLogsInfoMsgParam param);
 
 
+    List<SopUserLogsInfo> selectRestoreByIsDaysNotStudy(String sopId, String userLogsId);
+
     public List<ExtCourseSopWatchLogVO> getExtCourseSopWatchLog(QwExtCourseSopWatchLog qwExternalContactId);
     public List<ExtCourseSopWatchLogVO> getExtCourseSopWatchLog(QwExtCourseSopWatchLog qwExternalContactId);
 }
 }

+ 3 - 0
fs-service/src/main/java/com/fs/sop/service/ISopUserLogsService.java

@@ -61,4 +61,7 @@ public interface ISopUserLogsService {
     void updateLogDate(UpdateSopUserLogDateVo vo);
     void updateLogDate(UpdateSopUserLogDateVo vo);
 
 
     void addGroupChat(AddSopUserGroupChat vo);
     void addGroupChat(AddSopUserGroupChat vo);
+
+    public List<SopUserLogs> meetsTherestoreByIsDaysNotStudy(int offset,int pageSize,Integer notStudyDays);
+
 }
 }

+ 84 - 13
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -3,12 +3,15 @@ package com.fs.sop.service.impl;
 import cn.hutool.json.JSONUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONArray;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.base.BaseException;
 import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.date.DateUtil;
 import com.fs.common.utils.date.DateUtil;
+import com.fs.company.domain.CompanyMiniapp;
 import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.company.service.ICompanyMiniappService;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.FsCourseDomainName;
 import com.fs.course.domain.FsCourseDomainName;
 import com.fs.course.domain.FsCourseLink;
 import com.fs.course.domain.FsCourseLink;
@@ -153,6 +156,9 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
     @Autowired
     @Autowired
     private QwGroupChatUserMapper qwGroupChatUserMapper;
     private QwGroupChatUserMapper qwGroupChatUserMapper;
 
 
+    @Autowired
+    private ICompanyMiniappService companyMiniappService;
+
     @Override
     @Override
     public void save(SopUserLogsInfo sopUserLogsInfo) {
     public void save(SopUserLogsInfo sopUserLogsInfo) {
         sopUserLogsInfoMapper.insertSopUserLogsInfo(sopUserLogsInfo);
         sopUserLogsInfoMapper.insertSopUserLogsInfo(sopUserLogsInfo);
@@ -218,6 +224,21 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         sopUserLogsInfoMapper.batchInsertSopUserLogsInfo(logsToInsert);
         sopUserLogsInfoMapper.batchInsertSopUserLogsInfo(logsToInsert);
     }
     }
 
 
+    @Override
+    public void batchUpdateSopUserLogsInfoByMoreStudy(List<QwExternalContact> contactList) {
+        sopUserLogsInfoMapper.batchUpdateSopUserLogsInfoByMoreStudy(contactList);
+    }
+
+    @Override
+    public void batchUpdateSopUserLogsInfoByIsDaysNotStudy(List<QwExternalContact> contactList) {
+        sopUserLogsInfoMapper.batchUpdateSopUserLogsInfoByIsDaysNotStudy(contactList);
+    }
+
+    @Override
+    public void batchUpdateSopUserLogsInfoByLevel(List<QwExternalContact> contactList) {
+        sopUserLogsInfoMapper.batchUpdateSopUserLogsInfoByLevel(contactList);
+    }
+
     @Override
     @Override
     public void insertSopUserLogsInfo(SopUserLogsInfo logsToInsert) {
     public void insertSopUserLogsInfo(SopUserLogsInfo logsToInsert) {
         sopUserLogsInfoMapper.insertSopUserLogsInfo(logsToInsert);
         sopUserLogsInfoMapper.insertSopUserLogsInfo(logsToInsert);
@@ -698,6 +719,14 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                 }).collect(Collectors.toList());
                 }).collect(Collectors.toList());
             }
             }
         }else{
         }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<>();
             sopLogsList = new ArrayList<>();
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
             List<SopUserLogsInfo> sopUserLogsInfos = sopUserLogsInfoMapper.selectSopUserLogsInfoByIds(param.getIds());
             List<SopUserLogsInfo> sopUserLogsInfos = sopUserLogsInfoMapper.selectSopUserLogsInfoByIds(param.getIds());
@@ -810,12 +839,28 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             String linkByMiniApp = createLinkByMiniApp(st, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
                             String linkByMiniApp = createLinkByMiniApp(st, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
                                     qwUserId, companyUserId, companyId, item.getExternalId(), config);
                                     qwUserId, companyUserId, companyId, item.getExternalId(), config);
 
 
-                            if (StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())){
-                                log.error("企业未配置小程序-"+param.getCorpId());
-                            }else {
-                                //置换各自的小程序
+                            if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                                Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
+                                if (integerListMap != null) {
+
+                                    int effectiveGrade = (item.getGrade() == null) ? 5 : item.getGrade();
+                                    int listIndex = (effectiveGrade == 1 || effectiveGrade == 2) ? 0 : 1;
+                                    List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);
+
+                                    if (miniapps != null && !miniapps.isEmpty()) {
+                                        CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                        if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                            st.setMiniprogramAppid(companyMiniapp.getAppId());
+                                        }
+                                    }
+                                }
+                            } else if (!StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
                                 st.setMiniprogramAppid(qwCompany.getMiniAppId());
                                 st.setMiniprogramAppid(qwCompany.getMiniAppId());
+                            } else {
+                                log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
                             }
                             }
+
+
                             st.setMiniprogramPage(linkByMiniApp);
                             st.setMiniprogramPage(linkByMiniApp);
 
 
                             break;
                             break;
@@ -874,6 +919,11 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         return null;
         return null;
     }
     }
 
 
+    @Override
+    public List<SopUserLogsInfo> selectRestoreByIsDaysNotStudy(String sopId, String userLogsId) {
+        return sopUserLogsInfoMapper.selectRestoreByIsDaysNotStudy(sopId, userLogsId);
+    }
+
     @Override
     @Override
     public List<ExtCourseSopWatchLogVO> getExtCourseSopWatchLog(QwExtCourseSopWatchLog qwExternalContactId) {
     public List<ExtCourseSopWatchLogVO> getExtCourseSopWatchLog(QwExtCourseSopWatchLog qwExternalContactId) {
 
 
@@ -915,6 +965,9 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
 
         List<SopUserLogsInfo> sopUserLogs = sopUserLogsMapper.selectSopUserLogsByIds(param.getIds());
         List<SopUserLogsInfo> sopUserLogs = sopUserLogsMapper.selectSopUserLogsByIds(param.getIds());
 
 
+        // 查询公司关联小程序数据
+        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)));
 
 
         //排序
         //排序
         int sort = 0;
         int sort = 0;
@@ -955,7 +1008,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                 log.error("员工信息用户不存在:" + key.getKey()+",企业id:"+key.getValue());
                 log.error("员工信息用户不存在:" + key.getKey()+",企业id:"+key.getValue());
             }else {
             }else {
 
 
-                List<QwSopLogs> sopLogsList = processInsertSopUserLogsInfo(logs, qwUser, param, words, config, qwCompany, finalSort, finalSendType);
+                List<QwSopLogs> sopLogsList = processInsertSopUserLogsInfo(logs, qwUser, param, words, config, qwCompany, finalSort,
+                        finalSendType,miniMap );
 
 
                 //批量插入 发送记录
                 //批量插入 发送记录
                 if (!sopLogsList.isEmpty()) {
                 if (!sopLogsList.isEmpty()) {
@@ -972,7 +1026,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
 
     private List<QwSopLogs> processInsertSopUserLogsInfo(List<SopUserLogsInfo> sopUserLogsInfos,QwUser qwUser,
     private List<QwSopLogs> processInsertSopUserLogsInfo(List<SopUserLogsInfo> sopUserLogsInfos,QwUser qwUser,
                                                          SendUserLogsInfoMsgParam param,List<FastGptChatReplaceWords> words,
                                                          SendUserLogsInfoMsgParam param,List<FastGptChatReplaceWords> words,
-                                                         CourseConfig config,QwCompany qwCompany,int finalSort,int finalSendType){
+                                                         CourseConfig config,QwCompany qwCompany,int finalSort,int finalSendType,
+                                                         Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap){
 
 
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 
 
@@ -1026,7 +1081,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
 
             switch (finalSendType){
             switch (finalSendType){
                 case 5:
                 case 5:
-                    List<QwSopCourseFinishTempSetting.Setting> list = processSetting(item,qwUser, param, words, config, qwCompany,companyUserId,companyId,contact,dataTime, finalDomainName);
+                    List<QwSopCourseFinishTempSetting.Setting> list = processSetting(item,qwUser, param, words, config, qwCompany,companyUserId,companyId,
+                            contact,dataTime, finalDomainName,miniMap);
                     setting.setSetting(list);
                     setting.setSetting(list);
                     break;
                     break;
                 case 9:
                 case 9:
@@ -1088,7 +1144,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
     private List<QwSopCourseFinishTempSetting.Setting> processSetting(SopUserLogsInfo item, QwUser qwUser,
     private List<QwSopCourseFinishTempSetting.Setting> processSetting(SopUserLogsInfo item, QwUser qwUser,
                                                                       SendUserLogsInfoMsgParam param,List<FastGptChatReplaceWords> words,
                                                                       SendUserLogsInfoMsgParam param,List<FastGptChatReplaceWords> words,
                                                                       CourseConfig config,QwCompany qwCompany,String companyUserId, String companyId,
                                                                       CourseConfig config,QwCompany qwCompany,String companyUserId, String companyId,
-                                                                      QwExternalContact contact,Date dataTime,String domainName){
+                                                                      QwExternalContact contact,Date dataTime,String domainName,
+                                                                      Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap){
         List<QwSopCourseFinishTempSetting.Setting> list = JSONArray.parseArray(param.getSetting(),QwSopCourseFinishTempSetting.Setting.class);
         List<QwSopCourseFinishTempSetting.Setting> list = JSONArray.parseArray(param.getSetting(),QwSopCourseFinishTempSetting.Setting.class);
 
 
         for (QwSopCourseFinishTempSetting.Setting st : list) {
         for (QwSopCourseFinishTempSetting.Setting st : list) {
@@ -1158,13 +1215,27 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 //                    }else {
 //                    }else {
 //                        st.setMiniprogramAppid(config.getMiniprogramAppid());
 //                        st.setMiniprogramAppid(config.getMiniprogramAppid());
 //                    }
 //                    }
-                    if (StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())){
-                        log.error("企业未配置小程序-"+param.getCorpId());
-
-                    }else {
-                        //置换各自的小程序
+                    if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                        Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
+                        if (integerListMap != null) {
+
+                            int effectiveGrade = (item.getGrade() == null) ? 5 : item.getGrade();
+                            int listIndex = (effectiveGrade == 1 || effectiveGrade == 2) ? 0 : 1;
+                            List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);
+
+                            if (miniapps != null && !miniapps.isEmpty()) {
+                                CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                    st.setMiniprogramAppid(companyMiniapp.getAppId());
+                                }
+                            }
+                        }
+                    } else if (!StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
                         st.setMiniprogramAppid(qwCompany.getMiniAppId());
                         st.setMiniprogramAppid(qwCompany.getMiniAppId());
+                    } else {
+                        log.error("企业未配置小程序-" + param.getCorpId());
                     }
                     }
+
                     st.setMiniprogramPage(linkByMiniApp);
                     st.setMiniprogramPage(linkByMiniApp);
                     break;
                     break;
                 default:
                 default:

+ 5 - 0
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsServiceImpl.java

@@ -955,6 +955,11 @@ public class SopUserLogsServiceImpl  implements ISopUserLogsService {
         sopUserLogsMapper.batchInsertSopUserLogs(list);
         sopUserLogsMapper.batchInsertSopUserLogs(list);
     }
     }
 
 
+    @Override
+    public List<SopUserLogs> meetsTherestoreByIsDaysNotStudy(int offset, int pageSize, Integer notStudyDays) {
+        return sopUserLogsMapper.meetsTherestoreByIsDaysNotStudy(offset,pageSize,notStudyDays);
+    }
+
     //批量更新
     //批量更新
     private void batchUpdateQwExternalContact(List<QwExternalContact> notInExternalUseridList) {
     private void batchUpdateQwExternalContact(List<QwExternalContact> notInExternalUseridList) {
         // 定义批量插入的大小
         // 定义批量插入的大小

+ 1 - 1
fs-service/src/main/java/com/fs/sop/vo/QwRatingVO.java

@@ -16,7 +16,7 @@ public class QwRatingVO {
     */
     */
     private Integer allDuration;
     private Integer allDuration;
 
 
-
+    private Integer level;
 
 
     /**
     /**
      * 1升 2降 3未变动
      * 1升 2降 3未变动

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

@@ -85,7 +85,7 @@ cloud_host:
   company_name: 恒春来
   company_name: 恒春来
 #看课授权时显示的头像
 #看课授权时显示的头像
 headerImg:
 headerImg:
-  imgUrl: https://hcl-1b2b.obs.cn-south-1.myhuaweicloud.com/fs/20250803/1754213762409.png
+  imgUrl: http://hcl-1b2b.obs.cn-south-1.myhuaweicloud.com/fs/20250808/1754640068227.png
 ipad:
 ipad:
   ipadUrl: http://ipad.cdwjyyh.com
   ipadUrl: http://ipad.cdwjyyh.com
   aiApi:
   aiApi:

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

@@ -201,6 +201,30 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </foreach>
         </foreach>
     </update>
     </update>
 
 
+    <update id="batchUpdateQwExternalContactByMoreStudy" parameterType="map">
+        UPDATE qw_external_contact
+        SET
+        level = CASE id
+        <foreach collection="list" item="item">
+            WHEN #{item.id} THEN #{item.level}
+        </foreach>
+        ELSE level
+        END
+        WHERE id IN
+        <foreach collection="list" item="item" open="(" separator="," close=")">
+            #{item.id}
+        </foreach>
+    </update>
+
+    <update id="batchUpdateQwExternalByIsDaysNotStudy" parameterType="map">
+        UPDATE qw_external_contact
+        SET level =  NULL
+        WHERE id IN
+        <foreach collection="list" item="item" open="(" separator="," close=")">
+            #{item.id}
+        </foreach>
+    </update>
+
 
 
     <insert id="insertQwExternalContact" parameterType="QwExternalContact" useGeneratedKeys="true" keyProperty="id" >
     <insert id="insertQwExternalContact" parameterType="QwExternalContact" useGeneratedKeys="true" keyProperty="id" >
         insert into qw_external_contact
         insert into qw_external_contact

+ 12 - 1
fs-service/src/main/resources/mapper/sop/SopUserLogsInfoMapper.xml

@@ -20,6 +20,7 @@
         <result property="updateTime" column="update_time" jdbcType="VARCHAR" />
         <result property="updateTime" column="update_time" jdbcType="VARCHAR" />
         <result property="tagIds" column="tag_ids" jdbcType="VARCHAR" />
         <result property="tagIds" column="tag_ids" jdbcType="VARCHAR" />
         <result property="isDaysNotStudy" column="is_days_not_study"/>
         <result property="isDaysNotStudy" column="is_days_not_study"/>
+        <result property="grade" column="grade"/>
     </resultMap>
     </resultMap>
 
 
     <sql id="selectSopUserLogsInfoVo">
     <sql id="selectSopUserLogsInfoVo">
@@ -185,7 +186,7 @@
 
 
     <!-- 根据ID查询单条记录 -->
     <!-- 根据ID查询单条记录 -->
     <select id="selectById" parameterType="String" resultMap="SopUserLogsInfoResult">
     <select id="selectById" parameterType="String" resultMap="SopUserLogsInfoResult">
-        SELECT id, sop_id, user_logs_id, external_contact_id,qw_user_id,corp_id,external_id, fs_user_id, external_user_name,create_time,crt_Time,update_time
+        SELECT id, sop_id, user_logs_id, external_contact_id,qw_user_id,corp_id,external_id, fs_user_id, external_user_name,create_time,crt_Time,update_time,grade
         FROM sop_user_logs_info
         FROM sop_user_logs_info
         WHERE id = #{id}
         WHERE id = #{id}
     </select>
     </select>
@@ -235,6 +236,16 @@
         from sop_user_logs_info where sop_id = #{sopId} and user_logs_id=#{userLogsId}
         from sop_user_logs_info where sop_id = #{sopId} and user_logs_id=#{userLogsId}
     </select>
     </select>
 
 
+    <select id="selectRestoreByIsDaysNotStudy" parameterType="String" resultMap="SopUserLogsInfoResult">
+        select
+            id,external_id
+        from sop_user_logs_info
+        where sop_id = #{sopId}
+          and user_logs_id=#{userLogsId}
+          and is_days_not_study=1
+    </select>
+
+
     <!-- 查询所有记录 -->
     <!-- 查询所有记录 -->
     <select id="selectAll" resultMap="SopUserLogsInfoResult">
     <select id="selectAll" resultMap="SopUserLogsInfoResult">
         SELECT id, sop_id, user_logs_id, external_contact_id,qw_user_id,corp_id,external_id,
         SELECT id, sop_id, user_logs_id, external_contact_id,qw_user_id,corp_id,external_id,

+ 41 - 0
fs-service/src/main/resources/mapper/sop/SopUserLogsMapper.xml

@@ -253,6 +253,47 @@
             LIMIT #{offset}, #{pageSize}
             LIMIT #{offset}, #{pageSize}
     </select>
     </select>
 
 
+
+    <select id="meetsTheRatingByUserInfoWithPaginationStudyDays" resultType="Integer"  resultMap="SopUserLogsResult">
+        SELECT
+            ul.id,
+            ul.sop_id,
+            ul.sop_temp_id,
+            ul.qw_user_id,
+            ul.corp_id,
+            ul.start_time,
+            ul.`status`,
+            ul.user_id,
+            DATEDIFF( CURRENT_DATE, ul.start_time ) AS count_days
+        FROM
+            sop_user_logs ul  LEFT JOIN qw_sop qs on ul.sop_id=qs.id
+        WHERE
+            ul.`status` = '1'
+          and qs.type=2
+          and qs.send_type=2
+          and qs.`status` in (2,3)
+          AND ( DATEDIFF( CURRENT_DATE, ul.start_time ) ) >= #{notStudyDays}
+        ORDER BY id ASC
+            LIMIT #{offset}, #{pageSize}
+    </select>
+
+    <select id="meetsTherestoreByIsDaysNotStudy" resultType="Integer"  resultMap="SopUserLogsResult">
+        SELECT
+            ul.id,
+            ul.sop_id
+        FROM
+            sop_user_logs ul  LEFT JOIN qw_sop qs on ul.sop_id=qs.id
+        WHERE
+            ul.`status` = '1'
+          and qs.type=2
+          and qs.send_type=2
+          and qs.`status` in (2,3)
+          AND ( DATEDIFF( CURRENT_DATE, ul.start_time ) ) &lt; #{notStudyDays}
+        ORDER BY id ASC
+            LIMIT #{offset}, #{pageSize}
+    </select>
+
+
     <select id="meetsTheRatingByUserInfoBySopId" resultType="String"  resultMap="SopUserLogsResult">
     <select id="meetsTheRatingByUserInfoBySopId" resultType="String"  resultMap="SopUserLogsResult">
         SELECT
         SELECT
             ul.id,
             ul.id,