refactor(schedules): register pi.token_refresh and auth.cleanup as scheduled jobs

Pi token refresh (10 min) and auth session cleanup (1 h) no longer run
their own setInterval; both register in the maintain group of the
schedules registry with the same default intervals.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 13:44:48 +08:00
1 parent 31a54186c7
commit a0b0a64246
5 files changed
+90 -29

No files matched your search

+18 -9
View File
@@ -1,4 +1,5 @@
import { sql } from "@umt/db";
import { registerScheduledJob } from "../schedules/registry.ts";
import type { ServerContext, ServerModule } from "../types.ts";
import { registerAuthRoutes } from "./auth-routes.ts";
import { ensureSetupToken } from "./auth-service.ts";
@@ -18,7 +19,7 @@ export interface AuthSettingsModuleOptions {
engineSchema?: string;
/** api 角色启动时若未初始化管理员则生成并打印 setup token(默认 true;测试关掉自己调 ensureSetupToken) */
setupTokenOnStart?: boolean;
/** 过期会话/登录尝试清理间隔,默认 1 小时 */
/** 定时任务 auth.cleanup(过期会话/登录尝试清理)的默认周期,默认 1 小时;<=0 不登记 */
cleanupIntervalMs?: number;
}
@@ -30,7 +31,6 @@ export async function cleanupAuthTables(ctx: Pick<ServerContext, "db">): Promise
}
export function createAuthSettingsModule(opts: AuthSettingsModuleOptions = {}): ServerModule {
let timer: ReturnType<typeof setInterval> | null = null;
return {
name: "auth-settings",
registerRoutes(app, ctx) {
@@ -44,14 +44,23 @@ export function createAuthSettingsModule(opts: AuthSettingsModuleOptions = {}):
}
},
registerTasks() {},
/** 清理周期登记为定时任务 auth.cleanup(维护组,由 schedules 调度器驱动) */
start(ctx) {
const run = () => cleanupAuthTables(ctx).catch((err) => ctx.logger.warn({ err }, "认证表清理失败"));
timer = setInterval(run, opts.cleanupIntervalMs ?? 3600_000);
void run();
},
stop() {
if (timer) clearInterval(timer);
timer = null;
const everyMs = opts.cleanupIntervalMs ?? 3600_000;
if (everyMs <= 0) return;
registerScheduledJob(ctx, {
key: "auth.cleanup",
defaults: {
name: "登录会话清理",
description: "删除 7 天前的登录尝试和 30 天前过期或吊销的会话",
group: "maintain",
schedule: { type: "interval", everyMs },
},
run: async () => {
await cleanupAuthTables(ctx);
return { summary: "已清理" };
},
});
},
};
}
+18 -15
View File
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
import type { FakeOAuthControl } from "@umt/agent";
import { defineTask } from "@umt/tasks";
import { z } from "zod";
import { registerScheduledJob } from "../schedules/registry.ts";
import type { ServerContext, ServerModule } from "../types.ts";
import { type OAuthOptions, OAuthSessions } from "./oauth.ts";
import { refreshPiTokens } from "./refresh.ts";
@@ -11,14 +12,14 @@ export { type RunUpstream, resolveRunUpstream } from "./service.ts";
/**
* W7-A Pi 模型上游:pi_upstream CRUD、能力表、测试、模型表、服务端订阅登录(挂起式 interaction)、
* 定时任务 refreshPiTokens(OAuth 过期前刷新,行锁内写回)。runAgent 经 ./service.ts 的 resolveRunUpstream 取上游。
* 持久任务 refreshPiTokens(周期登记为定时任务 pi.token_refresh;OAuth 过期前刷新,行锁内写回)。runAgent 经 ./service.ts 的 resolveRunUpstream 取上游。
* 用法见 docs/runbook/agent.md「上游与登录」。
*/
export interface PiModuleOptions {
/** provider = fake 的模拟 OAuth(测试注入) */
fakeOAuth?: FakeOAuthControl;
oauth?: Omit<OAuthOptions, "client">;
/** 刷新任务入队间隔(默认 10 分钟;<=0 关闭) */
/** 定时任务 pi.token_refresh 的默认周期(默认 10 分钟;<=0 不登记) */
refreshIntervalMs?: number;
/** 剩余有效期少于它就刷新(默认 30 分钟) */
refreshWindowMs?: number;
@@ -37,7 +38,6 @@ export function createPiModule(
}
return s;
};
let timer: ReturnType<typeof setInterval> | null = null;
const live: OAuthSessions[] = [];
const interval = opts.refreshIntervalMs ?? 10 * 60_000;
const windowMs = opts.refreshWindowMs ?? 30 * 60_000;
@@ -67,25 +67,28 @@ export function createPiModule(
}),
);
},
/** 刷新周期登记为定时任务 pi.token_refresh(维护组,由 schedules 调度器驱动),执行体只入队持久任务 */
start(ctx) {
if (interval <= 0) return;
const enqueue = async () => {
try {
await ctx.engine.enqueue(
registerScheduledJob(ctx, {
key: "pi.token_refresh",
defaults: {
name: "上游登录刷新",
description: "订阅登录即将过期的 Agent 上游,提前刷新 token",
group: "maintain",
schedule: { type: "interval", everyMs: interval },
},
run: async (c) => {
const r = await ctx.engine.enqueue(
"refreshPiTokens",
{},
{ idempotencyKey: `refreshPiTokens:${Math.floor(Date.now() / interval)}`, traceId: randomUUID() },
{ idempotencyKey: `refreshPiTokens:${c.slot}`, traceId: randomUUID() },
);
} catch (err) {
ctx.logger.warn({ err: { message: (err as Error).message } }, "refreshPiTokens 入队失败");
}
};
void enqueue();
timer = setInterval(() => void enqueue(), interval);
return { summary: r.deduplicated ? "本周期已入队" : "已入队刷新" };
},
});
},
async stop() {
if (timer) clearInterval(timer);
timer = null;
await Promise.all(live.splice(0).map((s) => s.shutdown()));
},
};
@@ -12,9 +12,6 @@ export { registerScheduledJob, scheduleRegistry } from "./registry.ts";
* 各模块 start() 用 registerScheduledJob 登记周期任务;本模块在 io worker 上起唯一调度器(PG 咨询锁)按表驱动,
* 并登记内置 Agent 巡检模板(默认关闭)与备份占位。看板 API 见 routes.ts(全部只接受控制台会话)。
* 须放在 SERVER_MODULES 末尾:start 时其它模块已登记完(之后再登记的也会在下一轮同步)。
*
* TODO(W7-A):pi 模块的 refreshPiTokens 目前在 pi/index.ts 自建 setInterval,
* 待其改为 registerScheduledJob({ key: "pi.token_refresh", defaults: { group: "maintain", ... } })。
*/
export interface SchedulesModuleOptions {
scheduler?: SchedulerOptions;