场景指南

hook 与商业逻辑

把限额、usage、日志和扣费接入 Action 生命周期。

Downcity 的 hook 分三层:Action → Service → Global。执行顺序从外层到内层:

global.before
  → service.before
    → action.before
      → action.run()  ← 核心逻辑
    → action.after    ← 扣费
  → service.after
→ global.after

Action 级 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 hookService 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
}