refactor(cron): 重构Cron时间戳获取功能

- 将getNextCronTimestamp函数改为异步函数,使用HTTP请求替代本地计算
- 移除原有的本地Cron表达式解析和时间计算逻辑
- 添加对远程服务的HTTP请求实现时间戳计算
- 注释掉原有性能较差的本地计算方法
- 移除不再使用的辅助函数parseCron、parseField、parseCronField
- 保留isValidCron函数用于验证Cron表达式的功能
This commit is contained in:
yan
2026-01-13 10:58:14 +08:00
parent 251a6e7d38
commit 52ee973e41
4 changed files with 120 additions and 88 deletions

View File

@@ -700,10 +700,11 @@ async function init() {
return timeDifference.total.hours >= value; return timeDifference.total.hours >= value;
case timeType.cron: case timeType.cron:
const nextCronTimestamp = cronUtil.getNextCronTimestamp(`${value}`, timestamp, now); const nextCronTimestamp = cronUtil.getNextCronTimestamp(`${value}`, timestamp, now);
if (!nextCronTimestamp) { // if (!nextCronTimestamp) {
log.error(`cron表达式解析失败: {value}`, value) // log.error(`cron表达式解析失败: {value}`, value)
throw new Error(`cron表达式解析失败: ${value}`) // throw new Error(`cron表达式解析失败: ${value}`)
} // }
if (!nextCronTimestamp) return false;
return now >= nextCronTimestamp; return now >= nextCronTimestamp;
default: default:
return false; return false;
@@ -713,6 +714,7 @@ async function init() {
return false; return false;
}); });
if (timeFilter?.length > 0) { if (timeFilter?.length > 0) {
//移除CD //移除CD
list = Array.from(new Set(list).difference(new Set(timeFilter))) list = Array.from(new Set(list).difference(new Set(timeFilter)))
@@ -725,6 +727,8 @@ async function init() {
name: settingsAsName.settings_name name: settingsAsName.settings_name
}) })
} }
log.debug(`[CD]{0}[CD]`,JSON.stringify([...timeFilter]))
log.debug(`[RUN]{0}[RUN]`,JSON.stringify([...list]))
} }
} }
// 启用自动拾取的实时任务,并配置成启用急速拾取模式 // 启用自动拾取的实时任务,并配置成启用急速拾取模式

View File

@@ -12,6 +12,10 @@
"links": "https://github.com/Kirito520Asuna" "links": "https://github.com/Kirito520Asuna"
} }
], ],
"http_allowed_urls": [
"https://*",
"http://*"
],
"settings_ui": "settings.json", "settings_ui": "settings.json",
"main": "main.js" "main": "main.js"
} }

View File

