Skip to content

Commit bb09ac1

Browse files
committed
feat(tasks): 后台任务手动触发(管理台「立即运行」)
POST /api/tasks/{key}/run(admin + CSRF):手动跑一轮后台任务并同步返回结果。 - 复用 TaskRunner._loop_specs():周期循环与手动触发共用一份清单,避免 「手动能跑但定时没接线」的双份维护漂移。 - 绕时点窗口(due()),不绕开关:activity 关闭时 run_once 返回 None, 如实回 ran=false,不把「关了」谎报成「跑过了」。 - 不排队:self._running 在真跑期间占 key,撞上即回 busy=true。 - 未知 / 未装配 key → 400;task_status() 改由 _loop_specs() 派生装配判断, 并新增 running 字段供前端禁用按钮。 - 前端任务卡片加「立即运行」按钮 + 三态结果文案(已跑一轮 / 没排上 / 被开关跳过)。 测试:后端 100% 覆盖(含 _run_guarded 各分支、端点鉴权与 400 路径); 前端 tsc + vitest + build 全过。
1 parent 2e70ebf commit bb09ac1

13 files changed

Lines changed: 624 additions & 76 deletions

File tree

‎PROPOSAL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ DDL 以 [src/db/schema.sql](src/db/schema.sql) 为准,共 **12 张表**,`SCH
164164
- 账号:`GET|POST /api/users`、`PATCH /api/users/{username}`、`POST /api/users/{username}/{disable|enable|reset-password}`(**不提供 DELETE**);自助改密 `POST /api/auth/password`;无鉴权的一次性激活流 `GET|POST /api/auth/activate`;审计查询 `GET /api/audit`。
165165
- 凭证运维:`POST /api/credentials/{id}/checkin|toggle|revive|probe|accounts/select`、`PUT /api/credentials/{id}/limits`(限额护栏整体替换,全关时删行)、`GET|POST /api/credentials/{id}/growth`、`GET /api/credentials/{id}/credit-events|accounts`。
166166
- 凭证批量运维:`POST /api/credentials/batch`(`{action: probe|toggle|checkin|revive, ids: [...]}`,可选 `enabled` 用于 `toggle`)。逐条隔离执行——单条失败不影响其余条,响应含 `{total, succeeded, failed, results[]}`;`toggle`/`revive` 落一条汇总审计,`probe`/`checkin` 与单条端点同为只读语义、不写审计。
167-
- 运行时配置与任务运行态:`GET|PUT /api/settings` + `GET /api/tasks`。列表端点接受统一 `sort`(白名单键)+ `order`(`asc`/`desc`);未知键 / 非法值回落到默认,不报错。回调(无鉴权):`GET /authorize`。
167+
- 运行时配置与任务运行态:`GET|PUT /api/settings` + `GET /api/tasks` + `POST /api/tasks/{key}/run`(手动跑一轮)。列表端点接受统一 `sort`(白名单键)+ `order`(`asc`/`desc`);未知键 / 非法值回落到默认,不报错。回调(无鉴权):`GET /authorize`。
168168

169169
实现以代码为准,使用说明见 [README.md](README.md)。
170170

‎README.en.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ Key variables below; the full list is in `src/config.py` (authoritative), and **
441441

442442
**45 settings** apply immediately without editing `.env` or restarting: default model, blocklist, context-compression knobs, fallback groups, both expiry windows, sticky TTL, the per-credential in-flight cap, growth toggles/interval, refresh/probe/catalog intervals, `USD_CNY_RATE`, every per-channel chat interval, CodeArts concurrency/window, pacer bounds, activity toggle/hour, all alerting rules and `METRICS_ENABLED`.
443443

444-
**DB overrides beat `.env`**: once changed, `.env` no longer applies to that key; the row is marked "DB override" and *Reset* removes the override. Values live in the `runtime_settings` table (plain key/value), so adding a hot-updatable key needs no migration; unknown keys and invalid/out-of-range values are rejected on write. Startup-only knobs (`APP_SECRET`, `PORT`, `DATA_DIR`, `USERS_FILE`, `OPENROUTER_MODELS_URL`, endpoint allowlists) are **excluded**. API: `GET /api/settings`, `PUT /api/settings` (admin + CSRF; `null` resets). The page also shows the 9 tasks' state (`GET /api/tasks`, admin, 30s refresh), in-process only.
444+
**DB overrides beat `.env`**: once changed, `.env` no longer applies to that key; the row is marked "DB override" and *Reset* removes the override. Values live in the `runtime_settings` table (plain key/value), so adding a hot-updatable key needs no migration; unknown keys and invalid/out-of-range values are rejected on write. Startup-only knobs (`APP_SECRET`, `PORT`, `DATA_DIR`, `USERS_FILE`, `OPENROUTER_MODELS_URL`, endpoint allowlists) are **excluded**. API: `GET /api/settings`, `PUT /api/settings` (admin + CSRF; `null` resets). The page also shows the 10 tasks' state (`GET /api/tasks`, admin, 30s refresh), in-process only. Each task card has a **Run now** button (`POST /api/tasks/{key}/run`, admin + CSRF): it bypasses the due-time window but **not** the enable switch — a disabled task honestly returns `ran=false` instead of pretending it ran; if the same task is already running it returns `busy=true` immediately (no queueing, no concurrent upstream calls); the response is the finished round.
445445

446446
## Deployment notes
447447

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ OpenRouter 收录数百条,其中多数本服务没接。每行带 `available`
406406

407407
## 后台任务
408408

409-
额度探测、token 预刷新、每日签到、成长中心、活跃上报、明细清理、数据库备份、渠道模型列表刷新、模型目录刷新(OpenRouter)、运维告警共 10 类,由 `TaskRunner` 自动调度,失败互不影响。每类任务的上次执行时间、最近结果与错误在管理台**「任务与配置」页**查看(30 秒自动刷新),运行态只存在于**本次进程**内,重启归零。
409+
额度探测、token 预刷新、每日签到、成长中心、活跃上报、明细清理、数据库备份、渠道模型列表刷新、模型目录刷新(OpenRouter)、运维告警共 10 类,由 `TaskRunner` 自动调度,失败互不影响。每类任务的上次执行时间、最近结果与错误在管理台**「任务与配置」页**查看(30 秒自动刷新),运行态只存在于**本次进程**内,重启归零。每个任务卡片带「立即运行」按钮(`POST /api/tasks/{key}/run`,admin + CSRF):**绕过时点窗口但绕过不了开关**——`due()` 未到点也照跑,而任务被开关关掉时如实回 `ran=false`(不把「关了」谎报成「跑过了」);同一任务已在跑时立刻回 `busy=true`、不排队、不并发打上游;同步等这一轮跑完再返回结果。
410410

411411
**渠道模型列表刷新**(`MODEL_CATALOG_MINUTES`,默认 30、下限 5)是兜底保鲜:模型表本身按「谁访问 `/v1/models` 或 Playground 谁刷新、TTL 300s」更新,这条只保证**没人访问时也会更新**。周期取 30 分钟是跟 zen 免费模型判活缓存对齐——再密也不会让 zen 多探活。
412412

0 commit comments

Comments
 (0)