feat(automation): 拆开「多久跑一次」与「同一账号多久重检」—— 调度频率终于可配

用户反馈:「单账号重检间隔(小时) 很别扭,直接设置多久跑一次、一次跑多少个更符合直觉」。

**对,而且比"别扭"更严重:调度频率以前根本改不了。**

## 问题:两个概念挤在一个字段里

`_run_one` 里原本写着:

    job.next_run_at = finished + timedelta(seconds=JOB_INTERVAL_SECONDS)   # 硬编码 300 秒

也就是不管界面上填什么,下一轮永远是「跑完 + 5 分钟」。
而界面那个「单账号重检间隔(小时)」对应的是 `interval_seconds` ——
它只被作业**自己的选行逻辑**使用("这行多久该重检"),**完全不参与调度**。

于是出现最坏的情况:**看起来能配调度,其实配的是一个不相干的参数**。
上一轮我还拿它算过吞吐("10 分钟一轮"),算错了 —— 已在调度器注释里纠正。

## 拆法

| 概念 | 字段 | 界面 |
| --- | --- | --- |
| **多久跑一次** | `run_interval_seconds`(迁移 0051,默认 300)| 「多久跑一次(分钟)」|
| 同一账号多久重检 | `interval_seconds`(不变)| 「同一账号多久重检(小时)」|

- `next_run_at = finished + run_interval_seconds`(有下限 30 秒,避免忙轮询)
- 接口:`GET` 返回新字段、`PATCH` 可改
- 前端:两个输入框分开,并把原来那段绕口的 tooltip
  ("它不决定调度频率 —— 调度周期见页面顶部说明")改掉 ——
  那句话本身就是这个设计缺陷的自白

## 默认 300 秒

与改动前**实际行为一致** —— 不会因为这次迁移让已有作业突然变快或变慢。
想调快就自己填(比如「账号资料定时补全」填 5 分钟 + 每轮 400 条 ≈ 3 小时补完)。

## ⚠️ 顺带说明一个你截图里可能看到的现象

截图里「账号资料定时补全」显示的是 **0.5 小时 / 30 条** —— 那是我**最初的默认值**。
因为 `ensure_jobs` **只创建不更新**,我后面改成 10 分钟/400 **不会作用到已存在的行**。
这是刻意的(否则会覆盖运维的手工调整),但意味着**默认值改了要手动在界面上改**,
或者删掉那行让 `ensure_jobs` 重建。现在有了「多久跑一次」这个框,直接填就行。

## 测试(+3)

- 下一轮按**运行间隔**排,不是按「同一行重检间隔」(两个值刻意设成不同来钉住)
- 不填时默认 300 秒(保持旧行为)
- 过小的间隔被夹到下限

## 验证

TZ=Asia/Shanghai **639 passed / 6 skipped**;ruff 全绿;前端 build 通过、lint 0 errors。
This commit is contained in:
yml2213
2026-10-04 21:30:13 +08:00
parent aea8e4f73d
commit 55f899c615
8 changed files with 197 additions and 6 deletions
+8
View File
@@ -876,6 +876,9 @@ export interface HuyaPasswordLoginSelectedRequest {
has_cookie?: boolean;
force_new_device?: boolean;
max_attempts?: number;
/** 多久跑一次(秒)—— 调度频率 */
run_interval_seconds?: number;
/** 同一账号多久重检(秒)—— 不参与调度 */
interval_seconds?: number;
retry_delay_seconds?: number;
}
@@ -1031,6 +1034,8 @@ export interface AutomationRunItem {
}
export interface AutomationJobItem {
/** 多久跑一次(秒)—— 调度频率 */
run_interval_seconds: number;
key: string;
title: string;
enabled: boolean;
@@ -1068,6 +1073,9 @@ export interface AutomationRunsResponse {
export interface AutomationJobUpdate {
enabled?: boolean;
/** 多久跑一次(秒)—— 调度频率 */
run_interval_seconds?: number;
/** 同一账号多久重检(秒)—— 不参与调度 */
interval_seconds?: number;
batch_size?: number;
concurrency?: number;
+18 -2
View File
@@ -99,6 +99,11 @@ export default function AutomationPage() {
job.key,
{
enabled: job.enabled,
// 「多久跑一次」与「同一行多久重检」是两件事,分开两个输入框
run_interval_minutes: Math.max(
Math.round((job.run_interval_seconds ?? 300) / 60),
1,
),
interval_hours: secondsToHours(job.interval_seconds),
batch_size: job.batch_size,
concurrency: job.concurrency,
@@ -123,6 +128,10 @@ export default function AutomationPage() {
try {
await automationApi.updateJob(job.key, {
enabled: values.enabled,
run_interval_seconds: Math.max(
Math.round((values.run_interval_minutes ?? 5) * 60),
30,
),
interval_seconds: Math.max(
Math.round((values.interval_hours ?? 6) * 3600),
300,
@@ -312,10 +321,17 @@ export default function AutomationPage() {
>
<Switch disabled={!canManage} />
</Form.Item>
<Form.Item
name={[job.key, "run_interval_minutes"]}
label="多久跑一次(分钟)"
tooltip="调度频率:上一轮跑完后隔多久开始下一轮。这是作业「多久跑一次」。"
>
<InputNumber min={0.5} step={5} disabled={!canManage} />
</Form.Item>
<Form.Item
name={[job.key, "interval_hours"]}
label="单账号重检间隔(小时)"
tooltip="同一个账号多久重新检测/重试一次。它不决定调度频率 —— 调度周期见页面顶部说明。"
label="同一账号多久重检(小时)"
tooltip="同一个账号多久重新检测/重试一次。它**不决定**作业多久跑一次 —— 那是左边那一项。"
>
<InputNumber min={0.1} step={1} disabled={!canManage} />
</Form.Item>