Spring Boot定时发送短信的两种方式
不少项目中估计都有遇到定时发送这个梗,我也遇到了,页面中下拉框选项有立即发送以及定时发送,立即发送就不用说了,定时发送可以讲一下,定时的方式有很多种,Java自带的定时器,Spring的定时器,大致讲一下我做的时候思路
1. Java自带的定时器
java.util包里的Timer,它也可以实现定时任务但是功能过于单一所有使用很少,而还有一个类ScheduledExecutorService可以基本满足定时任务中的需求
首先要创建一个线程,这里实现了Runnable接口,自己写run方法
public class MessageJob implements Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(MessageJob.class);
private static volatile ConcurrentHashMap<String, String> idMapping = new ConcurrentHashMap<>();
private static volatile ConcurrentHashMap<String, MessageJob> cache = new ConcurrentHashMap<>();
private volatile AtomicBoolean canceled = new AtomicBoolean(false);
/** 任务id */
private String jobId;
/** 短信id */
private String messageId;
private UserDTO userDTO;
private String title;
private String context;
private String phone;
public MessageJob(String messageId, UserDTO UserDTO, String title, String context, String phone) {
this.jobId = GeneratorIDFactory.generatorUUID();
this.messageId = messageId;
this.userDTO = UserDTO;
this.title = title;
this.context = context;
this.phone = phone;
//取消并清除上一次任务
cancelAndClearLastJobIfExist();
//缓存本次任务
cacheThisJob();
}
/**
* 取消并清除上一次任务
*/
private void cancelAndClearLastJobIfExist() {
if (StringUtil.isNotEmpty(idMapping.get(messageId))) {
MessageJob lastJob = cache.get(idMapping.get(messageId));
if (null != lastJob) {
lastJob.cancelJob();
cache.remove(idMapping.get(messageId));
idMapping.remove(messageId);
}
}
}
@Override
public void run() {
//判断任务是否被取消
if (!canceled.get()) {
SendMessageUtil sendMessageUtil = (SendMessageUtil) SpringContextUtil.getBean("sendMessageUtil");
MsgMessageService msgMessageService = (MsgMessageService) SpringContextUtil.getBean("msgMessageService");
//发送
try {
sendMessageUtil.sendMessage(userDTO, title, context, phone);
LOGGER.info("线程已启动");
MsgMessage message = new MsgMessage();
message.setState(1);
message.setId(messageId);
msgMessageService.update(message);
LOGGER.info("短信状态已更新");
} catch (ServiceException e) {
LOGGER.error("线程出现问题::" + e.getMessage(), e);
}
//从缓存中清理本任务
clear();
}
}
/**
* 缓存本次任务
*/
private void cacheThisJob() {
//id映射
idMapping.put(this.messageId, this.jobId);
//短信发送任务缓存
cache.put(this.jobId, this);
}
/**
* 取消任务
*/
public void cancelJob() {
this.canceled.set(true);
}
/**
* 清理缓存
*/
public void clear() {
idMapping.remove(messageId);
cache.remove(this.jobId);
}
}
模拟了缓存,以及队列,jobId是UUID自动生成,防止ID重复,一个短信ID对应一个jobId,一个jobId对应一个线程的方式,确保线程分布执行,不干扰
这么做的意义在于,可在定时发送日期还没到时取消发送短信,一个短信发送时间经过编辑后可清空上一次任务新建一个线程,调用代码如下
ScheduledExecutorService service = new ScheduledThreadPoolExecutor(10, new BasicThreadFactory.Builder().namingPattern("scheduled-pool-%d").daemon(true).build());
MessageJob job = new MessageJob(msgMessage.getId(), sender, "短信通知", messagecontent, phones);
long delay = msgMessage.getSendertime().getTime() - System.currentTimeMillis();
service.schedule(job, delay, TimeUnit.MILLISECONDS);
delay是短信发送的时间减去当前时间的毫秒,让定时器在规定时间后执行这个线程发送短信
2. Spring的定时器注解
Spring Boot定时器就不介绍了,也很方便简单,在启动类上加上注解@EnableScheduling
就可以了
这种方式比较适用于在项目中,短信的所有信息都存在数据里里的情况下,根据type来区分是定时发送还是立即发送,state来区别是已发送还是未发送;这个定时器只需要读取出所有状态是未发送并且是定时发送的数据库数据,并且发送时间是小于当前时间的
select * from message where type = 2 and state = 2 and send_time <= now();
举例,有一条短信是2019-1-17 22:05:00要发送的,定时任务一直在开启,到17号22点05分00秒的时候的时候这个now()就是当前时间,<=正好查到这条数据,那就表示到了该发送的时间,将短信发送出去
@Component
public class MessageJobTwo {
private static final Logger logger = LoggerFactory.getLogger(MessageJobTwo.class);
@Autowired
private MsgMessageMapper messageMapper;
@Autowired
private MsgMessagemapperService msgMessagemapperService;
@Autowired
private SendMessageUtil sendMessageUtil;
@Value("${message.isopen}")
private boolean isOpen;
/**
* 每隔两秒定时查询小于等于当前时间的并且为未发送定时的数据
* 对数据进行发送并且更改短信状态
*/
@Scheduled(cron = "*/2 * * * * ?")
public void sendMessageJob(){
if (isOpen) {
logger.info("开始执行-----短信定时任务");
MsgMessageDTO msgMessageDTO = new MsgMessageDTO();
msgMessageDTO.setType(2);
msgMessageDTO.setState(2);
msgMessageDTO.setSendertime(new Date());
Map<String, Object> params = new HashMap<>(16);
params.put("condition", msgMessageDTO);
List<MsgMessageDTO> messageList = messageMapper.messageList(params);
for (MsgMessageDTO messageDTO : messageList) {
String phones = selectPhone(messageDTO.getId());
sendMessageUtil.sendMessage(null, "短信通知", messageDTO.getMessagecontent(), phones);
logger.info("短信发送成功---------");
MsgMessage message = new MsgMessage();
message.setState(1);
message.setId(messageDTO.getId());
messageMapper.update(message);
logger.info("短信状态修改完成---------");
}
}
}
/**
* 查询接收用户,拼接成字符串
* @param messageId
* @return
*/
private String selectPhone(String messageId){
Condition condition = new Condition(MsgMessagemapper.class);
condition.createCriteria().andEqualTo("messageid", messageId);
List<MsgMessagemapper> byCondition = msgMessagemapperService.findByCondition(condition);
StringBuilder builder = new StringBuilder();
for (MsgMessagemapper msgMessagemapper : byCondition) {
builder.append(msgMessagemapper.getReceivephone()).append(",");
}
builder.deleteCharAt(builder.length() - 1);
return builder.toString();
}
}
结语
第二种方式比较方便,是不需要调用,项目启动之后会根据cron表达式里的时间进行操作,定时在查询,查出有符合要求的数据就进行发送。
第一种方式也可以,是异步多线程,第二种是单线程,看需求选择。