Files
DDUp/docs/plans/2026-01-23-api-keys-db-design.md
2026-01-23 22:33:50 +08:00

4.7 KiB
Raw Blame History

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
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

# 修改前
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

# 修改前
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

# 修改前
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 接口:

@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 密钥管理卡片(仅管理员可见):

{% if user.is_admin %}
<div class="card">
    <h3>API 密钥配置</h3>
    <!-- 三个 API Key 输入框 -->
    <!-- 掩码显示,点击显示完整值 -->
    <!-- 保存/删除按钮 -->
</div>
{% endif %}

数据流

管理员 → Web界面 → /api/admin/api-keys → database.set_api_key() → config表
                                                                      ↓
食物识别 → qwen.py → database.get_api_key() → 查config表 → 有值返回 / 无值读环境变量

向后兼容

  • 现有环境变量配置继续有效
  • 数据库未配置时自动回退到环境变量
  • 无需数据迁移,新功能即插即用