方法比较简单,创建一个Worker每分钟触发一次,在Worker中调用其他Worker
需要使用worker binding
Cron版本
* * * * * : 分 时 天 月 周几
注意:为了简化实现,使用/进行步进的时间从0开始,如* * * */7 *会在每月的 1 8 15 22 29执行,跨月的天数会出现问题,留着以后解决(抄的库,我慢慢改)
export default {
async scheduled(event, env, ctx) {
ctx.waitUntil(handleEvent(event, env, ctx))
},
async fetch(request, env, ctx) {
return handleEvent(request, env, ctx)
},
}
const schedules = [
// minute hour dayOfMonth month dayOfWeek
['0 8,16 * * *', 'cron_1', 'https://cron'],
['0 8 * * *', 'cron_2', 'https://cron'],
['0 8 * * 6', 'cron_3', 'https://cron/'],
]
async function handleEvent(event, env, ctx) {
const cronParser = new CronParser()
const currentTime = new Date(new Date().toLocaleString('en-US', {timeZone: 'Asia/Shanghai'}))
for (const [cron, action, url] of schedules) {
const scheduleTime = await cronParser.parser(cron)
console.log(scheduleTime)
console.log(currentTime)
if(
scheduleTime.minutes.includes(currentTime.getMinutes())
&& scheduleTime.hours.includes(currentTime.getHours())
&& scheduleTime.dayOfMonth.includes(currentTime.getDate())
&& scheduleTime.month.includes(currentTime.getMonth() + 1) // month starts from 0
&& scheduleTime.dayOfWeek.includes(currentTime.getDay())
){
console.log(`[INFO] ${currentTime}: Match ${action}`)
try {
await env[action].fetch(url)
} catch (e) {
console.log(e)
}
}
}
return new Response(JSON.stringify({
'message': 'success',
'code': 200
}))
}
class CronParser{
constructor(){
const config = {
"minutes": {
"min": 0,
"max": 59
},
"hours": {
"min": 0,
"max": 23
},
"dayOfMonth": {
"min": 1,
"max": 31
},
"month": {
"min": 1,
"max": 12,
"values": {
"jan": 1,
"feb": 2,
"mar": 3,
"apr": 4,
"may": 5,
"jun": 6,
"jul": 7,
"aug": 8,
"sep": 9,
"oct": 10,
"nov": 11,
"dec": 12
}
},
"dayOfWeek": {
"min": 0,
"max": 6,
"values": {
"sun": 0,
"mon": 1,
"tue": 2,
"wed": 3,
"thu": 4,
"fri": 5,
"sat": 6
}
}
}
const getBoundries = (char, values, field, config) => {
return new Promise((resolve, reject) => {
if (values.length > 2 || !values[0] || !values[1])
return reject(`Unsupported value, ${field} -> ${values.join(char)}`)
let left = !isNaN(parseInt(values[0])) ? parseInt(values[0]) : null,
right = !isNaN(parseInt(values[1])) ? parseInt(values[1]) : null
if (!left && config.values && config.values[values[0].toLowerCase()]) {
left = config.values[values[0].toLowerCase()]
}
if (!right && config.values && config.values[values[1].toLowerCase()]) {
right = config.values[values[1].toLowerCase()]
}
if (
left === null || // cannot do !left because 0 is a valid value
right === null || // cannot do !right because 0 is a valid value
left < config.min ||
left > config.max ||
right < config.min ||
right > config.max ||
(char === '-' && left > right)
)
return reject(`Unsupported value, ${field} -> ${values.join(char)}`)
return resolve({ left, right })
})
}
this.parser = async (expression) => {
/*
Step-1: '1-10/5 10-13 ? * WED,FRI' becomes ['1-10/5', '10-13', '?', '*', 'WED,FRI']
*/
expression = expression.split(' ')
if (expression.length !== 5) return 'Unsupported expression. Exactly 5 time fields expected.'
else if (expression[2] === '?' && expression[4] === '?')
return 'Both dayOfMonth and dayOfWeek cannot be "?"'
const configKeys = Object.keys(config),
result = {
minutes: [],
hours: [],
dayOfMonth: [],
month: [],
dayOfWeek: []
}
for (let i = 0; i < 5; i++) {
/*
Step-2: '1-10/5' becomes ['1-10', '5']
*/
expression[i] = expression[i].split('/')
if (expression[i][0] === '*' || expression[i][0] === '?') {
for (let j = config[configKeys[i]].min; j <= config[configKeys[i]].max; j += +expression[i][1] || 1) {
// incrementing by steps value if available
result[configKeys[i]].push(j)
}
} else if (expression[i][0].indexOf(',') !== -1) {
try {
const { left, right } = await getBoundries(
',',
expression[i][0].split(','),
configKeys[i],
config[configKeys[i]]
)
result[configKeys[i]].push(left)
result[configKeys[i]].push(right)
} catch (err) {
return err
}
} else if (expression[i][0].indexOf('-') !== -1) {
try {
const { left, right } = await getBoundries(
'-',
expression[i][0].split('-'),
configKeys[i],
config[configKeys[i]]
)
for (let j = left; j <= right; j++) {
result[configKeys[i]].push(j)
}
} catch (err) {
return err
}
} else {
let value = !isNaN(parseInt(expression[i][0])) ? parseInt(expression[i][0]) : null
if (
!value &&
config[configKeys[i]].values &&
config[configKeys[i]].values[expression[i][0].toLowerCase()]
)
value = config[configKeys[i]].values[expression[i][0].toLowerCase()]
if (
value === null || // cannot do !value because 0 is a valid value
value < config[configKeys[i]].min ||
value > config[configKeys[i]].max
)
return `Unsupported value, ${configKeys[i]} -> ${expression[i][0]}`
result[configKeys[i]].push(value)
if (expression[i][1]) result[configKeys[i]].push(value + +expression[i][1]) // adding steps value if available
}
}
return result
}
}
}
数组版本,只实现了时分,其它的比如年月可以自行实现.
export default {
async scheduled(event, env, ctx) {
ctx.waitUntil(handleEvent(event, env, ctx))
},
async fetch(request, env, ctx) {
return handleEvent(request, env, ctx)
},
}
const schedule = [
[[[8, 0], [18, 0]], 'cron_1', 'https://cron'],
[[[8, 0]], 'cron_2', 'https://cron'],
[[[8, 0]], 'log', 'https://cron/clear'],
]
async function handleEvent(event, env, ctx) {
const currentTime = new Date(new Date().toLocaleString('en-US', {timeZone: 'Asia/Shanghai'}))
const currentHour = currentTime.getHours()
const currentMinute = currentTime.getMinutes()
for (const [times, action, url] of schedule) {
for (const [hour, minute] of times) {
if (currentHour === hour && currentMinute === minute) {
console.log(`[INFO] ${currentTime}: Match ${action}`)
try {
await env[action].fetch(url)
} catch (e) {
console.log(e)
}
}
}
}
return new Response(JSON.stringify({
'message': 'success',
'code': 200
}))
}