|
|
@@ -0,0 +1,2336 @@
|
|
|
+package com.fs.app.taskService.impl;
|
|
|
+
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.alibaba.fastjson.JSONArray;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
+import com.fs.app.taskService.SopLogsTaskService;
|
|
|
+import com.fs.common.config.FSSysConfig;
|
|
|
+import com.fs.common.utils.PubFun;
|
|
|
+import com.fs.common.utils.StringUtils;
|
|
|
+import com.fs.company.domain.Company;
|
|
|
+import com.fs.company.domain.CompanyMiniapp;
|
|
|
+import com.fs.company.domain.CompanyUser;
|
|
|
+import com.fs.company.mapper.CompanyMapper;
|
|
|
+import com.fs.company.service.ICompanyMiniappService;
|
|
|
+import com.fs.company.service.ICompanyUserService;
|
|
|
+import com.fs.config.cloud.CloudHostProper;
|
|
|
+import com.fs.course.config.CourseConfig;
|
|
|
+import com.fs.course.domain.*;
|
|
|
+import com.fs.course.mapper.*;
|
|
|
+import com.fs.course.service.IFsCourseLinkService;
|
|
|
+import com.fs.course.service.IFsUserCompanyBindService;
|
|
|
+import com.fs.qw.domain.*;
|
|
|
+import com.fs.qw.mapper.QwExternalContactMapper;
|
|
|
+import com.fs.qw.mapper.QwUserMapper;
|
|
|
+import com.fs.qw.service.IQwCompanyService;
|
|
|
+import com.fs.qw.service.IQwGroupChatService;
|
|
|
+import com.fs.qw.service.IQwGroupChatUserService;
|
|
|
+import com.fs.qw.service.impl.QwExternalContactServiceImpl;
|
|
|
+import com.fs.qw.vo.GroupUserExternalVo;
|
|
|
+import com.fs.qw.vo.QwSopCourseFinishTempSetting;
|
|
|
+import com.fs.qw.vo.QwSopRuleTimeVO;
|
|
|
+import com.fs.qw.vo.QwSopTempSetting;
|
|
|
+import com.fs.sop.domain.*;
|
|
|
+import com.fs.sop.mapper.*;
|
|
|
+import com.fs.sop.service.IQwSopLogsService;
|
|
|
+import com.fs.sop.service.IQwSopTempContentService;
|
|
|
+import com.fs.sop.service.IQwSopTempRulesService;
|
|
|
+import com.fs.sop.service.IQwSopTempVoiceService;
|
|
|
+import com.fs.sop.vo.QwCreateLinkByAppVO;
|
|
|
+import com.fs.sop.vo.SopUserLogsVo;
|
|
|
+import com.fs.system.service.ISysConfigService;
|
|
|
+import com.fs.voice.utils.StringUtil;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.BeanUtils;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.retry.annotation.Backoff;
|
|
|
+import org.springframework.retry.annotation.Retryable;
|
|
|
+import org.springframework.scheduling.annotation.Async;
|
|
|
+import org.springframework.scheduling.annotation.Scheduled;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import javax.annotation.PostConstruct;
|
|
|
+import javax.annotation.PreDestroy;
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.LocalTime;
|
|
|
+import java.time.ZoneId;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.time.temporal.ChronoUnit;
|
|
|
+import java.util.*;
|
|
|
+import java.util.concurrent.*;
|
|
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+import static com.fs.course.utils.LinkUtil.generateRandomStringWithLock;
|
|
|
+
|
|
|
+@Service
|
|
|
+@Slf4j
|
|
|
+public class SopLogsTaskServiceImpl implements SopLogsTaskService {
|
|
|
+
|
|
|
+
|
|
|
+ private static final String REAL_LINK_PREFIX = "/courseH5/pages/course/learning?course=";
|
|
|
+ private static final String SHORT_LINK_PREFIX = "/courseH5/pages/course/learning?s=";
|
|
|
+ private static final String miniappRealLink = "/pages_course/video.html?course=";
|
|
|
+ private static final String appRealLink = "/pages/courseAnswer/index?link=";
|
|
|
+ private static final String appLink = "https://jump.ylrztop.com/jumpapp/pages/index/index?link=";
|
|
|
+
|
|
|
+// private static final String miniappRealLink = "/pages/index/index?course=";
|
|
|
+
|
|
|
+ private static final String QWSOP_KEY_PREFIX = "qwsop:";
|
|
|
+ private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
|
|
+ private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
+ private static final DateTimeFormatter OUTPUT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd 07:00:00");
|
|
|
+
|
|
|
+
|
|
|
+ // Cached configurations and domain names
|
|
|
+ private CourseConfig cachedCourseConfig;
|
|
|
+ private final Object configLock = new Object();
|
|
|
+
|
|
|
+ private List<FsCourseDomainName> cachedDomainNames;
|
|
|
+ private final Object domainLock = new Object();
|
|
|
+
|
|
|
+
|
|
|
+ // Batch size for database inserts, configurable via application.properties
|
|
|
+ private final int BATCH_SIZE = 500;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private IFsCourseLinkService courseLinkService;
|
|
|
+ @Autowired
|
|
|
+ private SopUserLogsMapper sopUserLogsMapper;
|
|
|
+ @Autowired
|
|
|
+ private QwSopTagMapper qwSopTagMapper ;
|
|
|
+ @Autowired
|
|
|
+ private QwSopMapper sopMapper;
|
|
|
+
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private QwExternalContactServiceImpl qwExternalContactService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FsCourseWatchLogMapper fsCourseWatchLogMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private IQwSopLogsService qwSopLogsService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private QwSopLogsMapper qwSopLogsMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FsCourseLinkMapper fsCourseLinkMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FsCourseSopAppLinkMapper fsCourseSopAppLinkMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ISysConfigService configService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FsCourseDomainNameMapper fsCourseDomainNameMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private SopUserLogsInfoMapper sopUserLogsInfoMapper;
|
|
|
+ @Autowired
|
|
|
+ private QwUserMapper qwUserMapper;
|
|
|
+ @Autowired
|
|
|
+ private IQwSopTempRulesService qwSopTempRulesService;
|
|
|
+ @Autowired
|
|
|
+ private IQwSopTempContentService qwSopTempContentService;
|
|
|
+ @Autowired
|
|
|
+ private IQwSopTempVoiceService qwSopTempVoiceService;
|
|
|
+ @Autowired
|
|
|
+ private CloudHostProper cloudHostProper;
|
|
|
+
|
|
|
+ // Blocking queues with bounded capacity to implement backpressure
|
|
|
+ private final BlockingQueue<QwSopLogs> qwSopLogsQueue = new LinkedBlockingQueue<>(20000);
|
|
|
+ private final BlockingQueue<FsCourseWatchLog> watchLogsQueue = new LinkedBlockingQueue<>(20000);
|
|
|
+ private final BlockingQueue<FsCourseLink> linkQueue = new LinkedBlockingQueue<>(20000);
|
|
|
+ private final BlockingQueue<FsCourseSopAppLink> sopAppLinks = new LinkedBlockingQueue<>(20000);
|
|
|
+
|
|
|
+ // Executors for consumer threads
|
|
|
+ private ExecutorService qwSopLogsExecutor;
|
|
|
+ private ExecutorService watchLogsExecutor;
|
|
|
+ private ExecutorService courseLinkExecutor;
|
|
|
+ private ExecutorService courseSopAppLinkExecutor;
|
|
|
+ @Autowired
|
|
|
+ private IQwGroupChatService qwGroupChatService;
|
|
|
+ @Autowired
|
|
|
+ private IQwGroupChatUserService qwGroupChatUserService;
|
|
|
+ @Autowired
|
|
|
+ private ICompanyMiniappService companyMiniappService;
|
|
|
+ // Shutdown flags
|
|
|
+ private volatile boolean running = true;
|
|
|
+ @Autowired
|
|
|
+ private QwSopTempMapper qwSopTempMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ICompanyUserService companyUserService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private IQwCompanyService iQwCompanyService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private CompanyMapper companyMapper;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private AsyncCourseWatchFinishService asyncCourseWatchFinishService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private IFsUserCompanyBindService fsUserCompanyBindService;
|
|
|
+
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private IQwSopTempVoiceService sopTempVoiceService;
|
|
|
+
|
|
|
+ @PostConstruct
|
|
|
+ public void init() {
|
|
|
+ loadCourseConfig();
|
|
|
+ startConsumers();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void loadCourseConfig() {
|
|
|
+ try {
|
|
|
+ String json = configService.selectConfigByKey("course.config");
|
|
|
+ CourseConfig config = JSON.parseObject(json, CourseConfig.class);
|
|
|
+ if (config != null) {
|
|
|
+ cachedCourseConfig = config;
|
|
|
+ log.info("Loaded course.config successfully.");
|
|
|
+ } else {
|
|
|
+ log.error("Failed to load course.config from configService.");
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("Exception while loading course.config: {}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ private void startConsumers() {
|
|
|
+ qwSopLogsExecutor = Executors.newSingleThreadExecutor(r -> {
|
|
|
+ Thread t = new Thread(r, "QwSopLogsConsumer");
|
|
|
+ t.setDaemon(true);
|
|
|
+ return t;
|
|
|
+ });
|
|
|
+ watchLogsExecutor = Executors.newSingleThreadExecutor(r -> {
|
|
|
+ Thread t = new Thread(r, "WatchLogsConsumer");
|
|
|
+ t.setDaemon(true);
|
|
|
+ return t;
|
|
|
+ });
|
|
|
+ courseLinkExecutor = Executors.newSingleThreadExecutor(r -> {
|
|
|
+ Thread t = new Thread(r, "courseLinkConsumer");
|
|
|
+ t.setDaemon(true);
|
|
|
+ return t;
|
|
|
+ });
|
|
|
+
|
|
|
+ courseSopAppLinkExecutor = Executors.newSingleThreadExecutor(r -> {
|
|
|
+ Thread t = new Thread(r, "courseSopAppLinkConsumer");
|
|
|
+ t.setDaemon(true);
|
|
|
+ return t;
|
|
|
+ });
|
|
|
+
|
|
|
+
|
|
|
+ qwSopLogsExecutor.submit(this::consumeQwSopLogs);
|
|
|
+ watchLogsExecutor.submit(this::consumeWatchLogs);
|
|
|
+ courseLinkExecutor.submit(this::consumeCourseLink);
|
|
|
+ courseSopAppLinkExecutor.submit(this::consumeCourseSopAppLink);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Scheduled tasks to refresh configurations and domain names periodically
|
|
|
+ @Scheduled(fixedDelay = 60000) // 每60秒刷新一次
|
|
|
+ public void refreshCourseConfig() {
|
|
|
+ synchronized(configLock) {
|
|
|
+ try {
|
|
|
+ String json = configService.selectConfigByKey("course.config");
|
|
|
+ CourseConfig newConfig = JSON.parseObject(json, CourseConfig.class);
|
|
|
+ if (newConfig != null) {
|
|
|
+ cachedCourseConfig = newConfig;
|
|
|
+ log.info("Refreshed course.config.");
|
|
|
+ } else {
|
|
|
+ log.error("Failed to refresh course.config.");
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("Exception while refreshing course.config: {}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ @PreDestroy
|
|
|
+ public void shutdownConsumers() {
|
|
|
+ running = false;
|
|
|
+ qwSopLogsExecutor.shutdown();
|
|
|
+ watchLogsExecutor.shutdown();
|
|
|
+ courseLinkExecutor.shutdown();
|
|
|
+ courseSopAppLinkExecutor.shutdown();
|
|
|
+ try {
|
|
|
+ if (!qwSopLogsExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
|
|
|
+ qwSopLogsExecutor.shutdownNow();
|
|
|
+ }
|
|
|
+ if (!watchLogsExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
|
|
|
+ watchLogsExecutor.shutdownNow();
|
|
|
+ }
|
|
|
+ if (!courseLinkExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
|
|
|
+ courseLinkExecutor.shutdownNow();
|
|
|
+ }
|
|
|
+ if (!courseSopAppLinkExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
|
|
|
+ courseSopAppLinkExecutor.shutdownNow();
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ qwSopLogsExecutor.shutdownNow();
|
|
|
+ watchLogsExecutor.shutdownNow();
|
|
|
+ courseLinkExecutor.shutdownNow();
|
|
|
+ courseSopAppLinkExecutor.shutdownNow();
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void selectSopUserLogsListByTime(LocalDateTime currentTime, List<String> sopidList) throws Exception {
|
|
|
+ long startTimeMillis = System.currentTimeMillis();
|
|
|
+ log.info("====== 开始选择和处理 SOP 用户日志 ======");
|
|
|
+
|
|
|
+ // 获取缓存的配置
|
|
|
+ CourseConfig config;
|
|
|
+ synchronized(configLock) {
|
|
|
+ config = cachedCourseConfig;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<SopUserLogsVo> sopUserLogsVos = sopUserLogsMapper.selectSopUserLogsListByTime(sopidList);
|
|
|
+ if (sopUserLogsVos.isEmpty()) {
|
|
|
+ log.info("没有需要处理的 SOP 用户日志。");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ sopUserLogsVos = sopUserLogsVos.stream().filter(e -> e.getFilterMode() == 1 || (e.getFilterMode() == 2 && StringUtils.isNotEmpty(e.getChatId()))).collect(Collectors.toList());
|
|
|
+
|
|
|
+ String[] array = sopUserLogsVos.stream().map(SopUserLogsVo::getChatId).filter(StringUtils::isNotEmpty).toArray(String[]::new);
|
|
|
+ Map<String, QwGroupChat> groupChatMap = new HashMap<>();
|
|
|
+ if (array.length > 0) {
|
|
|
+ List<QwGroupChat> qwGroupChatList = qwGroupChatService.selectQwGroupChatByChatIds(array);
|
|
|
+ List<QwGroupChatUser> qwGroupChatUserList = qwGroupChatUserService.selectQwGroupChatUserByChatIds(array);
|
|
|
+ List<String> groupChatUserIds = PubFun.listToNewList(qwGroupChatUserList, QwGroupChatUser::getUserId);
|
|
|
+ if(!groupChatUserIds.isEmpty()){
|
|
|
+ List<GroupUserExternalVo> userList = qwExternalContactMapper.selectByGroupUser(groupChatUserIds);
|
|
|
+ Map<String, List<GroupUserExternalVo>> userMap = PubFun.listToMapByGroupList(userList, GroupUserExternalVo::getExternalUserId);
|
|
|
+ qwGroupChatUserList.forEach(e -> {
|
|
|
+ e.setUserList(userMap.getOrDefault(e.getUserId(), Collections.emptyList()));
|
|
|
+ });
|
|
|
+ }
|
|
|
+ Map<String, List<QwGroupChatUser>> chatUserMap = PubFun.listToMapByGroupList(qwGroupChatUserList, QwGroupChatUser::getChatId);
|
|
|
+ qwGroupChatList.stream().filter(e -> chatUserMap.containsKey(e.getChatId())).forEach(e -> e.setChatUserList(chatUserMap.get(e.getChatId())));
|
|
|
+ groupChatMap = PubFun.listToMapByGroupObject(qwGroupChatList, QwGroupChat::getChatId);
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, List<SopUserLogsVo>> sopLogsGroupedById = sopUserLogsVos.stream()
|
|
|
+ .collect(Collectors.groupingBy(SopUserLogsVo::getSopId));
|
|
|
+
|
|
|
+ // 查询公司关联小程序数据
|
|
|
+ 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)));
|
|
|
+
|
|
|
+
|
|
|
+ List<Company> companies = companyMapper.selectCompanyAllList();
|
|
|
+
|
|
|
+ log.info("共分组 {} 个 SOP ID 进行处理。", sopLogsGroupedById.size());
|
|
|
+
|
|
|
+ CountDownLatch sopGroupLatch = new CountDownLatch(sopLogsGroupedById.size());
|
|
|
+
|
|
|
+ for (Map.Entry<String, List<SopUserLogsVo>> entry : sopLogsGroupedById.entrySet()) {
|
|
|
+ String sopId = entry.getKey();
|
|
|
+ List<SopUserLogsVo> userLogsVos = entry.getValue();
|
|
|
+ processSopGroupAsync(sopId, userLogsVos, sopGroupLatch,currentTime, groupChatMap,config,miniMap,companies);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 等待所有 SOP 分组处理完成
|
|
|
+ sopGroupLatch.await();
|
|
|
+
|
|
|
+ // 触发批量插入(可选,如果需要立即插入队列中的数据)
|
|
|
+ // batchInsertQwSopLogs();
|
|
|
+ // batchInsertFsCourseWatchLogs();
|
|
|
+
|
|
|
+ long endTimeMillis = System.currentTimeMillis();
|
|
|
+ log.info("====== SOP 用户日志处理完成,耗时 {} 毫秒 ======", (endTimeMillis - startTimeMillis));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Async("sopTaskExecutor")
|
|
|
+ @Retryable(
|
|
|
+ value = { Exception.class },
|
|
|
+ maxAttempts = 3,
|
|
|
+ backoff = @Backoff(delay = 2000)
|
|
|
+ )
|
|
|
+ public void processSopGroupAsync(String sopId, List<SopUserLogsVo> userLogsVos, CountDownLatch latch ,LocalDateTime currentTime,
|
|
|
+ Map<String, QwGroupChat> groupChatMap,CourseConfig config,Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
|
|
|
+ List<Company> companies) {
|
|
|
+ try {
|
|
|
+ processSopGroup(sopId, userLogsVos,currentTime, groupChatMap, config,miniMap,companies);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理 SOP ID {} 时发生异常: {}", sopId, e.getMessage(), e);
|
|
|
+ } finally {
|
|
|
+ latch.countDown();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private void processSopGroup(String sopId, List<SopUserLogsVo> userLogsVos,LocalDateTime currentTime, Map<String,
|
|
|
+ QwGroupChat> groupChatMap,CourseConfig config,Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
|
|
|
+ List<Company> companies) throws Exception {
|
|
|
+ QwSopRuleTimeVO ruleTimeVO = sopMapper.selectQwSopByClickHouseId(sopId);
|
|
|
+
|
|
|
+ if (ruleTimeVO == null) {
|
|
|
+// sopUserLogsMapper.deleteSopUserLogsBySopId(sopId);
|
|
|
+ log.error("SOP ID {} 已删除或不存在,相关日志已清除。", sopId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ QwSopTemp qwSopTemp = qwSopTempMapper.selectQwSopTempById(ruleTimeVO.getTempId());
|
|
|
+ if (qwSopTemp == null) {
|
|
|
+// sopUserLogsMapper.deleteSopUserLogsBySopId(sopId);
|
|
|
+ log.error("SOP ID {} 模板不存在,相关日志已清除。", sopId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ ruleTimeVO.setTempStatus(qwSopTemp.getStatus());
|
|
|
+ ruleTimeVO.setTempGap(qwSopTemp.getGap());
|
|
|
+
|
|
|
+ if (ruleTimeVO.getStatus() == 0 || "0".equals(ruleTimeVO.getTempStatus())) {
|
|
|
+// SopUserLogs sopUserLogs = new SopUserLogs();
|
|
|
+// sopUserLogs.setSopId(sopId);
|
|
|
+// sopUserLogs.setStatus(2);
|
|
|
+// sopUserLogsMapper.updateSopUserLogsByStatus(sopUserLogs);
|
|
|
+ log.error("SOP ID {} 的状态为停用,相关日志状态已更新。", sopId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<QwSopTempRules> rulesList = qwSopTempRulesService.listByTempId(ruleTimeVO.getTempId());
|
|
|
+ if (rulesList.isEmpty()) {
|
|
|
+ log.error("SOP ID {} 的 TempSetting 为空,跳过处理。", sopId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(ruleTimeVO.getCorpId());
|
|
|
+
|
|
|
+ if (qwCompany == null ) {
|
|
|
+ log.error("SOP ID {} 的 公司信息为空 为空,跳过处理。", sopId);
|
|
|
+ return ;
|
|
|
+ }
|
|
|
+
|
|
|
+ CountDownLatch userLogsLatch = new CountDownLatch(userLogsVos.size());
|
|
|
+ for (SopUserLogsVo logVo : userLogsVos) {
|
|
|
+ processUserLogAsync(logVo, ruleTimeVO, rulesList, userLogsLatch, currentTime, groupChatMap,qwCompany.getMiniAppId(),
|
|
|
+ config,miniMap,companies);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 等待所有用户日志处理完成
|
|
|
+ try {
|
|
|
+ userLogsLatch.await();
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ log.error("等待用户日志处理完成时被中断: {}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ log.info("SOP ID {} 的所有用户日志已处理完毕。", sopId);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Async("sopTaskExecutor")
|
|
|
+ @Retryable(
|
|
|
+ value = { Exception.class },
|
|
|
+ maxAttempts = 3,
|
|
|
+ backoff = @Backoff(delay = 2000)
|
|
|
+ )
|
|
|
+ public void processUserLogAsync(SopUserLogsVo logVo, QwSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings,
|
|
|
+ CountDownLatch latch, LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap,
|
|
|
+ String miniAppId,CourseConfig config,Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
|
|
|
+ List<Company> companies) {
|
|
|
+ try {
|
|
|
+ processUserLog(logVo, ruleTimeVO, tempSettings,currentTime, groupChatMap, miniAppId, config,miniMap,companies);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理用户日志 {} 时发生异常: {}", logVo.getId(), e.getMessage(), e);
|
|
|
+ } finally {
|
|
|
+ latch.countDown();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private void processUserLog(SopUserLogsVo logVo, QwSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings,
|
|
|
+ LocalDateTime currentTime, Map<String, QwGroupChat> groupChatMap,String miniAppId,
|
|
|
+ CourseConfig config,Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
|
|
|
+ List<Company> companies) {
|
|
|
+ try {
|
|
|
+
|
|
|
+ LocalDate startDate = LocalDate.parse(logVo.getStartTime(), DATE_FORMATTER);
|
|
|
+ LocalDate currentDate = currentTime.toLocalDate();
|
|
|
+
|
|
|
+ long daysBetween = ChronoUnit.DAYS.between(startDate, currentDate);
|
|
|
+ int tempGap = ruleTimeVO.getTempGap();
|
|
|
+
|
|
|
+ if (tempGap <= 0) {
|
|
|
+ log.error("SOP ID {} 的 TempGap {} 无效,跳过处理。", logVo.getSopId(), tempGap);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ int intervalDay = (int) (daysBetween / tempGap);
|
|
|
+ if (intervalDay < 0 || intervalDay >= tempSettings.size()) {
|
|
|
+ log.info("用户日志 {} 的 intervalDay {} 超出 TempSettings 范围,跳过处理。", logVo.getId(), intervalDay);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ long day = daysBetween;
|
|
|
+ if(day == 0 && ruleTimeVO.getIsAutoSop() == 1){
|
|
|
+ day = 1;
|
|
|
+ }else{
|
|
|
+ day++;
|
|
|
+ }
|
|
|
+ List<QwSopTempSetting.Content> contents = getDay(tempSettings, day);
|
|
|
+ if (contents == null || contents.isEmpty()) {
|
|
|
+ log.error("SOP ID {} 的 TempSetting 内容为空,跳过处理。天数 {}", logVo.getSopId(),day);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ //获取企业微信员工的称呼//从redis里或者从库里取
|
|
|
+ QwUser qwUserByRedis = qwExternalContactService.getQwUserByRedis(logVo.getCorpId(),logVo.getQwUserId());
|
|
|
+ if (qwUserByRedis==null){
|
|
|
+ log.error("无企微员工信息 {} 跳过处理。:{}", logVo.getUserId(),logVo.getCorpId());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ String qwUserId = String.valueOf(qwUserByRedis.getId()).trim();
|
|
|
+ String companyUserId = String.valueOf(qwUserByRedis.getCompanyUserId()).trim();
|
|
|
+ String companyId = String.valueOf(qwUserByRedis.getCompanyId()).trim();
|
|
|
+ Integer sendMsgType = qwUserByRedis.getSendMsgType();
|
|
|
+
|
|
|
+ if (StringUtil.strIsNullOrEmpty(companyUserId) || StringUtil.strIsNullOrEmpty(companyId) || "null".equals(companyUserId)) {
|
|
|
+ log.error("员工未绑定销售账号或公司,跳过处理:"+qwUserId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ CompanyUser companyUser = companyUserService.selectCompanyUserByIdForRedis(Long.valueOf(companyUserId));
|
|
|
+ if (Objects.nonNull(companyUser)) {
|
|
|
+ if (!StringUtil.strIsNullOrEmpty(companyUser.getDomain())) {
|
|
|
+ logVo.setDomain(companyUser.getDomain().trim());
|
|
|
+ } else {
|
|
|
+ logVo.setDomain(config.getRealLinkDomainName().trim());
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ logVo.setDomain(config.getRealLinkDomainName().trim());
|
|
|
+ }
|
|
|
+
|
|
|
+ //寻找时间
|
|
|
+// LocalDateTime currentTime = LocalDateTime.of(2024, 12, 25,23 , 40);
|
|
|
+
|
|
|
+ // 先算好 60分钟后 ~ 再60分钟后的时间段
|
|
|
+ LocalDateTime startRangeFirst = currentTime.plusMinutes(60);
|
|
|
+
|
|
|
+ // 如果发现已经跨天
|
|
|
+ if (!startRangeFirst.toLocalDate().equals(currentDate)) {
|
|
|
+ // 更新 currentDate
|
|
|
+ currentDate = startRangeFirst.toLocalDate();
|
|
|
+
|
|
|
+ // 重新计算 daysBetween
|
|
|
+ daysBetween = ChronoUnit.DAYS.between(startDate, currentDate);
|
|
|
+ intervalDay = (int) (daysBetween / tempGap);
|
|
|
+ day = daysBetween;
|
|
|
+ if(day == 0 && ruleTimeVO.getIsAutoSop() == 1){
|
|
|
+ day = 1;
|
|
|
+ }else{
|
|
|
+ day++;
|
|
|
+ }
|
|
|
+//
|
|
|
+// // 再次验证 intervalDay 是否在范围内
|
|
|
+// if (intervalDay < 0 || intervalDay >= tempSettings.size()) {
|
|
|
+// log.info("跨天后,intervalDay={} 超出 TempSettings 范围,跳过。", intervalDay);
|
|
|
+// return;
|
|
|
+// }
|
|
|
+//
|
|
|
+// if (daysBetween % tempGap != 0) {
|
|
|
+// log.error("天数差 {} 不是 tempGap {} 的整数倍,跳过操作,SopId {} ", daysBetween, tempGap,logVo.getSopId());
|
|
|
+// return;
|
|
|
+// }
|
|
|
+
|
|
|
+ // 重新拿新的 “天” 的 Setting
|
|
|
+ contents = getDay(tempSettings, day);
|
|
|
+ if (contents == null || contents.isEmpty()) {
|
|
|
+ log.error("跨天-SOP ID {} 的 TempSetting 内容为空,跳过处理。", logVo.getSopId());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 只有整倍数才做事
|
|
|
+ if (daysBetween % tempGap != 0) {
|
|
|
+ log.error("天数差 {} 不是 tempGap {} 的整数倍,跳过操作,SopId {} ", daysBetween, tempGap,logVo.getSopId());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ for (QwSopTempSetting.Content content : contents) {
|
|
|
+ try {
|
|
|
+
|
|
|
+ LocalTime elementLocalTime = LocalTime.parse(content.getTime());
|
|
|
+ LocalDateTime elementDateTime = LocalDateTime.of(currentTime.toLocalDate(), elementLocalTime);
|
|
|
+
|
|
|
+ // 动态调整 elementDateTime 的日期
|
|
|
+ if (elementLocalTime.isBefore(currentTime.toLocalTime())) {
|
|
|
+ elementDateTime = elementDateTime.plusDays(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ LocalDateTime startRange = currentTime.plusMinutes(60);
|
|
|
+ LocalDateTime endRange = startRange.plusMinutes(60);
|
|
|
+
|
|
|
+ // 跨天逻辑修正:仅当 startRange 的时间晚于 endRange 的时间时调整
|
|
|
+ if (startRange.toLocalTime().isAfter(endRange.toLocalTime())
|
|
|
+ && startRange.toLocalDate().equals(endRange.toLocalDate())) {
|
|
|
+ endRange = endRange.plusDays(1); // 将 endRange 调整为第二天
|
|
|
+ }
|
|
|
+ if (!elementDateTime.isBefore(startRange) && !elementDateTime.isAfter(endRange.minusMinutes(1))) {
|
|
|
+
|
|
|
+ // 如果时间差在目标范围内,更新记录
|
|
|
+ // 组合年月日和element的时间
|
|
|
+ LocalDate targetDate = startDate.plusDays(intervalDay * tempGap);
|
|
|
+
|
|
|
+ // 将 targetDate 和 elementTime 组合成 LocalDateTime
|
|
|
+ LocalDateTime dateTime = LocalDateTime.of(targetDate, elementLocalTime);
|
|
|
+
|
|
|
+ // 将 LocalDateTime 转换为 Date
|
|
|
+ Date sendTime = Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
|
|
|
+
|
|
|
+ SopUserLogsInfo userLogsInfo=new SopUserLogsInfo();
|
|
|
+ userLogsInfo.setSopId(logVo.getSopId());
|
|
|
+ userLogsInfo.setUserLogsId(logVo.getId());
|
|
|
+
|
|
|
+ List<SopUserLogsInfo> sopUserLogsInfos = sopUserLogsInfoMapper.selectSopUserLogsInfoList(userLogsInfo);
|
|
|
+ if (logVo.getIsRegister() == 1) {
|
|
|
+ List<Long> externalContactIdList = PubFun.listToNewList(sopUserLogsInfos, SopUserLogsInfo::getExternalId);
|
|
|
+ if (!externalContactIdList.isEmpty()) {
|
|
|
+ List<QwExternalContact> list = qwExternalContactService.list(new QueryWrapper<QwExternalContact>().isNotNull("fs_user_id").in("id", externalContactIdList));
|
|
|
+ Map<Long, QwExternalContact> map = PubFun.listToMapByGroupObject(list, QwExternalContact::getId);
|
|
|
+ sopUserLogsInfos = sopUserLogsInfos.stream().filter(e -> map.containsKey(e.getExternalId())).collect(Collectors.toList());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取fsUserId TODO
|
|
|
+// Set<Long> externalIds = sopUserLogsInfos.stream().map(SopUserLogsInfo::getExternalId).collect(Collectors.toSet());
|
|
|
+// if (!externalIds.isEmpty()) {
|
|
|
+// List<QwExternalContact> externalContactList = qwExternalContactService.list(Wrappers.<QwExternalContact>lambdaQuery().in(QwExternalContact::getId, externalIds));
|
|
|
+// sopUserLogsInfos.forEach(s -> {
|
|
|
+// QwExternalContact qwExternalContact = externalContactList.stream().filter(e -> Objects.equals(s.getExternalId(), e.getId())).findFirst().orElse(null);
|
|
|
+// if (Objects.nonNull(qwExternalContact)) {
|
|
|
+// s.setFsUserId(qwExternalContact.getFsUserId());
|
|
|
+// }
|
|
|
+// });
|
|
|
+// }
|
|
|
+
|
|
|
+
|
|
|
+ insertSopUserLogs(sopUserLogsInfos, logVo, sendTime, ruleTimeVO, content, qwUserId,
|
|
|
+ companyUserId, companyId, qwUserByRedis.getWelcomeText(),qwUserByRedis.getQwUserName(),
|
|
|
+ groupChatMap, miniAppId,config,miniMap, sendMsgType,companies);
|
|
|
+
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("解析模板内容 {} 失败: {}", content.getTime(), e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("解析解析模板 {} 失败: {}", logVo.getStartTime(), e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private List<QwSopTempSetting.Content> getDay(List<QwSopTempRules> tempSettings, long days){
|
|
|
+ List<QwSopTempRules> collect = tempSettings.stream().filter(e -> e.getDayNum() == days && e.getTime() != null).collect(Collectors.toList());
|
|
|
+ AtomicInteger i = new AtomicInteger();
|
|
|
+ return collect.stream().map(e -> {
|
|
|
+ QwSopTempSetting.Content content = new QwSopTempSetting.Content();
|
|
|
+ content.setId(e.getId());
|
|
|
+ content.setType(e.getType());
|
|
|
+ content.setContentType(e.getContentType() != null ? e.getContentType().toString() : null);
|
|
|
+ content.setSetting(e.getSettingList().stream().map(s -> {
|
|
|
+ QwSopTempSetting.Content.Setting setting = JSON.parseObject(s.getContent(), QwSopTempSetting.Content.Setting.class);
|
|
|
+ setting.setId(s.getId());
|
|
|
+ return setting;
|
|
|
+ }).collect(Collectors.toList()));
|
|
|
+ content.setAddTag(e.getAddTag());
|
|
|
+ content.setDelTag(e.getDelTag());
|
|
|
+ content.setTime(e.getTime());
|
|
|
+ content.setIsOfficial(e.getIsOfficial());
|
|
|
+ content.setCourseId(e.getCourseId());
|
|
|
+ content.setVideoId(e.getVideoId());
|
|
|
+ content.setCourseType(e.getCourseType());
|
|
|
+ content.setAiTouch(e.getAiTouch());
|
|
|
+ return content;
|
|
|
+ }).sorted(Comparator.comparing(e -> LocalTime.parse(e.getTime() + ":00"))).peek(e -> e.setIndex(i.getAndIncrement())).collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ //消息处理
|
|
|
+ private void insertSopUserLogs(List<SopUserLogsInfo> sopUserLogsInfos, SopUserLogsVo logVo, Date sendTime,
|
|
|
+ QwSopRuleTimeVO ruleTimeVO, QwSopTempSetting.Content content,
|
|
|
+ String qwUserId,String companyUserId,String companyId,String welcomeText,String qwUserName,
|
|
|
+ Map<String, QwGroupChat> groupChatMap,String miniAppId,CourseConfig config,
|
|
|
+ Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap, Integer sendMsgType,
|
|
|
+ List<Company> companies) {
|
|
|
+ String formattedSendTime = sendTime.toInstant()
|
|
|
+ .atZone(ZoneId.systemDefault())
|
|
|
+ .format(DATE_TIME_FORMATTER);
|
|
|
+ int type = content.getType();
|
|
|
+ Long courseId = content.getCourseId();
|
|
|
+ Long videoId = content.getVideoId();
|
|
|
+ Integer isOfficial = content.getIsOfficial() != null ? Integer.valueOf(content.getIsOfficial()) : 0;
|
|
|
+
|
|
|
+
|
|
|
+ // 发送语音 start
|
|
|
+ if(content.getSetting() == null){
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ List<QwSopTempSetting.Content.Setting> setting = content.getSetting().stream().filter(e -> "7".equals(e.getContentType())).collect(Collectors.toList());
|
|
|
+ if (!setting.isEmpty()) {
|
|
|
+ List<String> valuesList = PubFun.listToNewList(setting, QwSopTempSetting.Content.Setting::getValue);
|
|
|
+ if (valuesList != null && !valuesList.isEmpty()) {
|
|
|
+ try {
|
|
|
+ List<QwSopTempVoice> voiceList = qwSopTempVoiceService.getVoiceByText(Long.parseLong(companyUserId), valuesList);
|
|
|
+ if (voiceList != null && !voiceList.isEmpty()) {
|
|
|
+ Map<String, QwSopTempVoice> collect = voiceList.stream().collect(Collectors.toMap(QwSopTempVoice::getVoiceTxt, e -> e));
|
|
|
+ setting.parallelStream().filter(e -> "7".equals(e.getContentType())).forEach(st -> {
|
|
|
+ QwSopTempVoice voice = collect.get(st.getValue());
|
|
|
+ if (voice.getVoiceUrl() == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ st.setVoiceUrl(voice.getVoiceUrl());
|
|
|
+ st.setVoiceDuration(voice.getDuration() + "");
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ throw new RuntimeException(e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+// // 发送语音 end
|
|
|
+ if (content.getType()==5){
|
|
|
+ sopAddTag(logVo,content,sendTime);
|
|
|
+ }
|
|
|
+
|
|
|
+ //当语音模板的qw_sop_temp_voice中无对应语音,就不生成qw_sop_logs记录
|
|
|
+ if (content.getType() == 7 && content.getSetting() != null && !content.getSetting().isEmpty()) {
|
|
|
+ if (content.getSetting().get(0).getVoiceUrl() == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (StringUtils.isNotEmpty(logVo.getChatId())) {
|
|
|
+ QwGroupChat groupChat = groupChatMap.get(logVo.getChatId());
|
|
|
+ ruleTimeVO.setSendType(6);
|
|
|
+ ruleTimeVO.setType(2);
|
|
|
+ if (groupChat.getChatUserList() != null && !groupChat.getChatUserList().isEmpty()) {
|
|
|
+ QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, groupChat.getChatId(), groupChat.getName(), null, isOfficial, null,null);
|
|
|
+ handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
|
|
|
+ type, qwUserId, companyUserId, companyId, groupChat.getChatId(), welcomeText, qwUserName,
|
|
|
+ null, true, miniAppId, groupChat,config, miniMap, null, sendMsgType,companies);
|
|
|
+ }
|
|
|
+// if (content.getIndex() == 0) {
|
|
|
+// QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, groupChat.getChatId(), groupChat.getName(), null, isOfficial, null);
|
|
|
+// handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
|
|
|
+// type, qwUserId, companyUserId, companyId, groupChat.getChatId(), welcomeText, qwUserName,
|
|
|
+// null, true, miniAppId, groupChat,config, miniMap, null, sendMsgType,companies);
|
|
|
+// } else {
|
|
|
+// if(groupChat.getChatUserList() != null && !groupChat.getChatUserList().isEmpty()){
|
|
|
+// groupChat.getChatUserList().forEach(user -> {
|
|
|
+// ruleTimeVO.setSendType(2);
|
|
|
+// ruleTimeVO.setRemark("客户群催课");
|
|
|
+// QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, user.getUserId(), user.getName(), null, isOfficial, null);
|
|
|
+// handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
|
|
|
+// type, qwUserId, companyUserId, companyId, user.getId().toString(), welcomeText, qwUserName,
|
|
|
+// null, false, miniAppId, groupChat,config, miniMap, null, sendMsgType,companies);
|
|
|
+// });
|
|
|
+// }
|
|
|
+// }
|
|
|
+ } else {
|
|
|
+ // 处理每个 externalContactId
|
|
|
+ sopUserLogsInfos.forEach(contactId -> {
|
|
|
+ try {
|
|
|
+ String externalId = contactId.getExternalId().toString();
|
|
|
+ String externalUserName = contactId.getExternalUserName();
|
|
|
+ Long fsUserId = contactId.getFsUserId();
|
|
|
+ Integer grade = contactId.getGrade();
|
|
|
+ QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, contactId.getExternalContactId(), externalUserName, fsUserId, isOfficial, contactId.getExternalId(),contactId.getIsDaysNotStudy());
|
|
|
+ handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
|
|
|
+ type, qwUserId, companyUserId, companyId, externalId, welcomeText, qwUserName, fsUserId, false, miniAppId,
|
|
|
+ null,config, miniMap, grade, sendMsgType,companies);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理 externalContactId {} 时发生异常: {}", contactId, e.getMessage(), e);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+// // 处理每个 externalContactId
|
|
|
+// sopUserLogsInfos.forEach(contactId -> {
|
|
|
+// try {
|
|
|
+// String externalId = contactId.getExternalId().toString();
|
|
|
+// String externalUserName = contactId.getExternalUserName();
|
|
|
+// Long fsUserId = contactId.getFsUserId();
|
|
|
+// QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, contactId.getExternalContactId(), externalUserName, fsUserId,isOfficial,contactId.getExternalId());
|
|
|
+// handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
|
|
|
+// type, qwUserId, companyUserId, companyId, externalId, welcomeText,qwUserName);
|
|
|
+// } catch (Exception e) {
|
|
|
+// log.error("处理 externalContactId {} 时发生异常: {}", contactId, e.getMessage(), e);
|
|
|
+// }
|
|
|
+// });
|
|
|
+ }
|
|
|
+
|
|
|
+ private void sopAddTag(SopUserLogsVo logVo, QwSopTempSetting.Content content, Date sendTime) {
|
|
|
+ String id = logVo.getId();
|
|
|
+ String addTag = content.getAddTag();
|
|
|
+ String delTag = content.getDelTag();
|
|
|
+ String corpId = logVo.getCorpId();
|
|
|
+ if (addTag!=null || delTag!=null) {
|
|
|
+ QwSopTag qwSopTag = new QwSopTag();
|
|
|
+ qwSopTag.setAddTags(addTag);
|
|
|
+ qwSopTag.setDelTags(delTag);
|
|
|
+ qwSopTag.setCorpId(corpId);
|
|
|
+ qwSopTag.setSopUserLogsId(id);
|
|
|
+ qwSopTag.setType(1);
|
|
|
+ qwSopTag.setStatus(1);
|
|
|
+ qwSopTag.setSendTime(sendTime);
|
|
|
+ qwSopTag.setCreateTime(new Date());
|
|
|
+ qwSopTagMapper.insertQwSopTag(qwSopTag);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private QwSopLogs createBaseLog(String formattedSendTime, SopUserLogsVo logVo,
|
|
|
+ QwSopRuleTimeVO ruleTimeVO, String externalContactId,
|
|
|
+ String externalUserName, Long fsUserId,Integer isOfficial,
|
|
|
+ Long externalId,Integer isDaysNotStudy) {
|
|
|
+ QwSopLogs sopLogs = new QwSopLogs();
|
|
|
+ sopLogs.setSendTime(formattedSendTime);
|
|
|
+ sopLogs.setQwUserid(logVo.getQwUserId());
|
|
|
+ sopLogs.setCorpId(logVo.getCorpId());
|
|
|
+ sopLogs.setLogType(ruleTimeVO.getType());
|
|
|
+ sopLogs.setTakeRecords(0);
|
|
|
+
|
|
|
+ if (isOfficial != 1 && Integer.valueOf(1).equals(isDaysNotStudy)) {
|
|
|
+ sopLogs.setSendStatus(5L);
|
|
|
+ sopLogs.setRemark("E级客户不发送");
|
|
|
+ }else {
|
|
|
+ sopLogs.setSendStatus(3L);
|
|
|
+ }
|
|
|
+
|
|
|
+ sopLogs.setReceivingStatus(0L);
|
|
|
+
|
|
|
+ if (isOfficial == 1) {
|
|
|
+
|
|
|
+ if (logVo.getIsSampSend()== 1) {
|
|
|
+ if (fsUserId == null || Long.valueOf(0L).equals(fsUserId)) {
|
|
|
+ sopLogs.setSendType(2);
|
|
|
+ sopLogs.setRemark("未绑定小程序用户,单链补发");
|
|
|
+ //时间设置成固定8点
|
|
|
+ LocalDateTime dateTime = LocalDateTime.parse(formattedSendTime, DATE_TIME_FORMATTER);
|
|
|
+ sopLogs.setSendTime(OUTPUT_FORMATTER.format(dateTime));
|
|
|
+ } else {
|
|
|
+ sopLogs.setSendType(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ }else {
|
|
|
+ if (fsUserId == null || Long.valueOf(0L).equals(fsUserId)) {
|
|
|
+ sopLogs.setTakeRecords(1);
|
|
|
+ sopLogs.setSendType(1);
|
|
|
+ }else {
|
|
|
+ sopLogs.setSendType(1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ } else if (isOfficial == 0) {
|
|
|
+ sopLogs.setSendType(ruleTimeVO.getSendType() == 1 ? 2 : ruleTimeVO.getSendType());
|
|
|
+ } else {
|
|
|
+ sopLogs.setSendType(ruleTimeVO.getSendType());
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ String[] userKey = logVo.getUserId().split("\\|");
|
|
|
+ sopLogs.setCompanyId(Long.valueOf(userKey[2].trim()));
|
|
|
+ if (StringUtils.isNotEmpty(userKey[0].trim())){
|
|
|
+ sopLogs.setQwUserKey(Long.valueOf(userKey[0].trim()));
|
|
|
+ }
|
|
|
+ sopLogs.setSopId(logVo.getSopId());
|
|
|
+ sopLogs.setSort(Integer.valueOf(logVo.getStartTime().replaceAll("-","")));
|
|
|
+ sopLogs.setExternalUserId(externalContactId);
|
|
|
+ sopLogs.setExternalId(externalId);
|
|
|
+ sopLogs.setExternalUserName(externalUserName);
|
|
|
+ sopLogs.setFsUserId(fsUserId);
|
|
|
+ sopLogs.setUserLogsId(logVo.getId());
|
|
|
+
|
|
|
+ if (ObjectUtil.isNotEmpty(logVo.getActualQwId())){
|
|
|
+ sopLogs.setQwUserKey(logVo.getActualQwId());
|
|
|
+ }
|
|
|
+ return sopLogs;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void handleLogBasedOnType(QwSopLogs sopLogs, QwSopTempSetting.Content content,
|
|
|
+ SopUserLogsVo logVo, Date sendTime, Long courseId, Long videoId, int type, String qwUserId,
|
|
|
+ String companyUserId, String companyId, String externalId, String welcomeText,
|
|
|
+ String qwUserName, Long fsUserId, boolean isGroupChat, String miniAppId,
|
|
|
+ QwGroupChat groupChat,CourseConfig config,
|
|
|
+ Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
|
|
|
+ Integer grade, Integer sendMsgType ,List<Company> companies ) {
|
|
|
+ switch (type) {
|
|
|
+ case 1:
|
|
|
+ handleNormalMessage(sopLogs, content,companyUserId);
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ handleCourseMessage(sopLogs, content, logVo, sendTime, courseId, videoId,
|
|
|
+ qwUserId, companyUserId, companyId, externalId, welcomeText,qwUserName, fsUserId,
|
|
|
+ isGroupChat, miniAppId, groupChat,config,miniMap, grade, sendMsgType,companies);
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ handleOrderMessage(sopLogs, content);
|
|
|
+ break;
|
|
|
+ case 4:
|
|
|
+// handleAIMessage(sopLogs, content);
|
|
|
+ break;
|
|
|
+ case 5:
|
|
|
+// handleTagMessage(sopLogs, content);
|
|
|
+ break;
|
|
|
+ case 7:
|
|
|
+ handleVoiceMessage(sopLogs, content, companyUserId);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ log.error("未知的消息类型 {},跳过处理。", type);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ private void handleVoiceMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content, String companyUserId) {
|
|
|
+ sopLogs.setContentJson(JSON.toJSONString(content));
|
|
|
+ enqueueQwSopLogs(sopLogs);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void handleNormalMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content,String companyUserId) {
|
|
|
+
|
|
|
+ sopLogs.setContentJson(JSON.toJSONString(content));
|
|
|
+ enqueueQwSopLogs(sopLogs);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void handleAIMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content) {
|
|
|
+ sopLogs.setContentJson(JSON.toJSONString(content));
|
|
|
+ sopLogs.setSort(3);
|
|
|
+ enqueueQwSopLogs(sopLogs);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void handleCourseMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content,
|
|
|
+ SopUserLogsVo logVo, Date sendTime, Long courseId, Long videoId, String qwUserId, String companyUserId,
|
|
|
+ String companyId, String externalId, String welcomeText, String qwUserName,
|
|
|
+ Long fsUserId, boolean isGroupChat, String miniAppId, QwGroupChat groupChat,CourseConfig config,Map<Long,
|
|
|
+ Map<Integer, List<CompanyMiniapp>>> miniMap,Integer grade, Integer sendMsgType,
|
|
|
+ List<Company> companies) {
|
|
|
+ QwExternalContact contact = null;
|
|
|
+ if(logVo.getExternalId() != null){
|
|
|
+ contact = qwExternalContactMapper.selectById(logVo.getExternalId());
|
|
|
+ }
|
|
|
+ // 深拷贝 Content 对象,避免使用 JSON
|
|
|
+ QwSopTempSetting.Content clonedContent = deepCopyContent(content);
|
|
|
+ if (clonedContent == null) {
|
|
|
+ log.error("Failed to clone content, skipping handleCourseMessage.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+//
|
|
|
+// Integer courseType = clonedContent.getCourseType();
|
|
|
+
|
|
|
+ String isOfficial = clonedContent.getIsOfficial();
|
|
|
+
|
|
|
+ List<QwSopTempSetting.Content.Setting> settings = clonedContent.getSetting();
|
|
|
+ if (settings == null || settings.isEmpty()) {
|
|
|
+ log.error("Cloned content settings are empty, skipping.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 顺序处理每个 Setting,避免过多的并行导致线程开销
|
|
|
+ for (QwSopTempSetting.Content.Setting setting : settings) {
|
|
|
+ switch (setting.getContentType()) {
|
|
|
+ //文字和短链一起
|
|
|
+ case "1":
|
|
|
+ case "3":
|
|
|
+// if ("1".equals(setting.getIsBindUrl())) {
|
|
|
+// String link;
|
|
|
+// if (isGroupChat) {
|
|
|
+// FsCourseLinkCreateParam createParam = new FsCourseLinkCreateParam();
|
|
|
+// createParam.setCourseId(courseId);
|
|
|
+// createParam.setVideoId(videoId);
|
|
|
+// createParam.setCorpId(logVo.getCorpId());
|
|
|
+// createParam.setCompanyUserId(Long.parseLong(companyUserId));
|
|
|
+// createParam.setCompanyId(Long.parseLong(companyId));
|
|
|
+// createParam.setChatId(logVo.getChatId());
|
|
|
+// createParam.setQwUserId(Long.valueOf(qwUserId));
|
|
|
+// createParam.setDays(setting.getExpiresDays());
|
|
|
+// R createLink = courseLinkService.createRoomLinkUrl(createParam);
|
|
|
+// if (createLink.get("code").equals(500)) {
|
|
|
+// throw new BaseException("链接生成失败!");
|
|
|
+// }
|
|
|
+// try {
|
|
|
+// groupChat.getChatUserList().stream().filter(e -> e.getUserList() != null && !e.getUserList().isEmpty()).forEach(e -> {
|
|
|
+// Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(e.getUserList(), GroupUserExternalVo::getUserId);
|
|
|
+// GroupUserExternalVo vo = userMap.get(groupChat.getOwner());
|
|
|
+// if (vo != null && vo.getId() != null) {
|
|
|
+// sopLogs.setFsUserId(vo.getFsUserId());
|
|
|
+// addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, vo.getId().toString(), logVo);
|
|
|
+// }
|
|
|
+// });
|
|
|
+// } catch (Exception e) {
|
|
|
+// log.error("群聊创建看课记录失败!", e);
|
|
|
+// }
|
|
|
+// link = (String) createLink.get("url");
|
|
|
+// } else {
|
|
|
+// addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo);
|
|
|
+// link = generateShortLink(setting, logVo, sendTime, courseId, videoId,
|
|
|
+// qwUserId, companyUserId, companyId, externalId,isOfficial,sopLogs.getFsUserId());
|
|
|
+// }
|
|
|
+
|
|
|
+// if (StringUtils.isNotEmpty(link)) {
|
|
|
+// if ("3".equals(setting.getContentType())) {
|
|
|
+// setting.setLinkUrl(link);
|
|
|
+// } else {
|
|
|
+// String currentValue = setting.getValue();
|
|
|
+// if (currentValue == null) {
|
|
|
+// setting.setValue(link);
|
|
|
+// } else {
|
|
|
+// setting.setValue(currentValue
|
|
|
+// .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(welcomeText) ? "" : welcomeText)
|
|
|
+// .replaceAll("#客户称呼#", contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus())|| "0".equals(contact.getStageStatus())?"同学":contact.getStageStatus())
|
|
|
+// + "\n" + link);
|
|
|
+// }
|
|
|
+// }
|
|
|
+// } else {
|
|
|
+// log.error("生成短链失败,跳过设置 URL。");
|
|
|
+// }
|
|
|
+
|
|
|
+// } else {
|
|
|
+ if ("1".equals(setting.getContentType())) {
|
|
|
+ String defaultName = "同学";
|
|
|
+ if(contact != null && StringUtils.isNotEmpty(contact.getName()) && !"待同步客户".equals(contact.getName())){
|
|
|
+ defaultName = contact.getName();
|
|
|
+ }
|
|
|
+ setting.setValue(setting.getValue()
|
|
|
+ .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(welcomeText) ? "" : welcomeText)
|
|
|
+ .replaceAll("#客户称呼#", contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus())|| "0".equals(contact.getStageStatus())?defaultName:contact.getStageStatus()));
|
|
|
+ }
|
|
|
+// }
|
|
|
+ break;
|
|
|
+ //小程序单独
|
|
|
+ case "4":
|
|
|
+ if (isGroupChat) {
|
|
|
+ try {
|
|
|
+ groupChat.getChatUserList().stream().filter(e -> e.getUserList() != null && !e.getUserList().isEmpty()).forEach(e -> {
|
|
|
+ Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(e.getUserList(), GroupUserExternalVo::getUserId);
|
|
|
+ GroupUserExternalVo vo = userMap.get(groupChat.getOwner());
|
|
|
+ if (vo != null && vo.getId() != null) {
|
|
|
+ sopLogs.setFsUserId(vo.getFsUserId());
|
|
|
+ addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, vo.getId().toString(), logVo);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("群聊创建看课记录失败!", e);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo);
|
|
|
+ }
|
|
|
+
|
|
|
+ String sortLink = createLinkByMiniApp(setting, logVo, sendTime, courseId, videoId,
|
|
|
+ qwUserId, companyUserId, companyId, externalId,isOfficial,sopLogs.getFsUserId(), isGroupChat ? groupChat.getChatId() : null);
|
|
|
+
|
|
|
+ if(sopLogs.getSendType()==1){
|
|
|
+ setting.setMiniprogramAppid(miniAppId);
|
|
|
+ }else {
|
|
|
+ int miniType = getLevel(grade);
|
|
|
+ //算主备小程序
|
|
|
+ String finalAppId = getAppIdFromMiniMap(miniMap, companyId, sendMsgType, grade);
|
|
|
+
|
|
|
+ if (StringUtil.strIsNullOrEmpty(finalAppId)) {
|
|
|
+ finalAppId = miniAppId;
|
|
|
+ }
|
|
|
+
|
|
|
+ setting.setMiniType(miniType);
|
|
|
+ if (!StringUtil.strIsNullOrEmpty(finalAppId)) {
|
|
|
+ setting.setMiniprogramAppid(finalAppId);
|
|
|
+ } else {
|
|
|
+ log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ setting.setMiniprogramPage(sortLink.replaceAll("^[\\s\\u2005]+", ""));
|
|
|
+
|
|
|
+ try {
|
|
|
+ setting.setMiniprogramPicUrl(StringUtil.strIsNullOrEmpty(setting.getMiniprogramPicUrl())? config.getSidebarImageUrl():setting.getMiniprogramPicUrl());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("赋值-小程序封面地址失败-" + e);
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ //app
|
|
|
+ case "9":
|
|
|
+ addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo);
|
|
|
+
|
|
|
+ QwCreateLinkByAppVO linkByApp = createLinkByApp(setting, logVo, sendTime, courseId, videoId,
|
|
|
+ qwUserId, companyUserId, companyId, externalId,sopLogs.getCorpId(),qwUserName);
|
|
|
+
|
|
|
+ setting.setLinkUrl(linkByApp.getSortLink().replaceAll("^[\\s\\u2005]+", ""));
|
|
|
+ setting.setAppLinkUrl(linkByApp.getAppMsgLink().replaceAll("^[\\s\\u2005]+", ""));
|
|
|
+ setting.setCourseUrl(setting.getLinkImageUrl());
|
|
|
+ setting.setTitle(setting.getLinkDescribe()); //小节名称
|
|
|
+
|
|
|
+ break;
|
|
|
+ //自定义小程序
|
|
|
+ case "10":
|
|
|
+ addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo);
|
|
|
+
|
|
|
+ Optional<Company> matchedCompany = companies.stream()
|
|
|
+ .filter(company -> String.valueOf(company.getCompanyId()).equals(companyId))
|
|
|
+ .findFirst();
|
|
|
+ if (matchedCompany.isPresent()) {
|
|
|
+ Company company = matchedCompany.get();
|
|
|
+
|
|
|
+ String customMiniAppId = company.getCustomMiniAppId();
|
|
|
+
|
|
|
+ if (customMiniAppId != null && !customMiniAppId.trim().isEmpty()) {
|
|
|
+ setting.setMiniprogramAppid(customMiniAppId);
|
|
|
+ } else {
|
|
|
+ setting.setMiniprogramAppid("该公司未配置自定义小程序:"+companyId);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ setting.setMiniprogramAppid("未找到匹配的公司的自定义小程序:"+companyId);
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ //直播小程序单独
|
|
|
+ case "12":
|
|
|
+ String sortLiveLink;
|
|
|
+ sortLiveLink = "/pages_course/living?companyId=" + companyId + "&companyUserId=" + companyUserId + "&liveId=" + setting.getLiveId();
|
|
|
+
|
|
|
+
|
|
|
+ String miniprogramLiveTitle = setting.getMiniprogramTitle();
|
|
|
+ int maxLiveLength = 17;
|
|
|
+ setting.setMiniprogramTitle(miniprogramLiveTitle.length() > maxLiveLength ? miniprogramLiveTitle.substring(0, maxLiveLength) + "..." : miniprogramLiveTitle);
|
|
|
+ String json = configService.selectConfigByKey("his.config");
|
|
|
+ FSSysConfig sysConfig= JSON.parseObject(json,FSSysConfig.class);
|
|
|
+ setting.setMiniprogramAppid(sysConfig.getAppId());
|
|
|
+ setting.setMiniprogramPage(sortLiveLink);
|
|
|
+ setting.setContentType("4");
|
|
|
+ try {
|
|
|
+ setting.setMiniprogramPicUrl(StringUtil.strIsNullOrEmpty(setting.getMiniprogramPicUrl()) ? "https://cos.his.cdwjyyh.com/fs/20250331/ec2b4e73be8048afbd526124a655ad56.png" : setting.getMiniprogramPicUrl());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("赋值-小程序封面地址失败-" + e);
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ clonedContent.getSetting().stream().filter(e -> "1".equals(e.getIsBindUrl())).forEach(e -> {
|
|
|
+ e.setIsBindUrl("0");
|
|
|
+// e.setLinkDescribe(null);
|
|
|
+ e.setLinkUrl(null);
|
|
|
+// e.setLinkImageUrl(null);
|
|
|
+ });
|
|
|
+ sopLogs.setContentJson(JSON.toJSONString(clonedContent));
|
|
|
+ enqueueQwSopLogs(sopLogs);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getAppIdFromMiniMap(Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
|
|
|
+ String companyId,
|
|
|
+ int sendMsgType,
|
|
|
+ Integer grade) {
|
|
|
+ if (miniMap.isEmpty() || sendMsgType != 1) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<Integer, List<CompanyMiniapp>> gradeMap = miniMap.get(Long.valueOf(companyId));
|
|
|
+ if (gradeMap == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ int listIndex = getLevel(grade);
|
|
|
+ List<CompanyMiniapp> miniapps = gradeMap.get(listIndex);
|
|
|
+
|
|
|
+ if (miniapps == null || miniapps.isEmpty()) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ CompanyMiniapp companyMiniapp = miniapps.get(0);
|
|
|
+ return (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId()))
|
|
|
+ ? companyMiniapp.getAppId()
|
|
|
+ : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static int getLevel(Integer grade) {
|
|
|
+ int effectiveGrade = (grade == null) ? 5 : grade;
|
|
|
+ int listIndex = (effectiveGrade == 1 || effectiveGrade == 2) ? 0 : 1;
|
|
|
+ return listIndex;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 深拷贝 Content 对象,避免使用 JSON 进行序列化和反序列化
|
|
|
+ */
|
|
|
+ private QwSopTempSetting.Content deepCopyContent(QwSopTempSetting.Content content) {
|
|
|
+ if (content == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return content.clone();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void handleOrderMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content) {
|
|
|
+ sopLogs.setContentJson(JSON.toJSONString(content));
|
|
|
+ enqueueQwSopLogs(sopLogs);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ private String generateShortLink(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
|
|
|
+ Long courseId, Long videoId, String qwUserId,
|
|
|
+ String companyUserId, String companyId, String externalId,String isOfficial, Long fsUserId) {
|
|
|
+ // 获取缓存的配置
|
|
|
+ CourseConfig config;
|
|
|
+ synchronized(configLock) {
|
|
|
+ config = cachedCourseConfig;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (config == null) {
|
|
|
+ log.error("CourseConfig is not loaded.");
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ // 手动创建 FsCourseLink 对象,避免使用 BeanUtils.copyProperties
|
|
|
+ FsCourseLink link = new FsCourseLink();
|
|
|
+ link.setCompanyId(Long.parseLong(companyId));
|
|
|
+ link.setQwUserId(Long.parseLong(qwUserId));
|
|
|
+ link.setCompanyUserId(Long.parseLong(companyUserId));
|
|
|
+ link.setVideoId(videoId.longValue());
|
|
|
+ link.setCorpId(logVo.getCorpId());
|
|
|
+ link.setCourseId(courseId.longValue());
|
|
|
+ link.setQwExternalId(Long.parseLong(externalId));
|
|
|
+
|
|
|
+ if (StringUtil.strIsNullOrEmpty(isOfficial)){
|
|
|
+ link.setLinkType(0);
|
|
|
+ }else {
|
|
|
+ if (isOfficial.equals("1")) {
|
|
|
+ if (fsUserId== null || Long.valueOf(0L).equals(fsUserId)){
|
|
|
+ link.setLinkType(0);
|
|
|
+ }else {
|
|
|
+ link.setLinkType(5);
|
|
|
+ }
|
|
|
+ }else if (isOfficial.equals("0")){
|
|
|
+ link.setLinkType(0);
|
|
|
+ }else{
|
|
|
+ link.setLinkType(0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ FsCourseRealLink courseMap = new FsCourseRealLink();
|
|
|
+ courseMap.setCompanyId(link.getCompanyId());
|
|
|
+ courseMap.setQwUserId(link.getQwUserId());
|
|
|
+ courseMap.setCompanyUserId(link.getCompanyUserId());
|
|
|
+ courseMap.setVideoId(link.getVideoId());
|
|
|
+ courseMap.setCorpId(link.getCorpId());
|
|
|
+ courseMap.setCourseId(link.getCourseId());
|
|
|
+ courseMap.setQwExternalId(link.getQwExternalId());
|
|
|
+ courseMap.setFsUserId(fsUserId);
|
|
|
+
|
|
|
+ if (StringUtil.strIsNullOrEmpty(isOfficial)){
|
|
|
+ courseMap.setLinkType(0);
|
|
|
+ }else {
|
|
|
+ if (isOfficial.equals("1")) {
|
|
|
+ if (fsUserId== null || Long.valueOf(0L).equals(fsUserId)){
|
|
|
+ courseMap.setLinkType(0);
|
|
|
+ }else {
|
|
|
+ courseMap.setLinkType(5);
|
|
|
+ }
|
|
|
+ }else if (isOfficial.equals("0")){
|
|
|
+ courseMap.setLinkType(0);
|
|
|
+ }else{
|
|
|
+ courseMap.setLinkType(0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ String courseJson = JSON.toJSONString(courseMap);
|
|
|
+ String realLinkFull = REAL_LINK_PREFIX + courseJson;
|
|
|
+ link.setRealLink(realLinkFull);
|
|
|
+
|
|
|
+ String randomString = generateRandomStringWithLock();
|
|
|
+ if (StringUtil.strIsNullOrEmpty(randomString)){
|
|
|
+ link.setLink(UUID.randomUUID().toString().replace("-", ""));
|
|
|
+ }else {
|
|
|
+ link.setLink(randomString);
|
|
|
+ }
|
|
|
+
|
|
|
+ link.setCreateTime(sendTime);
|
|
|
+
|
|
|
+ Integer expireDays = (setting.getExpiresDays() == null || setting.getExpiresDays() == 0)
|
|
|
+ ? config.getVideoLinkExpireDate()
|
|
|
+ : setting.getExpiresDays();
|
|
|
+
|
|
|
+ // 使用 Java 8 时间 API 计算过期时间
|
|
|
+ LocalDateTime sendDateTime = sendTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
|
|
+ LocalDateTime expireDateTime = sendDateTime.plusDays(expireDays-1);
|
|
|
+ expireDateTime = expireDateTime.toLocalDate().atTime(23, 59, 59);
|
|
|
+ Date updateTime = Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant());
|
|
|
+ link.setUpdateTime(updateTime);
|
|
|
+
|
|
|
+ //取销售绑定的二级域名
|
|
|
+ String sortLink = logVo.getDomain() + SHORT_LINK_PREFIX + link.getLink();
|
|
|
+ enqueueCourseLink(link);
|
|
|
+ return sortLink.replaceAll("^[\\s\\u2005]+", "");
|
|
|
+ }
|
|
|
+
|
|
|
+ private QwCreateLinkByAppVO createLinkByApp(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
|
|
|
+ Long courseId, Long videoId, String qwUserId,
|
|
|
+ String companyUserId, String companyId, String externalId,String corpId,String qwUserName){
|
|
|
+ // 获取缓存的配置
|
|
|
+ CourseConfig config;
|
|
|
+ synchronized(configLock) {
|
|
|
+ config = cachedCourseConfig;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (config == null) {
|
|
|
+ log.error("CourseConfig is not loaded.");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ FsCourseLink link = createFsCourseLink(corpId, sendTime, courseId, videoId, qwUserId,
|
|
|
+ companyUserId, companyId, externalId, 4);
|
|
|
+
|
|
|
+ FsCourseRealLink courseMap = new FsCourseRealLink();
|
|
|
+ BeanUtils.copyProperties(link,courseMap);
|
|
|
+
|
|
|
+ String courseJson = JSON.toJSONString(courseMap);
|
|
|
+ String realLinkFull = REAL_LINK_PREFIX + courseJson;
|
|
|
+ link.setRealLink(realLinkFull);
|
|
|
+
|
|
|
+ Date updateTime = createUpdateTime(setting, sendTime, config);
|
|
|
+
|
|
|
+ link.setUpdateTime(updateTime);
|
|
|
+
|
|
|
+ String sortLink = appLink+link.getLink()+"&videoId="+videoId;
|
|
|
+
|
|
|
+ String appMsgLink=appRealLink+link.getLink();
|
|
|
+
|
|
|
+ QwCreateLinkByAppVO byAppVO=new QwCreateLinkByAppVO();
|
|
|
+ byAppVO.setSortLink(sortLink);
|
|
|
+ byAppVO.setAppMsgLink(appMsgLink);
|
|
|
+
|
|
|
+ FsCourseSopAppLink fsCourseSopAppLink = createFsCourseSopAppLink(link.getLink(), sendTime, updateTime, companyId, companyUserId, qwUserId,
|
|
|
+ qwUserName, corpId, courseId, setting.getLinkTitle(), setting.getLinkImageUrl(), videoId,
|
|
|
+ setting.getLinkDescribe(), appMsgLink, externalId);
|
|
|
+
|
|
|
+ enqueueCourseSopAppLink(fsCourseSopAppLink);
|
|
|
+
|
|
|
+ enqueueCourseLink(link);
|
|
|
+
|
|
|
+ return byAppVO;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public FsCourseSopAppLink createFsCourseSopAppLink(String link, Date sendTime, Date updateTime, String companyId,
|
|
|
+ String companyUserId,String qwUserId,String qwUserName,String corpId,
|
|
|
+ Long courseId,String linkTile,String linkImageUrl,Long videoId,
|
|
|
+ String linkDescribe,String appMsgLink,String externalId){
|
|
|
+
|
|
|
+ FsCourseSopAppLink sopAppLink=new FsCourseSopAppLink();
|
|
|
+ sopAppLink.setLink(link);
|
|
|
+ sopAppLink.setCreateTime(sendTime);
|
|
|
+ sopAppLink.setUpdateTime(updateTime);
|
|
|
+ sopAppLink.setCompanyId(Long.parseLong(companyId));
|
|
|
+ sopAppLink.setCompanyUserId(Long.parseLong(companyUserId));
|
|
|
+ sopAppLink.setQwUserId(Long.parseLong(qwUserId));
|
|
|
+ sopAppLink.setQwUserName(qwUserName);
|
|
|
+ sopAppLink.setCorpId(corpId);
|
|
|
+ sopAppLink.setCourseId(courseId);
|
|
|
+ sopAppLink.setCourseTitle(linkTile);
|
|
|
+ sopAppLink.setCourseUrl(linkImageUrl);
|
|
|
+ sopAppLink.setVideoId(videoId);
|
|
|
+ sopAppLink.setVideoTitle(linkDescribe);
|
|
|
+ sopAppLink.setAppRealLink(appMsgLink);
|
|
|
+ sopAppLink.setQwExternalId(Long.parseLong(externalId));
|
|
|
+
|
|
|
+
|
|
|
+ return sopAppLink;
|
|
|
+ }
|
|
|
+
|
|
|
+ public FsCourseLink createFsCourseLink(String corpId, Date sendTime,Long courseId,Long videoId, String qwUserId,
|
|
|
+ String companyUserId, String companyId,String externalId,Integer type){
|
|
|
+ // 手动创建 FsCourseLink 对象,避免使用 BeanUtils.copyProperties
|
|
|
+ FsCourseLink link = new FsCourseLink();
|
|
|
+ link.setCompanyId(Long.parseLong(companyId));
|
|
|
+ link.setQwUserId(Long.parseLong(qwUserId));
|
|
|
+ link.setCompanyUserId(Long.parseLong(companyUserId));
|
|
|
+ link.setVideoId(videoId.longValue());
|
|
|
+ link.setCorpId(corpId);
|
|
|
+ link.setCourseId(courseId.longValue());
|
|
|
+ link.setQwExternalId(Long.parseLong(externalId));
|
|
|
+ link.setLinkType(type); //小程序
|
|
|
+
|
|
|
+ String randomString = generateRandomStringWithLock();
|
|
|
+ if (StringUtil.strIsNullOrEmpty(randomString)){
|
|
|
+ link.setLink(UUID.randomUUID().toString().replace("-", ""));
|
|
|
+ }else {
|
|
|
+ link.setLink(randomString);
|
|
|
+ }
|
|
|
+
|
|
|
+ link.setCreateTime(sendTime);
|
|
|
+
|
|
|
+ return link;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Date createUpdateTime(QwSopTempSetting.Content.Setting setting,Date sendTime,CourseConfig config){
|
|
|
+
|
|
|
+ Integer expireDays = (setting.getExpiresDays() == null || setting.getExpiresDays() == 0)
|
|
|
+ ? config.getVideoLinkExpireDate()
|
|
|
+ : setting.getExpiresDays();
|
|
|
+
|
|
|
+// 使用 Java 8 时间 API 计算过期时间
|
|
|
+ LocalDateTime sendDateTime = sendTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
|
|
+ LocalDateTime expireDateTime = sendDateTime.plusDays(expireDays-1);
|
|
|
+ expireDateTime = expireDateTime.toLocalDate().atTime(23, 59, 59);
|
|
|
+ Date updateTime = Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant());
|
|
|
+
|
|
|
+ return updateTime;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String createLinkByMiniApp(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
|
|
|
+ Long courseId, Long videoId, String qwUserId,
|
|
|
+ String companyUserId, String companyId, String externalId,String isOfficial,Long fsUserId, String chatId) {
|
|
|
+ // 获取缓存的配置
|
|
|
+ CourseConfig config;
|
|
|
+ synchronized(configLock) {
|
|
|
+ config = cachedCourseConfig;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (config == null) {
|
|
|
+ log.error("CourseConfig is not loaded.");
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+// if (StringUtils.isEmpty(config.getMiniprogramPage())){
|
|
|
+// log.error("miniprogramPage is not loaded.");
|
|
|
+// return "";
|
|
|
+// }
|
|
|
+
|
|
|
+ // 手动创建 FsCourseLink 对象,避免使用 BeanUtils.copyProperties
|
|
|
+ FsCourseLink link = new FsCourseLink();
|
|
|
+ link.setCompanyId(Long.parseLong(companyId));
|
|
|
+ link.setQwUserId(Long.parseLong(qwUserId));
|
|
|
+ link.setCompanyUserId(Long.parseLong(companyUserId));
|
|
|
+ link.setVideoId(videoId);
|
|
|
+ link.setCorpId(logVo.getCorpId());
|
|
|
+ link.setCourseId(courseId);
|
|
|
+ if(StringUtils.isEmpty(chatId)){
|
|
|
+ link.setQwExternalId(Long.parseLong(externalId));
|
|
|
+ }
|
|
|
+ link.setProjectCode(cloudHostProper.getProjectCode());
|
|
|
+ link.setChatId(chatId);
|
|
|
+
|
|
|
+ if (StringUtil.strIsNullOrEmpty(isOfficial)){
|
|
|
+ link.setLinkType(3);
|
|
|
+ }else {
|
|
|
+ if (isOfficial.equals("1")) {
|
|
|
+ if (fsUserId== null || Long.valueOf(0L).equals(fsUserId)){
|
|
|
+ link.setLinkType(3);
|
|
|
+ }else {
|
|
|
+ link.setLinkType(5);
|
|
|
+ }
|
|
|
+ }else if (isOfficial.equals("0")){
|
|
|
+ link.setLinkType(3);
|
|
|
+ }else{
|
|
|
+ link.setLinkType(3);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ String randomString = generateRandomStringWithLock();
|
|
|
+ if (StringUtil.strIsNullOrEmpty(randomString)){
|
|
|
+ link.setLink(UUID.randomUUID().toString().replace("-", ""));
|
|
|
+ }else {
|
|
|
+ link.setLink(randomString);
|
|
|
+ }
|
|
|
+
|
|
|
+ link.setCreateTime(sendTime);
|
|
|
+
|
|
|
+ FsCourseRealLink courseMap = new FsCourseRealLink();
|
|
|
+ BeanUtils.copyProperties(link,courseMap);
|
|
|
+
|
|
|
+ String courseJson = JSON.toJSONString(courseMap);
|
|
|
+ String realLinkFull = miniappRealLink + courseJson;
|
|
|
+ link.setRealLink(realLinkFull);
|
|
|
+
|
|
|
+
|
|
|
+ Integer expireDays = (setting.getExpiresDays() == null || setting.getExpiresDays() == 0)
|
|
|
+ ? config.getVideoLinkExpireDate()
|
|
|
+ : setting.getExpiresDays();
|
|
|
+
|
|
|
+ // 使用 Java 8 时间 API 计算过期时间
|
|
|
+ LocalDateTime sendDateTime = sendTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
|
|
+ LocalDateTime expireDateTime = sendDateTime.plusDays(expireDays-1);
|
|
|
+ expireDateTime = expireDateTime.toLocalDate().atTime(23, 59, 59);
|
|
|
+ Date updateTime = Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant());
|
|
|
+ link.setUpdateTime(updateTime);
|
|
|
+
|
|
|
+ //存短链-
|
|
|
+ enqueueCourseLink(link);
|
|
|
+ return link.getRealLink().replaceAll("^[\\s\\u2005]+", "");
|
|
|
+ }
|
|
|
+
|
|
|
+ private void addWatchLogIfNeeded(QwSopLogs sopLogs, Long videoId, Long courseId,
|
|
|
+ Date sendTime, String qwUserId, String companyUserId,
|
|
|
+ String companyId, String externalId,SopUserLogsVo logsVo) {
|
|
|
+ FsCourseWatchLog watchLog = new FsCourseWatchLog();
|
|
|
+ watchLog.setVideoId(videoId != null ? videoId.longValue() : null);
|
|
|
+ watchLog.setQwExternalContactId(externalId != null ? Long.valueOf(externalId) : null);
|
|
|
+ watchLog.setSendType(2);
|
|
|
+ watchLog.setQwUserId(Long.parseLong(qwUserId));
|
|
|
+ watchLog.setSopId(sopLogs.getSopId());
|
|
|
+ watchLog.setDuration(0L);
|
|
|
+ watchLog.setCourseId(courseId != null ? courseId.longValue() : null);
|
|
|
+ watchLog.setCompanyUserId(companyUserId != null ? Long.valueOf(companyUserId) : null);
|
|
|
+ watchLog.setCompanyId(companyId != null ? Long.valueOf(companyId) : null);
|
|
|
+ watchLog.setCreateTime(convertStringToDate(sopLogs.getSendTime(),"yyyy-MM-dd HH:mm:ss"));
|
|
|
+ watchLog.setUpdateTime(new Date());
|
|
|
+ watchLog.setLogType(3);
|
|
|
+ watchLog.setUserId(sopLogs.getFsUserId());
|
|
|
+ watchLog.setCampPeriodTime(convertStringToDate(logsVo.getStartTime(),"yyyy-MM-dd"));
|
|
|
+ enqueueWatchLog(watchLog);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 时间字符串转Date时间
|
|
|
+ * @param dateString
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public static Date convertStringToDate(String dateString,String pattern) {
|
|
|
+ if (dateString == null || dateString.isEmpty()) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
|
|
+ LocalDateTime localDateTime;
|
|
|
+ LocalDate localDate;
|
|
|
+ // 先解析成 LocalDate(只含年月日)
|
|
|
+ if (pattern.equals("yyyy-MM-dd")){
|
|
|
+ // 先解析成 LocalDate(只含年月日)
|
|
|
+ localDate = LocalDate.parse(dateString, formatter);
|
|
|
+ // 将 LocalDate 转为当天 00:00:00 的 LocalDateTime
|
|
|
+ localDateTime = localDate.atStartOfDay();
|
|
|
+ }else {
|
|
|
+ localDateTime = LocalDateTime.parse(dateString, formatter);
|
|
|
+ }
|
|
|
+ return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将 QwSopLogs 放入队列
|
|
|
+ */
|
|
|
+ private void enqueueQwSopLogs(QwSopLogs sopLogs) {
|
|
|
+ try {
|
|
|
+ boolean offered = qwSopLogsQueue.offer(sopLogs, 5, TimeUnit.SECONDS);
|
|
|
+ if (!offered) {
|
|
|
+ log.error("QwSopLogs 队列已满,无法添加日志: {}", JSON.toJSONString(sopLogs));
|
|
|
+ // 处理队列已满的情况,例如记录到失败队列或持久化存储
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ log.error("插入 QwSopLogs 队列时被中断: {}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将 FsCourseWatchLog 放入队列
|
|
|
+ */
|
|
|
+ private void enqueueWatchLog(FsCourseWatchLog watchLog) {
|
|
|
+ try {
|
|
|
+ boolean offered = watchLogsQueue.offer(watchLog, 5, TimeUnit.SECONDS);
|
|
|
+ if (!offered) {
|
|
|
+ log.error("FsCourseWatchLog 队列已满,无法添加日志: {}", JSON.toJSONString(watchLog));
|
|
|
+ // 处理队列已满的情况,例如记录到失败队列或持久化存储
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ log.error("插入 FsCourseWatchLog 队列时被中断: {}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将 FsCourseWatchLog 放入队列
|
|
|
+ */
|
|
|
+ private void enqueueCourseLink(FsCourseLink courseLink) {
|
|
|
+ try {
|
|
|
+ boolean offered = linkQueue.offer(courseLink, 5, TimeUnit.SECONDS);
|
|
|
+ if (!offered) {
|
|
|
+ log.error("FsCourseLink 队列已满,无法添加日志: {}", JSON.toJSONString(courseLink));
|
|
|
+ // 处理队列已满的情况,例如记录到失败队列或持久化存储
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ log.error("插入 FsCourseLink 队列时被中断: {}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将 FsCourseSopAppLing 放入队列
|
|
|
+ */
|
|
|
+ private void enqueueCourseSopAppLink(FsCourseSopAppLink sopAppLink) {
|
|
|
+ try {
|
|
|
+ boolean offered = sopAppLinks.offer(sopAppLink, 5, TimeUnit.SECONDS);
|
|
|
+ if (!offered) {
|
|
|
+ log.error("FsCourseSopAppLink 队列已满,无法添加日志: {}", JSON.toJSONString(sopAppLink));
|
|
|
+ // 处理队列已满的情况,例如记录到失败队列或持久化存储
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ log.error("插入 FsCourseLink 队列时被中断: {}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 消费 QwSopLogs 队列并进行批量插入
|
|
|
+ */
|
|
|
+ private void consumeQwSopLogs() {
|
|
|
+ List<QwSopLogs> batch = new ArrayList<>(BATCH_SIZE);
|
|
|
+ while (running || !qwSopLogsQueue.isEmpty()) {
|
|
|
+ try {
|
|
|
+ QwSopLogs log = qwSopLogsQueue.poll(1, TimeUnit.SECONDS);
|
|
|
+ if (log != null) {
|
|
|
+ batch.add(log);
|
|
|
+ }
|
|
|
+ if (batch.size() >= BATCH_SIZE || (!batch.isEmpty() && log == null)) {
|
|
|
+ if (!batch.isEmpty()) {
|
|
|
+ batchInsertQwSopLogs(new ArrayList<>(batch));
|
|
|
+ batch.clear();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ log.error("QwSopLogs 消费线程被中断: {}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理剩余的数据
|
|
|
+ if (!batch.isEmpty()) {
|
|
|
+ batchInsertQwSopLogs(batch);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 消费 FsCourseWatchLog 队列并进行批量插入
|
|
|
+ */
|
|
|
+ private void consumeCourseLink() {
|
|
|
+ List<FsCourseLink> batch = new ArrayList<>(BATCH_SIZE);
|
|
|
+ while (running || !linkQueue.isEmpty()) {
|
|
|
+ try {
|
|
|
+ FsCourseLink courseLink = linkQueue.poll(1, TimeUnit.SECONDS);
|
|
|
+ if (courseLink != null) {
|
|
|
+ batch.add(courseLink);
|
|
|
+ }
|
|
|
+ if (batch.size() >= BATCH_SIZE || (!batch.isEmpty() && courseLink == null)) {
|
|
|
+ if (!batch.isEmpty()) {
|
|
|
+ batchInsertFsCourseLink(new ArrayList<>(batch));
|
|
|
+ batch.clear();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ log.error("FsCourseLink 消费线程被中断: {}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理剩余的数据
|
|
|
+ if (!batch.isEmpty()) {
|
|
|
+ batchInsertFsCourseLink(batch);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 消费 FsCourseSopAppLink 队列并进行批量插入
|
|
|
+ */
|
|
|
+ private void consumeCourseSopAppLink() {
|
|
|
+ List<FsCourseSopAppLink> batch = new ArrayList<>(BATCH_SIZE);
|
|
|
+ while (running || !sopAppLinks.isEmpty()) {
|
|
|
+ try {
|
|
|
+ FsCourseSopAppLink courseSopAppLink = sopAppLinks.poll(1, TimeUnit.SECONDS);
|
|
|
+ if (courseSopAppLink != null) {
|
|
|
+ batch.add(courseSopAppLink);
|
|
|
+ }
|
|
|
+ if (batch.size() >= BATCH_SIZE || (!batch.isEmpty() && courseSopAppLink == null)) {
|
|
|
+ if (!batch.isEmpty()) {
|
|
|
+ batchInsertFsCourseSopAppLink(new ArrayList<>(batch));
|
|
|
+ batch.clear();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ log.error("FsCourseSopAppLink 消费线程被中断: {}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理剩余的数据
|
|
|
+ if (!batch.isEmpty()) {
|
|
|
+ batchInsertFsCourseSopAppLink(batch);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 消费 FsCourseWatchLog 队列并进行批量插入
|
|
|
+ */
|
|
|
+ private void consumeWatchLogs() {
|
|
|
+ List<FsCourseWatchLog> batch = new ArrayList<>(BATCH_SIZE);
|
|
|
+ while (running || !watchLogsQueue.isEmpty()) {
|
|
|
+ try {
|
|
|
+ FsCourseWatchLog watchLog = watchLogsQueue.poll(1, TimeUnit.SECONDS);
|
|
|
+ if (watchLog != null) {
|
|
|
+ batch.add(watchLog);
|
|
|
+ }
|
|
|
+ if (batch.size() >= BATCH_SIZE || (!batch.isEmpty() && watchLog == null)) {
|
|
|
+ if (!batch.isEmpty()) {
|
|
|
+ batchInsertFsCourseWatchLogs(new ArrayList<>(batch));
|
|
|
+ batch.clear();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ log.error("FsCourseWatchLog 消费线程被中断: {}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理剩余的数据
|
|
|
+ if (!batch.isEmpty()) {
|
|
|
+ batchInsertFsCourseWatchLogs(batch);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量插入 QwSopLogs
|
|
|
+ */
|
|
|
+ @Transactional
|
|
|
+ @Retryable(
|
|
|
+ value = { Exception.class },
|
|
|
+ maxAttempts = 3,
|
|
|
+ backoff = @Backoff(delay = 2000)
|
|
|
+ )
|
|
|
+ public void batchInsertQwSopLogs(List<QwSopLogs> logsToInsert) {
|
|
|
+ try {
|
|
|
+ qwSopLogsService.batchInsertQwSopLogs(logsToInsert);
|
|
|
+ log.info("批量插入 QwSopLogs 完成,共插入 {} 条记录。", logsToInsert.size());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("批量插入 QwSopLogs 失败: {}", e.getMessage(), e);
|
|
|
+ // 可选:将失败的数据记录到失败队列或持久化存储以便后续重试
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量插入 FsCourseWatchLog
|
|
|
+ */
|
|
|
+ @Transactional
|
|
|
+ @Retryable(
|
|
|
+ value = { Exception.class },
|
|
|
+ maxAttempts = 3,
|
|
|
+ backoff = @Backoff(delay = 2000)
|
|
|
+ )
|
|
|
+ public void batchInsertFsCourseWatchLogs(List<FsCourseWatchLog> watchLogsToInsert) {
|
|
|
+ try {
|
|
|
+ fsCourseWatchLogMapper.insertFsCourseWatchLogBatch(watchLogsToInsert);
|
|
|
+ log.info("批量插入 FsCourseWatchLog 完成,共插入 {} 条记录。", watchLogsToInsert.size());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("批量插入 FsCourseWatchLog 失败: {}", e.getMessage(), e);
|
|
|
+ // 可选:将失败的数据记录到失败队列或持久化存储以便后续重试
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量插入 FsCourseLink
|
|
|
+ */
|
|
|
+ @Transactional
|
|
|
+ @Retryable(
|
|
|
+ value = { Exception.class },
|
|
|
+ maxAttempts = 3,
|
|
|
+ backoff = @Backoff(delay = 2000)
|
|
|
+ )
|
|
|
+ public void batchInsertFsCourseLink(List<FsCourseLink> courseLinkToInsert) {
|
|
|
+ try {
|
|
|
+ fsCourseLinkMapper.insertFsCourseLinkBatch(courseLinkToInsert);
|
|
|
+ log.info("批量插入 FsCourseLink 完成,共插入 {} 条记录。", courseLinkToInsert.size());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("批量插入 FsCourseLink 失败: {}", e.getMessage(), e);
|
|
|
+ // 可选:将失败的数据记录到失败队列或持久化存储以便后续重试
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量插入 FsCourseSopAppLink
|
|
|
+ */
|
|
|
+ @Transactional
|
|
|
+ @Retryable(
|
|
|
+ value = { Exception.class },
|
|
|
+ maxAttempts = 3,
|
|
|
+ backoff = @Backoff(delay = 2000)
|
|
|
+ )
|
|
|
+ public void batchInsertFsCourseSopAppLink(List<FsCourseSopAppLink> courseSopAppLinkToInsert) {
|
|
|
+ try {
|
|
|
+ fsCourseSopAppLinkMapper.insertFsCourseSopAppLinkBatch(courseSopAppLinkToInsert);
|
|
|
+ log.info("批量插入 FsCourseSopAppLink 完成,共插入 {} 条记录。", courseSopAppLinkToInsert.size());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("批量插入 FsCourseSopAppLink 失败: {}", e.getMessage(), e);
|
|
|
+ // 可选:将失败的数据记录到失败队列或持久化存储以便后续重试
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void updateSopLogsByCancel() {
|
|
|
+ List<QwSopLogs> sopLogs = qwSopLogsMapper.selectQwSopLogsByCancel();
|
|
|
+ log.info("补发过期完课消息总条数:{}",sopLogs.size());
|
|
|
+ processUpdateQwSopLogs(sopLogs);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 定义一个方法来批量处理插入逻辑,支持每 500 条数据一次的批量插入
|
|
|
+ private void processUpdateQwSopLogs(List<QwSopLogs> sopLogs) {
|
|
|
+ // 定义批量插入的大小
|
|
|
+ int batchSize = 500;
|
|
|
+
|
|
|
+ // 循环处理外部用户 ID,每次处理批量大小的子集
|
|
|
+ for (int i = 0; i < sopLogs.size(); i += batchSize) {
|
|
|
+
|
|
|
+ int endIndex = Math.min(i + batchSize, sopLogs.size());
|
|
|
+ List<QwSopLogs> batchList = sopLogs.subList(i, endIndex); // 获取当前批次的子集
|
|
|
+
|
|
|
+ // 直接使用批次数据进行批量更新,不需要额外的 List
|
|
|
+ try {
|
|
|
+ qwSopLogsMapper.batchUpdateQwSopLogsByCancel(batchList);
|
|
|
+ log.info("正在补发条数:{}",batchSize);
|
|
|
+ } catch (Exception e) {
|
|
|
+ // 记录异常日志,方便后续排查问题
|
|
|
+ log.error("批量更新数据时发生异常,处理的批次起始索引为: " + i, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FsCourseFinishTempMapper fsCourseFinishTempMapper;
|
|
|
+ @Autowired
|
|
|
+ private QwExternalContactMapper qwExternalContactMapper;
|
|
|
+
|
|
|
+
|
|
|
+// @Override
|
|
|
+// @Transactional
|
|
|
+// public void creatMessMessage(QwSopLogs logs) {
|
|
|
+// // qwSopLogsMapper.insertQwSopLogs(logs);
|
|
|
+// QwSopTempSetting.Content content = JSON.parseObject(logs.getContentJson(), QwSopTempSetting.Content.class);
|
|
|
+// handleNormalMessage(logs, content,null);
|
|
|
+// }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void createCourseFinishMsg() {
|
|
|
+ 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();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理单个批次的方法
|
|
|
+ private void processBatch(List<FsCourseWatchLog> batch) {
|
|
|
+ List<FsCourseWatchLog> finishLogsToUpdate = new ArrayList<>();
|
|
|
+ List<QwSopLogs> sopLogsToInsert = new ArrayList<>();
|
|
|
+ log.info("开始执行处理批次方法-数量:{}",batch.size());
|
|
|
+ for (FsCourseWatchLog finishLog : batch) {
|
|
|
+ try {
|
|
|
+
|
|
|
+ try {
|
|
|
+
|
|
|
+ asyncCourseWatchFinishService.executeCourseWatchFinish(finishLog);
|
|
|
+
|
|
|
+ }catch (Exception e){
|
|
|
+ log.error("添加完课打备注失败",e);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询外部联系人信息
|
|
|
+ QwExternalContact externalContact = qwExternalContactMapper.selectQwExternalContactById(finishLog.getQwExternalContactId());
|
|
|
+ if (externalContact == null) {
|
|
|
+ log.error("外部联系人不存在: {}", finishLog.getQwExternalContactId());
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询完课模板信息
|
|
|
+ FsCourseFinishTemp finishTemp = fsCourseFinishTempMapper.selectFsCourseFinishTempByCompanyId(finishLog.getCompanyUserId(),finishLog.getCompanyId(), finishLog.getVideoId());
|
|
|
+
|
|
|
+ // 设置 finishLog 为已发送状态,并加入批量更新列表
|
|
|
+ finishLog.setSendFinishMsg(1);
|
|
|
+ finishLogsToUpdate.add(finishLog);
|
|
|
+
|
|
|
+ if (finishTemp == null) {
|
|
|
+// log.error("完课模板不存在: " + finishLog.getQwUserId() + ", " + finishLog.getVideoId());
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建 sopLogs 对象
|
|
|
+ QwSopLogs sopLogs = buildSopLogs(finishLog, externalContact, finishTemp);
|
|
|
+ if (sopLogs == null) {
|
|
|
+ log.error("生成完课发送记录为空-:{}", finishLog.getQwExternalContactId());
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果客户状态有效,则加入批量插入列表
|
|
|
+ if (isValidExternalContact(externalContact)) {
|
|
|
+ sopLogsToInsert.add(sopLogs);
|
|
|
+ } else {
|
|
|
+ log.info("完课消息-客户信息有误,不生成完课消息: {}", finishLog.getQwExternalContactId());
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ fsUserCompanyBindService.finish(externalContact.getFsUserId(), externalContact.getQwUserId(), externalContact.getCompanyUserId(), finishLog);
|
|
|
+ }catch (Exception e){
|
|
|
+ log.error("更新重粉看课状态失败",e);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理完课记录失败: {}", finishLog.getLogId(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 批量更新和插入
|
|
|
+ if (!finishLogsToUpdate.isEmpty()) {
|
|
|
+ try {
|
|
|
+ fsCourseWatchLogMapper.batchUpdateWatchLogSendMsg(finishLogsToUpdate);
|
|
|
+ log.info("批量更新 finishLog 成功,数量: {}", finishLogsToUpdate.size());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("批量更新 finishLog 失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!sopLogsToInsert.isEmpty()) {
|
|
|
+ try {
|
|
|
+ qwSopLogsService.batchInsertQwSopLogs(sopLogsToInsert);
|
|
|
+ log.info("批量插入 sopLogs 成功,数量: {}", sopLogsToInsert.size());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("批量插入 sopLogs 失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ log.info("结束处理批次方法-数量:{}",batch.size());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建 QwSopLogs 对象
|
|
|
+ */
|
|
|
+ private QwSopLogs buildSopLogs(FsCourseWatchLog finishLog, QwExternalContact externalContact, FsCourseFinishTemp finishTemp) {
|
|
|
+ QwSopCourseFinishTempSetting setting = new QwSopCourseFinishTempSetting();
|
|
|
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
+ LocalDateTime currentTime = LocalDateTime.now();
|
|
|
+ LocalDateTime newTime = currentTime.plusMinutes(3);
|
|
|
+ String newTimeString = newTime.format(formatter);
|
|
|
+
|
|
|
+ QwSopLogs sopLogs = new QwSopLogs();
|
|
|
+ sopLogs.setSendTime(newTimeString);
|
|
|
+ sopLogs.setQwUserid(externalContact.getUserId());
|
|
|
+ sopLogs.setCorpId(externalContact.getCorpId());
|
|
|
+ sopLogs.setLogType(2);
|
|
|
+ sopLogs.setSendType(3);
|
|
|
+ sopLogs.setSendStatus(3L);
|
|
|
+ sopLogs.setReceivingStatus(0L);
|
|
|
+ sopLogs.setSort(40000000);
|
|
|
+ sopLogs.setCompanyId(finishLog.getCompanyId());
|
|
|
+ sopLogs.setSopId(finishLog.getSopId());
|
|
|
+ sopLogs.setExternalUserId(externalContact.getExternalUserId());
|
|
|
+ sopLogs.setExternalUserName(externalContact.getName());
|
|
|
+ sopLogs.setFsUserId(finishLog.getUserId() != null ? finishLog.getUserId() : null );
|
|
|
+ sopLogs.setExternalId(finishLog.getQwExternalContactId());
|
|
|
+ sopLogs.setUserLogsId("-");
|
|
|
+
|
|
|
+ sopLogs.setQwUserKey(finishLog.getQwUserId() != null ? finishLog.getQwUserId() : null);
|
|
|
+
|
|
|
+ // 解析模板设置
|
|
|
+ List<QwSopCourseFinishTempSetting.Setting> settings = parseSettings(finishTemp.getSetting());
|
|
|
+ if (settings == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ //完课后若是小程序发送另外一堂课
|
|
|
+ saveWacthLogOfCourseLink(settings,sopLogs,newTimeString,finishLog,finishTemp);
|
|
|
+ // 处理音频内容
|
|
|
+ for (QwSopCourseFinishTempSetting.Setting st : settings) {
|
|
|
+ if (st.getContentType().equals("7")) {
|
|
|
+ Long companyUserId = finishLog.getCompanyUserId();
|
|
|
+ QwSopTempVoice qwSopTempVoice = sopTempVoiceService.selectQwSopTempVoiceByCompanyUserIdAndVoiceTxt(companyUserId, st.getValue());
|
|
|
+ if (qwSopTempVoice != null && qwSopTempVoice.getVoiceUrl() != null && qwSopTempVoice.getRecordType() == 1) {
|
|
|
+ st.setVoiceUrl(qwSopTempVoice.getVoiceUrl());
|
|
|
+ st.setVoiceDuration(String.valueOf(qwSopTempVoice.getDuration()));
|
|
|
+ } else if (qwSopTempVoice == null) {
|
|
|
+ if(companyUserId != null && st.getValue() != null){
|
|
|
+ qwSopTempVoice = new QwSopTempVoice();
|
|
|
+ qwSopTempVoice.setCompanyUserId(companyUserId);
|
|
|
+ qwSopTempVoice.setVoiceTxt(st.getValue());
|
|
|
+ qwSopTempVoice.setRecordType(0);
|
|
|
+ sopTempVoiceService.insertQwSopTempVoice(qwSopTempVoice);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+// for (QwSopCourseFinishTempSetting.Setting st : settings) {
|
|
|
+// if (st.getContentType().equals("7")) {
|
|
|
+// try {
|
|
|
+// AudioVO audioVO = AudioUtils.transferAudioSilkFromText(st.getValue(), finishLog.getCompanyUserId(), false);
|
|
|
+// st.setVoiceUrl(audioVO.getUrl());
|
|
|
+// st.setVoiceDuration(audioVO.getDuration() + "");
|
|
|
+// } catch (Exception e) {
|
|
|
+// log.error("音频生成失败: " + finishLog.getCompanyUserId(), e);
|
|
|
+// }
|
|
|
+// }
|
|
|
+// }
|
|
|
+
|
|
|
+ setting.setSetting(settings);
|
|
|
+ sopLogs.setContentJson(JSON.toJSONString(setting));
|
|
|
+ return sopLogs;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判定小程序的话新增创建看课记录,以及fsCourseLink
|
|
|
+ *
|
|
|
+ * @param settings
|
|
|
+ */
|
|
|
+ public void saveWacthLogOfCourseLink(List<QwSopCourseFinishTempSetting.Setting> settings, QwSopLogs sopLogs, String newTimeString, FsCourseWatchLog finishLog, FsCourseFinishTemp finishTemp){
|
|
|
+ String json = configService.selectConfigByKey("course.config");
|
|
|
+ CourseConfig config = JSON.parseObject(json, CourseConfig.class);
|
|
|
+ Date dataTime = new Date();
|
|
|
+ List<CompanyMiniapp> miniList = companyMiniappService.list(new QueryWrapper<CompanyMiniapp>().orderByAsc("sort_num"));
|
|
|
+ Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap = miniList.stream().collect(Collectors.groupingBy(CompanyMiniapp::getCompanyId, Collectors.groupingBy(CompanyMiniapp::getType)));
|
|
|
+
|
|
|
+ QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(sopLogs.getCorpId());
|
|
|
+ QwUser qwUser = qwExternalContactService.getQwUserByRedis(sopLogs.getCorpId(), sopLogs.getQwUserid());
|
|
|
+ if (qwUser == null){
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ for (QwSopCourseFinishTempSetting.Setting st : settings) {
|
|
|
+ switch (st.getContentType()) {
|
|
|
+ //小程序单独
|
|
|
+ case "4":
|
|
|
+ addWatchLogIfNeeded(sopLogs.getSopId(), st.getVideoId().intValue(), st.getCourseId().intValue(), sopLogs.getFsUserId(), String.valueOf(qwUser.getId()),qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(),
|
|
|
+ sopLogs.getExternalId(), newTimeString.substring(0, 10), dataTime);
|
|
|
+
|
|
|
+ String linkByMiniApp = createLinkByMiniApp(st, sopLogs.getCorpId(), dataTime, finishTemp.getCourseId().intValue(), Integer.valueOf(st.getVideoId().toString()),
|
|
|
+ String.valueOf(qwUser.getId()), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(), sopLogs.getExternalId(), config);
|
|
|
+
|
|
|
+
|
|
|
+ String miniAppId = null;
|
|
|
+ if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
|
|
|
+ Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(qwUser.getCompanyId()));
|
|
|
+ if (integerListMap != null) {
|
|
|
+ int listIndex = 0;
|
|
|
+ List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);
|
|
|
+
|
|
|
+ if (miniapps != null && !miniapps.isEmpty()) {
|
|
|
+ CompanyMiniapp companyMiniapp = miniapps.get(0);
|
|
|
+ if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
|
|
|
+ miniAppId = companyMiniapp.getAppId();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (StringUtil.strIsNullOrEmpty(miniAppId) && !StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
|
|
|
+ miniAppId = qwCompany.getMiniAppId();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
|
|
|
+ st.setMiniprogramAppid(miniAppId);
|
|
|
+ } else {
|
|
|
+ log.error("企业未配置小程序-" + sopLogs.getCorpId());
|
|
|
+ }
|
|
|
+
|
|
|
+ String miniprogramTitle = st.getMiniprogramTitle();
|
|
|
+ int maxLength = 17;
|
|
|
+ st.setMiniprogramTitle(miniprogramTitle.length() > maxLength ? miniprogramTitle.substring(0, maxLength)+"..." : miniprogramTitle);
|
|
|
+ st.setMiniprogramPage(linkByMiniApp);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ private Date processDate(String sendTimeParam) {
|
|
|
+ // 1. 获取当前日期(年月日)
|
|
|
+ LocalDate currentDate = LocalDate.now();
|
|
|
+
|
|
|
+ // 2. 解析传入的时分(支持 "HH:mm" 或 "H:mm")
|
|
|
+ LocalTime sendTime = LocalTime.parse(sendTimeParam);
|
|
|
+
|
|
|
+ // 3. 合并为 LocalDateTime
|
|
|
+ LocalDateTime dateTime = LocalDateTime.of(currentDate, sendTime);
|
|
|
+
|
|
|
+ // 4. 转换为 Date(需通过 Instant 和系统默认时区)
|
|
|
+ Date date = Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
|
|
|
+
|
|
|
+ return date;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 新增courseLink
|
|
|
+ *
|
|
|
+ * @param setting
|
|
|
+ * @param corpId
|
|
|
+ * @param sendTime
|
|
|
+ * @param courseId
|
|
|
+ * @param videoId
|
|
|
+ * @param qwUserId
|
|
|
+ * @param companyUserId
|
|
|
+ * @param companyId
|
|
|
+ * @param externalId
|
|
|
+ * @param config
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private String createLinkByMiniApp(QwSopCourseFinishTempSetting.Setting setting, String corpId, Date sendTime,
|
|
|
+ Integer courseId, Integer videoId, String qwUserId,
|
|
|
+ String companyUserId, String companyId, Long externalId, CourseConfig config) {
|
|
|
+
|
|
|
+ FsCourseLink link = createFsCourseLink(corpId, sendTime, courseId, videoId, qwUserId,
|
|
|
+ companyUserId, companyId, externalId, 3, null);
|
|
|
+
|
|
|
+ FsCourseRealLink courseMap = new FsCourseRealLink();
|
|
|
+ BeanUtils.copyProperties(link, courseMap);
|
|
|
+
|
|
|
+ String courseJson = JSON.toJSONString(courseMap);
|
|
|
+ String realLinkFull = miniappRealLink + courseJson;
|
|
|
+ link.setRealLink(realLinkFull);
|
|
|
+
|
|
|
+ Date updateTime = createUpdateTime(setting, sendTime, config);
|
|
|
+
|
|
|
+ link.setUpdateTime(updateTime);
|
|
|
+ //存短链-
|
|
|
+ fsCourseLinkMapper.insertFsCourseLink(link);
|
|
|
+ return link.getRealLink();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建courselink
|
|
|
+ * @param corpId
|
|
|
+ * @param sendTime
|
|
|
+ * @param courseId
|
|
|
+ * @param videoId
|
|
|
+ * @param qwUserId
|
|
|
+ * @param companyUserId
|
|
|
+ * @param companyId
|
|
|
+ * @param externalId
|
|
|
+ * @param type
|
|
|
+ * @param chatId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public FsCourseLink createFsCourseLink(String corpId, Date sendTime, Integer courseId, Integer videoId, String qwUserId,
|
|
|
+ String companyUserId, String companyId, Long externalId, Integer type, String chatId) {
|
|
|
+ // 手动创建 FsCourseLink 对象,避免使用 BeanUtils.copyProperties
|
|
|
+ FsCourseLink link = new FsCourseLink();
|
|
|
+ link.setCompanyId(Long.parseLong(companyId));
|
|
|
+ link.setQwUserId(Long.valueOf(qwUserId));
|
|
|
+ link.setCompanyUserId(Long.parseLong(companyUserId));
|
|
|
+ link.setVideoId(videoId.longValue());
|
|
|
+ link.setCorpId(corpId);
|
|
|
+ link.setCourseId(courseId.longValue());
|
|
|
+ link.setChatId(chatId);
|
|
|
+ link.setQwExternalId(externalId);
|
|
|
+ link.setLinkType(type); //小程序
|
|
|
+ link.setUNo(UUID.randomUUID().toString());
|
|
|
+ link.setProjectCode(cloudHostProper.getProjectCode());
|
|
|
+ String randomString = generateRandomStringWithLock();
|
|
|
+ if (StringUtil.strIsNullOrEmpty(randomString)) {
|
|
|
+ link.setLink(UUID.randomUUID().toString().replace("-", ""));
|
|
|
+ } else {
|
|
|
+ link.setLink(randomString);
|
|
|
+ }
|
|
|
+
|
|
|
+ link.setCreateTime(sendTime);
|
|
|
+
|
|
|
+ return link;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算过期时间
|
|
|
+ * @param setting
|
|
|
+ * @param sendTime
|
|
|
+ * @param config
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private Date createUpdateTime(QwSopCourseFinishTempSetting.Setting setting, Date sendTime, CourseConfig config) {
|
|
|
+
|
|
|
+ Integer expireDays = (setting.getExpiresDays() == null || setting.getExpiresDays() == 0)
|
|
|
+ ? config.getVideoLinkExpireDate()
|
|
|
+ : setting.getExpiresDays();
|
|
|
+
|
|
|
+// 使用 Java 8 时间 API 计算过期时间
|
|
|
+ LocalDateTime sendDateTime = sendTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
|
|
+ LocalDateTime expireDateTime = sendDateTime.plusDays(expireDays - 1);
|
|
|
+ expireDateTime = expireDateTime.toLocalDate().atTime(23, 59, 59);
|
|
|
+ Date updateTime = Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant());
|
|
|
+
|
|
|
+ return updateTime;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 增加看课记录
|
|
|
+ *
|
|
|
+ * @param sopId
|
|
|
+ * @param videoId
|
|
|
+ * @param courseId
|
|
|
+ * @param fsUserId
|
|
|
+ * @param qwUserId
|
|
|
+ * @param companyUserId
|
|
|
+ * @param companyId
|
|
|
+ * @param externalId
|
|
|
+ * @param startTime
|
|
|
+ * @param createTime
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private Long addWatchLogIfNeeded(String sopId, Integer videoId, Integer courseId,
|
|
|
+ Long fsUserId, String qwUserId, String companyUserId,
|
|
|
+ String companyId, Long externalId, String startTime, Date createTime) {
|
|
|
+
|
|
|
+ try {
|
|
|
+ FsCourseWatchLog watchLog = new FsCourseWatchLog();
|
|
|
+ watchLog.setVideoId(videoId != null ? videoId.longValue() : null);
|
|
|
+ watchLog.setQwExternalContactId(externalId);
|
|
|
+ watchLog.setSendType(2);
|
|
|
+ watchLog.setQwUserId(Long.valueOf(qwUserId));
|
|
|
+ watchLog.setSopId(sopId);
|
|
|
+ watchLog.setDuration(0L);
|
|
|
+ watchLog.setCourseId(courseId != null ? courseId.longValue() : null);
|
|
|
+ watchLog.setCompanyUserId(companyUserId != null ? Long.valueOf(companyUserId) : null);
|
|
|
+ watchLog.setCompanyId(companyId != null ? Long.valueOf(companyId) : null);
|
|
|
+ watchLog.setCreateTime(createTime);
|
|
|
+ watchLog.setUpdateTime(createTime);
|
|
|
+ watchLog.setLogType(3);
|
|
|
+ watchLog.setUserId(fsUserId);
|
|
|
+ watchLog.setCampPeriodTime(convertStringToDate(startTime, "yyyy-MM-dd"));
|
|
|
+
|
|
|
+ //存看课记录
|
|
|
+ int i = fsCourseWatchLogMapper.insertOrUpdateFsCourseWatchLog(watchLog);
|
|
|
+ return watchLog.getLogId();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("插入观看记录失败:" + e.getMessage());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析模板设置
|
|
|
+ */
|
|
|
+ private List<QwSopCourseFinishTempSetting.Setting> parseSettings(String jsonData) {
|
|
|
+ try {
|
|
|
+ if (jsonData.startsWith("[") && jsonData.endsWith("]")) {
|
|
|
+ return JSONArray.parseArray(jsonData, QwSopCourseFinishTempSetting.Setting.class);
|
|
|
+ } else {
|
|
|
+ String fixedJson = JSON.parseObject(jsonData, String.class);
|
|
|
+ return JSONArray.parseArray(fixedJson, QwSopCourseFinishTempSetting.Setting.class);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("解析模板设置失败", e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查外部联系人状态是否有效
|
|
|
+ */
|
|
|
+ private boolean isValidExternalContact(QwExternalContact externalContact) {
|
|
|
+ return externalContact.getStatus() == 0 || externalContact.getStatus() == 2 || externalContact.getStatus() == 3;
|
|
|
+ }
|
|
|
+}
|