文档目录
AI密钥表(dou_ai_key)

简介

本设计文档围绕 DouPHP 的 AI 密钥表 dou_ai_key,系统化说明其表结构设计、安全存储、生命周期管理、访问控制、审计日志、失败重试与熔断、以及配额控制的实现方案。面向 AI 密钥管理员,提供从“密钥创建—使用—监控—轮换—审计”的全流程实践指南。

项目结构

AI 密钥相关能力分布在以下模块:

  • 模型层:AiKey、AiLog、AiProvider
  • 服务层:KeyService(密钥增删改查与格式化)、AiGateway(调用网关、使用标记、失败计数)
  • 安全层:CredentialCipher(凭据加密/解密/迁移)
  • 配置与升级:ai_credential_encryption.upgrade.sql(字段扩容)
  • 界面与文案:后台语言包 ai.lang.php(字段提示、操作提示)
graph TB
A["Admin 控制器/表单"] --> B["KeyService<br/>密钥CRUD与格式化"]
B --> C["AiKey Model<br/>自动加解密属性"]
C --> D["数据库: ai_key"]
E["AiGateway<br/>调用网关"] --> F["CredentialCipher<br/>解密api_key/config"]
E --> G["外部AI供应商API"]
E --> H["AiLog Model<br/>记录用量/错误"]
B --> I["审计日志<br/>writeAdminLog"]

核心组件

  • AiKey 模型:定义 ai_key 表的 ORM 映射,内置 api_key 与 config 字段的自动加解密;提供筛选、排序与统计方法。
  • KeyService:封装密钥的插入、更新、格式化展示、重置失败计数等底层能力,并写入管理审计日志。
  • CredentialCipher:基于 AES-256-CBC + HMAC-SHA256 的凭据加密器,支持明文/旧密文迁移至当前应用密钥,并提供列容量校验。
  • AiGateway:AI 调用网关,负责选择可用密钥、解密凭据、标记最近使用时间、失败计数自增与成功复位。
  • AiLog:AI 使用日志模型,记录每次调用的上下文、Token 消耗、耗时、状态码、错误信息等。
  • AiProvider:供应商模型,用于关联密钥所属供应商。

架构总览

AI 密钥在系统中的流转如下:

  • 管理端新增/编辑密钥时,KeyService 将 api_key 与 config 通过 AiKey 模型的属性拦截器进行加密后落库。
  • 运行时 AiGateway 根据供应商选择候选密钥,解密 api_key/config 后发起调用。
  • 调用成功后,AiGateway 更新 last_used_at 并复位 failure_count;失败则增加 failure_count 并记录日志。
  • 所有密钥变更操作均通过 KeyService 写入审计日志,便于追踪。
sequenceDiagram
participant Admin as "管理端"
participant Service as "KeyService"
participant Model as "AiKey"
participant DB as "数据库(ai_key)"
participant Gateway as "AiGateway"
participant Cipher as "CredentialCipher"
participant Log as "AiLog"
Admin->>Service : 新增/更新密钥
Service->>Model : create/fill/save
Model->>Cipher : 加密api_key/config
Cipher-->>Model : 密文字符串
Model->>DB : 持久化
Service->>Service : 写入管理审计日志
Admin->>Gateway : 发起AI请求
Gateway->>DB : 读取密钥(含密文)
Gateway->>Cipher : 解密api_key/config
Cipher-->>Gateway : 明文凭据
Gateway->>Gateway : 选择可用密钥/校验过期
Gateway->>DB : 更新last_used_at / failure_count
Gateway->>Log : 记录使用日志
Gateway-->>Admin : 返回结果

详细组件分析

表结构与字段设计(ai_key)

  • 主键 id:整型自增
  • provider_id:供应商ID(整数),用于归属与筛选
  • api_key:API密钥(MEDIUMTEXT,已扩容),存储为加密后的字符串
  • alias:密钥别名(用于识别与管理)
  • expires_at:过期时间(可选,空表示永久有效)
  • last_used_at:最后使用时间(由网关在调用成功后更新)
  • failure_count:连续失败次数(达到阈值后被候选筛选自然熔断)
  • config:扩展配置(MEDIUMTEXT,已扩容),JSON格式,敏感子段如 client_secret/access_token/token_expires_at 会被合并与脱敏展示
  • created_at:创建时间(系统维护)

