Admin City
可信环境如何管理 town、签发 user_token,并维护 Runtime env。
Admin City 只应该运行在可信环境里。
典型场景:
- 你自己的产品后端
- 本地管理脚本
- 内网管理工具
- CI 或运维脚本
不要把 admin_secret_key 放到浏览器、公开前端或不受控客户端。
它在整条链路里负责什么
把 Admin City 看成“可信侧和 City 的管理桥”最准确。
它最常负责三件事:
- 管
town - 签
user_token - 维护 runtime env
- 执行可信侧 balance / redeem_code 管理动作
如果 User City 负责“用户态怎么调用 City”,那 Admin City 负责“可信侧怎么把这个调用环境准备好”。
最小示例
import { City } from "@downcity/city";
const admin = new City({
role: "admin",
city_url: "https://base.example.com",
admin_secret_key: process.env.DOWNCITY_CITY_ADMIN_SECRET_KEY,
});如果没有显式传 admin_secret_key,SDK 会尝试从 process.env.DOWNCITY_CITY_ADMIN_SECRET_KEY 读取。
典型调用链
/v1/towns/*、/v1/towns/tokens/apply、/v1/env/*、/v1/ai/models、/v1/base/instruction。admin_secret_key 后,再执行 town、token 和 env 管理动作。towns
list()
const items = await admin.towns.list();create()
const town = await admin.towns.create({
name: "Chrome Extension",
});返回值包含:
town_idnamestatuscreated_atupdated_at
pause() / activate()
await admin.towns.pause(town.town_id);
await admin.towns.activate(town.town_id);适合:
- 暂停某个产品的所有用户调用
- 临时下线某个产品
- 重新开放一个 town
remove()
await admin.towns.remove(town.town_id);当前语义就是删除 town 记录。调用前要先确认这是否符合你的业务预期。
tokens.apply()
这是最常用的管理端能力:为某个用户在某个 town 下申请 user_token。
const issued = await admin.towns.tokens.apply({
town_id: town.town_id,
user_id: "user_123",
metadata: {
plan: "pro",
org_id: "org_001",
},
ttl: "7d",
});返回值包含:
user_tokentown_iduser_idexpires_at
ttl 支持:
30m1h7d- 秒数
推荐登录链路
router.post("/login", async (c) => {
const user_id = await login(c);
const issued = await admin.towns.tokens.apply({
town_id: "town_xxx",
user_id,
ttl: "7d",
});
return c.json({
town_id: "town_xxx",
user_token: issued.user_token,
});
});也就是:
- 你的业务后端先完成用户登录
- 再由可信后端向 City 申请
user_token - 把
town_id + user_token返回给前端 - 前端再用
User City调 City
它和 accounts 服务是什么关系
Admin City 不会替代 accounts 服务,反过来也一样。
两种典型模式是:
模式 A:你自己的后端做登录
- 业务后端自己校验用户名密码或 session
- 业务后端再用
Admin City申请user_token - 前端拿到
town_id + user_token
模式 B:accounts 服务做登录
- 前端先用 guest
User City调accounts.login/register - accounts service 直接返回
user_token - 前端切换到正常
User City
所以:
Admin City负责可信侧管理动作- accounts 服务负责最小账号能力
- 两者不是替代关系,而是两种不同的登录接法
env
admin.env 负责 Runtime env 管理。写入的 provider key 会保存到 City 数据库,Runtime 读取时优先使用数据库值。
如果你想确认某个代码注册模型依赖哪些 provider env,可以直接通过 admin.listModels() 读取同一份模型目录,再和 admin.env.list() 对照:
const models = await admin.listModels();list()
const envs = await admin.env.list();upsert()
await admin.env.upsert({
key: "OPENAI_API_KEY",
value: "sk-xxx",
});remove()
await admin.env.remove("OPENAI_API_KEY");import()
await admin.env.import(`
OPENAI_API_KEY=sk-xxx
OPENAI_BASE_URL=https://api.openai.com/v1
`);refresh()
await admin.env.refresh();通过 admin.env.upsert()、remove()、import() 或 city CLI 修改 env 时,当前 Runtime cache 会自动更新。
如果你绕过 Admin API 直接修改数据库里的 env 表,需要调用 admin.env.refresh(),执行 city env refresh,或在 city CLI 的 Env 菜单执行 Refresh runtime cache。
这些修改会写入 City 数据库的 env 表。业务 env 和系统级 secret 都以 City 这张表为准,不需要再去手动改 Worker / Node 宿主 env。
listServices() / listModels() / instruction()
除了 towns.* 和 env.*,管理端还可以读取 City 当前暴露的能力目录。
listServices()
const services = await admin.listServices();返回当前 City 已注册的 service 列表,以及每个模块声明的 env 需求。
listModels()
const models = await admin.listModels();这会返回 admin 视角的完整模型目录。相比用户态模型目录,它还会包含:
env_requirementsdefault_modes
instruction()
const text = await admin.instruction();
console.log(text);它对应 GET /v1/base/instruction,返回 City 聚合后的纯文本说明文档,适合:
- 远程管理端查看当前 City 挂了哪些模块
- 确认每个模块暴露了哪些路由
- 确认每个模块声明了哪些 env 依赖
- 给 CLI 或 agent 读取运行时说明
Admin City 也可以调 service 的管理侧 service
除了 towns.* 和 env.*,Admin City 也可以调用 service 暴露出来的管理侧 service。
例如:
const users = await admin.service("accounts").get("users");
const sessions = await admin.service("accounts").get("sessions");
const payments = await admin.service("payment.stripe").get("payments");现在 balance 还提供了一层 typed invoker,适合管理余额和 redeem_code:
const issued = await admin.balance.redeemCodes.create({
amount: 300,
note: "campaign gift",
});
await admin.balance.redeemCodes.disable({
redeem_code_id: issued.redeem_code_id,
});也就是说,Admin City 不只是在调内建 towns/env 接口,它同样是可信侧调用统一 service 路由的入口。
错误处理
Admin City 收到非 2xx HTTP 响应时会抛出 Error。这个错误会带两个额外字段:
status:HTTP 状态码。body:City 返回的原始响应文本,通常是{"error":"..."}。
try {
await admin.towns.tokens.apply({
town_id: "town_xxx",
user_id: "user_123",
});
} catch (error) {
const status = error instanceof Error && "status" in error ? error.status : undefined;
const body = error instanceof Error && "body" in error ? error.body : undefined;
console.log(status, body);
}常见状态码:
401:admin_secret_key缺失或错误。403:目标 town 已暂停,不能继续签发 token。404:目标 town 不存在。500:City 缺少必要配置,或管理动作内部执行失败。
当前不归 Admin City 管的事情
这几件事当前不走 Admin City:
- 模型配置
- 注册 service handler
- 用户前端直接登录交互
- 浏览器里的用户态调用
它们分别属于:
City运行时层
也就是说,Admin City 负责维护 town、token 和 env;模型定义和 service 挂载方式仍然属于 City 运行时层。