From dba052bb5ed9442308f1d1110f4ec3750e5accca Mon Sep 17 00:00:00 2001 From: "liweiliang0905@gmail.com" Date: Fri, 23 Jan 2026 22:28:28 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20API=20Keys=20=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E5=AD=98=E5=82=A8=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.5 --- docs/plans/2026-01-23-api-keys-db-design.md | 168 ++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 docs/plans/2026-01-23-api-keys-db-design.md diff --git a/docs/plans/2026-01-23-api-keys-db-design.md b/docs/plans/2026-01-23-api-keys-db-design.md new file mode 100644 index 0000000..c6f8f2f --- /dev/null +++ b/docs/plans/2026-01-23-api-keys-db-design.md @@ -0,0 +1,168 @@ +# API Keys 数据库存储设计 + +## 背景 + +将 AI 服务的 API Keys 从环境变量迁移到数据库存储,实现: +- 运维便利:无需重启服务即可更新配置 +- 安全集中管理:敏感信息统一管理 +- Web 界面配置:管理员可通过设置页面直接管理 + +## 设计决策 + +| 决策点 | 选择 | 原因 | +|--------|------|------| +| 管理范围 | 仅 AI API Keys | JWT_SECRET 启动时需要,保持环境变量 | +| 存储方式 | 明文存储 | 简单直接,依赖数据库访问控制 | +| 优先级 | 数据库优先 | 方便运行时覆盖环境变量 | +| 访问权限 | 仅管理员 | Web 界面配置入口仅 admin 可见 | + +## 数据模型 + +复用现有 `config` 表,使用 `api_key.` 前缀: + +``` +key | value +---------------------------|------------------ +api_key.dashscope | sk-xxx... +api_key.deepseek | sk-xxx... +api_key.anthropic | sk-xxx... +``` + +## API 接口 + +| 方法 | 路径 | 功能 | +|------|------|------| +| GET | `/api/admin/api-keys` | 获取所有 API Keys(掩码显示) | +| GET | `/api/admin/api-keys/{provider}` | 获取指定 API Key 完整值 | +| PUT | `/api/admin/api-keys/{provider}` | 设置/更新 API Key | +| DELETE | `/api/admin/api-keys/{provider}` | 删除 API Key(回退到环境变量) | + +## 需修改的文件 + +### 1. src/vitals/core/database.py + +新增函数: +- `get_api_key(provider: str) -> str | None` - 读取 API Key +- `set_api_key(provider: str, value: str)` - 保存 API Key +- `delete_api_key(provider: str)` - 删除 API Key +- `get_all_api_keys(masked: bool = True) -> dict` - 获取所有 API Keys + +```python +API_KEY_ENV_MAP = { + "dashscope": "DASHSCOPE_API_KEY", + "deepseek": "DEEPSEEK_API_KEY", + "anthropic": "ANTHROPIC_API_KEY", +} + +def get_api_key(provider: str) -> str | None: + """获取 API Key,数据库优先,环境变量备用""" + with get_connection() as (conn, cursor): + cursor.execute( + "SELECT value FROM config WHERE `key` = %s", + (f"api_key.{provider}",) + ) + row = cursor.fetchone() + if row and row["value"]: + return row["value"] + + # 回退到环境变量 + env_var = API_KEY_ENV_MAP.get(provider) + return os.environ.get(env_var) if env_var else None +``` + +### 2. src/vitals/vision/providers/qwen.py + +```python +# 修改前 +api_key = os.environ.get("DASHSCOPE_API_KEY") + +# 修改后 +from vitals.core import database as db +api_key = db.get_api_key("dashscope") +``` + +### 3. src/vitals/vision/providers/deepseek.py + +```python +# 修改前 +api_key = os.environ.get("DEEPSEEK_API_KEY") + +# 修改后 +from vitals.core import database as db +api_key = db.get_api_key("deepseek") +``` + +### 4. src/vitals/vision/analyzer.py + +```python +# 修改前 +api_key = os.environ.get("ANTHROPIC_API_KEY") + +# 修改后 +from vitals.core import database as db +api_key = db.get_api_key("anthropic") +``` + +### 5. src/vitals/web/app.py + +新增管理员 API 接口: + +```python +@app.get("/api/admin/api-keys") +async def get_api_keys(request: Request): + """获取所有 API Keys(掩码显示)""" + user = await get_current_user(request) + if not user or not user.get("is_admin"): + raise HTTPException(status_code=403) + return db.get_all_api_keys(masked=True) + +@app.get("/api/admin/api-keys/{provider}") +async def get_api_key(provider: str, request: Request): + """获取指定 API Key 完整值""" + # 权限检查... + return {"provider": provider, "value": db.get_api_key(provider)} + +@app.put("/api/admin/api-keys/{provider}") +async def set_api_key(provider: str, request: Request): + """设置 API Key""" + # 权限检查... + body = await request.json() + db.set_api_key(provider, body["value"]) + return {"success": True} + +@app.delete("/api/admin/api-keys/{provider}") +async def delete_api_key(provider: str, request: Request): + """删除 API Key""" + # 权限检查... + db.delete_api_key(provider) + return {"success": True} +``` + +### 6. src/vitals/web/templates/settings.html + +在设置页面增加 API 密钥管理卡片(仅管理员可见): + +```html +{% if user.is_admin %} +
+

API 密钥配置

+ + + +
+{% endif %} +``` + +## 数据流 + +``` +管理员 → Web界面 → /api/admin/api-keys → database.set_api_key() → config表 + ↓ +食物识别 → qwen.py → database.get_api_key() → 查config表 → 有值返回 / 无值读环境变量 +``` + +## 向后兼容 + +- 现有环境变量配置继续有效 +- 数据库未配置时自动回退到环境变量 +- 无需数据迁移,新功能即插即用