说明:

  • api_key 与 config 在写入时通过 AiKey 的属性拦截器自动加密,读取时自动解密。
  • 升级脚本已将 api_key 与 config 字段扩容为 MEDIUMTEXT,以容纳更长的密文。

密钥安全管理(加密与迁移)

  • 加密算法:AES-256-CBC + HMAC-SHA256,输出带前缀标识的密文,支持版本化重打包。
  • 密钥来源:优先使用 DOU_APP_KEY,回退到 DOU_SHELL;升级路径中会读取 hash_code 作为旧密钥以便迁移。
  • 迁移能力:CredentialCipher::rewrapStoredCredentials 可批量将明文或旧密文迁移到当前应用密钥,且对每个字段做列容量校验。
  • 存储限制:encryptForStorage 会在超过列容量时抛出异常,避免写入失败。
flowchart TD
Start(["开始"]) --> CheckEnc["是否已是当前密钥密文?"]
CheckEnc --> |是| ReturnSame["原样返回"]
CheckEnc --> |否| TryLegacy["尝试用旧密钥解密"]
TryLegacy --> DecryptOK{"解密成功?"}
DecryptOK --> |否| ThrowErr["抛出无效凭据异常"]
DecryptOK --> |是| ReEncrypt["用新密钥重新加密"]
ReEncrypt --> SizeCheck{"长度不超过列容量?"}
SizeCheck --> |否| ThrowSize["抛出版本/容量异常"]
SizeCheck --> |是| Save["保存新密文"]
Save --> End(["结束"])

密钥生命周期管理

  • 创建:KeyService::insert 接收 provider_id、api_key、alias、expires_at、config,并写入审计日志。
  • 更新:KeyService::update 支持仅更新非空字段;若 api_key 为空则不覆盖原值;config 合并时会保留敏感字段(client_secret/access_token/token_expires_at)。
  • 失效与熔断:
    • 过期:expires_at 为空表示永久有效;网关在选择密钥时可结合过期时间过滤。
    • 失败熔断:failure_count 达到阈值后,网关在候选筛选中跳过该密钥,避免继续失败。
  • 恢复:KeyService::resetKey 可将 failure_count 重置为 0,并记录审计日志。
  • 删除:存在会话记录的密钥不可删除(语言包提示),需先清理关联数据。
stateDiagram-v2
[*] --> 已启用
已启用 --> 已熔断 : "failure_count >= 阈值"
已熔断 --> 已启用 : "管理端重置失败计数"
已启用 --> 已过期 : "expires_at <= 当前时间"
已过期 --> 已启用 : "续期/更新expires_at"

访问控制与审计日志

  • 访问控制:密钥的增删改由 KeyService 统一处理,所有写操作均通过管理端权限校验后执行。
  • 审计日志:KeyService 在创建、更新、重置失败计数等操作时调用 writeAdminLog,记录管理员ID、动作类型、对象名称与资源标识。
  • 使用日志:AiLog 记录每次 AI 调用的上下文(管理员、应用、供应商、模型、密钥、请求ID、Token 用量、耗时、状态码、错误信息、端点、IP、元数据等),可用于配额与成本核算。
classDiagram
class AiKey {
+int id
+int provider_id
+string api_key
+string alias
+datetime expires_at
+datetime last_used_at
+int failure_count
+string config
+string created_at
}
class AiLog {
+int id
+int admin_id
+int app_id
+int model_id
+int provider_id
+int key_id
+string request_id
+int prompt_tokens
+int completion_tokens
+int total_tokens
+float duration
+int status_code
+int has_error
+string error_message
+string endpoint
+string ip
+string metadata
+datetime created_at
}
class KeyService {
+insert(data) int
+update(data) void
+resetKey(id) void
+getKeysByProvider(providerId) array
+ensureStoredCredentialsEncrypted() void
}
class AiGateway {
+touchKey(keyId) void
+bumpFailureCount(keyId) void
+resetFailureCount(keyId) void
}
KeyService --> AiKey : "CRUD"
AiGateway --> AiKey : "更新last_used_at/failure_count"
AiGateway --> AiLog : "记录使用日志"

失败重试与熔断

  • 失败计数:AiGateway::bumpFailureCount 在调用失败时对 failure_count 自增,并记录警告日志。
  • 成功复位:AiGateway::resetFailureCount 在调用成功后将 failure_count 归零。
  • 熔断策略:当 failure_count 达到阈值(FAILURE_THRESHOLD)时,网关在候选密钥筛选中跳过该密钥,避免继续失败。
  • 管理恢复:KeyService::resetKey 允许管理员手动重置失败计数,解除熔断。