@@ -20,6 +20,12 @@
"label": "刷新黑名单 以,分割", "label": "刷新黑名单 以,分割",
"default": "其他,锄地专区,食材与炼金" "default": "其他,锄地专区,食材与炼金"
}, },
{
"name": "cron_http_url",
"type": "input-text",
"label": "cron解析Http 地址",
"default": "http://<ip:port>/bgi/cron/next-timestamp"
},
{ {
"name": "key", "name": "key",
"type": "input-text", "type": "input-text",

View File

@@ -114,88 +114,112 @@ function parseCron(cron) {
} }
} }
/** /**
* 根据 cron 表达式和当前时间,计算下一次执行的时间戳(毫秒) * 获取下一个Cron时间戳
* 支持标准 5 段 cron 分钟 时 日 月 星期 * @param {string} cronExpression - Cron表达式
* @param {string} cron - cron表达式例如 "30 2 * * 1-5" * @param {number} [startTimestamp=Date.now()] - 开始时间戳,默认为当前时间
* @param {number} [fromTime=Date.now()] - 从这个时间开始找下一个执行点 * @param {number} endTimestamp - 结束时间戳
* @returns {number|null} 下一次执行的时间戳(毫秒),找不到返回 null * @returns {Promise} 返回一个Promise解析为下一个Cron时间戳
*/ */
function getNextCronTimestamp(cron, fromTime = Date.now(),endTime) { async function getNextCronTimestamp(cronExpression, startTimestamp = Date.now(), endTimestamp, url = settings.cron_http_url) {
const parts = cron.trim().split(/\s+/); const result = await http.request("POST", url, JSON.stringify({
if (parts.length < 5 || parts.length > 6) { cronExpression: `${cronExpression}`,
throw new Error("不支持的 cron 格式,应为 5~6 段"); startTimestamp: startTimestamp,
endTimestamp: endTimestamp
}), JSON.stringify({
"Content-Type": "application/json"
})).then(res => {
log.debug(`[{0}]res=>{1}`, 'next', JSON.stringify(res))
if (res.status_code === 200 && res.body) {
let result_json = JSON.parse(res.body);
if (result_json?.code === 200) {
return result_json?.data
}
throw new Error("请求失败,error:" + result_json?.message)
}
return undefined
})
return result === null || !result ? undefined : result
} }
const [minStr, hourStr, dayStr, monthStr, dowStr] = parts; //影响到性能 改http 第三方
// function getNextCronTimestamp(cron, fromTime = Date.now(),endTime) {
// 解析每个字段 // const parts = cron.trim().split(/\s+/);
const minutes = parseField(minStr, 0, 59); // if (parts.length < 5 || parts.length > 6) {
const hours = parseField(hourStr, 0, 23); // throw new Error("不支持的 cron 格式,应为 5~6 段");
const days = parseField(dayStr, 1, 31); // }
const months = parseField(monthStr, 1, 12); //
const dows = parseField(dowStr, 0, 7); // const [minStr, hourStr, dayStr, monthStr, dowStr] = parts;
//
// 星期 7 → 0 (周日) // // 解析每个字段
if (dows.has(7)) dows.add(0); // const minutes = parseField(minStr, 0, 59);
// const hours = parseField(hourStr, 0, 23);
let current = new Date(fromTime); // const days = parseField(dayStr, 1, 31);
// 如果没有指定 endTime默认设置为明天 00:00:00 // const months = parseField(monthStr, 1, 12);
if (endTime === undefined) { // const dows = parseField(dowStr, 0, 7);
const tomorrow = new Date(current); //
tomorrow.setDate(tomorrow.getDate() + 1); // // 星期 7 → 0 (周日)
tomorrow.setHours(0, 0, 0, 0); // if (dows.has(7)) dows.add(0);
endTime = tomorrow.getTime(); //
} // let current = new Date(fromTime);
// // 如果没有指定 endTime默认设置为明天 00:00:00
// 动态计算最大迭代次数 // if (endTime === undefined) {
// 将时间差(毫秒)转换为分钟,向上取整,确保覆盖所有可能的分钟点 // const tomorrow = new Date(current);
const timeDiffMinutes = Math.ceil((endTime - fromTime) / 60000); // tomorrow.setDate(tomorrow.getDate() + 1);
// tomorrow.setHours(0, 0, 0, 0);
// 设置最大迭代次数,防止意外情况(如 endTime 极大)导致内存溢出或死循环 // endTime = tomorrow.getTime();
// 即使 endTime 是 10 年后,也限制在约 2 年内(避免极端情况) // }
// 2年 ≈ 365 * 2 * 24 * 60 = 1,051,200 //
const MAX_ITERATIONS_LIMIT = 1051200; // // 动态计算最大迭代次数
const MAX_ITERATIONS = Math.min(timeDiffMinutes, MAX_ITERATIONS_LIMIT); // // 将时间差(毫秒)转换为分钟,向上取整,确保覆盖所有可能的分钟点
// const timeDiffMinutes = Math.ceil((endTime - fromTime) / 60000);
let iteration = 0; //
while (iteration++ < MAX_ITERATIONS) { // // 设置最大迭代次数,防止意外情况(如 endTime 极大)导致内存溢出或死循环
// 先推进到下一分钟,避免死循环在同一分钟 // // 即使 endTime 是 10 年后,也限制在约 2 年内(避免极端情况)
current.setMinutes(current.getMinutes() + 1); // // 2年 ≈ 365 * 2 * 24 * 60 = 1,051,200
current.setSeconds(0); // const MAX_ITERATIONS_LIMIT = 1051200;
current.setMilliseconds(0); // const MAX_ITERATIONS = Math.min(timeDiffMinutes, MAX_ITERATIONS_LIMIT);
//
const m = current.getMinutes(); // let iteration = 0;
const h = current.getHours(); // while (iteration++ < MAX_ITERATIONS) {
const d = current.getDate(); // // 先推进到下一分钟,避免死循环在同一分钟
const mon = current.getMonth() + 1; // JS 月份 0~11 // current.setMinutes(current.getMinutes() + 1);
const dow = current.getDay(); // 0=周日, 1=周一, ..., 6=周六 // current.setSeconds(0);
// current.setMilliseconds(0);
// 核心匹配条件(日期和星期是 OR 关系) //
const minuteMatch = minutes.has(m) || minutes.size === 0; // const m = current.getMinutes();
const hourMatch = hours.has(h) || hours.size === 0; // const h = current.getHours();
const monthMatch = months.has(mon) || months.size === 0; // const d = current.getDate();
const dayMatch = days.has(d) || days.size === 0; // const mon = current.getMonth() + 1; // JS 月份 0~11
const dowMatch = dows.has(dow) || dows.size === 0; // const dow = current.getDay(); // 0=周日, 1=周一, ..., 6=周六
//
const dateOrDowMatch = (days.size === 0 && dows.size === 0) || // 两者都是 * // // 核心匹配条件(日期和星期是 OR 关系)
(days.size > 0 && dows.size === 0) || // 只指定了日期 // const minuteMatch = minutes.has(m) || minutes.size === 0;
(days.size === 0 && dows.size > 0) || // 只指定了星期 // const hourMatch = hours.has(h) || hours.size === 0;
(dayMatch && dowMatch); // 两者都满足才算(最严格) // const monthMatch = months.has(mon) || months.size === 0;
// const dayMatch = days.has(d) || days.size === 0;
if (minuteMatch && hourMatch && monthMatch && dateOrDowMatch) { // const dowMatch = dows.has(dow) || dows.size === 0;
return current.getTime(); //
} // const dateOrDowMatch = (days.size === 0 && dows.size === 0) || // 两者都是 *
} // (days.size > 0 && dows.size === 0) || // 只指定了日期
// (days.size === 0 && dows.size > 0) || // 只指定了星期
// 如果是因为超过 MAX_ITERATIONS_LIMIT 而退出,说明时间跨度太大 // (dayMatch && dowMatch); // 两者都满足才算(最严格)
if (timeDiffMinutes > MAX_ITERATIONS_LIMIT) { //
log.warn("查找范围过大,已达到最大迭代次数限制"); // if (minuteMatch && hourMatch && monthMatch && dateOrDowMatch) {
} else { // return current.getTime();
log.warn("未找到合理下一次执行时间"); // }
} // }
return null; //
} // // 如果是因为超过 MAX_ITERATIONS_LIMIT 而退出,说明时间跨度太大
// if (timeDiffMinutes > MAX_ITERATIONS_LIMIT) {
// log.warn("查找范围过大,已达到最大迭代次数限制");
// } else {
// log.warn("未找到合理下一次执行时间");
// }
// return null;
// }
/** /**
* 解析单个 cron 字段,返回匹配的数值 Set * 解析单个 cron 字段,返回匹配的数值 Set
@@ -264,13 +288,7 @@ function parseField(field, min, max) {
return result; return result;
} }
function a(){
return true
}
this.cronUtil = { this.cronUtil = {
getNextCronTimestamp, getNextCronTimestamp,
parseCron,
parseField,
parseCronField,
} }