mirror of
https://github.com/babalae/bettergi-scripts-list.git
synced 2026-03-25 04:59:52 +08:00
refactor(cron): 重构Cron时间戳获取功能
- 将getNextCronTimestamp函数改为异步函数,使用HTTP请求替代本地计算 - 移除原有的本地Cron表达式解析和时间计算逻辑 - 添加对远程服务的HTTP请求实现时间戳计算 - 注释掉原有性能较差的本地计算方法 - 移除不再使用的辅助函数parseCron、parseField、parseCronField - 保留isValidCron函数用于验证Cron表达式的功能
This commit is contained in:
@@ -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]))
|
||||
}
|
||||
}
|
||||
// 启用自动拾取的实时任务,并配置成启用急速拾取模式
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
"links": "https://github.com/Kirito520Asuna"
|
||||
}
|
||||
],
|
||||
"http_allowed_urls": [
|
||||
"https://*",
|
||||
"http://*"
|
||||
],
|
||||
"settings_ui": "settings.json",
|
||||
"main": "main.js"
|
||||
}
|
||||
@@ -20,6 +20,12 @@
|
||||
"label": "刷新黑名单 以,分割",
|
||||
"default": "其他,锄地专区,食材与炼金"
|
||||
},
|
||||
{
|
||||
"name": "cron_http_url",
|
||||
"type": "input-text",
|
||||
"label": "cron解析Http 地址",
|
||||
"default": "http://<ip:port>/bgi/cron/next-timestamp"
|
||||
},
|
||||
{
|
||||
"name": "key",
|
||||
"type": "input-text",
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
Reference in New Issue
Block a user