背景介绍
定时发布是一个很实用的功能,微博有定时发布,QQ中有定时送礼物,信用卡有定时还款。 对于博客,定时发布功能可以提前把写作好的文章安排好,有规律的更新写作,更有利于搜索引擎抓取,这本身就是一件比较有意义的事情。
利用到的关键技术点
node-schedule
Node Schedule 是一个Node.js的灵活的类似cron又不类似的任务调度库.它允许你调度任务(任意函数)在特殊的日期执行,并循环执行。他只在在任何给定的时间里使用一个定时器(而不是每隔一秒/一分钟来重新判断将要执行的任务。
node-schedule中引入了cron-parser,支持cron表达式:
* * * * * *
┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ │
│ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
│ │ │ │ └───── month (1 - 12)
│ │ │ └────────── day of month (1 - 31)
│ │ └─────────────── hour (0 - 23)
│ └──────────────────── minute (0 - 59)
└───────────────────────── second (0 - 59, OPTIONAL)
// 6个占位符从左到右分别代表:秒、分、时、日、月、周几
// '*'表示通配符,匹配任意,当秒是'*'时,表示任意秒数都触发,其它类推
这里只需要实现在某天的某个时刻定时发布文章,cron表达式不继续深入,而是采用了另外种基于具体日期的任务调度。
var schedule = require('node-schedule');
var date = new Date(2019, 07, 11, 21, 00, 0);
var j = schedule.scheduleJob(date, function(){
// 业务逻辑
// 将文章状态对应的字段 *status* 改为 *已发布*
console.log('定时发布成功!');
});
mongodb持久化存储任务
Node Schedule 是被设计用来进行进程内调度的,也就是说调度任务只能在你的脚本运行时才能有效。 所以我用mongodb来记录这些任务。就算中途服务重启了,还可以从数据库中读取将要执行的文章定时发布任务的数据,重新创建Node Schedule下对应的定时任务。
mongodb的集合(表结构)可能是这样的:
# | 类型 | 说明 |
---|---|---|
name | string | 任务名称 |
task_run_at | date | 任务计划执行时间 |
status | string | 执行结果/状态 |
create_time | date | 创建时间 |
modify_time | date | 修改时间 |
如果服务因为些情况需要重启,我们可以根据task_run_at
设定的时间,也就是文章定时发布的时间。如果某条记录中此字段描述的时间大于当前时间,则表示将来需要执行这个定时任务,所以应该重新装载Node Schedule下对应的定时任务。这样就达到了即使服务重启,也不会丢失先前已有的定时发布任务。
当然这里实现比较简单,没有考虑服务器处于异常很久导致任务到期未执行以及集群下如何保证只执行一次的情况。
管理后台交互的样子
遇到的问题
在实现的差不多的时候,遇到了个棘手的小问题。
- 当定时发布选项填完后,再次重新编辑发布的时间,将导致任务重复执行。解决方案是将任务收集起来放在一个全局变量中并用文章id作为唯一标识,当再次修改发布时间时,先取消之前的定时任务,再以新时间重新创建任务。
schedule.scheduleJob(id, new Date(date), async function(){
// code here
})
JOBS[id] = schedule.scheduledJobs[id];
// 使用id标记任务,并将任务用全局变量JOBS存起来,
// 当再次接收到该id对应文章的发布时间修改请求时,
// 调用`cancel()`方法取消此前存的定时任务.