feat(sms-task): 实现主题日短信功能
- 完善主题日短信服务接口,添加活动任务查询和发送方法 - 实现主题日短信服务类,支持按时间段查询活动任务 - 添加主题日短信发送功能,支持个性化模板替换 - 实现定时任务:每分钟检查并发送主题日短信 - 添加每日状态重置定时任务,防止重复发送 - 支持批量发送多个主题日短信任务 - 优化异常处理和日志记录机制 - 使用并发Map保证多线程安全 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>master
parent
9f1baf17d2
commit
a7d890843e
|
|
@ -2,12 +2,34 @@ package com.whdc.service;
|
|||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.whdc.model.entity.SmsTask;
|
||||
import com.whdc.model.entity.Specialist;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 短信任务服务接口
|
||||
* 主题日短信服务接口
|
||||
*
|
||||
* @author lyf
|
||||
* @since 2025-09-23
|
||||
*/
|
||||
public interface ISmsTaskService extends IService<SmsTask> {
|
||||
/**
|
||||
* 查询当前活动中的主题日短信任务
|
||||
*/
|
||||
List<SmsTask> listActiveTasks();
|
||||
|
||||
/**
|
||||
* 查询需要执行的今日主题日任务
|
||||
*/
|
||||
List<SmsTask> listTodayTasks();
|
||||
|
||||
/**
|
||||
* 发送主题日短信
|
||||
*/
|
||||
void sendThemeSms(SmsTask smsTask);
|
||||
|
||||
/**
|
||||
* 批量发送主题日短信
|
||||
*/
|
||||
void sendBatchThemeSms(List<SmsTask> smsTasks);
|
||||
}
|
||||
|
|
@ -1,17 +1,273 @@
|
|||
package com.whdc.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.whdc.mapper.SmsLogMapper;
|
||||
import com.whdc.mapper.SmsTaskMapper;
|
||||
import com.whdc.mapper.SpecialistMapper;
|
||||
import com.whdc.model.entity.SmsLog;
|
||||
import com.whdc.model.entity.SmsTask;
|
||||
import com.whdc.model.entity.Specialist;
|
||||
import com.whdc.service.ISmsTaskService;
|
||||
import com.whdc.utils.SmsHelper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* 短信任务服务实现类
|
||||
* 主题日短信服务实现类
|
||||
*
|
||||
* @author lyf
|
||||
* @since 2025-09-23
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class SmsTaskServiceImpl extends ServiceImpl<SmsTaskMapper, SmsTask> implements ISmsTaskService {
|
||||
|
||||
@Autowired
|
||||
private SpecialistMapper specialistMapper;
|
||||
|
||||
@Autowired
|
||||
private SmsLogMapper smsLogMapper;
|
||||
|
||||
@Autowired
|
||||
private SmsHelper smsHelper;
|
||||
|
||||
// 记录每个任务今日是否已发送短信 - 使用并发Map保证线程安全
|
||||
private final Map<Long, AtomicBoolean> taskSmsSentToday = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public List<SmsTask> listActiveTasks() {
|
||||
try {
|
||||
LocalDate today = LocalDate.now();
|
||||
|
||||
// 查询当前活动中的主题日短信任务
|
||||
LambdaQueryWrapper<SmsTask> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper
|
||||
.eq(SmsTask::getStatus, 1) // 1:有效
|
||||
.le(SmsTask::getStartDate, today) // 开始日期小于等于今天
|
||||
.ge(SmsTask::getEndDate, today); // 结束日期大于等于今天
|
||||
|
||||
List<SmsTask> tasks = this.list(queryWrapper);
|
||||
return tasks != null ? tasks : Collections.emptyList();
|
||||
} catch (Exception e) {
|
||||
log.error("查询活动中的主题日短信任务失败: {}", e.getMessage(), e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SmsTask> listTodayTasks() {
|
||||
try {
|
||||
// 获取当前活动中的任务
|
||||
List<SmsTask> activeTasks = listActiveTasks();
|
||||
if (activeTasks.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LocalDate today = LocalDate.now();
|
||||
List<SmsTask> todayTasks = new java.util.ArrayList<>();
|
||||
|
||||
// 筛选今天需要执行的任务
|
||||
for (SmsTask task : activeTasks) {
|
||||
// 检查是否已经发送过
|
||||
AtomicBoolean sentFlag = taskSmsSentToday.computeIfAbsent(task.getId(), k -> new AtomicBoolean(false));
|
||||
if (sentFlag.get()) {
|
||||
continue; // 今日已发送,跳过
|
||||
}
|
||||
|
||||
// 检查执行时间
|
||||
String executionTime = task.getExecutionTmStr();
|
||||
if (executionTime == null || executionTime.trim().isEmpty()) {
|
||||
log.warn("主题日任务{}执行时间未配置", task.getSubjectName());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查当前时间是否达到执行时间
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
try {
|
||||
String todayWithExecutionTime = today.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + " " + executionTime;
|
||||
LocalDateTime scheduledTime = LocalDateTime.parse(
|
||||
todayWithExecutionTime,
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||
);
|
||||
|
||||
if (now.isEqual(scheduledTime) || now.isAfter(scheduledTime)) {
|
||||
todayTasks.add(task);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("主题日任务{}执行时间格式解析失败: {}", task.getSubjectName(), executionTime, e);
|
||||
}
|
||||
}
|
||||
|
||||
return todayTasks;
|
||||
} catch (Exception e) {
|
||||
log.error("查询今日主题日任务失败: {}", e.getMessage(), e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendThemeSms(SmsTask smsTask) {
|
||||
if (smsTask == null) {
|
||||
log.warn("主题日短信任务为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取所有有效专家
|
||||
List<Specialist> specialists = getValidSpecialists();
|
||||
if (specialists.isEmpty()) {
|
||||
log.info("没有有效专家,跳过主题日短信发送");
|
||||
return;
|
||||
}
|
||||
|
||||
String template = smsTask.getTemplate();
|
||||
if (template == null || template.trim().isEmpty()) {
|
||||
log.warn("主题日任务{}模板内容为空", smsTask.getSubjectName());
|
||||
return;
|
||||
}
|
||||
|
||||
// 逐个发送个性化短信
|
||||
for (Specialist specialist : specialists) {
|
||||
try {
|
||||
// 替换模板中的占位符
|
||||
String content = template.replace("{姓名}", specialist.getName())
|
||||
.replace("{称呼}", specialist.getTitle() != null ? specialist.getTitle() : "")
|
||||
.replace("{主题}", smsTask.getSubjectName());
|
||||
|
||||
// 创建短信日志记录
|
||||
SmsLog smsLog = new SmsLog();
|
||||
smsLog.setName(specialist.getName())
|
||||
.setPhone(specialist.getPhone())
|
||||
.setContent(content)
|
||||
.setRemark("主题日短信-" + smsTask.getSubjectName())
|
||||
.setSendTm(new java.util.Date());
|
||||
|
||||
// 使用SmsHelper发送个性化短信
|
||||
List<String> phoneList = Collections.singletonList(specialist.getPhone());
|
||||
String sendResult = smsHelper.send(phoneList, content);
|
||||
log.info("向专家{}发送主题日短信[{}]结果: {}", specialist.getName(), smsTask.getSubjectName(), sendResult);
|
||||
|
||||
// 根据发送结果设置备注
|
||||
if ("发送成功".equals(sendResult)) {
|
||||
smsLog.setRemark("主题日短信-" + smsTask.getSubjectName() + "-发送成功");
|
||||
} else {
|
||||
smsLog.setRemark("主题日短信-" + smsTask.getSubjectName() + "-发送失败: " + sendResult);
|
||||
}
|
||||
|
||||
// 保存短信日志记录
|
||||
smsLogMapper.insert(smsLog);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("向专家{}发送主题日短信[{}]时发生异常: {}", specialist.getName(), smsTask.getSubjectName(), e.getMessage(), e);
|
||||
|
||||
// 即使发送失败,也保存短信记录
|
||||
try {
|
||||
SmsLog failedSmsLog = new SmsLog();
|
||||
failedSmsLog.setName(specialist.getName())
|
||||
.setPhone(specialist.getPhone())
|
||||
.setContent(template.replace("{姓名}", specialist.getName())
|
||||
.replace("{称呼}", specialist.getTitle() != null ? specialist.getTitle() : "")
|
||||
.replace("{主题}", smsTask.getSubjectName()))
|
||||
.setRemark("主题日短信-" + smsTask.getSubjectName() + "-发送异常: " + e.getMessage())
|
||||
.setSendTm(new java.util.Date());
|
||||
smsLogMapper.insert(failedSmsLog);
|
||||
} catch (Exception logException) {
|
||||
log.error("保存发送失败的短信日志时发生异常: {}", logException.getMessage(), logException);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 标记任务今日已发送
|
||||
AtomicBoolean sentFlag = taskSmsSentToday.computeIfAbsent(smsTask.getId(), k -> new AtomicBoolean(false));
|
||||
sentFlag.set(true);
|
||||
log.info("主题日短信任务[{}]发送完成", smsTask.getSubjectName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendBatchThemeSms(List<SmsTask> smsTasks) {
|
||||
if (smsTasks == null || smsTasks.isEmpty()) {
|
||||
log.info("没有需要发送的主题日短信任务");
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("开始批量发送{}个主题日短信任务", smsTasks.size());
|
||||
for (SmsTask task : smsTasks) {
|
||||
try {
|
||||
sendThemeSms(task);
|
||||
} catch (Exception e) {
|
||||
log.error("发送主题日短信任务[{}]失败: {}", task.getSubjectName(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
log.info("批量主题日短信任务发送完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有有效专家
|
||||
*/
|
||||
private List<Specialist> getValidSpecialists() {
|
||||
try {
|
||||
LambdaQueryWrapper<Specialist> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(Specialist::getStatus, 1); // 1:有效
|
||||
queryWrapper.isNotNull(Specialist::getPhone); // 手机号不为空
|
||||
|
||||
List<Specialist> specialists = specialistMapper.selectList(queryWrapper);
|
||||
return specialists != null ? specialists : Collections.emptyList();
|
||||
} catch (Exception e) {
|
||||
log.error("查询有效专家失败: {}", e.getMessage(), e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 定时检查并发送主题日短信
|
||||
* 每分钟执行一次
|
||||
*/
|
||||
@Scheduled(cron = "0 * * * * ?")
|
||||
public void checkAndSendThemeSms() {
|
||||
try {
|
||||
// 查询今日需要执行的主题日任务
|
||||
List<SmsTask> todayTasks = listTodayTasks();
|
||||
|
||||
if (todayTasks.isEmpty()) {
|
||||
log.debug("今日没有需要执行的主题日短信任务");
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("检测到{}个主题日短信任务需要执行", todayTasks.size());
|
||||
|
||||
// 批量发送主题日短信
|
||||
sendBatchThemeSms(todayTasks);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("定时检查主题日短信任务执行异常: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置每日发送状态
|
||||
* 每天凌晨00:00:01执行
|
||||
*/
|
||||
@Scheduled(cron = "1 0 0 * * ?")
|
||||
public void resetDailySendStatus() {
|
||||
try {
|
||||
// 重置所有任务的发送状态
|
||||
for (AtomicBoolean sentFlag : taskSmsSentToday.values()) {
|
||||
sentFlag.set(false);
|
||||
}
|
||||
log.info("主题日短信每日发送状态已重置");
|
||||
} catch (Exception e) {
|
||||
log.error("重置主题日短信每日发送状态异常: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue