|
|
@@ -0,0 +1,542 @@
|
|
|
+package com.fs.app.task;
|
|
|
+
|
|
|
+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.fs.common.core.domain.R;
|
|
|
+import com.fs.common.core.redis.RedisCache;
|
|
|
+import com.fs.common.service.ISmsService;
|
|
|
+import com.fs.common.utils.date.DateUtil;
|
|
|
+import com.fs.course.domain.FsCourseWatchLog;
|
|
|
+import com.fs.course.service.IFsCourseWatchLogService;
|
|
|
+import com.fs.his.domain.FsUser;
|
|
|
+import com.fs.his.dto.SendResultDetailDTO;
|
|
|
+import com.fs.his.service.IFsUserService;
|
|
|
+import com.fs.his.utils.PhoneUtil;
|
|
|
+import com.fs.qw.domain.QwIpadServer;
|
|
|
+import com.fs.qw.domain.QwSopSmsLogs;
|
|
|
+import com.fs.qw.mapper.QwIpadServerMapper;
|
|
|
+import com.fs.qw.service.IQwSopSmsLogsService;
|
|
|
+import com.fs.qw.vo.QwSopCourseFinishTempSetting;
|
|
|
+import com.fs.sop.domain.QwSopLogs;
|
|
|
+import com.fs.sop.mapper.QwSopLogsMapper;
|
|
|
+import com.fs.sop.service.IQwSopLogsService;
|
|
|
+import com.fs.sop.service.impl.QwSopLogsServiceImpl;
|
|
|
+import com.google.common.cache.Cache;
|
|
|
+import com.google.common.cache.CacheBuilder;
|
|
|
+import com.google.common.util.concurrent.RateLimiter;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.context.annotation.Lazy;
|
|
|
+import org.springframework.scheduling.annotation.Scheduled;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
+
|
|
|
+import javax.annotation.PreDestroy;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.ZoneId;
|
|
|
+import java.util.*;
|
|
|
+import java.util.concurrent.*;
|
|
|
+import java.util.concurrent.atomic.AtomicLong;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 短信发送服务
|
|
|
+ */
|
|
|
+@Component
|
|
|
+@Slf4j
|
|
|
+public class SendSmsMsg {
|
|
|
+
|
|
|
+ @Value("${group-no}")
|
|
|
+ private String groupNo;
|
|
|
+
|
|
|
+ private final RedisCache redisCache;
|
|
|
+
|
|
|
+ private final ISmsService smsService;
|
|
|
+
|
|
|
+ private final IFsUserService fsUserService;
|
|
|
+
|
|
|
+ private final QwSopLogsMapper qwSopLogsMapper;
|
|
|
+
|
|
|
+ private final QwIpadServerMapper qwIpadServerMapper;
|
|
|
+
|
|
|
+ private final IQwSopSmsLogsService qwSopSmsLogsService;
|
|
|
+
|
|
|
+ private final IQwSopLogsService qwSopLogsService;
|
|
|
+
|
|
|
+ private final IFsCourseWatchLogService watchLogService;
|
|
|
+
|
|
|
+ // 线程池配置
|
|
|
+ private static final int CORE_POOL_SIZE = 50;
|
|
|
+ private static final int MAX_POOL_SIZE = 200;
|
|
|
+ private static final int QUEUE_CAPACITY = 1000;
|
|
|
+ private static final long KEEP_ALIVE_TIME = 60L;
|
|
|
+
|
|
|
+ // 分页大小
|
|
|
+ private static final int PAGE_SIZE = 5000;
|
|
|
+
|
|
|
+ // 手机号缓存
|
|
|
+ private final Cache<Long, String> phoneCache = CacheBuilder.newBuilder()
|
|
|
+ .expireAfterWrite(1, TimeUnit.HOURS)
|
|
|
+ .maximumSize(100000)
|
|
|
+ .build();
|
|
|
+
|
|
|
+ // 限流器:控制全局发送速率
|
|
|
+ private final RateLimiter rateLimiter = RateLimiter.create(1000); // 每秒1000条
|
|
|
+
|
|
|
+ // 短信发送线程池
|
|
|
+ private final ThreadPoolExecutor smsExecutor = new ThreadPoolExecutor(
|
|
|
+ CORE_POOL_SIZE,
|
|
|
+ MAX_POOL_SIZE,
|
|
|
+ KEEP_ALIVE_TIME,
|
|
|
+ TimeUnit.SECONDS,
|
|
|
+ new LinkedBlockingQueue<>(QUEUE_CAPACITY),
|
|
|
+ new ThreadPoolExecutor.CallerRunsPolicy() // 队列满时由调用线程执行,避免任务丢失
|
|
|
+ );
|
|
|
+
|
|
|
+ private final ExecutorService statusExecutor = Executors.newSingleThreadExecutor();
|
|
|
+
|
|
|
+ // 批量失败状态更新阈值
|
|
|
+ private static final int BATCH_UPDATE_SIZE = 500;
|
|
|
+
|
|
|
+ public SendSmsMsg(QwIpadServerMapper qwIpadServerMapper,
|
|
|
+ IQwSopSmsLogsService qwSopSmsLogsService,
|
|
|
+ @Lazy ISmsService smsService,
|
|
|
+ IFsUserService fsUserService, RedisCache redisCache,
|
|
|
+ QwSopLogsMapper qwSopLogsMapper,
|
|
|
+ IQwSopLogsService qwSopLogsService,
|
|
|
+ IFsCourseWatchLogService watchLogService) {
|
|
|
+ this.qwIpadServerMapper = qwIpadServerMapper;
|
|
|
+ this.qwSopSmsLogsService = qwSopSmsLogsService;
|
|
|
+ this.smsService = smsService;
|
|
|
+ this.fsUserService = fsUserService;
|
|
|
+ this.redisCache = redisCache;
|
|
|
+ this.qwSopLogsMapper = qwSopLogsMapper;
|
|
|
+ this.qwSopLogsService = qwSopLogsService;
|
|
|
+ this.watchLogService = watchLogService;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Scheduled(cron = "0 0 * * * ?") // 每小时执行一次
|
|
|
+ public synchronized void sendSms() {
|
|
|
+ sendSms(null);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public void sendSms(String groupNum) {
|
|
|
+ if (groupNum != null) {
|
|
|
+ groupNo = groupNum;
|
|
|
+ }
|
|
|
+ long startTime = System.currentTimeMillis();
|
|
|
+ log.info("sendSms: 开始执行,groupNo={}", groupNo);
|
|
|
+
|
|
|
+ if (StringUtils.isEmpty(groupNo)) {
|
|
|
+ log.warn("sendSms: groupNo 为空,跳过执行");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ long groupOn;
|
|
|
+ try {
|
|
|
+ groupOn = Long.parseLong(groupNo.trim());
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ log.warn("sendSms: groupNo 无法转为数字, groupNo={}", groupNo);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取server_ids
|
|
|
+ List<Long> serverIds = getServerIds(groupOn);
|
|
|
+ if (serverIds.isEmpty()) {
|
|
|
+ log.info("sendSms: 分组 groupNo={} 无 server,跳过", groupNo);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算时间范围
|
|
|
+ TimeRange timeRange = calculateTimeRange();
|
|
|
+
|
|
|
+ long lastId = 0L;
|
|
|
+ AtomicLong totalProcessed = new AtomicLong(0);
|
|
|
+ AtomicLong totalFailed = new AtomicLong(0);
|
|
|
+
|
|
|
+ // 收集所有任务的Future
|
|
|
+ List<CompletableFuture<Void>> futures = new ArrayList<>();
|
|
|
+
|
|
|
+ while (true) {
|
|
|
+ // 分页查询待发送短信
|
|
|
+ List<QwSopSmsLogs> page = qwSopSmsLogsService.selectPendingSmsByGroupAndTimePage(
|
|
|
+ serverIds, "0", timeRange.endTime, lastId, PAGE_SIZE);
|
|
|
+
|
|
|
+ if (page == null || page.isEmpty()) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 批量更新状态为“发送中”
|
|
|
+ List<Long> batchIds = page.stream().map(QwSopSmsLogs::getId).collect(Collectors.toList());
|
|
|
+ qwSopSmsLogsService.updateStatusByIds(batchIds, "1");
|
|
|
+
|
|
|
+ //获取执行记录数据
|
|
|
+ List<QwSopLogs> sopLogs = qwSopLogsMapper.getQwSopInfoByUid(page.stream().map(QwSopSmsLogs::getSopLogId).collect(Collectors.toList()));
|
|
|
+ Map<Long, QwSopLogs> sopLogsMap = sopLogs.stream().collect(Collectors.toMap(QwSopLogs::getSmsLogsId,s->s));
|
|
|
+ // 按server_id分组
|
|
|
+ Map<Long, List<QwSopSmsLogs>> groupByServer = page.stream()
|
|
|
+ .collect(Collectors.groupingBy(QwSopSmsLogs::getServerId));
|
|
|
+
|
|
|
+ // 为每个server的数据创建独立任务
|
|
|
+ for (Map.Entry<Long, List<QwSopSmsLogs>> entry : groupByServer.entrySet()) {
|
|
|
+ List<QwSopSmsLogs> serverBatch = entry.getValue();
|
|
|
+ CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
|
|
+ try {
|
|
|
+ SendResult result = processServerBatch(serverBatch,sopLogsMap);
|
|
|
+ totalProcessed.addAndGet(result.success);
|
|
|
+ totalFailed.addAndGet(result.failed);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理server {} 批次失败", entry.getKey(), e);
|
|
|
+ }
|
|
|
+ }, smsExecutor);
|
|
|
+ futures.add(future);
|
|
|
+ }
|
|
|
+
|
|
|
+ lastId = page.get(page.size() - 1).getId();
|
|
|
+
|
|
|
+ if (page.size() < PAGE_SIZE) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 每处理10页,等待当前任务完成,避免内存堆积
|
|
|
+ if (futures.size() >= 10) {
|
|
|
+ CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
|
|
+ futures.clear();
|
|
|
+ log.info("sendSms: 已处理 {} 条,失败 {} 条,继续...", totalProcessed.get(), totalFailed.get());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 等待所有剩余任务完成
|
|
|
+ if (!futures.isEmpty()) {
|
|
|
+ CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
|
|
+ }
|
|
|
+
|
|
|
+ long cost = System.currentTimeMillis() - startTime;
|
|
|
+ log.info("sendSms: 分组 groupNo={} 处理完成, 成功: {}, 失败: {}, 耗时: {}ms",
|
|
|
+ groupNo, totalProcessed.get(), totalFailed.get(), cost);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取分组下的所有server ID
|
|
|
+ */
|
|
|
+ private List<Long> getServerIds(long groupOn) {
|
|
|
+ List<QwIpadServer> serverList = qwIpadServerMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<QwIpadServer>()
|
|
|
+ .select(QwIpadServer::getId)
|
|
|
+ .eq(QwIpadServer::getGroupNo, groupOn));
|
|
|
+ return serverList.stream().map(QwIpadServer::getId).collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算查询时间范围
|
|
|
+ */
|
|
|
+ private TimeRange calculateTimeRange() {
|
|
|
+ LocalDateTime now = LocalDateTime.now();
|
|
|
+ LocalDateTime nextHourStart = now.withMinute(0).withSecond(0).withNano(0).plusHours(1);
|
|
|
+ LocalDateTime startOfDay = now.withHour(0).withMinute(0).withSecond(0).withNano(0);
|
|
|
+ Date startTime = Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant());
|
|
|
+ Date endTime = Date.from(nextHourStart.atZone(ZoneId.systemDefault()).toInstant());
|
|
|
+ return new TimeRange(startTime, endTime);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理单个server的一批短信(同步发送,避免线程爆炸)
|
|
|
+ */
|
|
|
+ private SendResult processServerBatch(List<QwSopSmsLogs> batch,Map<Long, QwSopLogs> sopLogsMap) {
|
|
|
+ int success = 0;
|
|
|
+ List<SendResultDetailDTO> failReasonsList = Collections.synchronizedList(new ArrayList<>());
|
|
|
+ List<Long> failedIds = Collections.synchronizedList(new ArrayList<>());
|
|
|
+ // 批量获取手机号
|
|
|
+ Map<Long, String> userPhoneMap = getPhoneNumbers(batch);
|
|
|
+
|
|
|
+ for (QwSopSmsLogs logRecord : batch) {
|
|
|
+ rateLimiter.acquire();
|
|
|
+ //数据走数据校验
|
|
|
+ if(!sopLogsMap.containsKey(logRecord.getSopLogId())){
|
|
|
+ log.error("处理sopLogId {} 失败,sopLogsMap 中不存在执行记录", logRecord.getSopLogId());
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ QwSopLogs qwSopLogs = sopLogsMap.get(logRecord.getSopLogId());
|
|
|
+ QwSopCourseFinishTempSetting setting = JSON.parseObject(qwSopLogs.getContentJson(), QwSopCourseFinishTempSetting.class);
|
|
|
+ // 判断消息状态是否满足发送条件
|
|
|
+ SendResultDetailDTO checkDto= isSendLogs(qwSopLogs, setting,logRecord);
|
|
|
+ if (!checkDto.isSuccess()) {
|
|
|
+ log.info("销售:{}, 消息发送条件未满足:{}", qwSopLogs.getQwUserKey(), qwSopLogs.getId());
|
|
|
+ failReasonsList.add(checkDto);
|
|
|
+ failedIds.add(logRecord.getId());
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ String redisKey = groupNo + ":" + logRecord.getId();
|
|
|
+ try {
|
|
|
+ redisCache.setCacheObject(redisKey, logRecord.getId(), 2, TimeUnit.HOURS);
|
|
|
+ SendResultDetailDTO detail = sendSingleSms(logRecord, userPhoneMap, redisKey);
|
|
|
+ if (detail.isSuccess()) {
|
|
|
+ success++;
|
|
|
+ } else {
|
|
|
+ redisCache.deleteObject(redisKey);
|
|
|
+ failReasonsList.add(detail);
|
|
|
+ failedIds.add(logRecord.getId());
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("发送异常 id={}", logRecord.getId(), e);
|
|
|
+ failReasonsList.add(new SendResultDetailDTO(false, e.getMessage(), logRecord.getSopLogId()));
|
|
|
+ failedIds.add(logRecord.getId());
|
|
|
+ redisCache.deleteObject(redisKey);
|
|
|
+ }
|
|
|
+
|
|
|
+ //批量阈值
|
|
|
+ if (failReasonsList.size() >= BATCH_UPDATE_SIZE) {
|
|
|
+ submitFailedUpdate(failedIds, failReasonsList, sopLogsMap);
|
|
|
+ failReasonsList.clear();
|
|
|
+ failedIds.clear();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 提交剩余的失败记录
|
|
|
+ if (!failReasonsList.isEmpty()) {
|
|
|
+ submitFailedUpdate(failedIds, failReasonsList,sopLogsMap);
|
|
|
+ }
|
|
|
+
|
|
|
+ return new SendResult(success, failedIds.size(), failedIds);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取手机号映射(缓存+批量查询)
|
|
|
+ */
|
|
|
+ private Map<Long, String> getPhoneNumbers(List<QwSopSmsLogs> batch) {
|
|
|
+ // 收集所有用户ID
|
|
|
+ Set<Long> userIds = batch.stream()
|
|
|
+ .map(QwSopSmsLogs::getFsUserId)
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+
|
|
|
+ Map<Long, String> result = new HashMap<>();
|
|
|
+
|
|
|
+ // 先从缓存获取
|
|
|
+ Set<Long> missingUserIds = new HashSet<>();
|
|
|
+ for (Long userId : userIds) {
|
|
|
+ String phone = phoneCache.getIfPresent(userId);
|
|
|
+ if (phone != null) {
|
|
|
+ result.put(userId, phone);
|
|
|
+ } else {
|
|
|
+ missingUserIds.add(userId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 批量查询缺失的手机号
|
|
|
+ if (!missingUserIds.isEmpty()) {
|
|
|
+ List<FsUser> userList = fsUserService.selectUserListByUserIds(new ArrayList<>(missingUserIds));
|
|
|
+ for (FsUser user : userList) {
|
|
|
+ if (user.getPhone() != null) {
|
|
|
+ result.put(user.getUserId(), user.getPhone());
|
|
|
+ phoneCache.put(user.getUserId(), user.getPhone()); // 放入缓存
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送单条短信,返回是否成功
|
|
|
+ */
|
|
|
+ private SendResultDetailDTO sendSingleSms(QwSopSmsLogs logRecord, Map<Long, String> userPhoneMap, String redisKey) {
|
|
|
+ String phone = userPhoneMap.get(logRecord.getFsUserId());
|
|
|
+ String content = logRecord.getContent();
|
|
|
+
|
|
|
+ if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(content)) {
|
|
|
+ String reason = StringUtils.isEmpty(phone) ? "手机号为空" : "内容为空";
|
|
|
+ log.warn("记录 id={} {},跳过", logRecord.getId(), reason);
|
|
|
+ return new SendResultDetailDTO(false, reason, logRecord.getSopLogId());
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ R r = smsService.sendUrl(
|
|
|
+ PhoneUtil.decryptPhone(phone),
|
|
|
+ content,
|
|
|
+ logRecord.getSmsTemplateCode(),
|
|
|
+ logRecord.getSopLogId(),
|
|
|
+ logRecord.getSmsIndex(),
|
|
|
+ redisKey
|
|
|
+ );
|
|
|
+
|
|
|
+ if (r != null && "200".equals(String.valueOf(r.get("code")))) {
|
|
|
+ return new SendResultDetailDTO(true, null, null);
|
|
|
+ } else {
|
|
|
+ String msg = r != null && r.get("msg") != null ? r.get("msg").toString() : "未知错误";
|
|
|
+ log.warn("短信发送失败 id={}, phone={}, msg={}", logRecord.getId(), phone, msg);
|
|
|
+ return new SendResultDetailDTO(false, msg, logRecord.getSopLogId());
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("发送异常 id=" + logRecord.getId(), e);
|
|
|
+ return new SendResultDetailDTO(false, e.getMessage(), logRecord.getSopLogId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 异步批量更新失败状态
|
|
|
+ */
|
|
|
+ private void submitFailedUpdate(List<Long> failedIds, List<SendResultDetailDTO> failReasonsList,Map<Long, QwSopLogs> sopLogsMap) {
|
|
|
+ statusExecutor.submit(() -> {
|
|
|
+ try {
|
|
|
+ qwSopSmsLogsService.updateStatusByIds(failedIds, "3"); // 失败状态
|
|
|
+ if (!failReasonsList.isEmpty()) {
|
|
|
+ //组装数据
|
|
|
+ List<QwSopLogs> updateList = failReasonsList.stream().map(f -> {
|
|
|
+ QwSopLogs update = new QwSopLogs();
|
|
|
+ update.setSmsLogsId(f.getSopLogId());
|
|
|
+ update.setRemark(f.getFailReason());
|
|
|
+ //同步json发送状态
|
|
|
+ if (sopLogsMap != null && sopLogsMap.containsKey(f.getSopLogId())) {
|
|
|
+ try {
|
|
|
+ String contentJson = sopLogsMap.get(f.getSopLogId()).getContentJson();
|
|
|
+ JSONObject jsonObject = JSONObject.parseObject(contentJson);
|
|
|
+ JSONArray settingArray = jsonObject.getJSONArray("setting");
|
|
|
+ if (settingArray != null && !settingArray.isEmpty()) {
|
|
|
+ for (int i = 0; i < settingArray.size(); i++) {
|
|
|
+ JSONObject item = settingArray.getJSONObject(i);
|
|
|
+ item.put("sendStatus", "0");
|
|
|
+ }
|
|
|
+ update.setContentJson(jsonObject.toJSONString());
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("解析ContentJson失败,sopLogId:{}", f.getSopLogId(), e);
|
|
|
+ update.setContentJson(sopLogsMap.get(f.getSopLogId()).getContentJson());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return update;
|
|
|
+ }).collect(Collectors.toList());
|
|
|
+
|
|
|
+ //批量同步执行记录表
|
|
|
+ qwSopLogsMapper.batchUpdateQwSopLogsBySmsLogsId(updateList);
|
|
|
+ }
|
|
|
+ log.debug("批量更新失败状态完成,条数:{}", failedIds.size());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("批量更新失败状态异常", e);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @PreDestroy
|
|
|
+ public void destroy() {
|
|
|
+ log.info("SmsSendService 正在关闭...");
|
|
|
+ smsExecutor.shutdown();
|
|
|
+ statusExecutor.shutdown();
|
|
|
+ try {
|
|
|
+ if (!smsExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
|
|
|
+ smsExecutor.shutdownNow();
|
|
|
+ }
|
|
|
+ if (!statusExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
|
|
|
+ statusExecutor.shutdownNow();
|
|
|
+ }
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ smsExecutor.shutdownNow();
|
|
|
+ statusExecutor.shutdownNow();
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ }
|
|
|
+ log.info("SmsSendService 已关闭");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 时间范围内部类
|
|
|
+ */
|
|
|
+ private static class TimeRange {
|
|
|
+ Date endTime;
|
|
|
+
|
|
|
+ TimeRange(Date startTime, Date endTime) {
|
|
|
+ this.endTime = endTime;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送结果内部类
|
|
|
+ */
|
|
|
+ @lombok.Data
|
|
|
+ @lombok.AllArgsConstructor
|
|
|
+ private static class SendResult {
|
|
|
+ private int success;
|
|
|
+ private int failed;
|
|
|
+ private List<Long> failedIds;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ //处理特定场景,电脑被重启后,发送短信任务会丢失
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ public SendResultDetailDTO isSendLogs(QwSopLogs qwSopLogs, QwSopCourseFinishTempSetting setting,QwSopSmsLogs logRecord) {
|
|
|
+ Long qwUserId = qwSopLogs.getQwUserKey();
|
|
|
+ if(qwSopLogs.getSendStatus() != 3){
|
|
|
+ log.info("状态异常不发送:{}, LOGID: {}", qwSopLogs.getQwUserKey(), qwSopLogs.getId());
|
|
|
+ return new SendResultDetailDTO(false, "状态异常不发送", logRecord.getSopLogId());
|
|
|
+ }
|
|
|
+
|
|
|
+ boolean noSop = qwSopLogs.getSendType() != 3 && qwSopLogs.getSendType() != 7;
|
|
|
+
|
|
|
+ if (qwSopLogs.getExpiryTime() == null && noSop) {
|
|
|
+ // 作废消息
|
|
|
+ log.info("SOP_LOG_ID:{}, SOP任务被删除", qwSopLogs.getId());
|
|
|
+ return new SendResultDetailDTO(false, "SOP任务被删除", logRecord.getSopLogId());
|
|
|
+ }
|
|
|
+
|
|
|
+ LocalDateTime sendTime = DateUtil.stringToLocalDateTime(qwSopLogs.getSendTime());
|
|
|
+ LocalDateTime expiryDateTime;
|
|
|
+
|
|
|
+ // 判断是否过期
|
|
|
+ if(qwSopLogs.getSendType() == 3 || qwSopLogs.getSendType() == 7){
|
|
|
+ expiryDateTime = sendTime.plusHours(12);
|
|
|
+ }else{
|
|
|
+ expiryDateTime = sendTime.plusHours(qwSopLogs.getExpiryTime());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (LocalDateTime.now().isAfter(expiryDateTime) ) {
|
|
|
+ // 作废消息
|
|
|
+ log.info("SOP_LOG_ID:{}, 已过期,不发送", qwSopLogs.getId());
|
|
|
+ return new SendResultDetailDTO(false, "已过期,不发送", logRecord.getSopLogId());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (setting.getCourseType() == null && noSop && setting.getType() == 2) {
|
|
|
+ log.info("SOP_LOG_ID:{}, 模板未选消息类型,不发送", qwSopLogs.getId());
|
|
|
+ return new SendResultDetailDTO(false, "模板未选消息类型,不发送", logRecord.getSopLogId());
|
|
|
+ }
|
|
|
+ Integer cacheValue = redisCache.getCacheObject("sopCourse:video:isPause:" + setting.getVideoId());
|
|
|
+ int isPause = (cacheValue != null) ? cacheValue : 0;
|
|
|
+ log.info("SOP_LOG_ID:{},判断课程({})当前状态:{}", qwSopLogs.getId(), setting.getVideoId(), isPause);
|
|
|
+ if (isPause == 1){
|
|
|
+ log.info("SOP_LOG_ID:{}, 课程暂停,不发送", qwSopLogs.getId());
|
|
|
+ return new SendResultDetailDTO(false, "课程暂停,AI不发送", logRecord.getSopLogId());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (qwSopLogs.getSendType() != 12 && noSop) {
|
|
|
+ // 客户的信息
|
|
|
+ Integer courseType = setting.getCourseType();
|
|
|
+ if (setting.getType() == 2 && courseType != 0) {// 课程消息,进行复杂的条件判断
|
|
|
+ FsCourseWatchLog watchLog = watchLogService.getWatchCourseLogVideoBySop(
|
|
|
+ setting.getVideoId().longValue(),
|
|
|
+ String.valueOf(qwUserId),
|
|
|
+ qwSopLogs.getExternalId()
|
|
|
+ );
|
|
|
+ log.debug("ID:{}-看课记录参数:videoID:{}, qwUserID:{}, extID:{}", qwSopLogs.getId(), setting.getVideoId().longValue(), qwUserId, qwSopLogs.getExternalId());
|
|
|
+ log.debug("ID:{}-看课记录:{}", qwSopLogs.getId(), watchLog);
|
|
|
+ String logId = qwSopLogs.getId();
|
|
|
+ if (watchLog != null) {
|
|
|
+ //新逻辑
|
|
|
+ if (!QwSopLogsServiceImpl.isCourseTypeValid(courseType, watchLog.getLogType())) {
|
|
|
+ // 作废消息
|
|
|
+ log.info("SOP_LOG_ID:{}, 看课状态未满足,不发送", qwSopLogs.getId());
|
|
|
+ return new SendResultDetailDTO(false, "看课状态未满足,不发送", logRecord.getSopLogId());
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ log.info("SOP_LOG_ID:{}, 无观看记录,不发送", qwSopLogs.getId());
|
|
|
+ return new SendResultDetailDTO(false, "无观看记录,不发送", logRecord.getSopLogId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return new SendResultDetailDTO(true, "检测通过", logRecord.getSopLogId());
|
|
|
+ }
|
|
|
+
|
|
|
+}
|