flowchart TD
Call["发起AI调用"] --> Result{"调用成功?"}
Result --> |是| Reset["复位failure_count=0"]
Result --> |否| Bump["failure_count++"]
Bump --> Threshold{"达到阈值?"}
Threshold --> |是| Skip["本次候选跳过该密钥"]
Threshold --> |否| Retry["下次仍可能重试"]
Reset --> End["完成"]
Skip --> End
Retry --> End

配额控制

  • 密钥维度:ai_key 未直接存储配额字段;配额通常由上层订阅/套餐体系控制(语言包包含配额相关文案)。
  • 使用记录:AiLog 记录每次调用的 Token 用量与状态,可作为配额扣减与统计依据。
  • 建议方案:在调用链入口处按订阅/套餐维度检查剩余配额,不足则拒绝调用;调用成功后按 AiLog 中的 token 用量扣减配额。

依赖关系分析

  • AiKey 依赖 CredentialCipher 进行字段级加解密。
  • KeyService 依赖 AiKey 与审计日志接口,提供统一的密钥管理能力。
  • AiGateway 依赖数据库、CredentialCipher、日志与密钥模型,负责运行时的密钥选择与使用跟踪。
  • AiLog 与 AiProvider 为辅助模型,分别记录使用信息与供应商信息。
graph LR
Cipher["CredentialCipher"] --> Model["AiKey"]
Model --> Service["KeyService"]
Service --> Audit["审计日志"]
Gateway["AiGateway"] --> Cipher
Gateway --> Model
Gateway --> Log["AiLog"]
Provider["AiProvider"] --> Model

性能与安全考量

  • 加密开销:每次读写 api_key/config 都会触发加解密,建议在高频场景下缓存解密结果(注意缓存安全性与过期策略)。
  • 列容量:MEDIUMTEXT 可容纳较长密文,但仍有上限;CredentialCipher::encryptForStorage 会在超限时抛出异常,部署前应确保列扩容已完成。
  • 并发安全:failure_count 使用原子自增,避免竞态条件;last_used_at 更新为简单赋值,适合高并发场景。
  • 审计与可观测性:管理端操作与运行期调用均有日志记录,便于问题定位与合规审计。

故障排查指南

  • 无法解密凭据:检查 DOU_APP_KEY/DOU_SHELL 是否正确配置;确认密文是否被篡改;查看 CredentialCipher 异常信息。
  • 写入失败:确认 ai_key.api_key/config 已扩容为 MEDIUMTEXT;检查 encryptForStorage 是否抛出容量异常。
  • 密钥持续失败:查看 AiLog 的错误消息与状态码;检查 failure_count 是否达到阈值;必要时重置失败计数。
  • 配额耗尽:核对订阅/套餐状态与剩余配额;根据 AiLog 的 token 用量进行扣减与核对。

结论

dou_ai_key 表通过“字段级加密 + 网关统一调用 + 审计与使用日志”的组合,实现了 AI 密钥的安全存储、生命周期管理与可观测性。配合失败熔断与配额控制,可在多供应商、多密钥场景下保障系统的稳定性与成本可控。管理员应重点关注密钥的加密迁移、过期与熔断策略、以及审计与使用日志的分析。

附录:字段与机制对照

  • 供应商ID(provider_id):用于密钥归属与筛选,关联 AiProvider。
  • API密钥(api_key):加密存储,运行时由网关解密后使用。
  • 加密方式(plain/encrypted/vault):当前实现为 encrypted(AES-256-CBC + HMAC-SHA256),明文与旧密文可自动迁移;vault 模式可扩展为对接外部密钥管理服务。
  • 密钥别名(alias):便于识别与管理。
  • 启用状态:通过 expires_at 与 failure_count 共同决定可用性。
  • 过期时间(expires_at):空表示永久有效;网关可选择性过滤过期密钥。
  • 最后使用时间(last_used_at):调用成功后更新。
  • 连续失败次数(failure_count):失败自增,达到阈值熔断;成功复位;管理端可重置。
  • 扩展配置(config):JSON 格式,敏感字段(client_secret/access_token/token_expires_at)会被合并与脱敏展示。
添加日期:2026-10-05