触发条件视图 — 开发者页(DeveloperPage)
页面:DeveloperPage
路由:/dev
可见性:仅 role = 'developer' 或 'admin' 的用户可见(普通 user 看不到入口)
数据来源:直接调用 API,不走 DataContext
---
页面进入时
触发 API(并行请求):
1. GET /api/apikeys/user?billId=xxx
说明:获取当前用户在当前账单下配置的 Key 列表
请求:Query Param:billId
响应:
{
"keys": [
{
"id": "string",
"owner_type": "user",
"owner_id": "string",
"provider": "openai",
"model": "gpt-4o",
"is_shared": 0 | 1,
"priority": 100,
"created_at": "ISO8601"
}
]
}
> ⚠️ 不返回 api_key_encrypted 明文(只存 Base64 编码,非真正加密) ⚠️ P06
2. GET /api/apikeys/resolve-all/:billId
说明:获取账单所有可用 Key(含其他用户共享的 Key)
响应:与 /user 类似,但包含更多上下文(owner_nickname 等)
3. GET /api/apikeys/suggestions?currentBillId=xxx
说明:获取跨账单 Key 导入建议(从其他账单导入已有配置)
查询参数:currentBillId(排除当前账单)
响应:
{
"suggestions": [
{
"bill_id": "string",
"bill_name": "string",
"providers": ["openai", "anthropic"]
}
]
}
---
添加新 Key(填写表单 → 点击保存)
触发 API:
PUT /api/apikeys/user
请求:
{
"provider": "openai",
"model": "gpt-4o",
"api_key": "sk-xxxxx",
"billId": "string",
"priority": 100,
"is_shared": false
}
约束:
api_key 明文(后端 Base64 编码存储)api_key(使用数据库已有值)owner_id + provider + model 组合唯一,重复则覆盖后端处理:
1. 校验 billId 有效,用户是账单成员
2. Base64 编码 api_key(⚠️ 非真正加密,见 P06)
3. UPSERT api_keys 表
DB 表:api_keys
后续动作:保存成功后刷新 Key 列表 + 刷新 suggestions
---
测试 Key 连通性(点击「测试连接」)
触发 API:
POST /api/apikeys/verify/user
请求:
{
"provider": "openai",
"api_key": "sk-xxxxx",
"model": "gpt-4o",
"billId": "string"
}
后端处理:调用对应 Provider 的 ping 接口验证 Key 有效性
响应:
{
"success": true | false,
"message": "string"
}
> ⚠️ 测试失败不影响已保存的 Key,只做验证
---
修改 Key 共享状态(点击 Key 卡片的「共享」开关)
触发 API:
PATCH /api/apikeys/user/share
请求:
{
"billId": "string",
"provider": "openai",
"model": "gpt-4o",
"is_shared": true
}
说明:
is_shared = true → 同账单成员可使用此 Keyis_shared = false → 仅自己可用is_shared 是 integer(0/1),不是 boolean,传布尔值会被 Number(is_shared) === 1 处理DB 表:api_keys
后续动作:成功后刷新 Key 列表
---
拖拽调整 Key 顺序(调整优先级)
触发 API:
PATCH /api/apikeys/order/user
请求:
{
"billId": "string",
"orders": [
{
"provider": "openai",
"model": "gpt-4o",
"owner_type": "user",
"owner_id": "user_id",
"priority": 1
},
{
"provider": "anthropic",
"model": "claude-3-opus",
"owner_type": "user",
"owner_id": "user_id",
"priority": 2
}
]
}
说明:priority 数字越小优先级越高,AI 请求时优先使用排序靠前的 Key
> ⚠️ 前端可能未实现拖拽排序 UI(见 P12 备注)
DB 表:user_provider_order
---
删除 Key(点击 Key 卡片的「删除」按钮)
触发 API:
DELETE /api/apikeys/user/:provider
Query 参数:
DELETE /api/apikeys/user/openai?billId=xxx&model=gpt-4o
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| billId | string | 必填 | 账单ID |
| model | string | 选填 | 不传则删除该 provider 下所有 Key |
DB 表:api_keys(级联删除 user_provider_order 中相关记录)
后续动作:成功后刷新 Key 列表
---
从其他账单导入 Key(点击「从其他账单导入」)
触发 API(选择源账单后):
POST /api/apikeys/import
请求:
{
"fromBillId": "源账单ID",
"toBillId": "当前账单ID",
"provider": "openai",
"model": "gpt-4o"
}
说明:
api_key_encrypted(Base64),无重加密(见 P11)DB 表:api_keys(UPSERT)、user_provider_order
---
Key 解析规则(后端 AI 模块内部逻辑)
当用户发起 AI 对话时,后端 /api/ai/chat 内部调用 Key 解析:
1. 个人 Key 优先:owner_type = 'user' AND owner_id = 当前用户
2. 同账单已共享 Key 其次:is_shared = 1
3. 按 priority 升序排列(数字越小越优先)
4. 排除已撤销:revoked_at IS NOT NULL
5. 排除已过期:expires_at IS NOT NULL AND expires_at < NOW()(⚠️ expires_at 字段存在但前端无管理 UI,见 P12)
---
权限判断汇总
| 操作 | 权限 | API 端点 |
|------|------|---------|
| 查看 DeveloperPage | developer / admin 角色 | — |
| 查看自己的 Key 列表 | 账单成员 | GET /api/apikeys/user?billId=xxx |
| 查看账单所有可用 Key | 账单成员 | GET /api/apikeys/resolve-all/:billId |
| 添加 / 更新 Key | 账单成员 | PUT /api/apikeys/user |
| 测试 Key 连通性 | 账单成员 | POST /api/apikeys/verify/user |
| 切换 Key 共享状态 | Key 所有者 | PATCH /api/apikeys/user/share |
| 调整 Key 顺序 | 账单成员 | PATCH /api/apikeys/order/user |
| 删除 Key | Key 所有者 | DELETE /api/apikeys/user/:provider |
| 获取跨账单导入建议 | 账单成员 | GET /api/apikeys/suggestions |
| 从其他账单导入 Key | 账单成员 | POST /api/apikeys/import |