From 52ee973e417111cfe64725a6fbb78f7c54147ec7 Mon Sep 17 00:00:00 2001 From: yan Date: Tue, 13 Jan 2026 10:58:14 +0800 Subject: [PATCH] =?UTF-8?q?refactor(cron):=20=E9=87=8D=E6=9E=84Cron?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E6=88=B3=E8=8E=B7=E5=8F=96=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将getNextCronTimestamp函数改为异步函数,使用HTTP请求替代本地计算 - 移除原有的本地Cron表达式解析和时间计算逻辑 - 添加对远程服务的HTTP请求实现时间戳计算 - 注释掉原有性能较差的本地计算方法 - 移除不再使用的辅助函数parseCron、parseField、parseCronField - 保留isValidCron函数用于验证Cron表达式的功能 --- repo/js/FullyAutoAndSemiAutoTools/main.js | 12 +- .../FullyAutoAndSemiAutoTools/manifest.json | 4 + .../FullyAutoAndSemiAutoTools/settings.json | 6 + .../FullyAutoAndSemiAutoTools/utils/cron.js | 186 ++++++++++-------- 4 files changed, 120 insertions(+), 88 deletions(-) diff --git a/repo/js/FullyAutoAndSemiAutoTools/main.js b/repo/js/FullyAutoAndSemiAutoTools/main.js index 287676f55..d862e7cf2 100644 --- a/repo/js/FullyAutoAndSemiAutoTools/main.js +++ b/repo/js/FullyAutoAndSemiAutoTools/main.js @@ -700,10 +700,11 @@ async function init() { return timeDifference.total.hours >= value; case timeType.cron: const nextCronTimestamp = cronUtil.getNextCronTimestamp(`${value}`, timestamp, now); - if (!nextCronTimestamp) { - log.error(`cron表达式解析失败: {value}`, value) - throw new Error(`cron表达式解析失败: ${value}`) - } + // if (!nextCronTimestamp) { + // log.error(`cron表达式解析失败: {value}`, value) + // throw new Error(`cron表达式解析失败: ${value}`) + // } + if (!nextCronTimestamp) return false; return now >= nextCronTimestamp; default: return false; @@ -713,6 +714,7 @@ async function init() { return false; }); + if (timeFilter?.length > 0) { //移除CD list = Array.from(new Set(list).difference(new Set(timeFilter))) @@ -725,6 +727,8 @@ async function init() { name: settingsAsName.settings_name }) } + log.debug(`[CD]{0}[CD]`,JSON.stringify([...timeFilter])) + log.debug(`[RUN]{0}[RUN]`,JSON.stringify([...list])) } } // 启用自动拾取的实时任务,并配置成启用急速拾取模式 diff --git a/repo/js/FullyAutoAndSemiAutoTools/manifest.json b/repo/js/FullyAutoAndSemiAutoTools/manifest.json index f8b9b6f39..1633f8c8c 100644 --- a/repo/js/FullyAutoAndSemiAutoTools/manifest.json +++ b/repo/js/FullyAutoAndSemiAutoTools/manifest.json @@ -12,6 +12,10 @@ "links": "https://github.com/Kirito520Asuna" } ], + "http_allowed_urls": [ + "https://*", + "http://*" + ], "settings_ui": "settings.json", "main": "main.js" } \ No newline at end of file diff --git a/repo/js/FullyAutoAndSemiAutoTools/settings.json b/repo/js/FullyAutoAndSemiAutoTools/settings.json index 175faf084..ab2883b94 100644 --- a/repo/js/FullyAutoAndSemiAutoTools/settings.json +++ b/repo/js/FullyAutoAndSemiAutoTools/settings.json @@ -20,6 +20,12 @@ "label": "刷新黑名单 以,分割", "default": "其他,锄地专区,食材与炼金" }, + { + "name": "cron_http_url", + "type": "input-text", + "label": "cron解析Http 地址", + "default": "http:///bgi/cron/next-timestamp" + }, { "name": "key", "type": "input-text", diff --git a/repo/js/FullyAutoAndSemiAutoTools/utils/cron.js b/repo/js/FullyAutoAndSemiAutoTools/utils/cron.js index 00622864a..6c6485eee 100644 --- a/repo/js/FullyAutoAndSemiAutoTools/utils/cron.js +++ b/repo/js/FullyAutoAndSemiAutoTools/utils/cron.js @@ -110,93 +110,117 @@ function parseCron(cron) { isValid: true }; } catch (e) { - return { isValid: false, error: e.message, original: cron }; + return {isValid: false, error: e.message, original: cron}; } } + /** - * 根据 cron 表达式和当前时间,计算下一次执行的时间戳(毫秒) - * 支持标准 5 段 cron: 分钟 时 日 月 星期 - * @param {string} cron - cron表达式,例如 "30 2 * * 1-5" - * @param {number} [fromTime=Date.now()] - 从这个时间开始找下一个执行点 - * @returns {number|null} 下一次执行的时间戳(毫秒),找不到返回 null + * 获取下一个Cron时间戳 + * @param {string} cronExpression - Cron表达式 + * @param {number} [startTimestamp=Date.now()] - 开始时间戳,默认为当前时间 + * @param {number} endTimestamp - 结束时间戳 + * @returns {Promise} 返回一个Promise,解析为下一个Cron时间戳 */ -function getNextCronTimestamp(cron, fromTime = Date.now(),endTime) { - const parts = cron.trim().split(/\s+/); - if (parts.length < 5 || parts.length > 6) { - throw new Error("不支持的 cron 格式,应为 5~6 段"); - } - - const [minStr, hourStr, dayStr, monthStr, dowStr] = parts; - - // 解析每个字段 - const minutes = parseField(minStr, 0, 59); - const hours = parseField(hourStr, 0, 23); - const days = parseField(dayStr, 1, 31); - const months = parseField(monthStr, 1, 12); - const dows = parseField(dowStr, 0, 7); - - // 星期 7 → 0 (周日) - if (dows.has(7)) dows.add(0); - - let current = new Date(fromTime); - // 如果没有指定 endTime,默认设置为明天 00:00:00 - if (endTime === undefined) { - const tomorrow = new Date(current); - tomorrow.setDate(tomorrow.getDate() + 1); - tomorrow.setHours(0, 0, 0, 0); - endTime = tomorrow.getTime(); - } - - // 动态计算最大迭代次数 - // 将时间差(毫秒)转换为分钟,向上取整,确保覆盖所有可能的分钟点 - const timeDiffMinutes = Math.ceil((endTime - fromTime) / 60000); - - // 设置最大迭代次数,防止意外情况(如 endTime 极大)导致内存溢出或死循环 - // 即使 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); - - let iteration = 0; - while (iteration++ < MAX_ITERATIONS) { - // 先推进到下一分钟,避免死循环在同一分钟 - current.setMinutes(current.getMinutes() + 1); - current.setSeconds(0); - current.setMilliseconds(0); - - const m = current.getMinutes(); - const h = current.getHours(); - const d = current.getDate(); - const mon = current.getMonth() + 1; // JS 月份 0~11 - const dow = current.getDay(); // 0=周日, 1=周一, ..., 6=周六 - - // 核心匹配条件(日期和星期是 OR 关系) - const minuteMatch = minutes.has(m) || minutes.size === 0; - const hourMatch = hours.has(h) || hours.size === 0; - const monthMatch = months.has(mon) || months.size === 0; - const dayMatch = days.has(d) || days.size === 0; - const dowMatch = dows.has(dow) || dows.size === 0; - - const dateOrDowMatch = (days.size === 0 && dows.size === 0) || // 两者都是 * - (days.size > 0 && dows.size === 0) || // 只指定了日期 - (days.size === 0 && dows.size > 0) || // 只指定了星期 - (dayMatch && dowMatch); // 两者都满足才算(最严格) - - if (minuteMatch && hourMatch && monthMatch && dateOrDowMatch) { - return current.getTime(); +async function getNextCronTimestamp(cronExpression, startTimestamp = Date.now(), endTimestamp, url = settings.cron_http_url) { + const result = await http.request("POST", url, JSON.stringify({ + cronExpression: `${cronExpression}`, + 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 + }) - // 如果是因为超过 MAX_ITERATIONS_LIMIT 而退出,说明时间跨度太大 - if (timeDiffMinutes > MAX_ITERATIONS_LIMIT) { - log.warn("查找范围过大,已达到最大迭代次数限制"); - } else { - log.warn("未找到合理下一次执行时间"); - } - return null; + return result === null || !result ? undefined : result } +//影响到性能 改http 第三方 +// function getNextCronTimestamp(cron, fromTime = Date.now(),endTime) { +// const parts = cron.trim().split(/\s+/); +// if (parts.length < 5 || parts.length > 6) { +// throw new Error("不支持的 cron 格式,应为 5~6 段"); +// } +// +// const [minStr, hourStr, dayStr, monthStr, dowStr] = parts; +// +// // 解析每个字段 +// const minutes = parseField(minStr, 0, 59); +// const hours = parseField(hourStr, 0, 23); +// const days = parseField(dayStr, 1, 31); +// const months = parseField(monthStr, 1, 12); +// const dows = parseField(dowStr, 0, 7); +// +// // 星期 7 → 0 (周日) +// if (dows.has(7)) dows.add(0); +// +// let current = new Date(fromTime); +// // 如果没有指定 endTime,默认设置为明天 00:00:00 +// if (endTime === undefined) { +// const tomorrow = new Date(current); +// tomorrow.setDate(tomorrow.getDate() + 1); +// tomorrow.setHours(0, 0, 0, 0); +// endTime = tomorrow.getTime(); +// } +// +// // 动态计算最大迭代次数 +// // 将时间差(毫秒)转换为分钟,向上取整,确保覆盖所有可能的分钟点 +// const timeDiffMinutes = Math.ceil((endTime - fromTime) / 60000); +// +// // 设置最大迭代次数,防止意外情况(如 endTime 极大)导致内存溢出或死循环 +// // 即使 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); +// +// let iteration = 0; +// while (iteration++ < MAX_ITERATIONS) { +// // 先推进到下一分钟,避免死循环在同一分钟 +// current.setMinutes(current.getMinutes() + 1); +// current.setSeconds(0); +// current.setMilliseconds(0); +// +// const m = current.getMinutes(); +// const h = current.getHours(); +// const d = current.getDate(); +// const mon = current.getMonth() + 1; // JS 月份 0~11 +// const dow = current.getDay(); // 0=周日, 1=周一, ..., 6=周六 +// +// // 核心匹配条件(日期和星期是 OR 关系) +// const minuteMatch = minutes.has(m) || minutes.size === 0; +// const hourMatch = hours.has(h) || hours.size === 0; +// const monthMatch = months.has(mon) || months.size === 0; +// const dayMatch = days.has(d) || days.size === 0; +// const dowMatch = dows.has(dow) || dows.size === 0; +// +// const dateOrDowMatch = (days.size === 0 && dows.size === 0) || // 两者都是 * +// (days.size > 0 && dows.size === 0) || // 只指定了日期 +// (days.size === 0 && dows.size > 0) || // 只指定了星期 +// (dayMatch && dowMatch); // 两者都满足才算(最严格) +// +// if (minuteMatch && hourMatch && monthMatch && dateOrDowMatch) { +// return current.getTime(); +// } +// } +// +// // 如果是因为超过 MAX_ITERATIONS_LIMIT 而退出,说明时间跨度太大 +// if (timeDiffMinutes > MAX_ITERATIONS_LIMIT) { +// log.warn("查找范围过大,已达到最大迭代次数限制"); +// } else { +// log.warn("未找到合理下一次执行时间"); +// } +// return null; +// } + /** * 解析单个 cron 字段,返回匹配的数值 Set * 支持: * , - / 数值列表 * /n @@ -264,13 +288,7 @@ function parseField(field, min, max) { return result; } -function a(){ - return true -} this.cronUtil = { getNextCronTimestamp, - parseCron, - parseField, - parseCronField, } \ No newline at end of file