用户反馈:「单账号重检间隔(小时) 很别扭,直接设置多久跑一次、一次跑多少个更符合直觉」。
**对,而且比"别扭"更严重:调度频率以前根本改不了。**
## 问题:两个概念挤在一个字段里
`_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。
65 lines
1.9 KiB
Python
65 lines
1.9 KiB
Python
"""作业的「运行间隔」独立出来:`automation_jobs.run_interval_seconds`
|
||
|
||
## 问题
|
||
|
||
界面上只有一个「单账号重检间隔(小时)」,但它其实承担了两个互不相干的概念,
|
||
而且**调度频率根本改不了**:
|
||
|
||
| 概念 | 之前 | 之后 |
|
||
| --- | --- | --- |
|
||
| **多久跑一次** | **硬编码** `JOB_INTERVAL_SECONDS`(5 分钟)| `run_interval_seconds` |
|
||
| 同一行多久重检 | `interval_seconds` | `interval_seconds`(不变,只是不再兼任调度)|
|
||
|
||
`_run_one` 里原本写着:
|
||
|
||
job.next_run_at = finished + timedelta(seconds=JOB_INTERVAL_SECONDS)
|
||
|
||
也就是不管在界面上填什么,下一轮永远是「跑完 + 5 分钟」。
|
||
用户反馈「直接设置多久跑一次、一次跑多少个更符合直觉」—— 对。
|
||
|
||
默认 300 秒,与改动前的实际行为一致(不会因为这次迁移让已有作业突然变快/变慢)。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import sqlalchemy as sa
|
||
from alembic import op
|
||
|
||
revision: str = "20261004_0051"
|
||
down_revision: str | None = "20261004_0050"
|
||
branch_labels: str | None = None
|
||
depends_on: str | None = None
|
||
|
||
COLUMN = "run_interval_seconds"
|
||
|
||
|
||
def _column_exists(name: str) -> bool:
|
||
conn = op.get_bind()
|
||
count = conn.execute(
|
||
sa.text(
|
||
"SELECT COUNT(*) FROM information_schema.columns "
|
||
"WHERE table_schema = DATABASE() "
|
||
"AND table_name = 'automation_jobs' AND column_name = :name"
|
||
),
|
||
{"name": name},
|
||
).scalar()
|
||
return bool(count)
|
||
|
||
|
||
def upgrade() -> None:
|
||
if not _column_exists(COLUMN):
|
||
op.add_column(
|
||
"automation_jobs",
|
||
sa.Column(
|
||
COLUMN,
|
||
sa.Integer(),
|
||
nullable=False,
|
||
server_default="300",
|
||
comment="多久跑一次(秒);与「同一行多久重检」是两回事",
|
||
),
|
||
)
|
||
|
||
|
||
def downgrade() -> None:
|
||
op.drop_column("automation_jobs", COLUMN)
|