|
|
@@ -0,0 +1,3431 @@
|
|
|
+package com.fs.app.taskService.impl;
|
|
|
+
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.alibaba.fastjson.JSONArray;
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
+import com.fs.app.taskService.SopWxLogsTaskService;
|
|
|
+import com.fs.common.config.FSSysConfig;
|
|
|
+import com.fs.common.core.redis.RedisCache;
|
|
|
+import com.fs.common.utils.CloudHostUtils;
|
|
|
+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.mapper.CompanyWxAccountMapper;
|
|
|
+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.crm.domain.CrmCustomer;
|
|
|
+import com.fs.crm.mapper.CrmCustomerMapper;
|
|
|
+import com.fs.his.domain.FsUser;
|
|
|
+import com.fs.his.mapper.FsUserMapper;
|
|
|
+import com.fs.his.utils.ConfigUtil;
|
|
|
+import com.fs.live.domain.LiveWatchLog;
|
|
|
+import com.fs.live.mapper.LiveWatchLogMapper;
|
|
|
+import com.fs.qw.domain.*;
|
|
|
+import com.fs.qw.mapper.LuckyBagCollectRecordMapper;
|
|
|
+import com.fs.qw.mapper.LuckyBagMapper;
|
|
|
+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.*;
|
|
|
+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.sop.vo.WxSopUserVo;
|
|
|
+import com.fs.system.domain.SysConfig;
|
|
|
+import com.fs.system.mapper.SysConfigMapper;
|
|
|
+import com.fs.system.service.ISysConfigService;
|
|
|
+import com.fs.voice.utils.StringUtil;
|
|
|
+import com.fs.wx.sop.domain.WxSopLogs;
|
|
|
+import com.fs.wx.sop.domain.WxSopUserInfo;
|
|
|
+import com.fs.wx.sop.service.IWxSopLogsService;
|
|
|
+import com.fs.wxcid.domain.WxContact;
|
|
|
+import com.fs.wxcid.mapper.WxContactMapper;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.BeanUtils;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.context.ApplicationContext;
|
|
|
+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.*;
|
|
|
+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 SopWxLogsTaskServiceImpl implements SopWxLogsTaskService {
|
|
|
+ private static final String APP_LINK_PREFIX = "/appcourse/pages/course/learning?course=";
|
|
|
+ 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 appActivitlLink = "/pages_course/activity.html?link=";
|
|
|
+ // 注册
|
|
|
+ private static final String registeredRealLink = "/pages_course/register.html?link=";
|
|
|
+ private static final String h5LiveShortLink = "/pages_course/livingInvite.html?s=";
|
|
|
+ private static final String h5miniappLink = "/pages_course/shortLink.html?s=";
|
|
|
+
|
|
|
+// 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 IWxSopLogsService wxSopLogsService;
|
|
|
+ @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;
|
|
|
+ @Autowired
|
|
|
+ private FsUserMapper fsUserMapper;
|
|
|
+ @Autowired
|
|
|
+ private LuckyBagCollectRecordMapper luckyBagCollectRecordMapper;
|
|
|
+ @Autowired
|
|
|
+ private LuckyBagMapper luckyBagMapper;
|
|
|
+ @Autowired
|
|
|
+ private CompanyMapper companyMapper;
|
|
|
+ @Autowired
|
|
|
+ private RedisCache redisCache;
|
|
|
+ @Autowired
|
|
|
+ private SysConfigMapper sysConfigMapper;
|
|
|
+ @Autowired
|
|
|
+ private ApplicationContext applicationContext;
|
|
|
+
|
|
|
+
|
|
|
+ // Blocking queues with bounded capacity to implement backpressure
|
|
|
+ private final BlockingQueue<QwSopLogs> qwSopLogsQueue = new LinkedBlockingQueue<>(20000);
|
|
|
+ private final BlockingQueue<WxSopLogs> wxSopLogsQueue = 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);
|
|
|
+ private final BlockingQueue<LiveWatchLog> zmLiveWatchQueue = new LinkedBlockingQueue<>(20000);
|
|
|
+
|
|
|
+ // Executors for consumer threads
|
|
|
+ private ExecutorService qwSopLogsExecutor;
|
|
|
+ private ExecutorService watchLogsExecutor;
|
|
|
+ private ExecutorService courseLinkExecutor;
|
|
|
+ private ExecutorService courseSopAppLinkExecutor;
|
|
|
+ private ExecutorService zmLiveWatchLogExecutor;
|
|
|
+ @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 AsyncCourseWatchFinishService asyncCourseWatchFinishService;
|
|
|
+ @Autowired
|
|
|
+ private IQwSopTempVoiceService sopTempVoiceService;
|
|
|
+ @Autowired
|
|
|
+ LiveWatchLogMapper liveWatchLogMapper;
|
|
|
+ @Autowired
|
|
|
+ private ConfigUtil configUtil;
|
|
|
+ @Autowired
|
|
|
+ private WxContactMapper wxContactMapper;
|
|
|
+ @Autowired
|
|
|
+ private CompanyWxAccountMapper companyWxAccountMapper;
|
|
|
+ @Autowired
|
|
|
+ private CrmCustomerMapper crmCustomerMapper;
|
|
|
+
|
|
|
+ @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;
|
|
|
+ });
|
|
|
+
|
|
|
+ zmLiveWatchLogExecutor = Executors.newSingleThreadExecutor(r -> {
|
|
|
+ Thread t = new Thread(r, "zmLiveWatchLogConsumer");
|
|
|
+ t.setDaemon(true);
|
|
|
+ return t;
|
|
|
+ });
|
|
|
+
|
|
|
+ qwSopLogsExecutor.submit(this::consumeQwSopLogs);
|
|
|
+ watchLogsExecutor.submit(this::consumeWatchLogs);
|
|
|
+ courseLinkExecutor.submit(this::consumeCourseLink);
|
|
|
+ courseSopAppLinkExecutor.submit(this::consumeCourseSopAppLink);
|
|
|
+ zmLiveWatchLogExecutor.submit(this::consumeZmLiveWatchQueue);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 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();
|
|
|
+ zmLiveWatchLogExecutor.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();
|
|
|
+ }
|
|
|
+ if (!zmLiveWatchLogExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
|
|
|
+ zmLiveWatchLogExecutor.shutdownNow();
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ qwSopLogsExecutor.shutdownNow();
|
|
|
+ watchLogsExecutor.shutdownNow();
|
|
|
+ courseLinkExecutor.shutdownNow();
|
|
|
+ courseSopAppLinkExecutor.shutdownNow();
|
|
|
+ zmLiveWatchLogExecutor.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<WxSopUserVo> sopUserLogsVos = sopUserLogsMapper.selectWxSopUserLogsListByTime(sopidList);
|
|
|
+ if (sopUserLogsVos.isEmpty()) {
|
|
|
+ log.info("没有需要处理的 个微SOP 用户日志。");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ Map<String, List<WxSopUserVo>> sopLogsGroupedById = sopUserLogsVos.stream()
|
|
|
+ .collect(Collectors.groupingBy(WxSopUserVo::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<WxSopUserVo>> entry : sopLogsGroupedById.entrySet()) {
|
|
|
+ String sopId = entry.getKey();
|
|
|
+ List<WxSopUserVo> userLogsVos = entry.getValue();
|
|
|
+ processSopGroupAsync(sopId, userLogsVos, sopGroupLatch, currentTime, 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<WxSopUserVo> wxSopUserVos, CountDownLatch latch, LocalDateTime currentTime,
|
|
|
+ CourseConfig config, Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,List<Company> companies) {
|
|
|
+ try {
|
|
|
+ log.info("当前线程: {}", Thread.currentThread().getName());
|
|
|
+ processSopGroup(sopId, wxSopUserVos, currentTime, config, miniMap, companies);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理 SOP ID {} 时发生异常: {}", sopId, e.getMessage(), e);
|
|
|
+ } finally {
|
|
|
+ latch.countDown();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private void processSopGroup(String sopId, List<WxSopUserVo> wxSopUserVos, LocalDateTime currentTime, CourseConfig config, Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
|
|
|
+ List<Company> companies) throws Exception {
|
|
|
+ WxSopRuleTimeVO ruleTimeVO = sopMapper.selectWxSopByClickHouseId(sopId);
|
|
|
+
|
|
|
+ if (ruleTimeVO == null) {
|
|
|
+// sopUserLogsMapper.deleteSopUserLogsBySopId(sopId);
|
|
|
+ log.error("SOP ID {} 已删除或不存在,相关日志已清除。", sopId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ QwSopTemp qwSopTemp = qwSopTempMapper.selectQwSopTempById(ruleTimeVO.getTempId());
|
|
|
+ if (qwSopTemp == null) {
|
|
|
+ log.error("SOP ID {} 模板不存在,相关日志已清除。", sopId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ ruleTimeVO.setTempStatus(qwSopTemp.getStatus());
|
|
|
+ ruleTimeVO.setTempGap(qwSopTemp.getGap());
|
|
|
+
|
|
|
+ if (ruleTimeVO.getStatus() == 0 || "0".equals(ruleTimeVO.getTempStatus())) {
|
|
|
+ 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.getQwCompanyByCompanyId(ruleTimeVO.getCompanyId());
|
|
|
+
|
|
|
+// QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(ruleTimeVO.getCorpId());
|
|
|
+
|
|
|
+// if (qwCompany == null) {
|
|
|
+// log.error("SOP ID {} 的 公司信息为空 为空,跳过处理。", sopId);
|
|
|
+// return;
|
|
|
+// }
|
|
|
+
|
|
|
+ CountDownLatch userLogsLatch = new CountDownLatch(wxSopUserVos.size());
|
|
|
+ for (WxSopUserVo logVo : wxSopUserVos) {
|
|
|
+ processUserLogAsync(logVo, ruleTimeVO, rulesList, userLogsLatch, currentTime, 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(WxSopUserVo logVo, WxSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings,
|
|
|
+ CountDownLatch latch, LocalDateTime currentTime,String miniAppId, CourseConfig config,
|
|
|
+ Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
|
|
|
+ List<Company> companies) {
|
|
|
+ try {
|
|
|
+ processUserLog(logVo, ruleTimeVO, tempSettings, currentTime, miniAppId, config, miniMap, companies);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理用户日志 {} 时发生异常: {}", logVo.getId(), e.getMessage(), e);
|
|
|
+ } finally {
|
|
|
+ latch.countDown();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private void processUserLog(WxSopUserVo logVo, WxSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings,
|
|
|
+ LocalDateTime currentTime, 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 && ObjectUtil.isNotEmpty(ruleTimeVO.getIsAutoSop()) && 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里或者从库里取
|
|
|
+ CrmCustomer wxUserByRedis = qwExternalContactService.getWxUserByRedis(logVo.getCustomerId());
|
|
|
+ if (wxUserByRedis == null) {
|
|
|
+ log.error("无企微员工信息 {} 跳过处理。:{}", logVo.getUserId(), logVo.getAccountId());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ WxContact wxContact = wxContactMapper.selectOne(new LambdaQueryWrapper<WxContact>().eq(WxContact::getCustomerId, logVo.getCustomerId()));
|
|
|
+ // 获取客户信息
|
|
|
+ CrmCustomer crmCustomer = crmCustomerMapper.selectCrmCustomerById(wxContact.getCustomerId());
|
|
|
+
|
|
|
+ String wxUserId = String.valueOf(wxUserByRedis.getCustomerId()).trim();
|
|
|
+ String companyId = String.valueOf(wxUserByRedis.getCompanyId()).trim();
|
|
|
+ String companyUserId = String.valueOf(wxContact.getCompanyUserId()).trim();
|
|
|
+ Integer sendMsgType = null;
|
|
|
+ String welcomeText = "hello";
|
|
|
+
|
|
|
+ if (StringUtil.strIsNullOrEmpty(companyUserId) || StringUtil.strIsNullOrEmpty(companyId) || "null".equals(companyUserId)) {
|
|
|
+ log.error("员工未绑定销售账号或公司,跳过处理:" + wxUserId);
|
|
|
+ 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());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 先算好 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++;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重新拿新的 “天” 的 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());
|
|
|
+
|
|
|
+ WxSopUserInfo wxSopUserInfo = new WxSopUserInfo();
|
|
|
+ wxSopUserInfo.setSopId(Long.valueOf(logVo.getSopId()));
|
|
|
+ wxSopUserInfo.setSopUserId(Long.valueOf(logVo.getId()));
|
|
|
+ wxSopUserInfo.setCustomerId(Long.valueOf(logVo.getCustomerId()));
|
|
|
+
|
|
|
+ List<WxSopUserInfo> sopUserLogsInfos = sopUserLogsInfoMapper.selectWxSopUserLogsInfoList(wxSopUserInfo);
|
|
|
+ insertSopUserLogs(sopUserLogsInfos, logVo, sendTime, ruleTimeVO, content, wxUserId,
|
|
|
+ companyUserId, companyId, welcomeText, wxUserByRedis.getCustomerName(),
|
|
|
+ miniAppId, config, miniMap, sendMsgType, companies,crmCustomer);
|
|
|
+ }
|
|
|
+ } 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());
|
|
|
+ content.setIsAtAll(e.getIsAtAll());
|
|
|
+ return content;
|
|
|
+ }).sorted(Comparator.comparing(e -> LocalTime.parse(e.getTime() + ":00"))).peek(e -> e.setIndex(i.getAndIncrement())).collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ //消息处理
|
|
|
+ private void insertSopUserLogs(List<WxSopUserInfo> sopUserLogsInfos, WxSopUserVo logVo, Date sendTime,
|
|
|
+ WxSopRuleTimeVO ruleTimeVO, QwSopTempSetting.Content content,
|
|
|
+ String wxUserId, String companyUserId, String companyId, String welcomeText, String qwUserName,
|
|
|
+ String miniAppId, CourseConfig config,Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap, Integer sendMsgType,
|
|
|
+ List<Company> companies,CrmCustomer crmCustomer) {
|
|
|
+ String formattedSendTime = sendTime.toInstant()
|
|
|
+ .atZone(ZoneId.systemDefault())
|
|
|
+ .format(DATE_TIME_FORMATTER);
|
|
|
+ int type = content.getType();
|
|
|
+ Long courseId = content.getCourseId();
|
|
|
+ Long videoId = content.getVideoId();
|
|
|
+ Long liveId = content.getLiveId();
|
|
|
+ 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()) || "16".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().forEach(st -> {
|
|
|
+ QwSopTempVoice voice = collect.get(st.getValue());
|
|
|
+ if (voice == null || voice.getVoiceUrl() == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 企微语音
|
|
|
+ if ("7".equals(st.getContentType())) {
|
|
|
+ st.setVoiceUrl(voice.getVoiceUrl());
|
|
|
+ st.setVoiceDuration(voice.getDuration() + "");
|
|
|
+ }
|
|
|
+ // app语音
|
|
|
+ else if ("16".equals(st.getContentType())) {
|
|
|
+ st.setVoiceUrl(voice.getUserVoiceUrl());
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理每个 externalContactId
|
|
|
+ sopUserLogsInfos.forEach(contactId -> {
|
|
|
+ try {
|
|
|
+// String externalId = contactId.getExternalId().toString();
|
|
|
+// String externalUserName = contactId.getExternalUserName();
|
|
|
+ Long customerId = contactId.getCustomerId();
|
|
|
+
|
|
|
+ String customerName = crmCustomer.getCustomerName();
|
|
|
+// String fsUserId = miniMap.get(ruleTimeVO.getCompanyId()).get(0).get(0).getAppId();
|
|
|
+ Long fsUserId = null;
|
|
|
+ Integer grade = contactId.getGrade();
|
|
|
+ WxSopLogs wxSopLogs = createWxBaseLog(formattedSendTime, logVo, ruleTimeVO, null, customerName, fsUserId, isOfficial, customerId, contactId.getIsDaysNotStudy());
|
|
|
+ handleLogBasedOnType(wxSopLogs, content, logVo, sendTime, courseId, videoId,
|
|
|
+ type, wxUserId, companyUserId, companyId, String.valueOf(customerId), welcomeText, qwUserName, fsUserId, false, miniAppId,
|
|
|
+ null, config, miniMap, grade, sendMsgType, companies, liveId);
|
|
|
+ } 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(WxSopUserVo 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 WxSopLogs createWxBaseLog(String formattedSendTime, WxSopUserVo logVo,
|
|
|
+ WxSopRuleTimeVO ruleTimeVO, String externalContactId,
|
|
|
+ String customerName, Long fsUserId, Integer isOfficial,
|
|
|
+ Long customerId, Integer isDaysNotStudy) {
|
|
|
+ WxSopLogs wxSopLogs = new WxSopLogs();
|
|
|
+
|
|
|
+ // 基础信息
|
|
|
+ wxSopLogs.setSendTime(formattedSendTime);
|
|
|
+ wxSopLogs.setAccountId(Long.valueOf(logVo.getCustomerId())); // 个微账号ID
|
|
|
+ wxSopLogs.setType(logVo.getType());
|
|
|
+ wxSopLogs.setFsUserId(fsUserId);
|
|
|
+ wxSopLogs.setWxContactId(customerId);
|
|
|
+ wxSopLogs.setWxContactName(customerName);
|
|
|
+
|
|
|
+
|
|
|
+ // 发送状态设置
|
|
|
+ if (isOfficial != 1 && Integer.valueOf(1).equals(isDaysNotStudy)) {
|
|
|
+ wxSopLogs.setSendStatus(5); // 5-消息作废
|
|
|
+ wxSopLogs.setRemark("E级客户不发送");
|
|
|
+ } else {
|
|
|
+ wxSopLogs.setSendStatus(3); // 3-待发送
|
|
|
+ }
|
|
|
+
|
|
|
+ // 发送类型设置
|
|
|
+ if (isOfficial == 1) {
|
|
|
+// if (logVo.getIsSampSend() == 1) {
|
|
|
+// if (fsUserId == null || Long.valueOf(0L).equals(fsUserId)) {
|
|
|
+// wxSopLogs.setSendType(2); // 2-单链补发
|
|
|
+// wxSopLogs.setRemark("未绑定小程序用户,单链补发");
|
|
|
+// // 时间设置成固定8点
|
|
|
+// LocalDateTime dateTime = LocalDateTime.parse(formattedSendTime, DATE_TIME_FORMATTER);
|
|
|
+// wxSopLogs.setSendTime(OUTPUT_FORMATTER.format(dateTime));
|
|
|
+// } else {
|
|
|
+// wxSopLogs.setSendType(1); // 1-正常发送
|
|
|
+// }
|
|
|
+// } else {
|
|
|
+ wxSopLogs.setSendType(1);
|
|
|
+// }
|
|
|
+ } else if (isOfficial == 0) {
|
|
|
+// wxSopLogs.setSendType(ruleTimeVO.getSendType() == 1 ? 2 : ruleTimeVO.getSendType());
|
|
|
+ wxSopLogs.setSendType(1);
|
|
|
+ } else {
|
|
|
+ wxSopLogs.setSendType(ruleTimeVO.getSendType());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 任务相关信息
|
|
|
+ wxSopLogs.setSopId(Long.valueOf(logVo.getSopId()));
|
|
|
+ wxSopLogs.setSopUserId(Long.valueOf(logVo.getId())); // 对应wx_sop_logs的sop_user_id字段
|
|
|
+
|
|
|
+ // 发送排序(使用开始时间去除横线后的数值)
|
|
|
+ wxSopLogs.setSendSort(Integer.valueOf(logVo.getStartTime().replaceAll("-", "")));
|
|
|
+
|
|
|
+ // 小程序用户ID
|
|
|
+ wxSopLogs.setFsUserId(fsUserId);
|
|
|
+
|
|
|
+ // 生成类型(默认为0自动生成)
|
|
|
+ wxSopLogs.setGenerateType(0);
|
|
|
+
|
|
|
+ // 备注信息(如果有额外需要记录的信息)
|
|
|
+ // wxSopLogs.setSendRemark(""); // 如果有需要可以设置
|
|
|
+
|
|
|
+ return wxSopLogs;
|
|
|
+ }
|
|
|
+
|
|
|
+ private QwSopLogs createBaseLog(String formattedSendTime, SopUserLogsVo logVo,
|
|
|
+ WxSopRuleTimeVO 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(WxSopLogs sopLogs, QwSopTempSetting.Content content,
|
|
|
+ WxSopUserVo 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, Long liveId) {
|
|
|
+ switch (type) {
|
|
|
+ case 1:
|
|
|
+ handleNormalMessage(sopLogs, content, companyUserId, companyId, isGroupChat, qwUserId, groupChat, externalId, logVo,sendTime);
|
|
|
+ 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;
|
|
|
+ //直播间发送类型
|
|
|
+ case 20:
|
|
|
+ handleLiveMessage(sopLogs, content, companyUserId, companyId, isGroupChat, qwUserId, groupChat, externalId, logVo, liveId);
|
|
|
+ break;
|
|
|
+ case 21:
|
|
|
+ handleGroupNoticeMessage(sopLogs, content, isGroupChat);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ log.error("未知的消息类型 {},跳过处理。", type);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理群公告消息
|
|
|
+ * @param sopLogs 日志对象
|
|
|
+ * @param content 内容对象
|
|
|
+ * @param isGroupChat 是否为群聊
|
|
|
+ */
|
|
|
+ private void handleGroupNoticeMessage(WxSopLogs sopLogs, QwSopTempSetting.Content content, boolean isGroupChat) {
|
|
|
+ // 群公告只能发给群聊
|
|
|
+ if (!isGroupChat) {
|
|
|
+ log.warn("群公告只能发给群聊,跳过处理");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置发送类型为21(群公告)
|
|
|
+ sopLogs.setSendType(21);
|
|
|
+ sopLogs.setContentJson(JSON.toJSONString(content));
|
|
|
+ enqueueQwSopLogs(sopLogs);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void handleVoiceMessage(WxSopLogs sopLogs, QwSopTempSetting.Content content, String companyUserId) {
|
|
|
+ sopLogs.setContentJson(JSON.toJSONString(content));
|
|
|
+ enqueueQwSopLogs(sopLogs);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void handleNormalMessage(WxSopLogs sopLogs, QwSopTempSetting.Content content, String companyUserId, String companyId,
|
|
|
+ boolean isGroupChat, String qwUserId, QwGroupChat groupChat, String externalId, WxSopUserVo logVo, Date sendTime) {
|
|
|
+
|
|
|
+ // 深拷贝 Content 对象,避免使用 JSON
|
|
|
+ QwSopTempSetting.Content clonedContent = deepCopyContent(content);
|
|
|
+ if (clonedContent == null) {
|
|
|
+// log.error("Failed to clone content, skipping handleCourseMessage.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<QwSopTempSetting.Content.Setting> settings = clonedContent.getSetting();
|
|
|
+ List<QwSopTempSetting.Content.Setting> settingAll = new ArrayList<>();
|
|
|
+ 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":break;
|
|
|
+ //图片
|
|
|
+ case "2":
|
|
|
+ //小程序
|
|
|
+ case "4":
|
|
|
+ //文件
|
|
|
+ case "5":
|
|
|
+ case "6":
|
|
|
+ case "7":
|
|
|
+ case "8":
|
|
|
+ case "9":
|
|
|
+ //app语音
|
|
|
+ case "16":
|
|
|
+ //app文本
|
|
|
+ case "15":
|
|
|
+ //群公告
|
|
|
+ case "11":
|
|
|
+ //直播小程序
|
|
|
+ case "12":
|
|
|
+ //直播发送类型
|
|
|
+ sopLogs.setSendType(20);
|
|
|
+ clonedContent.setLiveId(setting.getLiveId());
|
|
|
+ String sortLiveLink;
|
|
|
+ sortLiveLink = "/pages_course/living.html?companyId=" + companyId + "&companyUserId=" + companyUserId + "&liveId=" + setting.getLiveId() + "&corpId=" + logVo.getCorpId() + "&qwUserId=" + qwUserId;
|
|
|
+ String json = configService.selectConfigByKey("his.config");
|
|
|
+ FSSysConfig sysConfig = JSON.parseObject(json, FSSysConfig.class);
|
|
|
+ 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());
|
|
|
+ //写入直播待看课记录
|
|
|
+ createLiveWatchLogAndEnQueue(companyId, companyUserId, vo.getId().toString(), setting.getLiveId(), sysConfig.getAppId(), 2, qwUserId, logVo.getCorpId());
|
|
|
+ }
|
|
|
+ });
|
|
|
+ sortLiveLink += "&chatId=" + groupChat.getChatId();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("直播小程序群聊新增报错,{}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ try {
|
|
|
+ createLiveWatchLogAndEnQueue(companyId, companyUserId, externalId, setting.getLiveId(), sysConfig.getAppId(), 1, qwUserId, logVo.getCorpId());
|
|
|
+ sortLiveLink += "&externalId=" + externalId;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("直播小程序个人新增报错,{}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ String miniprogramLiveTitle = setting.getMiniprogramTitle();
|
|
|
+ int maxLiveLength = 17;
|
|
|
+ setting.setMiniprogramTitle(miniprogramLiveTitle.length() > maxLiveLength ? miniprogramLiveTitle.substring(0, maxLiveLength) + "..." : miniprogramLiveTitle);
|
|
|
+ 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;
|
|
|
+ case "13":
|
|
|
+ try {
|
|
|
+ if (sopLogs.getFsUserId() != null && !Objects.equals(0L, sopLogs.getFsUserId())) {
|
|
|
+ sopLogs.setSendStatus(5);
|
|
|
+ sopLogs.setReceivingStatus(0);
|
|
|
+ sopLogs.setRemark("已经注册过的客户不发送");
|
|
|
+ }
|
|
|
+ if (ObjectUtil.isNotEmpty(setting.getValue())) {
|
|
|
+ QwSopTempSetting.Content.Setting setting1 =new QwSopTempSetting.Content.Setting();
|
|
|
+ setting1.setContentType("1");
|
|
|
+ setting1.setValue(setting.getValue());
|
|
|
+ settingAll.add(setting1);
|
|
|
+ }
|
|
|
+
|
|
|
+ String link;
|
|
|
+ if (isGroupChat && groupChat != null) {
|
|
|
+ link = createRegisteredGroupLinkByMiniApp(setting, logVo, sendTime,
|
|
|
+ qwUserId, Long.parseLong(companyUserId), companyId, logVo.getChatId());
|
|
|
+ }else {
|
|
|
+ link = createRegisteredLinkByMiniApp(setting, logVo, sendTime,
|
|
|
+ qwUserId, companyUserId, companyId, externalId, sopLogs.getFsUserId());
|
|
|
+ }
|
|
|
+ //算主备小程序
|
|
|
+ String luckyjson1 = configService.selectConfigByKey("luckyBag.config");
|
|
|
+ Map<String, Object> luckyBagConfig1 = JSON.parseObject(luckyjson1, Map.class);
|
|
|
+ String finalAppId = String.valueOf(luckyBagConfig1.get("appId"));
|
|
|
+ /*getAppIdFromMiniMap(miniMap, companyId, sendMsgType, grade);
|
|
|
+
|
|
|
+ if (StringUtil.strIsNullOrEmpty(finalAppId)) {
|
|
|
+ finalAppId = miniAppId;
|
|
|
+ }*/
|
|
|
+
|
|
|
+ if (!StringUtil.strIsNullOrEmpty(finalAppId)) {
|
|
|
+ setting.setMiniprogramAppid(finalAppId);
|
|
|
+ } else {
|
|
|
+ log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
|
|
|
+ }
|
|
|
+
|
|
|
+ setting.setMiniprogramTitle("点击注册");
|
|
|
+ setting.setMiniprogramPage(link);
|
|
|
+// try {
|
|
|
+// item.setMiniprogramPicUrl(StringUtil.strIsNullOrEmpty(setting.getMiniprogramPicUrl()) ? "https://cos.his.cdwjyyh.com/fs/20250331/ec2b4e73be8048afbd526124a655ad56.png" : setting.getMiniprogramPicUrl());
|
|
|
+// } catch (Exception e) {
|
|
|
+// log.error("赋值-小程序封面地址失败-" + e);
|
|
|
+// }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("任务模板发送注册页面失败:" + e);
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ //福袋
|
|
|
+ case "14":
|
|
|
+ try {
|
|
|
+ // 1. 检查必要对象是否为空
|
|
|
+ if (sopLogs == null) {
|
|
|
+ log.warn("sopLogs为空,跳过福袋处理");
|
|
|
+ }
|
|
|
+ // 查询福袋信息
|
|
|
+ Long fsUserId = sopLogs.getFsUserId();
|
|
|
+
|
|
|
+ LuckyBag luckyBag = luckyBagMapper.selectLuckyBagById(setting.getLuckyBagId());
|
|
|
+ if(ObjectUtil.isNotEmpty(luckyBag)&&luckyBag.getDataStatus().equals("0")){
|
|
|
+ setSopLogsStatus(sopLogs, 5, 0, "福袋配置被禁用");
|
|
|
+ }else if (ObjectUtil.isNotEmpty(fsUserId)){
|
|
|
+
|
|
|
+ // 2. 获取系统配置
|
|
|
+ SysConfig luckyBagConfig = sysConfigMapper.selectConfigByConfigKey("luckyBag.config");
|
|
|
+ if (ObjectUtil.isEmpty(luckyBagConfig) || StringUtil.strIsNullOrEmpty(luckyBagConfig.getConfigValue())) {
|
|
|
+// log.warn("福袋配置为空,设置发送状态为失败");
|
|
|
+ setSopLogsStatus(sopLogs, 5, 0, "福袋配置不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 解析配置
|
|
|
+ JSONObject jsonObject = null;
|
|
|
+ try {
|
|
|
+ jsonObject = JSON.parseObject(luckyBagConfig.getConfigValue());
|
|
|
+ } catch (Exception e) {
|
|
|
+// log.error("解析福袋配置JSON失败: {}", luckyBagConfig.getConfigValue(), e);
|
|
|
+ setSopLogsStatus(sopLogs, 5, 0, "福袋配置格式错误");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 获取周限制次数
|
|
|
+ Integer count = null;
|
|
|
+ try {
|
|
|
+ Object weekLimitObj = jsonObject.get("weekLimit");
|
|
|
+ if (weekLimitObj != null) {
|
|
|
+ count = Integer.valueOf(weekLimitObj.toString());
|
|
|
+ }
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ log.error("周限制次数格式错误: {}", jsonObject.get("weekLimit"), e);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (count == null) {
|
|
|
+// log.warn("周限制次数配置为空");
|
|
|
+ setSopLogsStatus(sopLogs, 5, 0, "周限制次数配置错误");
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 5. 查询用户福袋收集记录(带计数缓存)
|
|
|
+ LuckyBagCollectRecord luckyBagCollectRecord = new LuckyBagCollectRecord();
|
|
|
+ luckyBagCollectRecord.setUserId(fsUserId);
|
|
|
+// 动态计算时间范围
|
|
|
+ LocalDate endDate = LocalDate.now();
|
|
|
+ LocalDate startDate = endDate.minusDays(6); // 包含今天
|
|
|
+
|
|
|
+ Map<String, Object> params = new HashMap<>();
|
|
|
+ params.put("beginSendTime", startDate.toString());
|
|
|
+ params.put("endSendTime", endDate.toString());
|
|
|
+ luckyBagCollectRecord.setParams(params);
|
|
|
+ luckyBagCollectRecord.setCollectType("1");
|
|
|
+
|
|
|
+ List<LuckyBagCollectRecord> luckyBagCollectRecords;
|
|
|
+ int recordCount;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 生成缓存key
|
|
|
+ String dateRangeKey = startDate.toString() + "_to_" + endDate.toString();
|
|
|
+ String countCacheKey = "luckyBag:user:" + fsUserId + ":recent7days:" + dateRangeKey + ":count";
|
|
|
+
|
|
|
+ // 1. 先尝试从计数缓存获取
|
|
|
+ Object cachedCount = redisCache.getCacheObject(countCacheKey);
|
|
|
+ if (cachedCount != null && cachedCount instanceof Integer) {
|
|
|
+ recordCount = (Integer) cachedCount;
|
|
|
+// log.debug("福袋计数缓存命中,userId: {},次数: {}", fsUserId, recordCount);
|
|
|
+
|
|
|
+ // 如果只需要判断是否超限,且已超限,直接返回
|
|
|
+ if (recordCount >= count) {
|
|
|
+// log.info("用户福袋次数已达上限(计数缓存), userId: {}, 当前次数: {}, 限制次数: {}",
|
|
|
+// fsUserId, recordCount, count);
|
|
|
+ setSopLogsStatus(sopLogs, 5, 0, "超过福袋发放次数");
|
|
|
+ luckyBagCollectRecords = Collections.emptyList();
|
|
|
+ // 可以直接返回,不需要查询完整记录
|
|
|
+ // return; // 根据你的流程决定是否返回
|
|
|
+ } else {
|
|
|
+ // 未超限,查询完整记录
|
|
|
+ String recordsCacheKey = "luckyBag:user:" + fsUserId + ":recent7days:" + dateRangeKey + ":records";
|
|
|
+ luckyBagCollectRecords = getCachedOrQueryRecords(fsUserId, recordsCacheKey, luckyBagCollectRecord);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 计数缓存未命中,查询完整记录
|
|
|
+ String recordsCacheKey = "luckyBag:user:" + fsUserId + ":recent7days:" + dateRangeKey + ":records";
|
|
|
+ luckyBagCollectRecords = getCachedOrQueryRecords(fsUserId, recordsCacheKey, luckyBagCollectRecord);
|
|
|
+ recordCount = luckyBagCollectRecords != null ? luckyBagCollectRecords.size() : 0;
|
|
|
+
|
|
|
+ // 更新计数缓存
|
|
|
+ cacheUserCount(fsUserId, countCacheKey, recordCount);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("查询用户福袋记录失败, userId: {}", fsUserId, e);
|
|
|
+ luckyBagCollectRecords = Collections.emptyList();
|
|
|
+ recordCount = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+// // 5. 查询用户福袋收集记录
|
|
|
+// LuckyBagCollectRecord luckyBagCollectRecord = new LuckyBagCollectRecord();
|
|
|
+// luckyBagCollectRecord.setUserId(fsUserId);
|
|
|
+// // 动态计算时间范围
|
|
|
+// LocalDate endDate = LocalDate.now();
|
|
|
+// LocalDate startDate = endDate.minusDays(6); // 包含今天
|
|
|
+//
|
|
|
+// Map<String, Object> params = new HashMap<>();
|
|
|
+// params.put("beginSendTime", startDate.toString());
|
|
|
+// params.put("endSendTime", endDate.toString());
|
|
|
+// luckyBagCollectRecord.setParams(params);
|
|
|
+// luckyBagCollectRecord.setCollectType("1");
|
|
|
+// List<LuckyBagCollectRecord> luckyBagCollectRecords;
|
|
|
+// try {
|
|
|
+// luckyBagCollectRecords = luckyBagCollectRecordMapper.selectLuckyBagCollectRecordList(luckyBagCollectRecord);
|
|
|
+// } catch (Exception e) {
|
|
|
+// log.error("查询用户福袋记录失败, userId: {}", fsUserId, e);
|
|
|
+// luckyBagCollectRecords = Collections.emptyList();
|
|
|
+// }
|
|
|
+
|
|
|
+ // 6. 检查次数限制
|
|
|
+ if (recordCount >= count) {
|
|
|
+// log.info("用户福袋次数已达上限, userId: {}, 当前次数: {}, 限制次数: {}", fsUserId, recordCount, count);
|
|
|
+ setSopLogsStatus(sopLogs, 5, 0, "超过福袋发放次数");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 7. 生成活动链接
|
|
|
+ String link;
|
|
|
+
|
|
|
+ String luckyjson1 = configService.selectConfigByKey("luckyBag.config");
|
|
|
+ Map<String, Object> luckyBagConfig1 = JSON.parseObject(luckyjson1, Map.class);
|
|
|
+ String finalAppId = String.valueOf(luckyBagConfig1.get("appId"));
|
|
|
+ /* try {
|
|
|
+ finalAppId = getAppIdFromMiniMap(miniMap, companyId, sendMsgType, grade);
|
|
|
+ if (StringUtil.strIsNullOrEmpty(finalAppId)) {
|
|
|
+ finalAppId = miniAppId;
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("获取小程序ID失败,使用默认值", e);
|
|
|
+ finalAppId = miniAppId;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (StringUtil.strIsNullOrEmpty(finalAppId)) {
|
|
|
+ log.error("公司的小程序id为空,sopId: {}", sopLogs.getSopId());
|
|
|
+ setSopLogsStatus(sopLogs, 5L, 0L, "小程序配置错误");
|
|
|
+ return;
|
|
|
+ }*/
|
|
|
+
|
|
|
+ // 10. 设置小程序参数
|
|
|
+ setting.setMiniprogramAppid(finalAppId);
|
|
|
+ setting.setMiniprogramTitle("福袋发放");
|
|
|
+
|
|
|
+// log.info("福袋配置成功,userId: {}, appId: {}", fsUserId, finalAppId);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("任务模板福袋发放失败", e);
|
|
|
+ // 确保在最终异常时也设置状态
|
|
|
+ if (sopLogs != null) {
|
|
|
+ setSopLogsStatus(sopLogs, 5, 0, "福袋发放系统异常");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ //直播h5跳转卡片
|
|
|
+ case "18":
|
|
|
+ //直播h5跳转短链
|
|
|
+ case "19":
|
|
|
+ String corpId = logVo.getCorpId();
|
|
|
+ String shortH5Link = createH5LiveShortLink(setting, corpId,
|
|
|
+ qwUserId, companyUserId, companyId);
|
|
|
+ shortH5Link = shortH5Link.substring(0, shortH5Link.length() - 1);
|
|
|
+
|
|
|
+ sopLogs.setSendType(Integer.valueOf(setting.getContentType()));
|
|
|
+ clonedContent.setLiveId(setting.getLiveId());
|
|
|
+ json = configService.selectConfigByKey("his.config");
|
|
|
+ sysConfig= JSON.parseObject(json,FSSysConfig.class);
|
|
|
+
|
|
|
+ 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());
|
|
|
+ //写入直播待看课记录
|
|
|
+ createLiveWatchLogAndEnQueue(companyId,companyUserId,vo.getId().toString(), setting.getLiveId(),sysConfig.getAppId(),2,qwUserId,logVo.getCorpId());
|
|
|
+ }
|
|
|
+ });
|
|
|
+ shortH5Link += ",\"chatId\":\"" + groupChat.getChatId() + "\"";
|
|
|
+ }catch(Exception e){
|
|
|
+ log.error("直播H5群聊新增报错,{}", e.getMessage(),e);
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ try{
|
|
|
+ createLiveWatchLogAndEnQueue(companyId,companyUserId,externalId, setting.getLiveId(),sysConfig.getAppId(),1,qwUserId,logVo.getCorpId());
|
|
|
+ shortH5Link += ",\"externalId\":\"" + externalId + "\"";
|
|
|
+ }catch(Exception e){
|
|
|
+ log.error("直播H5个人新增报错,{}", e.getMessage(),e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ shortH5Link += "}";
|
|
|
+ miniprogramLiveTitle = setting.getMiniprogramTitle();
|
|
|
+ maxLiveLength = 17;
|
|
|
+ setting.setMiniprogramTitle(miniprogramLiveTitle.length() > maxLiveLength ? miniprogramLiveTitle.substring(0, maxLiveLength) + "..." : miniprogramLiveTitle);
|
|
|
+ setting.setMiniprogramAppid(sysConfig.getAppId());
|
|
|
+ setting.setMiniprogramPage(shortH5Link);
|
|
|
+
|
|
|
+
|
|
|
+ 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;
|
|
|
+ //TODO 其他消息类型继续添加case
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ sopLogs.setContentJson(JSON.toJSONString(clonedContent));
|
|
|
+// sopLogs.setContentJson(JSON.toJSONString(content));
|
|
|
+ enqueueQwSopLogs(sopLogs);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String createRegisteredLinkByMiniApp(QwSopTempSetting.Content.Setting setting, WxSopUserVo logVo, Date sendTime,
|
|
|
+ String qwUserId,
|
|
|
+ String companyUserId, String companyId, String externalId, Long fsUserId) {
|
|
|
+ // 获取缓存的配置
|
|
|
+ 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(qwUserId!=null?Long.parseLong(qwUserId):null);
|
|
|
+ link.setCompanyUserId(Long.parseLong(companyUserId));
|
|
|
+ //link.setCorpId(logVo.getCorpId());
|
|
|
+ link.setQwExternalId(Long.parseLong(externalId));
|
|
|
+ link.setUNo(UUID.randomUUID().toString());
|
|
|
+ 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 = registeredRealLink + 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();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public String createRegisteredGroupLinkByMiniApp(QwSopTempSetting.Content.Setting setting, WxSopUserVo logVo, Date sendTime,
|
|
|
+ String qwUserId,
|
|
|
+ Long companyUserId, String companyId, String chatId) {
|
|
|
+ // 获取缓存的配置
|
|
|
+ CourseConfig config;
|
|
|
+ synchronized (configLock) {
|
|
|
+ config = cachedCourseConfig;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (config == null) {
|
|
|
+// log.error("CourseConfig is not loaded.");
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ FsCourseLink link = new FsCourseLink();
|
|
|
+ link.setCompanyId(Long.parseLong(companyId));
|
|
|
+ link.setQwUserId(qwUserId!=null?Long.parseLong(qwUserId): null);
|
|
|
+ link.setCompanyUserId(companyUserId);
|
|
|
+// link.setVideoId(null);
|
|
|
+ //link.setCorpId(logVo.getCorpId());
|
|
|
+// link.setCourseId(null);
|
|
|
+ link.setChatId(chatId);
|
|
|
+ link.setIsRoom(1);
|
|
|
+ link.setLinkType(3);
|
|
|
+ link.setUNo(UUID.randomUUID().toString());
|
|
|
+
|
|
|
+ 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 = registeredRealLink + 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();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理直播消息
|
|
|
+ */
|
|
|
+ public void handleLiveMessage(WxSopLogs sopLogs, QwSopTempSetting.Content content, String companyUserId, String companyId,
|
|
|
+ boolean isGroupChat, String qwUserId, QwGroupChat groupChat, String externalId, WxSopUserVo logVo, Long liveId){
|
|
|
+ // 深拷贝 Content 对象,避免使用 JSON
|
|
|
+ QwSopTempSetting.Content clonedContent = deepCopyContent(content);
|
|
|
+ if (clonedContent == null) {
|
|
|
+// log.error("Failed to clone content, skipping handleCourseMessage.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ clonedContent.setLiveId(liveId);
|
|
|
+ List<QwSopTempSetting.Content.Setting> settings = clonedContent.getSetting();
|
|
|
+ if (settings == null || settings.isEmpty()) {
|
|
|
+// log.error("Cloned content settings are empty, skipping.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ //直播发送类型
|
|
|
+ sopLogs.setSendType(20);
|
|
|
+
|
|
|
+ // 顺序处理每个 Setting,避免过多的并行导致线程开销
|
|
|
+ for (QwSopTempSetting.Content.Setting setting : settings) {
|
|
|
+ switch (setting.getContentType()) {
|
|
|
+ //直播小程序单独
|
|
|
+ case "12":
|
|
|
+ clonedContent.setLiveId(setting.getLiveId());
|
|
|
+ String sortLiveLink;
|
|
|
+ sortLiveLink = "/pages_course/living.html?companyId=" + companyId + "&companyUserId=" + companyUserId + "&liveId=" + setting.getLiveId() + "&corpId=" + logVo.getCorpId() + "&qwUserId=" + qwUserId;
|
|
|
+ String json = configService.selectConfigByKey("his.config");
|
|
|
+ FSSysConfig sysConfig = JSON.parseObject(json, FSSysConfig.class);
|
|
|
+ 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());
|
|
|
+ //写入直播待看课记录
|
|
|
+ createLiveWatchLogAndEnQueue(companyId, companyUserId, vo.getId().toString(), setting.getLiveId(), sysConfig.getAppId(), 2, qwUserId, logVo.getCorpId());
|
|
|
+ }
|
|
|
+ });
|
|
|
+ sortLiveLink += "&chatId=" + groupChat.getChatId();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("直播小程序群聊新增报错,{}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ try {
|
|
|
+ createLiveWatchLogAndEnQueue(companyId, companyUserId, externalId, setting.getLiveId(), sysConfig.getAppId(), 1, qwUserId, logVo.getCorpId());
|
|
|
+ sortLiveLink += "&externalId=" + externalId;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("直播小程序个人新增报错,{}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ String miniprogramLiveTitle = setting.getMiniprogramTitle();
|
|
|
+ int maxLiveLength = 17;
|
|
|
+ setting.setMiniprogramTitle(miniprogramLiveTitle.length() > maxLiveLength ? miniprogramLiveTitle.substring(0, maxLiveLength) + "..." : miniprogramLiveTitle);
|
|
|
+ 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;
|
|
|
+ //直播h5跳转卡片
|
|
|
+ case "18":
|
|
|
+ //直播h5跳转短链
|
|
|
+ case "19":
|
|
|
+ String corpId = logVo.getCorpId();
|
|
|
+ String shortH5Link = createH5LiveShortLink(setting, corpId,
|
|
|
+ qwUserId, companyUserId, companyId);
|
|
|
+
|
|
|
+ sopLogs.setSendType(Integer.valueOf(setting.getContentType()));
|
|
|
+ clonedContent.setLiveId(setting.getLiveId());
|
|
|
+ json = configService.selectConfigByKey("his.config");
|
|
|
+ sysConfig= JSON.parseObject(json,FSSysConfig.class);
|
|
|
+
|
|
|
+ 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());
|
|
|
+ //写入直播待看课记录
|
|
|
+ createLiveWatchLogAndEnQueue(companyId,companyUserId,vo.getId().toString(), setting.getLiveId(),sysConfig.getAppId(),2,qwUserId,logVo.getCorpId());
|
|
|
+ }
|
|
|
+ });
|
|
|
+ shortH5Link += "&chatId=" + groupChat.getChatId();
|
|
|
+ }catch(Exception e){
|
|
|
+ log.error("直播小程序群聊新增报错,{}", e.getMessage(),e);
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ try{
|
|
|
+ createLiveWatchLogAndEnQueue(companyId,companyUserId,externalId, setting.getLiveId(),sysConfig.getAppId(),1,qwUserId,logVo.getCorpId());
|
|
|
+ shortH5Link += "&externalId=" + externalId;
|
|
|
+ }catch(Exception e){
|
|
|
+ log.error("直播小程序个人新增报错,{}", e.getMessage(),e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ miniprogramLiveTitle = setting.getMiniprogramTitle();
|
|
|
+ maxLiveLength = 17;
|
|
|
+ setting.setMiniprogramTitle(miniprogramLiveTitle.length() > maxLiveLength ? miniprogramLiveTitle.substring(0, maxLiveLength) + "..." : miniprogramLiveTitle);
|
|
|
+ setting.setMiniprogramAppid(sysConfig.getAppId());
|
|
|
+ setting.setMiniprogramPage(shortH5Link);
|
|
|
+
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ sopLogs.setContentJson(JSON.toJSONString(clonedContent));
|
|
|
+
|
|
|
+ enqueueQwSopLogs(sopLogs);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void handleAIMessage(WxSopLogs sopLogs, QwSopTempSetting.Content content) {
|
|
|
+ sopLogs.setContentJson(JSON.toJSONString(content));
|
|
|
+ enqueueQwSopLogs(sopLogs);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void handleCourseMessage(WxSopLogs sopLogs, QwSopTempSetting.Content content,
|
|
|
+ WxSopUserVo 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;
|
|
|
+ }
|
|
|
+ String isOfficial = clonedContent.getIsOfficial();
|
|
|
+
|
|
|
+ List<QwSopTempSetting.Content.Setting> settings = clonedContent.getSetting();
|
|
|
+ if (settings == null || settings.isEmpty()) {return;}
|
|
|
+ //如果是@所有人,就添加
|
|
|
+ if (1 == content.getIsAtAll()) {
|
|
|
+ QwSopTempSetting.Content.Setting atMsg = new QwSopTempSetting.Content.Setting();
|
|
|
+ atMsg.setContentType("99");
|
|
|
+ settings.add(atMsg);
|
|
|
+ }
|
|
|
+ // 顺序处理每个 Setting,避免过多的并行导致线程开销
|
|
|
+ for (QwSopTempSetting.Content.Setting setting : settings) {
|
|
|
+ switch (setting.getContentType()) {
|
|
|
+ //文字和短链一起
|
|
|
+ case "1":
|
|
|
+ case "3":
|
|
|
+ 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, 2);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("群聊创建看课记录失败!", e);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo, 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ 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":
|
|
|
+ break;
|
|
|
+ //自定义小程序
|
|
|
+ case "10":
|
|
|
+ addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo, 2);
|
|
|
+
|
|
|
+ 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":
|
|
|
+ sopLogs.setSendType(20);
|
|
|
+ clonedContent.setLiveId(setting.getLiveId());
|
|
|
+ String sortLiveLink;
|
|
|
+ sortLiveLink = "/pages_course/living.html?companyId=" + companyId + "&companyUserId=" + companyUserId + "&liveId=" + setting.getLiveId() + "&corpId=" + logVo.getCorpId() + "&qwUserId=" + qwUserId;
|
|
|
+ String json = configService.selectConfigByKey("his.config");
|
|
|
+ FSSysConfig sysConfig = JSON.parseObject(json, FSSysConfig.class);
|
|
|
+ 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());
|
|
|
+ //写入直播待看课记录
|
|
|
+ createLiveWatchLogAndEnQueue(companyId, companyUserId, vo.getId().toString(), setting.getLiveId(), sysConfig.getAppId(), 2, qwUserId, logVo.getCorpId());
|
|
|
+ }
|
|
|
+ });
|
|
|
+ sortLiveLink += "&chatId=" + groupChat.getChatId();
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("直播小程序群聊新增报错,{}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ try {
|
|
|
+ createLiveWatchLogAndEnQueue(companyId, companyUserId, externalId, setting.getLiveId(), sysConfig.getAppId(), 2, qwUserId, logVo.getCorpId());
|
|
|
+ sortLiveLink += "&externalId=" + externalId;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("直播小程序个人新增报错,{}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ String miniprogramLiveTitle = setting.getMiniprogramTitle();
|
|
|
+ int maxLiveLength = 17;
|
|
|
+ setting.setMiniprogramTitle(miniprogramLiveTitle.length() > maxLiveLength ? miniprogramLiveTitle.substring(0, maxLiveLength) + "..." : miniprogramLiveTitle);
|
|
|
+ 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;
|
|
|
+ case "14":
|
|
|
+ case "15": //app文本
|
|
|
+ case "16": //app语音
|
|
|
+ break;
|
|
|
+ case "17":
|
|
|
+ try {
|
|
|
+ String sroth5link;
|
|
|
+ addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo,2);
|
|
|
+
|
|
|
+ sroth5link = createH5LinkByMiniApp(setting, logVo, sendTime, courseId, videoId,
|
|
|
+ qwUserId, companyUserId, companyId, externalId, isOfficial, sopLogs.getFsUserId());
|
|
|
+
|
|
|
+ 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.setMiniprogramTitle("邀请链接");
|
|
|
+ setting.setMiniprogramPage(sroth5link);
|
|
|
+
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建福袋链接
|
|
|
+ *
|
|
|
+ * @param st
|
|
|
+ * @param sopLogs
|
|
|
+ * @param corpId
|
|
|
+ * @param sendTime
|
|
|
+ * @param courseId
|
|
|
+ * @param videoId
|
|
|
+ * @param qwUserId
|
|
|
+ * @param companyUserId
|
|
|
+ * @param companyId
|
|
|
+ * @param config
|
|
|
+ * @param chatId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public String createActivityLinkByMiniApp(QwSopTempSetting.Content.Setting st, QwSopLogs sopLogs, String corpId, Date sendTime, Long courseId, Long videoId, String qwUserId, String companyUserId, String companyId, CourseConfig config, String chatId) {
|
|
|
+ FsCourseLink link = createFsCourseLink(corpId, sendTime, courseId, videoId, qwUserId.toString(),
|
|
|
+ companyUserId, companyId, null, 3);
|
|
|
+ link.setChatId(chatId);
|
|
|
+ Date updateTime = createUpdateTime(st, sendTime, config);
|
|
|
+ link.setUpdateTime(updateTime);
|
|
|
+ FsCourseRealLink courseMap = new FsCourseRealLink();
|
|
|
+ BeanUtils.copyProperties(link, courseMap);
|
|
|
+ Long businessId = addLuckyBagCollectRecord(st, sopLogs, updateTime, companyUserId, companyId, chatId);
|
|
|
+ courseMap.setBusinessId(String.valueOf(businessId));
|
|
|
+ st.setBusinessId(String.valueOf(businessId));
|
|
|
+ String realLinkFull = appActivitlLink + JSON.toJSONString(courseMap);
|
|
|
+ link.setRealLink(realLinkFull);
|
|
|
+// log.error("存入fs_course_link:" + registeredRealLink);
|
|
|
+// log.error("QwSopCourseFinishTempSetting.Setting:{}", st);
|
|
|
+ //存短链-
|
|
|
+ fsCourseLinkMapper.insertFsCourseLink(link);
|
|
|
+ return link.getRealLink();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 增加福袋发放记录、领取记录
|
|
|
+ *
|
|
|
+ * @param content
|
|
|
+ * @param qwSopLogs
|
|
|
+ * @param sendTime
|
|
|
+ * @param companyUserId
|
|
|
+ * @param companyId
|
|
|
+ * @param chatId
|
|
|
+ */
|
|
|
+ private Long addLuckyBagCollectRecord(QwSopTempSetting.Content.Setting content,
|
|
|
+ QwSopLogs qwSopLogs,
|
|
|
+ Date sendTime,
|
|
|
+ String companyUserId,
|
|
|
+ String companyId,
|
|
|
+ String chatId) {
|
|
|
+ try {
|
|
|
+ // 参数校验
|
|
|
+ if (content == null || qwSopLogs == null || sendTime == null) {
|
|
|
+ log.warn("添加福袋记录失败:必要参数为空 [content:{}, qwSopLogs:{}, sendTime:{}]",
|
|
|
+ content, qwSopLogs, sendTime);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (StringUtils.isEmpty(companyId) || StringUtils.isEmpty(companyUserId)) {
|
|
|
+ log.warn("公司ID或用户ID为空 [companyId:{}, companyUserId:{}]", companyId, companyUserId);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证福袋ID
|
|
|
+ if (content.getLuckyBagId() == null) {
|
|
|
+ log.warn("福袋ID为空");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询福袋信息
|
|
|
+ LuckyBag luckyBag = luckyBagMapper.selectLuckyBagById(content.getLuckyBagId());
|
|
|
+ if (luckyBag == null) {
|
|
|
+ log.warn("未找到对应的福袋信息 [luckyBagId:{}]", content.getLuckyBagId());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查福袋状态
|
|
|
+ if (luckyBag.getDataStatus() != null && luckyBag.getDataStatus().equals(0)) {
|
|
|
+ log.warn("福袋被禁用 [luckyBagId:{}]", content.getLuckyBagId());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询公司信息
|
|
|
+ Company company = companyMapper.selectCompanyById(Long.valueOf(companyId));
|
|
|
+ if (company == null) {
|
|
|
+ log.warn("未找到对应的公司信息 [companyId:{}]", companyId);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建福袋记录
|
|
|
+ LuckyBagCollectRecord luckyBagCollectRecord = buildLuckyBagRecord(content, qwSopLogs, sendTime,
|
|
|
+ companyUserId, companyId, chatId, company, luckyBag);
|
|
|
+
|
|
|
+ // 插入记录并返回ID
|
|
|
+ int result = luckyBagCollectRecordMapper.insertLuckyBagCollectRecord(luckyBagCollectRecord);
|
|
|
+ if (result <= 0) {
|
|
|
+ log.warn("福袋记录插入失败 [luckyBagId:{}]", content.getLuckyBagId());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 返回新增记录的ID
|
|
|
+ Long recordId = luckyBagCollectRecord.getId();
|
|
|
+ if (recordId == null) {
|
|
|
+ log.warn("福袋记录插入成功但未返回ID [luckyBagId:{}]", content.getLuckyBagId());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+// log.info("福袋记录添加成功 [recordId:{}, luckyBagId:{}]", recordId, content.getLuckyBagId());
|
|
|
+ return recordId;
|
|
|
+
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ log.error("ID转换失败 [companyId:{}, companyUserId:{}]", companyId, companyUserId, e);
|
|
|
+ return null;
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("ID:" + (content != null ? content.getLuckyBagId() : "unknown") + "-添加福袋记录失败", e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建福袋记录对象
|
|
|
+ */
|
|
|
+ private LuckyBagCollectRecord buildLuckyBagRecord(QwSopTempSetting.Content.Setting content,
|
|
|
+ QwSopLogs qwSopLogs,
|
|
|
+ Date sendTime,
|
|
|
+ String companyUserId,
|
|
|
+ String companyId,
|
|
|
+ String chatId,
|
|
|
+ Company company,
|
|
|
+ LuckyBag luckyBag) {
|
|
|
+ LuckyBagCollectRecord record = new LuckyBagCollectRecord();
|
|
|
+
|
|
|
+ record.setLuckyBagId(content.getLuckyBagId());
|
|
|
+ record.setExpiryTime(sendTime);
|
|
|
+ record.setCollectType("3");
|
|
|
+ record.setCompanyId(Long.valueOf(companyId));
|
|
|
+ record.setUserId(qwSopLogs.getFsUserId());
|
|
|
+ if (ObjectUtil.isNotEmpty(qwSopLogs.getFsUserId())) {
|
|
|
+ FsUser fsUser = fsUserMapper.selectFsUserByUserId(qwSopLogs.getFsUserId());
|
|
|
+ record.setUserName(ObjectUtil.isNotEmpty(fsUser) ? fsUser.getNickName() : null);
|
|
|
+ }
|
|
|
+ record.setCompanyName(company.getCompanyName());
|
|
|
+ record.setCompanyUserId(Long.valueOf(companyUserId));
|
|
|
+ record.setSendLink(content.getMiniprogramPage());
|
|
|
+
|
|
|
+ // 设置奖励类型和聊天信息
|
|
|
+ if (StringUtils.isNotEmpty(chatId)) {
|
|
|
+ record.setRewardType(1L);
|
|
|
+ record.setChatId(chatId);
|
|
|
+ record.setExternalUserName(qwSopLogs.getExternalUserName());
|
|
|
+ } else {
|
|
|
+ record.setRewardType(2L);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置币种金额
|
|
|
+ if (luckyBag.getRewardType() != null && luckyBag.getRewardType().equals("1")) {
|
|
|
+ record.setCoinAmount(luckyBag.getAmount());
|
|
|
+ }
|
|
|
+
|
|
|
+ return record;
|
|
|
+ }
|
|
|
+ private String createH5LinkByMiniApp(QwSopTempSetting.Content.Setting setting, WxSopUserVo 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 "";
|
|
|
+ }
|
|
|
+// 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.valueOf(qwUserId));
|
|
|
+ link.setCompanyUserId(Long.parseLong(companyUserId));
|
|
|
+ link.setVideoId(videoId.longValue());
|
|
|
+ link.setCorpId(logVo.getCorpId());
|
|
|
+ link.setCourseId(courseId.longValue());
|
|
|
+ link.setQwExternalId(Long.parseLong(externalId));
|
|
|
+ link.setUNo(UUID.randomUUID().toString());
|
|
|
+
|
|
|
+
|
|
|
+ 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 = h5miniappLink + 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();
|
|
|
+ }
|
|
|
+
|
|
|
+ private String createH5LiveShortLink(QwSopTempSetting.Content.Setting setting, String corpId, String qwUserId, String companyUserId, String companyId) {
|
|
|
+
|
|
|
+ // 手动创建 FsCourseLink 对象,避免使用 BeanUtils.copyProperties
|
|
|
+ FsCourseLink link = new FsCourseLink();
|
|
|
+ link.setCompanyId(Long.parseLong(companyId));
|
|
|
+ link.setQwUserId(Long.valueOf(qwUserId));
|
|
|
+ link.setCompanyUserId(Long.parseLong(companyUserId));
|
|
|
+ link.setLiveId(setting.getLiveId());
|
|
|
+ link.setCorpId(corpId);
|
|
|
+ link.setUNo(UUID.randomUUID().toString());
|
|
|
+
|
|
|
+ String randomString = generateRandomStringWithLock();
|
|
|
+ if (StringUtil.strIsNullOrEmpty(randomString)) {
|
|
|
+ link.setLink(UUID.randomUUID().toString().replace("-", ""));
|
|
|
+ } else {
|
|
|
+ link.setLink(randomString);
|
|
|
+ }
|
|
|
+
|
|
|
+ /*FsCourseRealLink courseMap = new FsCourseRealLink();
|
|
|
+ BeanUtils.copyProperties(link, courseMap);*/
|
|
|
+
|
|
|
+ String courseJson = JSON.toJSONString(link);
|
|
|
+ String realLinkFull = h5LiveShortLink + courseJson;
|
|
|
+ link.setRealLink(realLinkFull);
|
|
|
+
|
|
|
+ //存短链-
|
|
|
+ enqueueCourseLink(link);
|
|
|
+ return link.getRealLink();
|
|
|
+
|
|
|
+ }
|
|
|
+ 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(WxSopLogs 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 = APP_LINK_PREFIX + courseJson;
|
|
|
+
|
|
|
+ if (CloudHostUtils.hasCloudHostName("木易华康")) {
|
|
|
+ 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, WxSopUserVo 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(WxSopLogs sopLogs, Long videoId, Long courseId,
|
|
|
+ Date sendTime, String qwUserId, String companyUserId,
|
|
|
+ String companyId, String externalId, WxSopUserVo logsVo, Integer watchType) {
|
|
|
+ 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(String.valueOf(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.setWatchType(watchType);
|
|
|
+ watchLog.setCampPeriodTime(convertStringToDate(logsVo.getStartTime(), "yyyy-MM-dd"));
|
|
|
+ enqueueWatchLog(watchLog);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 直播看课记录处理
|
|
|
+ *
|
|
|
+ * @param companyId
|
|
|
+ * @param companyUserId
|
|
|
+ * @param externalId
|
|
|
+ * @param liveId
|
|
|
+ * @param appId
|
|
|
+ * @param logSource
|
|
|
+ * @param qwUserId
|
|
|
+ * @param corpId
|
|
|
+ */
|
|
|
+ public void createLiveWatchLogAndEnQueue(String companyId, String companyUserId, String externalId, Long liveId, String appId, Integer logSource, String qwUserId, String corpId) {
|
|
|
+ // 写入对应数据源的记录表
|
|
|
+ LiveWatchLog itemLiveWatchLog = new LiveWatchLog();
|
|
|
+ itemLiveWatchLog.setLiveId(liveId);
|
|
|
+ itemLiveWatchLog.setLogType(3);
|
|
|
+ itemLiveWatchLog.setSopCreateTime(new Date());
|
|
|
+ itemLiveWatchLog.setCompanyId(Long.valueOf(companyId));
|
|
|
+ itemLiveWatchLog.setCompanyUserId(Long.valueOf(companyUserId));
|
|
|
+ itemLiveWatchLog.setSendAppId(appId);
|
|
|
+ itemLiveWatchLog.setLogSource(logSource);
|
|
|
+ itemLiveWatchLog.setQwUserId(qwUserId);
|
|
|
+ itemLiveWatchLog.setExternalContactId(Long.valueOf(externalId));
|
|
|
+ itemLiveWatchLog.setCorpId(corpId);
|
|
|
+ enqueueZmLiveWatchLog(itemLiveWatchLog);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void enqueueZmLiveWatchLog(LiveWatchLog liveWatchLog) {
|
|
|
+ try {
|
|
|
+ boolean offered = zmLiveWatchQueue.offer(liveWatchLog, 5, TimeUnit.SECONDS);
|
|
|
+ if (!offered) {
|
|
|
+ log.error("LiveWatchLog 队列已满,无法添加日志: {}", JSON.toJSONString(liveWatchLog));
|
|
|
+ // 处理队列已满的情况,例如记录到失败队列或持久化存储
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ log.error("插入 LiveWatchLog 队列时被中断: {}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 时间字符串转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(WxSopLogs sopLogs) {
|
|
|
+ try {
|
|
|
+ boolean offered = wxSopLogsQueue.offer(sopLogs, 5, TimeUnit.SECONDS);
|
|
|
+ System.out.println(sopLogs.getSopId() + "插入队列结果: " + offered + "内容: " + JSON.toJSONString(sopLogs));
|
|
|
+ if (!offered) {
|
|
|
+ log.error("QwSopLogs 队列已满,无法添加日志: {}", JSON.toJSONString(sopLogs));
|
|
|
+ // 处理队列已满的情况,例如记录到失败队列或持久化存储
|
|
|
+ }
|
|
|
+ //wxSopLogsQueue.clear();
|
|
|
+ } 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<WxSopLogs> batch = new ArrayList<>(BATCH_SIZE);
|
|
|
+ while (running || !wxSopLogsQueue.isEmpty()) {
|
|
|
+ try {
|
|
|
+ WxSopLogs log = wxSopLogsQueue.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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 消费 FsCourseSopAppLink 队列并进行批量插入
|
|
|
+ */
|
|
|
+ private void consumeZmLiveWatchQueue() {
|
|
|
+ List<LiveWatchLog> batch = new ArrayList<>(BATCH_SIZE);
|
|
|
+ while (running || !zmLiveWatchQueue.isEmpty()) {
|
|
|
+ try {
|
|
|
+ LiveWatchLog livewatchLog = zmLiveWatchQueue.poll(1, TimeUnit.SECONDS);
|
|
|
+ if (livewatchLog != null) {
|
|
|
+ batch.add(livewatchLog);
|
|
|
+ }
|
|
|
+ if (batch.size() >= BATCH_SIZE || (!batch.isEmpty() && livewatchLog == null)) {
|
|
|
+ if (!batch.isEmpty()) {
|
|
|
+ batchInsertLiveWatchLog(new ArrayList<>(batch));
|
|
|
+ batch.clear();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ log.error("zmLiveWatchQueue 消费线程被中断: {}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理剩余的数据
|
|
|
+ if (!batch.isEmpty()) {
|
|
|
+ batchInsertLiveWatchLog(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<WxSopLogs> logsToInsert) {
|
|
|
+ try {
|
|
|
+// qwSopLogsService.batchInsertQwSopLogs(logsToInsert);
|
|
|
+ wxSopLogsService.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);
|
|
|
+ // 可选:将失败的数据记录到失败队列或持久化存储以便后续重试
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量插入 卓美直播看课记录
|
|
|
+ */
|
|
|
+ @Transactional
|
|
|
+ @Retryable(
|
|
|
+ value = {Exception.class},
|
|
|
+ maxAttempts = 3,
|
|
|
+ backoff = @Backoff(delay = 2000)
|
|
|
+ )
|
|
|
+ public void batchInsertLiveWatchLog(List<LiveWatchLog> liveWatchLogToInsert) {
|
|
|
+ try {
|
|
|
+ //更改为set 避免同一批生成的消息里面有重复数据 插入会报错
|
|
|
+ Set<LiveWatchLog> lastInsertSet = new HashSet<>();
|
|
|
+ //判断是否存在数据 liveId + his_qw_external_contact_id + qwUserId 唯一
|
|
|
+ for (LiveWatchLog liveWatchLog : liveWatchLogToInsert) {
|
|
|
+ //判断是否存在数据 存在的数据直接更新发送时间
|
|
|
+ if (liveWatchLogMapper.updateLiveWatchLogCondition(liveWatchLog) > 0) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ lastInsertSet.add(liveWatchLog);
|
|
|
+ }
|
|
|
+ if (!lastInsertSet.isEmpty()) {
|
|
|
+ liveWatchLogMapper.insertLiveWatchLogBatch(new ArrayList<>(lastInsertSet));
|
|
|
+ }
|
|
|
+// log.info("批量插入 LiveWatchLog 完成,共插入 {} 条记录。", liveWatchLogToInsert.size());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("批量插入 LiveWatchLog 失败: {}", liveWatchLogToInsert, 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置SOP日志状态的辅助方法
|
|
|
+ */
|
|
|
+ private void setSopLogsStatus(WxSopLogs sopLogs, Integer sendStatus, Integer receivingStatus, String remark) {
|
|
|
+ if (sopLogs != null) {
|
|
|
+ sopLogs.setSendStatus(sendStatus);
|
|
|
+ sopLogs.setReceivingStatus(receivingStatus);
|
|
|
+ sopLogs.setRemark(remark);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询并缓存用户记录
|
|
|
+ *
|
|
|
+ * @param userId
|
|
|
+ * @param cacheKey
|
|
|
+ * @param query
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ // 获取缓存或查询记录
|
|
|
+ private List<LuckyBagCollectRecord> getCachedOrQueryRecords(Long userId, String cacheKey,
|
|
|
+ LuckyBagCollectRecord query) {
|
|
|
+ Object cachedData = redisCache.getCacheObject(cacheKey);
|
|
|
+ if (cachedData != null && cachedData instanceof List) {
|
|
|
+ log.debug("福袋记录缓存命中,userId: {}", userId);
|
|
|
+ return (List<LuckyBagCollectRecord>) cachedData;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 缓存未命中,查询数据库
|
|
|
+ log.debug("福袋记录缓存未命中,查询数据库,userId: {}", userId);
|
|
|
+ List<LuckyBagCollectRecord> records = luckyBagCollectRecordMapper.selectLuckyBagCollectRecordList(query);
|
|
|
+ cacheUserRecords(userId, cacheKey, records);
|
|
|
+ return records != null ? records : Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 缓存用户福袋记录
|
|
|
+ *
|
|
|
+ * @param userId
|
|
|
+ * @param cacheKey
|
|
|
+ * @param records
|
|
|
+ */
|
|
|
+ private void cacheUserRecords(Long userId, String cacheKey, List<LuckyBagCollectRecord> records) {
|
|
|
+ if (records != null && !records.isEmpty()) {
|
|
|
+ try {
|
|
|
+ // 计算到明天凌晨的剩余时间(秒)
|
|
|
+ LocalDateTime now = LocalDateTime.now();
|
|
|
+ LocalDateTime tomorrowStart = LocalDate.now().plusDays(1).atStartOfDay();
|
|
|
+ long secondsUntilTomorrow = Duration.between(now, tomorrowStart).getSeconds();
|
|
|
+
|
|
|
+ // 设置缓存,过期时间到明天凌晨
|
|
|
+ int ttlSeconds = (int) Math.max(60, secondsUntilTomorrow); // 至少缓存1分钟
|
|
|
+ redisCache.setCacheObject(cacheKey, records, ttlSeconds, TimeUnit.SECONDS);
|
|
|
+
|
|
|
+ log.debug("缓存用户福袋记录,userId: {},记录数: {},过期时间: {}秒",
|
|
|
+ userId, records.size(), ttlSeconds);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("缓存用户福袋记录失败,userId: {}", userId, e);
|
|
|
+ // 缓存失败不影响主流程
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 缓存用户计数
|
|
|
+ * @param userId
|
|
|
+ * @param cacheKey
|
|
|
+ * @param count
|
|
|
+ */
|
|
|
+ private void cacheUserCount(Long userId, String cacheKey, int count) {
|
|
|
+ try {
|
|
|
+ // 计算到明天凌晨的剩余时间
|
|
|
+ LocalDateTime now = LocalDateTime.now();
|
|
|
+ LocalDateTime tomorrowStart = LocalDate.now().plusDays(1).atStartOfDay();
|
|
|
+ long secondsUntilTomorrow = Duration.between(now, tomorrowStart).getSeconds();
|
|
|
+
|
|
|
+ int ttlSeconds = (int) Math.max(60, secondsUntilTomorrow);
|
|
|
+ redisCache.setCacheObject(cacheKey, count, ttlSeconds, TimeUnit.SECONDS);
|
|
|
+
|
|
|
+ log.debug("缓存用户福袋计数,userId: {},次数: {},过期时间: {}秒", userId, count, ttlSeconds);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("缓存用户计数失败,userId: {}", userId, e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|