场景指南
hook 与商业逻辑
把限额、usage、日志和扣费接入 Action 生命周期。
Downcity 的 hook 分三层:Action → Service → Global。执行顺序从外层到内层:
global.before
→ service.before
→ action.before
→ action.run() ← 核心逻辑
→ action.after ← 扣费
→ service.after
→ global.afterAction 级 hook
每个 Action 有独立的 hook,适合挂业务逻辑:
const zh2en = svc.action("zh2en", async (ctx) => {
return await translate(ctx.input.text, "en");
});
// 调用前检查余额
zh2en.before(async (ctx) => {
const bal = await balance.get(ctx.user!.user_id);
if (bal <= 0) throw new Error("余额不足");
});
// 调用后扣费(可访问 ctx.output)
zh2en.after(async (ctx) => {
await deductBalance(ctx.user!.user_id, 10);
await logUsage(ctx);
});Service 级 hook
同一 Service 下所有 Action 共享:
svc.hook.before(async (ctx) => {
console.log(`${ctx.user?.id} → ${ctx.service?.id}.${ctx.action?.id}`);
});
svc.hook.after(async (ctx) => {
// 聚合统计、统一日志
});对比
| Action hook | Service hook | |
|---|---|---|
| 粒度 | 单个 action | 该 service 下所有 action |
| 用途 | 余额检查、扣费、per-action 日志 | 公共校验、聚合统计 |
| 注册 | action.before(fn) | svc.hook.before(fn) |
ctx 中的关键字段
interface Context {
input: Record<string, unknown> // 请求参数
output?: unknown // Action 执行结果(after 可读)
user?: { user_id, metadata } // 当前用户
town?: { town_id, status } // 所属 town
service?: { id, name } // 当前 Service
action?: { id } // 当前 Action
}