AI Gateway Go 0.10.0 源码快照 + 旗舰版需求规划报告

M0-M7 已完成:核心网关(身份/RBAC/TOTP/OIDC/SAML/Provider/配额/路由/内容策略/审计/定价)+ 资源市场(MCP/Skills/数字员工)。
含 22 个 PostgreSQL 迁移、管理端/门户端前端源码、OpenAPI 契约、部署 compose。

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ben
2026-08-12 11:45:54 +08:00
commit 5759c1862e
807 changed files with 114727 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
# ADR-0001:基线存储只使用 PostgreSQL 与 Redis
状态:已接受
日期:2026-08-10
## 决策
第一版部署不包含 MinIO/S3 与 ClickHouse。
- PostgreSQL 保存权威业务数据、outbox、有限长度的审计请求/响应预览和聚合统计。
- critical Redis 保存限流、配额、幂等、配置协调与短期可靠流,使用 AOF 和 `noeviction`
- cache Redis 保存可丢弃缓存,使用 `allkeys-lfu`,故障时网关降级运行。
- 不把完整且无上限的请求正文、响应正文和知识库原始文件写入本地磁盘。
- 知识库只保存经过提取、大小不超过 2 MiB 的纯文本及其分块;`Retriever` 隔离检索实现,基线使用 PostgreSQL FTS 和中文词片评分。
## 影响
基线更容易部署和运维,但不承诺无限期保存完整审计正文,也不针对超大规模多年期分析查询优化。未来只有在容量和合规需求明确时,才通过 `AuditBodyStore``AnalyticsSink` 接口接入对象存储或分析库。
+35
View File
@@ -0,0 +1,35 @@
# 双系统切换与回退手册
## 进入切换窗口前
1. 对照 `route-contract-matrix.md`,确认所有仍有调用量的旧契约已经完成客户端迁移或兼容层验证。
2. 连续观察影子指标;`gateway_shadow_errors_total` 不增长,契约 mismatch 已逐项解释,且 shadow 专用 Key 没有生产权限扩张。
3. 使用生产等价数据量执行 `gateway-loadtest`,确认错误率、p95、数据库连接池、critical Redis 延迟和审计队列丢弃数均在阈值内。
4. 冻结旧系统配置写入,执行最后一次幂等数据导入和数量/外键校验。
5. 确认 Go 与旧网关的 `/readyz` 均为成功,并备份 Nginx 活动 upstream include。
## 原子切换
`scripts/cutover.sh` 只修改一个专用 Nginx include。它先探活目标,写临时文件后原子替换,执行 `nginx -t`,再 reload;验证或 reload 失败会恢复备份。
```bash
export NGINX_ACTIVE_INCLUDE=/etc/nginx/conf.d/ai-gateway-active.conf
export GO_UPSTREAM=10.0.0.21:8080
export LEGACY_UPSTREAM=10.0.0.20:8000
export GO_HEALTH_URL=http://10.0.0.21:8080/readyz
export LEGACY_HEALTH_URL=http://10.0.0.20:8000/readyz
export PUBLIC_HEALTH_URL=https://gateway.example.com/readyz
scripts/cutover.sh preflight
scripts/cutover.sh go
scripts/cutover.sh status
```
## 回退触发条件
- 5 分钟窗口错误率超过既定 SLO,或出现鉴权/配额错误的系统性抬升;
- p95/p99 持续超过容量验收阈值;
- 审计队列出现丢弃、critical Redis 不稳定、数据库连接池耗尽;
- 与旧系统相比出现未经批准的响应结构差异或关键业务功能缺失。
执行 `scripts/cutover.sh rollback`。回退只切流量,不回写/删除 Go 数据;outbox 与审计 Worker 保持运行,便于事后核对。确认旧系统恢复后保存本次 include 备份、指标和日志,再分析问题。
+625
View File
@@ -0,0 +1,625 @@
# 旧 Python 网关数据字典与 ID 迁移映射
> 由 `scripts/export_legacy_dictionary.py` 从旧工程 SQLAlchemy metadata 生成。
> `audit_log_views` 是兼容查询投影,不作为独立持久化实体,因此这里统计 38 个实体。
## 映射约定
- 旧整数主键统一通过 `gateway.legacy_id_mappings` 映射为 UUID,不在新业务表保留双主键。
- UUID 使用固定 namespace 的 UUIDv5 生成,输入为 `source_system/entity_type/legacy_id`,重复导入保持幂等。
- 外键迁移必须先查映射表;缺失映射时整批失败,不允许写入悬空引用。
- 旧密文不能直接复用:Provider、TOTP、内部服务 Key 等敏感字段在导入时解密后用新用途标签重新加密。
- `audit_logs` 的完整请求/响应正文不进入新库,只迁移受长度约束的预览与结构化元数据。
## 实体去向
| 旧表 | 新域/目标 | 状态 |
|---|---|---|
| `admin_accounts` | gateway.admin_accounts(已落地) | 已建表,待批量导入器 |
| `application_revisions` | gateway.application_versions | 已建表,待批量导入器转换 config 引用 |
| `application_runs` | gateway.application_runs | 已建表,待批量导入器 |
| `applications` | gateway.applications | 已建表,待批量导入器 |
| `audit_logs` | gateway.audit_events(已落地,不迁移无限长正文) | 已建表,待批量导入器 |
| `audit_search_records` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `audit_settings` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `content_audit_scopes` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `departments` | gateway.departments(已落地) | 已建表,待批量导入器 |
| `dept_budgets` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `feishu_auth_settings` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `filter_rules` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `hallucination_events` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `hallucination_policies` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `hallucination_settings` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `internal_service_keys` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `knowledge_bases` | gateway.knowledge_bases | 已建表,旧 embedding_model 转检索器迁移策略待导入器处理 |
| `knowledge_chunks` | gateway.knowledge_chunks | 已建表,基线重新分块/建 PostgreSQL 搜索向量 |
| `knowledge_documents` | gateway.knowledge_documents | 已建表,只导入不超过 2 MiB 的提取文本 |
| `managed_conversations` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `managed_messages` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `model_aliases` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `model_pricing` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `model_qps_configs` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `model_requests` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `notify_config` | gateway.notification_channels | 已建表,待解密后重新加密签名密钥 |
| `pricing_config` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `prompt_categories` | gateway.prompt_categories | 已建表,待批量导入器 |
| `prompt_favorites` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `prompt_templates` | gateway.prompt_templates | 已建表,待批量导入器 |
| `prompt_versions` | gateway.prompt_versions | 已建表,待批量导入器 |
| `providers` | gateway.providers + gateway.provider_models(已落地,models 拆表) | 已建表,待批量导入器 |
| `rate_limit_rules` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `routing_groups` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `routing_targets` | 对应 M2–M4 领域表 | 已登记,随对应里程碑建表 |
| `tool_definitions` | gateway.tool_definitions | 已建表,待解密后用新用途标签加密请求头 |
| `users` | gateway.portal_users(已落地) | 已建表,待批量导入器 |
| `virtual_keys` | gateway.api_keys(已落地,字段需转换) | 已建表,待批量导入器 |
## 字段字典
### `admin_accounts`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `—` | PK |
| `username` | `VARCHAR(64)` | 否 | `—` | UNIQUE |
| `password_hash` | `VARCHAR(128)` | 否 | `—` | — |
| `display_name` | `VARCHAR(64)` | 是 | `` | — |
| `role` | `VARCHAR(16)` | 是 | `superadmin` | — |
| `active` | `BOOLEAN` | 是 | `True` | — |
| `last_login` | `DATETIME` | 是 | `` | — |
| `failed_logins` | `INTEGER` | 是 | `0` | — |
| `locked_until` | `DATETIME` | 是 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
| `totp_secret` | `VARCHAR(255)` | 是 | `` | — |
| `totp_enabled` | `BOOLEAN` | 是 | `False` | — |
| `totp_last_step` | `INTEGER` | 是 | `` | — |
| `totp_backup_codes` | `TEXT` | 是 | `[]` | — |
| `totp_confirmed_at` | `DATETIME` | 是 | `` | — |
### `application_revisions`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `application_id` | `INTEGER` | 否 | `` | FK → `applications.id` |
| `version` | `INTEGER` | 否 | `` | — |
| `config` | `TEXT` | 否 | `` | — |
| `change_note` | `VARCHAR(256)` | 是 | `` | — |
| `published_by` | `VARCHAR(128)` | 是 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
### `application_runs`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `application_id` | `INTEGER` | 否 | `` | FK → `applications.id` |
| `version` | `INTEGER` | 是 | `0` | — |
| `user_id` | `INTEGER` | 是 | `` | FK → `users.id` |
| `status` | `VARCHAR(16)` | 是 | `success` | — |
| `latency_ms` | `INTEGER` | 是 | `0` | — |
| `retrieval_count` | `INTEGER` | 是 | `0` | — |
| `tool_calls` | `INTEGER` | 是 | `0` | — |
| `error` | `VARCHAR(512)` | 是 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
### `applications`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `app_code` | `VARCHAR(64)` | 否 | `` | UNIQUE |
| `name` | `VARCHAR(128)` | 否 | `` | — |
| `description` | `TEXT` | 是 | `` | — |
| `dept_id` | `INTEGER` | 是 | `` | FK → `departments.id` |
| `owner_user_id` | `INTEGER` | 是 | `` | FK → `users.id` |
| `status` | `VARCHAR(16)` | 是 | `active` | — |
| `allowed_models` | `TEXT` | 是 | `[]` | — |
| `quota_tokens` | `INTEGER` | 是 | `` | — |
| `rpm_limit` | `INTEGER` | 是 | `` | — |
| `draft_config` | `TEXT` | 是 | `{}` | — |
| `published_config` | `TEXT` | 是 | `{}` | — |
| `release_version` | `INTEGER` | 是 | `0` | — |
| `published_at` | `DATETIME` | 是 | `` | — |
| `runtime_key_id` | `INTEGER` | 是 | `` | — |
| `runtime_key_secret` | `TEXT` | 是 | `` | — |
| `created_by` | `VARCHAR(128)` | 是 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
| `updated_at` | `DATETIME` | 是 | `now` | — |
### `audit_logs`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `event_id` | `VARCHAR(32)` | 是 | `` | UNIQUE |
| `ts` | `DATETIME` | 是 | `now` | — |
| `key_id` | `INTEGER` | 是 | `` | — |
| `key_name` | `VARCHAR(128)` | 是 | `` | — |
| `user_id` | `INTEGER` | 是 | `` | — |
| `owner` | `VARCHAR(128)` | 是 | `` | — |
| `model` | `VARCHAR(64)` | 是 | `` | — |
| `client_ip` | `VARCHAR(64)` | 是 | `` | — |
| `request_body` | `TEXT` | 是 | `` | — |
| `response_body` | `TEXT` | 是 | `` | — |
| `request_body_ref` | `VARCHAR(512)` | 是 | `` | — |
| `response_body_ref` | `VARCHAR(512)` | 是 | `` | — |
| `request_body_size` | `INTEGER` | 是 | `0` | — |
| `response_body_size` | `INTEGER` | 是 | `0` | — |
| `request_body_sha256` | `VARCHAR(64)` | 是 | `` | — |
| `response_body_sha256` | `VARCHAR(64)` | 是 | `` | — |
| `prompt_tokens` | `INTEGER` | 是 | `0` | — |
| `completion_tokens` | `INTEGER` | 是 | `0` | — |
| `filter_hits` | `TEXT` | 是 | `[]` | — |
| `outcome` | `VARCHAR(16)` | 是 | `pass` | — |
| `blocked` | `BOOLEAN` | 是 | `False` | — |
| `status_code` | `INTEGER` | 是 | `200` | — |
| `latency_ms` | `INTEGER` | 是 | `0` | — |
| `search_text` | `TEXT` | 是 | `` | — |
### `audit_search_records`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `viewer` | `VARCHAR(128)` | 否 | `` | — |
| `query_hash` | `VARCHAR(64)` | 否 | `` | — |
| `start_at` | `DATETIME` | 否 | `` | — |
| `end_at` | `DATETIME` | 否 | `` | — |
| `owner_filter` | `VARCHAR(128)` | 是 | `` | — |
| `model_filter` | `VARCHAR(128)` | 是 | `` | — |
| `result_count` | `INTEGER` | 是 | `0` | — |
| `duration_ms` | `INTEGER` | 是 | `0` | — |
| `ts` | `DATETIME` | 是 | `now` | — |
### `audit_settings`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `image_audit_enabled` | `BOOLEAN` | 是 | `True` | — |
| `media_audit_async` | `BOOLEAN` | 是 | `False` | — |
| `text_audit_enabled` | `BOOLEAN` | 是 | `False` | — |
| `deep_scan_enabled` | `BOOLEAN` | 是 | `False` | — |
| `text_audit_api_base` | `VARCHAR(256)` | 是 | `` | — |
| `text_audit_api_key` | `VARCHAR(512)` | 是 | `` | — |
| `text_audit_model` | `VARCHAR(128)` | 是 | `` | — |
| `image_audit_api_base` | `VARCHAR(256)` | 是 | `` | — |
| `image_audit_api_key` | `VARCHAR(512)` | 是 | `` | — |
| `image_audit_model` | `VARCHAR(128)` | 是 | `` | — |
| `updated_at` | `DATETIME` | 是 | `now` | — |
### `content_audit_scopes`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `kind` | `VARCHAR(16)` | 否 | `` | — |
| `scope` | `VARCHAR(32)` | 否 | `` | — |
| `enabled` | `BOOLEAN` | 是 | `True` | — |
| `mode` | `VARCHAR(16)` | 是 | `async` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
### `departments`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `name` | `VARCHAR(64)` | 否 | `` | UNIQUE |
| `created_at` | `DATETIME` | 是 | `now` | — |
### `dept_budgets`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `dept_id` | `INTEGER` | 否 | `` | UNIQUEFK → `departments.id` |
| `monthly_point_budget` | `FLOAT` | 是 | `0.0` | — |
| `alert_threshold_pct` | `INTEGER` | 是 | `80` | — |
| `notify_enabled` | `BOOLEAN` | 是 | `True` | — |
| `last_alert_period` | `VARCHAR(7)` | 是 | `` | — |
| `last_alert_threshold` | `INTEGER` | 是 | `0` | — |
| `updated_at` | `DATETIME` | 是 | `now` | — |
### `feishu_auth_settings`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `enabled` | `BOOLEAN` | 是 | `False` | — |
| `app_id` | `VARCHAR(128)` | 是 | `` | — |
| `app_secret` | `VARCHAR(512)` | 是 | `` | — |
| `default_dept` | `VARCHAR(128)` | 是 | `飞书用户` | — |
| `allowed_tenants` | `TEXT` | 是 | `[]` | — |
| `provisioning_mode` | `VARCHAR(16)` | 是 | `pending` | — |
| `updated_at` | `DATETIME` | 是 | `now` | — |
### `filter_rules`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `name` | `VARCHAR(64)` | 否 | `` | — |
| `rule_type` | `VARCHAR(16)` | 是 | `keyword` | — |
| `pattern` | `TEXT` | 否 | `` | — |
| `action` | `VARCHAR(16)` | 是 | `block` | — |
| `severity` | `VARCHAR(8)` | 是 | `` | — |
| `enabled` | `BOOLEAN` | 是 | `True` | — |
| `builtin` | `BOOLEAN` | 是 | `False` | — |
| `scope` | `VARCHAR(32)` | 是 | `global` | — |
| `dimension` | `VARCHAR(32)` | 是 | `sensitive_content` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
### `hallucination_events`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `request_id` | `VARCHAR(64)` | 是 | `` | — |
| `key_id` | `INTEGER` | 是 | `` | FK → `virtual_keys.id` |
| `application_id` | `INTEGER` | 是 | `` | FK → `applications.id` |
| `dept_id` | `INTEGER` | 是 | `` | FK → `departments.id` |
| `model` | `VARCHAR(128)` | 是 | `` | — |
| `mode` | `VARCHAR(16)` | 是 | `async` | — |
| `action` | `VARCHAR(16)` | 是 | `observe` | — |
| `verdict` | `VARCHAR(24)` | 是 | `insufficient` | — |
| `support_score` | `INTEGER` | 是 | `0` | — |
| `question` | `TEXT` | 是 | `` | — |
| `answer` | `TEXT` | 是 | `` | — |
| `claims` | `TEXT` | 是 | `[]` | — |
| `evidence` | `TEXT` | 是 | `[]` | — |
| `latency_ms` | `INTEGER` | 是 | `0` | — |
| `error` | `VARCHAR(512)` | 是 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
### `hallucination_policies`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `scope` | `VARCHAR(32)` | 否 | `global` | — |
| `enabled` | `BOOLEAN` | 是 | `False` | — |
| `mode` | `VARCHAR(16)` | 是 | `async` | — |
| `action` | `VARCHAR(16)` | 是 | `observe` | — |
| `kb_ids` | `TEXT` | 是 | `[]` | — |
| `support_threshold` | `INTEGER` | 是 | `70` | — |
| `evidence_threshold` | `FLOAT` | 是 | `0.35` | — |
| `top_k` | `INTEGER` | 是 | `4` | — |
| `max_claims` | `INTEGER` | 是 | `8` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
| `updated_at` | `DATETIME` | 是 | `now` | — |
### `hallucination_settings`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `1` | PK |
| `api_base` | `VARCHAR(256)` | 是 | `` | — |
| `api_key` | `VARCHAR(512)` | 是 | `` | — |
| `model` | `VARCHAR(128)` | 是 | `` | — |
| `timeout_seconds` | `INTEGER` | 是 | `15` | — |
| `updated_at` | `DATETIME` | 是 | `now` | — |
### `internal_service_keys`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `virtual_key_id` | `INTEGER` | 是 | `` | FK → `virtual_keys.id` |
| `raw_key_encrypted` | `VARCHAR(512)` | 是 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
### `knowledge_bases`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `name` | `VARCHAR(128)` | 否 | `` | UNIQUE |
| `description` | `TEXT` | 是 | `` | — |
| `embedding_model` | `VARCHAR(128)` | 否 | `` | — |
| `chunk_size` | `INTEGER` | 是 | `800` | — |
| `chunk_overlap` | `INTEGER` | 是 | `100` | — |
| `dept_scope` | `TEXT` | 是 | `[]` | — |
| `enabled` | `BOOLEAN` | 是 | `True` | — |
| `created_by` | `VARCHAR(128)` | 是 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
| `updated_at` | `DATETIME` | 是 | `now` | — |
### `knowledge_chunks`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `kb_id` | `INTEGER` | 否 | `` | FK → `knowledge_bases.id` |
| `doc_id` | `INTEGER` | 否 | `` | FK → `knowledge_documents.id` |
| `chunk_index` | `INTEGER` | 是 | `0` | — |
| `content` | `TEXT` | 是 | `` | — |
| `embedding` | `TEXT` | 是 | `[]` | — |
| `token_count` | `INTEGER` | 是 | `0` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
### `knowledge_documents`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `kb_id` | `INTEGER` | 否 | `` | FK → `knowledge_bases.id` |
| `filename` | `VARCHAR(256)` | 是 | `` | — |
| `source_type` | `VARCHAR(16)` | 是 | `upload` | — |
| `status` | `VARCHAR(16)` | 是 | `pending` | — |
| `status_message` | `TEXT` | 是 | `` | — |
| `content` | `TEXT` | 是 | `` | — |
| `char_count` | `INTEGER` | 是 | `0` | — |
| `chunk_count` | `INTEGER` | 是 | `0` | — |
| `uploaded_by` | `VARCHAR(128)` | 是 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
| `updated_at` | `DATETIME` | 是 | `now` | — |
### `managed_conversations`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `VARCHAR(48)` | 否 | `` | PK |
| `user_id` | `INTEGER` | 否 | `` | FK → `users.id` |
| `application_id` | `INTEGER` | 否 | `` | FK → `applications.id` |
| `title` | `VARCHAR(160)` | 是 | `新会话` | — |
| `status` | `VARCHAR(16)` | 否 | `active` | — |
| `next_seq` | `INTEGER` | 否 | `1` | — |
| `head_hash` | `VARCHAR(64)` | 否 | `` | — |
| `busy` | `BOOLEAN` | 否 | `False` | — |
| `busy_token` | `VARCHAR(48)` | 否 | `` | — |
| `busy_since` | `DATETIME` | 是 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
| `updated_at` | `DATETIME` | 是 | `now` | — |
### `managed_messages`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `conversation_id` | `VARCHAR(48)` | 否 | `` | FK → `managed_conversations.id` |
| `seq` | `INTEGER` | 否 | `` | — |
| `role` | `VARCHAR(16)` | 否 | `` | — |
| `content` | `TEXT` | 否 | `` | — |
| `content_sha256` | `VARCHAR(64)` | 否 | `` | — |
| `prev_hash` | `VARCHAR(64)` | 否 | `` | — |
| `chain_hash` | `VARCHAR(64)` | 否 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
### `model_aliases`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `alias` | `VARCHAR(128)` | 否 | `` | UNIQUE |
| `target_model` | `VARCHAR(128)` | 否 | `` | — |
| `enabled` | `BOOLEAN` | 是 | `True` | — |
| `note` | `VARCHAR(256)` | 是 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
| `updated_at` | `DATETIME` | 是 | `now` | — |
### `model_pricing`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `model` | `VARCHAR(128)` | 否 | `` | — |
| `input_price_per_1k` | `FLOAT` | 是 | `0.0` | — |
| `output_price_per_1k` | `FLOAT` | 是 | `0.0` | — |
| `effective_from` | `DATETIME` | 是 | `now` | — |
| `note` | `VARCHAR(256)` | 是 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
### `model_qps_configs`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `model` | `VARCHAR(128)` | 否 | `` | UNIQUE |
| `qps` | `INTEGER` | 否 | `` | — |
| `enabled` | `BOOLEAN` | 是 | `True` | — |
| `updated_at` | `DATETIME` | 是 | `now` | — |
### `model_requests`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `ts` | `DATETIME` | 是 | `now` | — |
| `user_id` | `INTEGER` | 否 | `` | FK → `users.id` |
| `dept_id` | `INTEGER` | 是 | `` | — |
| `user_snapshot` | `VARCHAR(128)` | 是 | `` | — |
| `model_name` | `VARCHAR(128)` | 否 | `` | — |
| `provider_name` | `VARCHAR(64)` | 是 | `` | — |
| `purpose` | `TEXT` | 是 | `` | — |
| `quota_required` | `INTEGER` | 是 | `` | — |
| `status` | `VARCHAR(16)` | 是 | `pending` | — |
| `target_key_id` | `INTEGER` | 是 | `` | — |
| `reviewer` | `VARCHAR(128)` | 是 | `` | — |
| `review_comment` | `TEXT` | 是 | `` | — |
| `reviewed_at` | `DATETIME` | 是 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
### `notify_config`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `enabled` | `BOOLEAN` | 是 | `False` | — |
| `webhook_url` | `VARCHAR(512)` | 是 | `` | — |
| `signing_secret` | `VARCHAR(512)` | 是 | `` | — |
| `notify_actions` | `VARCHAR(32)` | 是 | `block,redact` | — |
| `dept_scope` | `TEXT` | 是 | `[]` | — |
| `updated_at` | `DATETIME` | 是 | `now` | — |
### `pricing_config`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `points_per_yuan` | `FLOAT` | 是 | `100.0` | — |
| `overhead_factor` | `FLOAT` | 是 | `1.0` | — |
| `updated_at` | `DATETIME` | 是 | `now` | — |
### `prompt_categories`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `name` | `VARCHAR(64)` | 否 | `` | UNIQUE |
| `description` | `VARCHAR(256)` | 是 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
### `prompt_favorites`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `user_id` | `INTEGER` | 否 | `` | FK → `users.id` |
| `template_id` | `INTEGER` | 否 | `` | FK → `prompt_templates.id` |
| `created_at` | `DATETIME` | 是 | `now` | — |
### `prompt_templates`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `name` | `VARCHAR(128)` | 否 | `` | UNIQUE |
| `description` | `TEXT` | 是 | `` | — |
| `category_id` | `INTEGER` | 是 | `` | FK → `prompt_categories.id` |
| `tags` | `TEXT` | 是 | `[]` | — |
| `dept_scope` | `TEXT` | 是 | `[]` | — |
| `current_version_id` | `INTEGER` | 是 | `` | — |
| `use_count` | `INTEGER` | 是 | `0` | — |
| `enabled` | `BOOLEAN` | 是 | `True` | — |
| `created_by` | `VARCHAR(128)` | 是 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
| `updated_at` | `DATETIME` | 是 | `now` | — |
### `prompt_versions`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `template_id` | `INTEGER` | 否 | `` | FK → `prompt_templates.id` |
| `version_number` | `INTEGER` | 否 | `` | — |
| `content` | `TEXT` | 否 | `` | — |
| `variables` | `TEXT` | 是 | `[]` | — |
| `change_note` | `VARCHAR(256)` | 是 | `` | — |
| `created_by` | `VARCHAR(128)` | 是 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
### `providers`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `name` | `VARCHAR(64)` | 否 | `` | UNIQUE |
| `base_url` | `VARCHAR(256)` | 否 | `` | — |
| `api_key` | `VARCHAR(512)` | 是 | `` | — |
| `models` | `TEXT` | 是 | `[]` | — |
| `backends` | `TEXT` | 是 | `` | — |
| `enabled` | `BOOLEAN` | 是 | `True` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
### `rate_limit_rules`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `name` | `VARCHAR(64)` | 否 | `` | — |
| `enabled` | `BOOLEAN` | 是 | `True` | — |
| `dimension` | `VARCHAR(16)` | 是 | `consumer` | — |
| `field_name` | `VARCHAR(64)` | 是 | `` | — |
| `match_type` | `VARCHAR(16)` | 是 | `any` | — |
| `pattern` | `VARCHAR(256)` | 是 | `` | — |
| `limit_count` | `INTEGER` | 否 | `60` | — |
| `window_seconds` | `INTEGER` | 是 | `60` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
| `updated_at` | `DATETIME` | 是 | `now` | — |
### `routing_groups`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `virtual_model` | `VARCHAR(128)` | 否 | `` | UNIQUE |
| `strategy` | `VARCHAR(16)` | 是 | `priority` | — |
| `description` | `VARCHAR(256)` | 是 | `` | — |
| `enabled` | `BOOLEAN` | 是 | `True` | — |
| `created_by` | `VARCHAR(128)` | 是 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
| `updated_at` | `DATETIME` | 是 | `now` | — |
### `routing_targets`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `group_id` | `INTEGER` | 否 | `` | FK → `routing_groups.id` |
| `target_model` | `VARCHAR(128)` | 否 | `` | — |
| `priority` | `INTEGER` | 是 | `100` | — |
| `capability_tags` | `TEXT` | 是 | `[]` | — |
| `enabled` | `BOOLEAN` | 是 | `True` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
### `tool_definitions`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `name` | `VARCHAR(128)` | 否 | `` | UNIQUE |
| `description` | `TEXT` | 是 | `` | — |
| `endpoint_url` | `VARCHAR(512)` | 否 | `` | — |
| `http_method` | `VARCHAR(8)` | 是 | `POST` | — |
| `headers_template` | `TEXT` | 是 | `` | — |
| `input_schema` | `TEXT` | 是 | `{}` | — |
| `timeout_seconds` | `INTEGER` | 是 | `15` | — |
| `dept_scope` | `TEXT` | 是 | `[]` | — |
| `enabled` | `BOOLEAN` | 是 | `True` | — |
| `created_by` | `VARCHAR(128)` | 是 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
| `updated_at` | `DATETIME` | 是 | `now` | — |
### `users`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `account` | `VARCHAR(128)` | 否 | `` | UNIQUE |
| `name` | `VARCHAR(64)` | 是 | `` | — |
| `dept_id` | `INTEGER` | 是 | `` | FK → `departments.id` |
| `password_hash` | `VARCHAR(128)` | 是 | `` | — |
| `feishu_open_id` | `VARCHAR(128)` | 是 | `` | UNIQUE |
| `feishu_union_id` | `VARCHAR(128)` | 是 | `` | — |
| `auth_source` | `VARCHAR(16)` | 是 | `local` | — |
| `active` | `BOOLEAN` | 是 | `True` | — |
| `provisioning_status` | `VARCHAR(16)` | 是 | `active` | — |
| `failed_logins` | `INTEGER` | 是 | `0` | — |
| `locked_until` | `DATETIME` | 是 | `` | — |
| `deleted_name_snapshot` | `VARCHAR(128)` | 是 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
| `totp_secret` | `VARCHAR(255)` | 是 | `` | — |
| `totp_enabled` | `BOOLEAN` | 是 | `False` | — |
| `totp_last_step` | `INTEGER` | 是 | `` | — |
| `totp_backup_codes` | `TEXT` | 是 | `[]` | — |
| `totp_confirmed_at` | `DATETIME` | 是 | `` | — |
### `virtual_keys`
| 字段 | 旧类型 | 可空 | 默认值 | 键/引用 |
|---|---|---:|---|---|
| `id` | `INTEGER` | 否 | `` | PK |
| `key_hash` | `VARCHAR(64)` | 否 | `` | UNIQUE |
| `key_prefix` | `VARCHAR(16)` | 是 | `` | — |
| `name` | `VARCHAR(128)` | 是 | `` | — |
| `user_id` | `INTEGER` | 是 | `` | FK → `users.id` |
| `application_id` | `INTEGER` | 是 | `` | FK → `applications.id` |
| `owner` | `VARCHAR(128)` | 是 | `` | — |
| `allowed_models` | `TEXT` | 是 | `[]` | — |
| `quota_tokens` | `INTEGER` | 是 | `` | — |
| `used_tokens` | `INTEGER` | 是 | `0` | — |
| `rpm_limit` | `INTEGER` | 是 | `` | — |
| `active` | `BOOLEAN` | 是 | `True` | — |
| `expires_at` | `DATETIME` | 是 | `` | — |
| `created_at` | `DATETIME` | 是 | `now` | — |
+42
View File
@@ -0,0 +1,42 @@
# 旧数据导入手册
当前工程提供两阶段、可重复执行的迁移入口,避免直接把旧 SQLite 密文或不完整外键写进新业务表。
## 1. 只读导出
在旧系统停止配置写入后,对数据库快照执行:
```bash
python3 scripts/export_legacy_data.py /secure-snapshot/gateway.db > /secure-transfer/gateway.jsonl
```
默认不导出密码、API Key、TOTP、请求头等敏感列。需要迁移 Provider/工具等凭据时,只能在隔离迁移环境使用 `--include-encrypted`;旧密文不能作为新密文直接写入,必须由专用转换步骤用旧密钥解密,再用新的用途标签和活动 KEK 加密。
## 2. 校验和暂存
先执行不连接数据库的校验:
```bash
gateway-legacy-import --input /secure-transfer/gateway.jsonl --dry-run
```
再写入 PostgreSQL 不可变暂存区:
```bash
export DATABASE_URL='postgres://...'
gateway-legacy-import --input /secure-transfer/gateway.jsonl
```
每条记录会验证 SHA-256,使用固定 namespace 生成 UUIDv5,并同时写入 `legacy_import_records``legacy_id_mappings`。相同快照重复执行会复用 batch;同一旧 ID 的内容发生变化时失败关闭,不会静默覆盖先前暂存数据。
## 3. 领域转换与验收
暂存成功不等于已写入生产业务表。必须按外键拓扑执行领域转换:部门 → 身份 → Provider/API Key → Prompt/知识/工具 → 应用版本 → 审计历史。每批转换后核对:
- 源记录数、目标记录数、明确跳过数和失败数之和相等;
- 所有旧外键都能从 `legacy_id_mappings` 找到映射;
- Provider、工具、通知、TOTP 等密文可使用新 keyring 解密,且数据库中不存在旧明文;
- 审计只迁移有界预览与结构化元数据,不迁移无限长原始正文;
- 应用发布版本引用的 Prompt、知识库和工具均存在且启用。
当前仓库没有用户的实际 SQLite 数据文件,因此已完成的是可验证暂存管道;生产领域转换必须在拿到脱敏快照及旧密钥托管授权后执行,不能凭空假设旧密文格式。
+115
View File
@@ -0,0 +1,115 @@
# 重构进度
更新时间:2026-08-11
## 已完成:M0 工程基线
- Go 1.26 模块、配置校验、结构化日志和优雅退出。
- PostgreSQL `pgx` 连接池、critical/cache 双 Redis 客户端。
- 独立且带校验和、事务和 advisory lock 的数据库迁移器。
- OpenAI 兼容入口、bootstrap key 鉴权、请求体限制、连接池和 SSE 透传。
- `healthz`、依赖感知 `readyz`、基础 Prometheus 指标。
- PostgreSQL outbox、API Key、Provider 与分区审计表的首版 schema。
- 不包含 MinIO/S3、ClickHouse 的 Docker Compose 基线。
- Art Design Pro v3.0.2 管理端与门户端,已执行官方精简流程并通过生产构建。
- OpenAPI 运行时契约和共享 TypeScript 系统客户端起点。
## 已完成:M1 身份、Provider 与配置面
已完成:
- 管理员、门户用户 schema 和独立 bootstrap 命令。
- 兼容 Python 版本 60 万轮/旧 12 万轮 PBKDF2-SHA256,并在成功登录时升级旧哈希。
- critical Redis 可撤销不透明会话、数据库 active/role 复核、失败次数原子更新和账号锁定。
- `/api/v1/admin/*``/api/v1/portal/*` 登录、身份、菜单、退出契约。
- 管理端与门户端 TOTP 配置、二次登录、停用、备用码重置;含临时挑战令牌、时间步原子防重放和备用码一次性消费。
- 两套 Art 登录页已接入动态验证码/备用码,并通过生产构建。
- 数据库 API Key 创建、列表、scope、过期、Redis 共享缓存和即时撤销;明文只显示一次,创建/撤销与 outbox 同事务。
- Art 管理端 API Key 页面、动态菜单入口及生产构建。
- Provider AES-256-GCM 凭据、SSRF 地址校验、revision 和事务 outbox。
- Provider 数据库运行时原子快照、默认/显式路由、能力约束、共享连接池、周期刷新和上一有效版本回退。
- Art 管理端供应商页面及生产后端权限模式。
- Provider 连接测试、模型目录同步、历史模型停用策略和 `provider.models_synced` 事务 outbox。
- Art 管理端连接测试、同步结果和模型目录查看交互。
- 版本化 KEK keyring、历史密钥解密、Provider 凭据事务轮换和管理端轮换入口。
- PostgreSQL 权威 Provider 配置、critical Redis 多实例变更通知、进程内不可变快照与周期兜底刷新。
- 管理员/门户账号管理、内置角色、直接权限字符串、权限感知动态菜单和即时数据库复核。
- bootstrap API Key 显式关闭开关及兼容入口使用量指标。
- 层级部门 CRUD、循环检测、在用停用保护、门户账号部门绑定和部门 outbox。
- OIDC 配置面、加密 Client Secret、Authorization Code + PKCE、RS256/JWKS 校验、自动开户和一次性交换码。
- SAML 配置面、SP metadata、SP-initiated Redirect/POST 流程、SHA-256+ XML 签名断言校验、RelayState/InResponseTo 绑定、断言防重放、自动开户和部门绑定。
- 旧 Python 网关 38 个持久化实体字段字典、实体去向清单、确定性 UUIDv5 生成器和幂等旧 ID 映射表。
- SAML 与 OIDC 均已通过 PostgreSQL + Redis + 本地模拟 IdP 的端到端登录、自动开户、一次性交换和重放拦截测试。
## 已完成:M2 流量治理与路由
已完成:
- API Key 级每分钟请求上限和 UTC 自然月请求配额,`0` 明确表示不限制。
- critical Redis Lua 原子准入;限流返回 `429`/`Retry-After`/`X-RateLimit-*`,配置限制时 Redis 故障失败关闭。
- API Key 限额的 PostgreSQL 权威配置、鉴权缓存传递、Art 管理表单与 OpenAPI 契约。
- PostgreSQL + Redis + 模拟上游端到端测试:RPM=2 精确放行 2 次,月配额=3 精确放行 3 次。
- API Key UTC 自然月 Token 配额:请求前按输入体积与最大输出预算原子预留,响应结束按上游 usage 原子校准。
- OpenAI/Responses/Anthropic 的 JSON 与 SSE usage 增量提取;不缓冲完整流式响应,上游未返回 usage 时按预留预算保守计量。
- Art 管理端展示当月 Token 用量并可即时调整 RPM、月请求和月 Token 配额;更新同时清除共享鉴权缓存并写入 outbox。
- Provider 级响应头超时、有限指数退避和熔断;仅 GET/HEAD 或携带 `Idempotency-Key` 的可重放请求允许重试,所有阈值均可通过环境变量配置。
- PostgreSQL 模型别名/路由规则、事务 outbox、管理 CRUD、Redis 多实例变更通知和进程内不可变路由快照。
- 按 endpoint、API Key、tenant 条件匹配;最高优先级分组内使用请求 ID 做确定性加权选择,显式 Provider 作为候选约束。
- 已登记的模型别名在条件不匹配、规则停用或显式 Provider 不属于候选集时失败关闭,避免把别名透传到默认上游绕过策略。
- 代理在命中规则时改写上游模型并返回 `X-Gateway-Model`;未配置规则时不读取请求体,保持原代理热路径性能。
- Art 管理端模型路由页面,可维护别名、上游模型、Provider、权重、优先级、条件和启停状态。
## 已完成:M3 审计、usage、内容策略与成本
已完成:
- 网关调用的有界异步审计队列、批量 PostgreSQL COPY、失败保留/重试、队列满降级和优雅停机排空。
- 请求 ID、API Key、tenant、Provider、原始/目标模型、协议、HTTP 状态、延迟及输入/输出 Token 采集。
- PostgreSQL 按日 API Key/Provider/模型 usage 聚合,不在请求热路径同步写数据库。
- `audit:read`/`usage:read` 权限、筛选查询 API、Art 管理端审计与用量页面。
- 审计 accepted/dropped/written/flush failures Prometheus 指标。
- 独立 `gateway-outbox-worker`,使用 PostgreSQL `FOR UPDATE SKIP LOCKED` 租约支持多实例并行抢占。
- Redis Lua 原子事件 ID 去重与 Stream 写入,数据库确认丢失后重投不会产生重复 Stream 消息。
- 指数退避、最大尝试次数、死信状态、`outbox:read`/`outbox:manage` 权限、管理 API 与 Art 人工重试页面。
- `event_consumptions` 与业务 handler 共用 PostgreSQL 事务的消费者幂等入口,handler 回滚时消费标记同步回滚。
- 独立 `gateway-maintenance` 周期任务,按 UTC 月预创建审计分区,事务迁移 default 分区数据,并按配置直接丢弃完整过期分区、精确清理边界月和长期 usage。
- 审计维护使用 PostgreSQL advisory lock 与表级事务锁,多副本启动时仍只有一个实例执行;重复运行保持幂等。
- PostgreSQL 内容策略 CRUD、事务 outbox、`content_policy:read/manage` 权限与多实例周期刷新;运行时使用不可变 RE2 编译快照。
- 内容策略支持 `audit``block``redact`,按 endpoint、模型和 API Key 限定范围;只遍历提示词文本字段,不改写工具 schema、图片/Base64 等非文本载荷。
- 默认敏感信息脱敏策略、阻断响应、`X-Gateway-Content-Redacted` 响应头,以及不包含原始命中值的审计标签。
- Art 管理端内容策略页面,可维护优先级、范围、规则、替换文本和启停状态。
- 带生效/失效时间的模型价格版本,支持 Provider + 精确模型或末尾 `*` 前缀,精确规则优先并使用请求时刻选择版本。
- 输入/输出价格均以“每百万 Token 的微货币单位”精确存储;usage 完成时核算请求成本,审计与按日聚合均保存 `cost_microunits`
- `pricing:read/manage` 权限、模型价格管理 API/Art 页面,审计与用量页面展示估算成本。
## 已完成:M4 Prompt、知识、工具、应用编排和通知
- Prompt 分类/模板和只增不改的版本,显式变量声明、必填/默认值校验、历史版本激活、管理端和 API Key 渲染接口。
- PostgreSQL 知识库保存不超过 2 MiB 的提取文本,不依赖 MinIO/S3;段落感知重叠分块,FTS 与中文二元词片混合召回,并通过 `Retriever` 接口隔离未来向量实现。
- 声明式 HTTP 工具、基础 JSON Schema 入参验证、KEK 加密请求头、注册及拨号时 SSRF 校验、禁止重定向、1 MiB 响应上限和工具调用记录。
- AI 应用草稿、引用完整性校验、不可变发布版本和运行记录;应用入口组合 Prompt/RAG/OpenAI 工具调用循环,内部每个模型请求仍走原网关全治理链路。
- 通知通道与可靠投递记录,独立 Worker 消费 outbox,精确/前缀事件订阅、HMAC-SHA256 签名、失败留痕与人工重试。
- 内容策略命中由异步审计批处理在同一事务写入 `content_policy.matched` outbox,不在模型请求热路径同步投递 Webhook,也不包含原始敏感命中值。
- Art Design Pro 管理端新增 Prompt、知识库、工具、应用和通知五个页面,权限与动态菜单均由服务端控制。
- 独立前端多阶段镜像将 Art 管理端生产构建交付给 Nginx,并同源代理管理 API、模型 API、SSE 与健康检查;Compose 可直接启动完整管理面。
## 已完成:M5 契约、迁移与切换工程
- 通过 Python AST 自动盘点旧源码,确认实际为 201 个路由装饰器,而不是早期估算的 141 个;与 OpenAPI 生成可重复的覆盖/替代/退役/缺口矩阵。
- 可选影子中间件只复制确定性采样的非流式 JSON POST,使用独立影子 Key,不转发生产凭据;异步比较状态码和 JSON 结构签名并输出 Prometheus 指标。
- 独立 `gateway-loadtest` 支持并发、时长、超时、错误率和 p95 阈值,输出机器可读 JSON 并以退出码阻断不达标发布。
- 原子 Nginx upstream 切换脚本在目标探活、`nginx -t` 和 reload 任一步失败时恢复备份;提供明确的回退触发条件与演练手册。
- 旧 SQLite 只读 JSONL 导出、逐记录 SHA-256、确定性 UUIDv5 和 PostgreSQL 不可变暂存管道已完成;默认排除敏感列,相同快照幂等,旧记录变化失败关闭。
- 201 条旧路由已全部形成明确决策:85 条同契约覆盖、112 条由新契约替代、4 条基于安全或架构 ADR 退役、未决缺口 0;OpenAPI 0.10.0 覆盖全部 146 条 Go 字面量路由。
## 已完成:M6 门户工作台与扩展治理
- 门户资产目录、Prompt 查看/搜索/收藏、个人审计/Token/成本统计、接入说明和模型访问申请;所有列表按登录用户与部门范围服务端过滤。
- 管理端模型申请审批、事实核验 Provider 设置、作用域策略与核验事件页面;事实核验复用 Provider 加密凭据,不重复保存 API Key/Base URL。
- 已发布应用的门户单轮入口和服务端托管会话;运行凭证使用独立 AEAD purpose 加密,明文不返回浏览器。
- 会话串行租约避免并发回答交错,用户/助手消息以不可变顺序和 SHA-256 哈希链保存,重放前验证完整性,最多 200 条消息。
- 应用目录/资产依赖分析、历史版本回滚、知识文档重新分块、Prompt 详情、运行时快照刷新、系统信息和 24 小时监控汇总。
- Art Design Pro 管理端新增模型治理页面;门户端新增资产目录、Prompt 广场、个人用量与模型权限四个生产页面。
- Compose 同时交付 API、管理端 `8081` 和门户端 `8082`;同一前端 Dockerfile 通过受限 build arg 构建两套独立 SPA。
工程实现与本地生产等价验证已关闭。正式上线仍是外部发布门禁:拿到实际旧数据库脱敏快照和旧密钥迁移授权后执行领域转换,并在真实新旧双系统与生产等价流量下完成影子观察、容量验收及切换/回退演练;这些动作不会在缺少生产数据和授权时伪造为已执行。
+213
View File
@@ -0,0 +1,213 @@
# 旧 Python → Go 路由契约矩阵
> 由 `scripts/compare_route_contracts.py` 通过 AST 与 OpenAPI 自动生成;`replaced` 表示能力有新契约承接,仍需兼容层或客户端迁移,不能视为原路由原样可用。
- 旧源码路由装饰器:201
- 同方法同规范路径:85
- 新契约替代:112
- 明确退役:4
- 尚无契约:0
| 状态 | 方法 | 旧路径 | Go 契约/预期目标 | 来源 |
|---|---|---|---|---|
| 新契约替代 | `GET` | `/admin/api/admins` | `/api/v1/admin/identities/admins` | `admin.py:290` |
| 新契约替代 | `POST` | `/admin/api/admins` | `/api/v1/admin/identities/admins` | `admin.py:306` |
| 新契约替代 | `POST` | `/admin/api/admins/change_password` | `/api/v1/admin/password` | `admin.py:361` |
| 新契约替代 | `DELETE` | `/admin/api/admins/{admin_id}` | `/api/v1/admin/identities/*/{identity_id} active=false` | `admin.py:381` |
| 新契约替代 | `PATCH` | `/admin/api/admins/{admin_id}` | `/api/v1/admin/identities/admins/{identity_id} (PUT with full revision payload)` | `admin.py:329` |
| 已覆盖 | `GET` | `/admin/api/applications` | `/api/v1/admin/applications` | `applications.py:158` |
| 已覆盖 | `POST` | `/admin/api/applications` | `/api/v1/admin/applications` | `applications.py:287` |
| 已覆盖 | `GET` | `/admin/api/applications/asset-dependencies` | `/api/v1/admin/applications/asset-dependencies` | `applications.py:172` |
| 已覆盖 | `GET` | `/admin/api/applications/catalog` | `/api/v1/admin/applications/catalog` | `applications.py:165` |
| 已覆盖 | `DELETE` | `/admin/api/applications/{app_id}` | `/api/v1/admin/applications/{application_id}` | `applications.py:370` |
| 新契约替代 | `PATCH` | `/admin/api/applications/{app_id}` | `/api/v1/admin/applications/{application_id} (PUT with full revision payload)` | `applications.py:312` |
| 新契约替代 | `GET` | `/admin/api/applications/{app_id}/composition` | `/api/v1/admin/applications/{application_id}` | `applications.py:190` |
| 新契约替代 | `PUT` | `/admin/api/applications/{app_id}/composition` | `/api/v1/admin/applications/{application_id}` | `applications.py:202` |
| 新契约替代 | `POST` | `/admin/api/applications/{app_id}/keys` | `/api/v1/admin/api-keys (scoped credentials)` | `applications.py:384` |
| 已覆盖 | `POST` | `/admin/api/applications/{app_id}/publish` | `/api/v1/admin/applications/{application_id}/publish` | `applications.py:214` |
| 已覆盖 | `POST` | `/admin/api/applications/{app_id}/rollback/{version}` | `/api/v1/admin/applications/{application_id}/rollback/{version}` | `applications.py:233` |
| 已覆盖 | `GET` | `/admin/api/applications/{app_id}/runs` | `/api/v1/admin/applications/{application_id}/runs` | `applications.py:254` |
| 新契约替代 | `POST` | `/admin/api/applications/{app_id}/test` | `/v1/applications/{application_code}/chat/completions` | `applications.py:272` |
| 新契约替代 | `GET` | `/admin/api/audit_settings` | `/api/v1/admin/content-policies and /api/v1/admin/notification-channels` | `admin.py:2297` |
| 新契约替代 | `PUT` | `/admin/api/audit_settings` | `/api/v1/admin/content-policies and /api/v1/admin/notification-channels` | `admin.py:2340` |
| 新契约替代 | `POST` | `/admin/api/audit_settings/test_image_model` | `/api/v1/admin/content-policies and /api/v1/admin/notification-channels` | `admin.py:2379` |
| 新契约替代 | `GET` | `/admin/api/content_audit_scopes` | `/api/v1/admin/content-policies` | `admin.py:2413` |
| 新契约替代 | `POST` | `/admin/api/content_audit_scopes` | `/api/v1/admin/content-policies` | `admin.py:2436` |
| 新契约替代 | `DELETE` | `/admin/api/content_audit_scopes/{scope_id}` | `/api/v1/admin/content-policies` | `admin.py:2470` |
| 新契约替代 | `GET` | `/admin/api/cost_report` | `/api/v1/admin/usage/daily and /api/v1/admin/model-prices` | `admin.py:3936` |
| 新契约替代 | `GET` | `/admin/api/cost_report/export` | `/api/v1/admin/usage/daily and /api/v1/admin/model-prices` | `admin.py:3967` |
| 已覆盖 | `GET` | `/admin/api/departments` | `/api/v1/admin/departments` | `admin.py:550` |
| 已覆盖 | `POST` | `/admin/api/departments` | `/api/v1/admin/departments` | `admin.py:565` |
| 新契约替代 | `DELETE` | `/admin/api/departments/{dept_id}` | `/api/v1/admin/departments/{department_id} active=false` | `admin.py:615` |
| 新契约替代 | `PATCH` | `/admin/api/departments/{dept_id}` | `/api/v1/admin/departments/{department_id} (PUT with full revision payload)` | `admin.py:584` |
| 新契约替代 | `GET` | `/admin/api/dept_budgets` | `/api/v1/admin/usage/daily and /api/v1/admin/model-prices` | `admin.py:3781` |
| 新契约替代 | `POST` | `/admin/api/dept_budgets` | `/api/v1/admin/usage/daily and /api/v1/admin/model-prices` | `admin.py:3807` |
| 新契约替代 | `DELETE` | `/admin/api/dept_budgets/{budget_id}` | `/api/v1/admin/usage/daily and /api/v1/admin/model-prices` | `admin.py:3839` |
| 新契约替代 | `GET` | `/admin/api/dept_stats` | `/api/v1/admin/usage/daily and /api/v1/admin/model-prices` | `admin.py:2934` |
| 已覆盖 | `GET` | `/admin/api/fact-check/events` | `/api/v1/admin/fact-check/events` | `hallucination.py:390` |
| 已覆盖 | `GET` | `/admin/api/fact-check/events/{event_id}` | `/api/v1/admin/fact-check/events/{event_id}` | `hallucination.py:405` |
| 已覆盖 | `GET` | `/admin/api/fact-check/policies` | `/api/v1/admin/fact-check/policies` | `hallucination.py:348` |
| 已覆盖 | `POST` | `/admin/api/fact-check/policies` | `/api/v1/admin/fact-check/policies` | `hallucination.py:355` |
| 已覆盖 | `DELETE` | `/admin/api/fact-check/policies/{policy_id}` | `/api/v1/admin/fact-check/policies/{policy_id}` | `hallucination.py:378` |
| 已覆盖 | `GET` | `/admin/api/fact-check/settings` | `/api/v1/admin/fact-check/settings` | `hallucination.py:312` |
| 已覆盖 | `PUT` | `/admin/api/fact-check/settings` | `/api/v1/admin/fact-check/settings` | `hallucination.py:323` |
| 新契约替代 | `GET` | `/admin/api/feishu_auth` | `/api/v1/admin/identity-providers (generic OIDC/SAML)` | `admin.py:3314` |
| 新契约替代 | `POST` | `/admin/api/feishu_auth` | `/api/v1/admin/identity-providers (generic OIDC/SAML)` | `admin.py:3335` |
| 新契约替代 | `POST` | `/admin/api/feishu_auth/test` | `/api/v1/admin/identity-providers (generic OIDC/SAML)` | `admin.py:3364` |
| 新契约替代 | `GET` | `/admin/api/kb` | `/api/v1/admin/knowledge-bases` | `knowledge.py:395` |
| 新契约替代 | `POST` | `/admin/api/kb` | `/api/v1/admin/knowledge-bases` | `knowledge.py:410` |
| 新契约替代 | `DELETE` | `/admin/api/kb/{kb_id}` | `/api/v1/admin/knowledge-bases/{knowledge_base_id}` | `knowledge.py:475` |
| 新契约替代 | `PATCH` | `/admin/api/kb/{kb_id}` | `/api/v1/admin/knowledge-bases/{knowledge_base_id} (PUT with full revision payload)` | `knowledge.py:439` |
| 新契约替代 | `GET` | `/admin/api/kb/{kb_id}/documents` | `/api/v1/admin/knowledge-bases/{knowledge_base_id}/documents` | `knowledge.py:494` |
| 新契约替代 | `POST` | `/admin/api/kb/{kb_id}/documents/paste` | `/api/v1/admin/knowledge-bases/{knowledge_base_id}/documents` | `knowledge.py:550` |
| 新契约替代 | `POST` | `/admin/api/kb/{kb_id}/documents/upload` | `/api/v1/admin/knowledge-bases/{knowledge_base_id}/documents` | `knowledge.py:508` |
| 新契约替代 | `DELETE` | `/admin/api/kb/{kb_id}/documents/{doc_id}` | `/api/v1/admin/knowledge-bases/{knowledge_base_id}/documents/{document_id}` | `knowledge.py:590` |
| 新契约替代 | `POST` | `/admin/api/kb/{kb_id}/documents/{doc_id}/reprocess` | `/api/v1/admin/knowledge-bases/{knowledge_base_id}/documents/{document_id}/reprocess` | `knowledge.py:574` |
| 新契约替代 | `POST` | `/admin/api/kb/{kb_id}/test_search` | `/api/v1/admin/knowledge-bases/{knowledge_base_id}/search` | `knowledge.py:613` |
| 新契约替代 | `GET` | `/admin/api/keys` | `/api/v1/admin/api-keys` | `admin.py:912` |
| 新契约替代 | `POST` | `/admin/api/keys` | `/api/v1/admin/api-keys` | `admin.py:943` |
| 新契约替代 | `DELETE` | `/admin/api/keys/{key_id}` | `/api/v1/admin/api-keys/{api_key_id}` | `admin.py:1017` |
| 新契约替代 | `PATCH` | `/admin/api/keys/{key_id}` | `/api/v1/admin/api-keys/{api_key_id}/limits` | `admin.py:977` |
| 新契约替代 | `GET` | `/admin/api/log_search` | `/api/v1/admin/audit-events` | `admin.py:2817` |
| 新契约替代 | `GET` | `/admin/api/log_views` | `/api/v1/admin/audit-events` | `admin.py:2879` |
| 已覆盖 | `POST` | `/admin/api/login` | `/api/v1/admin/login` | `admin.py:151` |
| 已覆盖 | `POST` | `/admin/api/login/totp` | `/api/v1/admin/login/totp` | `admin.py:193` |
| 新契约替代 | `DELETE` | `/admin/api/logs` | `/api/v1/admin/audit-events (bounded immutable audit query)` | `admin.py:2633` |
| 新契约替代 | `GET` | `/admin/api/logs` | `/api/v1/admin/audit-events` | `admin.py:2540` |
| 新契约替代 | `GET` | `/admin/api/logs/search` | `/api/v1/admin/audit-events (bounded immutable audit query)` | `admin.py:2689` |
| 新契约替代 | `GET` | `/admin/api/logs/{log_id}/view_history` | `/api/v1/admin/audit-events (bounded immutable audit query)` | `admin.py:2862` |
| 明确退役 | `POST` | `/admin/api/logs/{log_id}/view_raw` | `retired: unbounded raw-body access is excluded by storage/compliance ADR` | `admin.py:2653` |
| 新契约替代 | `GET` | `/admin/api/malicious_domains` | `/api/v1/admin/content-policies` | `admin.py:2501` |
| 新契约替代 | `POST` | `/admin/api/malicious_domains` | `/api/v1/admin/content-policies` | `admin.py:2512` |
| 新契约替代 | `DELETE` | `/admin/api/malicious_domains/{domain}` | `/api/v1/admin/content-policies` | `admin.py:2528` |
| 已覆盖 | `GET` | `/admin/api/model-requests` | `/api/v1/admin/model-requests` | `admin.py:3506` |
| 已覆盖 | `POST` | `/admin/api/model-requests/{req_id}/approve` | `/api/v1/admin/model-requests/{request_id}/approve` | `admin.py:3522` |
| 已覆盖 | `POST` | `/admin/api/model-requests/{req_id}/reject` | `/api/v1/admin/model-requests/{request_id}/reject` | `admin.py:3569` |
| 新契约替代 | `GET` | `/admin/api/model_aliases` | `/api/v1/admin/model-routes` | `admin.py:2204` |
| 新契约替代 | `POST` | `/admin/api/model_aliases` | `/api/v1/admin/model-routes` | `admin.py:2224` |
| 新契约替代 | `DELETE` | `/admin/api/model_aliases/{alias_id}` | `/api/v1/admin/model-routes` | `admin.py:2262` |
| 新契约替代 | `PUT` | `/admin/api/model_aliases/{alias_id}` | `/api/v1/admin/model-routes` | `admin.py:2240` |
| 新契约替代 | `GET` | `/admin/api/model_pricing` | `/api/v1/admin/model-prices` | `admin.py:3615` |
| 新契约替代 | `POST` | `/admin/api/model_pricing` | `/api/v1/admin/model-prices` | `admin.py:3646` |
| 新契约替代 | `DELETE` | `/admin/api/model_pricing/{pricing_id}` | `/api/v1/admin/model-prices/{price_id}` | `admin.py:3677` |
| 新契约替代 | `GET` | `/admin/api/model_qps` | `/api/v1/admin/api-keys and /api/v1/admin/model-routes` | `admin.py:2114` |
| 新契约替代 | `POST` | `/admin/api/model_qps` | `/api/v1/admin/api-keys and /api/v1/admin/model-routes` | `admin.py:2125` |
| 新契约替代 | `DELETE` | `/admin/api/model_qps/{config_id}` | `/api/v1/admin/api-keys and /api/v1/admin/model-routes` | `admin.py:2147` |
| 已覆盖 | `GET` | `/admin/api/models` | `/api/v1/admin/models` | `admin.py:1093` |
| 已覆盖 | `GET` | `/admin/api/monitoring/overview` | `/api/v1/admin/monitoring/overview` | `admin.py:3153` |
| 新契约替代 | `GET` | `/admin/api/notify_config` | `/api/v1/admin/notification-channels` | `admin.py:3392` |
| 新契约替代 | `POST` | `/admin/api/notify_config` | `/api/v1/admin/notification-channels` | `admin.py:3415` |
| 新契约替代 | `POST` | `/admin/api/notify_config/test` | `/api/v1/admin/notification-channels` | `admin.py:3447` |
| 新契约替代 | `GET` | `/admin/api/pricing_config` | `/api/v1/admin/usage/daily and /api/v1/admin/model-prices` | `admin.py:3702` |
| 新契约替代 | `PUT` | `/admin/api/pricing_config` | `/api/v1/admin/usage/daily and /api/v1/admin/model-prices` | `admin.py:3717` |
| 已覆盖 | `GET` | `/admin/api/prompts` | `/api/v1/admin/prompts` | `prompts.py:227` |
| 已覆盖 | `POST` | `/admin/api/prompts` | `/api/v1/admin/prompts` | `prompts.py:246` |
| 新契约替代 | `GET` | `/admin/api/prompts/categories` | `/api/v1/admin/prompt-categories` | `prompts.py:135` |
| 新契约替代 | `POST` | `/admin/api/prompts/categories` | `/api/v1/admin/prompt-categories` | `prompts.py:150` |
| 新契约替代 | `DELETE` | `/admin/api/prompts/categories/{cat_id}` | `/api/v1/admin/prompt-categories` | `prompts.py:168` |
| 已覆盖 | `DELETE` | `/admin/api/prompts/{tpl_id}` | `/api/v1/admin/prompts/{prompt_id}` | `prompts.py:323` |
| 已覆盖 | `GET` | `/admin/api/prompts/{tpl_id}` | `/api/v1/admin/prompts/{prompt_id}` | `prompts.py:279` |
| 新契约替代 | `PATCH` | `/admin/api/prompts/{tpl_id}` | `/api/v1/admin/prompts/{prompt_id} (PUT with full revision payload)` | `prompts.py:292` |
| 新契约替代 | `POST` | `/admin/api/prompts/{tpl_id}/preview` | `/api/v1/admin/prompts/{prompt_id}/render` | `prompts.py:392` |
| 已覆盖 | `POST` | `/admin/api/prompts/{tpl_id}/versions` | `/api/v1/admin/prompts/{prompt_id}/versions` | `prompts.py:344` |
| 已覆盖 | `POST` | `/admin/api/prompts/{tpl_id}/versions/{version_id}/activate` | `/api/v1/admin/prompts/{prompt_id}/versions/{version}/activate` | `prompts.py:371` |
| 已覆盖 | `GET` | `/admin/api/providers` | `/api/v1/admin/providers` | `admin.py:1083` |
| 已覆盖 | `POST` | `/admin/api/providers` | `/api/v1/admin/providers` | `admin.py:1106` |
| 新契约替代 | `DELETE` | `/admin/api/providers/{provider_id}` | `/api/v1/admin/providers/{provider_id} enabled=false` | `admin.py:1152` |
| 新契约替代 | `PATCH` | `/admin/api/providers/{provider_id}` | `/api/v1/admin/providers/{provider_id} (PUT with full revision payload)` | `admin.py:1169` |
| 已覆盖 | `PUT` | `/admin/api/providers/{provider_id}` | `/api/v1/admin/providers/{provider_id}` | `admin.py:1128` |
| 新契约替代 | `GET` | `/admin/api/providers/{provider_id}/backends` | `/api/v1/admin/providers/{provider_id}/models and /api/v1/admin/model-routes` | `admin.py:1333` |
| 新契约替代 | `PUT` | `/admin/api/providers/{provider_id}/backends` | `/api/v1/admin/providers/{provider_id}/models and /api/v1/admin/model-routes` | `admin.py:1358` |
| 新契约替代 | `DELETE` | `/admin/api/providers/{provider_id}/backends/{backend_name}` | `/api/v1/admin/providers/{provider_id}/models and /api/v1/admin/model-routes` | `admin.py:1455` |
| 新契约替代 | `PATCH` | `/admin/api/providers/{provider_id}/backends/{backend_name}` | `/api/v1/admin/providers/{provider_id}/models and /api/v1/admin/model-routes` | `admin.py:1414` |
| 新契约替代 | `POST` | `/admin/api/providers/{provider_id}/fetch_models` | `/api/v1/admin/providers/{provider_id}/models/sync` | `admin.py:1255` |
| 已覆盖 | `POST` | `/admin/api/providers/{provider_id}/test` | `/api/v1/admin/providers/{provider_id}/test` | `admin.py:1231` |
| 新契约替代 | `GET` | `/admin/api/rate_limit_rules` | `/api/v1/admin/api-keys and /api/v1/admin/model-routes` | `admin.py:2033` |
| 新契约替代 | `POST` | `/admin/api/rate_limit_rules` | `/api/v1/admin/api-keys and /api/v1/admin/model-routes` | `admin.py:2044` |
| 新契约替代 | `DELETE` | `/admin/api/rate_limit_rules/{rule_id}` | `/api/v1/admin/api-keys and /api/v1/admin/model-routes` | `admin.py:2090` |
| 新契约替代 | `PATCH` | `/admin/api/rate_limit_rules/{rule_id}` | `/api/v1/admin/api-keys and /api/v1/admin/model-routes` | `admin.py:2064` |
| 已覆盖 | `POST` | `/admin/api/reload` | `/api/v1/admin/reload` | `admin.py:3292` |
| 新契约替代 | `GET` | `/admin/api/routing_groups` | `/api/v1/admin/model-routes` | `smart_router.py:309` |
| 新契约替代 | `POST` | `/admin/api/routing_groups` | `/api/v1/admin/model-routes` | `smart_router.py:326` |
| 新契约替代 | `DELETE` | `/admin/api/routing_groups/{group_id}` | `/api/v1/admin/model-routes` | `smart_router.py:372` |
| 新契约替代 | `PATCH` | `/admin/api/routing_groups/{group_id}` | `/api/v1/admin/model-routes` | `smart_router.py:346` |
| 新契约替代 | `POST` | `/admin/api/routing_groups/{group_id}/preview` | `/api/v1/admin/model-routes` | `smart_router.py:468` |
| 新契约替代 | `POST` | `/admin/api/routing_groups/{group_id}/targets` | `/api/v1/admin/model-routes` | `smart_router.py:390` |
| 新契约替代 | `DELETE` | `/admin/api/routing_groups/{group_id}/targets/{target_id}` | `/api/v1/admin/model-routes` | `smart_router.py:448` |
| 新契约替代 | `PATCH` | `/admin/api/routing_groups/{group_id}/targets/{target_id}` | `/api/v1/admin/model-routes` | `smart_router.py:422` |
| 新契约替代 | `GET` | `/admin/api/rule_dimensions` | `/api/v1/admin/content-policies` | `admin.py:1854` |
| 新契约替代 | `GET` | `/admin/api/rules` | `/api/v1/admin/content-policies` | `admin.py:1717` |
| 新契约替代 | `POST` | `/admin/api/rules` | `/api/v1/admin/content-policies` | `admin.py:1753` |
| 新契约替代 | `POST` | `/admin/api/rules/batch` | `/api/v1/admin/content-policies` | `admin.py:1775` |
| 新契约替代 | `DELETE` | `/admin/api/rules/{rule_id}` | `/api/v1/admin/content-policies` | `admin.py:1835` |
| 新契约替代 | `PATCH` | `/admin/api/rules/{rule_id}` | `/api/v1/admin/content-policies` | `admin.py:1808` |
| 新契约替代 | `GET` | `/admin/api/stats` | `/api/v1/admin/usage/daily and /api/v1/admin/model-prices` | `admin.py:3022` |
| 新契约替代 | `GET` | `/admin/api/system_info` | `/api/v1/admin/system-info` | `admin.py:2488` |
| 已覆盖 | `GET` | `/admin/api/tools` | `/api/v1/admin/tools` | `tools.py:218` |
| 已覆盖 | `POST` | `/admin/api/tools` | `/api/v1/admin/tools` | `tools.py:229` |
| 已覆盖 | `DELETE` | `/admin/api/tools/{tool_id}` | `/api/v1/admin/tools/{tool_id}` | `tools.py:305` |
| 新契约替代 | `PATCH` | `/admin/api/tools/{tool_id}` | `/api/v1/admin/tools/{tool_id} (PUT with full revision payload)` | `tools.py:260` |
| 明确退役 | `GET` | `/admin/api/tools/{tool_id}/headers` | `retired: decrypted secret headers are intentionally write-only` | `tools.py:320` |
| 新契约替代 | `POST` | `/admin/api/tools/{tool_id}/test_invoke` | `/api/v1/admin/tools/{tool_id}/test` | `tools.py:338` |
| 新契约替代 | `POST` | `/admin/api/totp/backup_codes/regenerate` | `/api/v1/admin/totp/backup-codes/regenerate` | `admin.py:521` |
| 已覆盖 | `POST` | `/admin/api/totp/confirm` | `/api/v1/admin/totp/confirm` | `admin.py:464` |
| 已覆盖 | `POST` | `/admin/api/totp/disable` | `/api/v1/admin/totp/disable` | `admin.py:487` |
| 已覆盖 | `POST` | `/admin/api/totp/setup` | `/api/v1/admin/totp/setup` | `admin.py:439` |
| 已覆盖 | `GET` | `/admin/api/totp/status` | `/api/v1/admin/totp/status` | `admin.py:422` |
| 新契约替代 | `GET` | `/admin/api/users` | `/api/v1/admin/identities/portal-users` | `admin.py:683` |
| 新契约替代 | `POST` | `/admin/api/users` | `/api/v1/admin/identities/portal-users` | `admin.py:721` |
| 新契约替代 | `DELETE` | `/admin/api/users/{user_id}` | `/api/v1/admin/identities/*/{identity_id} active=false` | `admin.py:826` |
| 新契约替代 | `PATCH` | `/admin/api/users/{user_id}` | `/api/v1/admin/identities/portal-users/{identity_id} (PUT with full revision payload)` | `admin.py:745` |
| 新契约替代 | `POST` | `/admin/api/users/{user_id}/approve` | `/api/v1/admin/identities/portal-users/{identity_id} active=true` | `admin.py:788` |
| 新契约替代 | `DELETE` | `/admin/api/users/{user_id}/feishu` | `/api/v1/admin/identity-providers (generic identity link lifecycle)` | `admin.py:806` |
| 新契约替代 | `POST` | `/admin/api/users/{user_id}/reset_password` | `/api/v1/admin/identities/portal-users/{identity_id} password field` | `admin.py:771` |
| 已覆盖 | `GET` | `/admin/api/whoami` | `/api/v1/admin/whoami` | `admin.py:256` |
| 明确退役 | `GET` | `/admin/ui` | `Art Design Pro SPA deployment` | `main.py:3838` |
| 已覆盖 | `GET` | `/healthz` | `/healthz` | `main.py:3756` |
| 已覆盖 | `GET` | `/metrics` | `/metrics` | `main.py:3793` |
| 已覆盖 | `GET` | `/portal/api/applications` | `/api/v1/portal/applications` | `applications.py:408` |
| 已覆盖 | `POST` | `/portal/api/apps/{app_code}/chat` | `/api/v1/portal/apps/{application_code}/chat` | `orchestration.py:227` |
| 已覆盖 | `POST` | `/portal/api/apps/{app_code}/conversations` | `/api/v1/portal/apps/{application_code}/conversations` | `orchestration.py:245` |
| 已覆盖 | `GET` | `/portal/api/apps/{app_code}/conversations/{conversation_id}` | `/api/v1/portal/apps/{application_code}/conversations/{conversation_id}` | `orchestration.py:260` |
| 已覆盖 | `POST` | `/portal/api/apps/{app_code}/conversations/{conversation_id}/messages` | `/api/v1/portal/apps/{application_code}/conversations/{conversation_id}/messages` | `orchestration.py:275` |
| 已覆盖 | `GET` | `/portal/api/catalog` | `/api/v1/portal/catalog` | `portal.py:938` |
| 已覆盖 | `GET` | `/portal/api/cost` | `/api/v1/portal/cost` | `portal.py:550` |
| 已覆盖 | `GET` | `/portal/api/docs-info` | `/api/v1/portal/docs-info` | `portal.py:690` |
| 新契约替代 | `GET` | `/portal/api/feishu/callback` | `/api/v1/portal/sso (generic OIDC/SAML)` | `portal.py:214` |
| 新契约替代 | `GET` | `/portal/api/feishu/enabled` | `/api/v1/portal/sso (generic OIDC/SAML)` | `portal.py:189` |
| 新契约替代 | `GET` | `/portal/api/feishu/login` | `/api/v1/portal/sso (generic OIDC/SAML)` | `portal.py:195` |
| 已覆盖 | `GET` | `/portal/api/knowledge` | `/api/v1/portal/knowledge` | `portal.py:830` |
| 已覆盖 | `POST` | `/portal/api/login` | `/api/v1/portal/login` | `portal.py:76` |
| 已覆盖 | `POST` | `/portal/api/login/totp` | `/api/v1/portal/login/totp` | `portal.py:123` |
| 已覆盖 | `POST` | `/portal/api/logout` | `/api/v1/portal/logout` | `portal.py:181` |
| 已覆盖 | `GET` | `/portal/api/logs` | `/api/v1/portal/logs` | `portal.py:597` |
| 已覆盖 | `GET` | `/portal/api/logs/{log_id}` | `/api/v1/portal/logs/{audit_event_id}` | `portal.py:657` |
| 已覆盖 | `GET` | `/portal/api/me` | `/api/v1/portal/me` | `portal.py:432` |
| 已覆盖 | `GET` | `/portal/api/model-requests` | `/api/v1/portal/model-requests` | `portal.py:803` |
| 已覆盖 | `POST` | `/portal/api/model-requests` | `/api/v1/portal/model-requests` | `portal.py:754` |
| 已覆盖 | `GET` | `/portal/api/model-requests/available` | `/api/v1/portal/model-requests/available` | `portal.py:716` |
| 已覆盖 | `POST` | `/portal/api/password` | `/api/v1/portal/password` | `portal.py:273` |
| 已覆盖 | `GET` | `/portal/api/prompts` | `/api/v1/portal/prompts` | `portal.py:855` |
| 已覆盖 | `GET` | `/portal/api/prompts/{tpl_id}` | `/api/v1/portal/prompts/{prompt_id}` | `portal.py:887` |
| 已覆盖 | `DELETE` | `/portal/api/prompts/{tpl_id}/favorite` | `/api/v1/portal/prompts/{prompt_id}/favorite` | `portal.py:926` |
| 已覆盖 | `POST` | `/portal/api/prompts/{tpl_id}/favorite` | `/api/v1/portal/prompts/{prompt_id}/favorite` | `portal.py:910` |
| 已覆盖 | `GET` | `/portal/api/stats` | `/api/v1/portal/stats` | `portal.py:457` |
| 已覆盖 | `GET` | `/portal/api/tools` | `/api/v1/portal/tools` | `portal.py:838` |
| 新契约替代 | `POST` | `/portal/api/totp/backup_codes/regenerate` | `/api/v1/portal/totp/backup-codes/regenerate` | `portal.py:409` |
| 已覆盖 | `POST` | `/portal/api/totp/confirm` | `/api/v1/portal/totp/confirm` | `portal.py:354` |
| 已覆盖 | `POST` | `/portal/api/totp/disable` | `/api/v1/portal/totp/disable` | `portal.py:377` |
| 已覆盖 | `POST` | `/portal/api/totp/setup` | `/api/v1/portal/totp/setup` | `portal.py:328` |
| 已覆盖 | `GET` | `/portal/api/totp/status` | `/api/v1/portal/totp/status` | `portal.py:310` |
| 明确退役 | `GET` | `/portal/ui` | `Art Design Pro SPA deployment` | `main.py:3846` |
| 已覆盖 | `GET` | `/readyz` | `/readyz` | `main.py:3762` |
| 新契约替代 | `POST` | `/v1/apps/{app_code}/chat/completions` | `/v1/applications/{application_code}/chat/completions` | `orchestration.py:213` |
| 已覆盖 | `POST` | `/v1/chat/completions` | `/v1/chat/completions` | `main.py:2502` |
| 已覆盖 | `POST` | `/v1/embeddings` | `/v1/embeddings` | `main.py:2541` |
| 已覆盖 | `POST` | `/v1/knowledge/search` | `/v1/knowledge/search` | `main.py:3543` |
| 已覆盖 | `POST` | `/v1/messages` | `/v1/messages` | `main.py:2515` |
| 已覆盖 | `GET` | `/v1/models` | `/v1/models` | `main.py:3512` |
| 已覆盖 | `GET` | `/v1/prompts` | `/v1/prompts` | `main.py:3657` |
| 已覆盖 | `POST` | `/v1/prompts/{prompt_name}/render` | `/v1/prompts/{prompt_name}/render` | `main.py:3680` |
| 已覆盖 | `POST` | `/v1/responses` | `/v1/responses` | `main.py:2530` |
| 已覆盖 | `GET` | `/v1/tools` | `/v1/tools` | `main.py:3601` |
| 已覆盖 | `POST` | `/v1/tools/{tool_name}/invoke` | `/v1/tools/{tool_code}/invoke` | `main.py:3610` |
@@ -0,0 +1,170 @@
# AI Gateway Go — 旗舰版(Ultra)需求规划与实现情况报告
- **报告日期**: 2026-08-12
- **工程**: AI Gateway 全量 Go 重构(替代原 Python/FastAPI 网关)
- **活跃工作树**: `/home/ben/ai-gateway-src/ai-gateway-go-deploy-0.10.0`
- **当前版本**: 0.10.0(Go 1.26,PostgreSQL 17 + 双 Redis,22 个迁移)
---
## 一、项目现状概览
| 项 | 状态 |
|---|---|
| 部署形态 | Docker Compose(项目名 `deploy`),gateway-api `:8080` / admin-web `:8081` / portal-web `:8082` |
| 数据层 | PostgreSQL 17(权威配置 + 审计分区)+ critical/cache 双 Redis;22 个迁移已应用 |
| 迁移 | `000001``000022`(含旗舰版资源市场 `000022_resource_marketplace.sql`) |
| 验证 | `go build ./...``go vet`、全套单测、资源市场集成测试连真实库 **全部通过** |
| 前端 | Art Design Pro 管理端 + 门户端,已构建进镜像并运行 |
| 版本控制 | 无 git(目录无 `.git`),靠目录快照;建议尽快接入版本控制 |
---
## 二、已完成里程碑(M0M7)
### M0 工程基线
Go 1.26 模块、配置校验、结构化日志、优雅退出;pgx 连接池、双 Redis 客户端;带校验和/事务/advisory lock 的独立迁移器;OpenAI 兼容入口、bootstrap key、请求体限制、SSE 透传;healthz/readyz/Prometheus;outbox/API Key/Provider/审计分区首版 schema。
### M1 身份、Provider 与配置面
管理员/门户用户 + PBKDF2 旧密码兼容升级;可撤销会话/账号锁定;admin/portal 登录、身份、菜单、退出;TOTP 两步验证 + 备用码;API Key 全生命周期 + Redis 即时撤销;Provider AES-256-GCM 凭据 + SSRF 校验 + 事务 outbox + 原子快照/路由/能力约束;KEK 版本化轮换;层级部门 CRUD;OIDC(PKCE/RS256/JWKS)与 SAML 2.0(SP-initiated/断言防重放/自动开户)端到端通过。
### M2 流量治理与路由
API Key 级 RPM + UTC 月请求配额 + Token 配额(请求前预留、usage 回写校准),Redis Lua 原子准入,429/Retry-After/X-RateLimit-*;Provider 超时/退避/熔断(幂等安全重试);模型别名条件路由(确定性加权 + 失败关闭 + X-Gateway-Model);Art 管理端限流/路由页面。
### M3 审计、usage、内容策略与成本
有界异步审计批量 COPY + 失败保留 + 优雅停机;按日 usage 聚合;audit/usage 权限隔离查询;outbox-worker(SKIP LOCKED 多实例 + Redis Stream 原子去重 + 死信重试);审计月分区维护(advisory lock 防多实例重复);内容策略(RE2 不可变快照,audit/block/redact,默认脱敏);时间版本模型定价 + 按日成本核算;Art 审计/用量/内容策略/定价页面。
### M4 Prompt、知识、工具、应用编排和通知
Prompt 分类/模板/不可变版本/必填校验;知识库(2 MiB 有界正文、段落感知重叠分块、FTS+中文二元词片检索、Retriever 接口);声明式 HTTP 工具(JSON Schema 校验、KEK 加密头、双层 SSRF、禁止重定向、1 MiB 响应限制);AI 应用草稿 + 不可发布版本 → `/v1/applications/{code}/chat/completions`(仍过鉴权/配额/内容策略/路由/成本/审计);通知 worker(HMAC-SHA256 Webhook、精确/通配事件、失败重试)。
### M5 契约、迁移与切换工程
旧 Python 网关 38 个持久化实体数据字典;确定性 UUIDv5 旧 ID 映射;201 条旧路由全覆盖/替代/退役决策,未决契约缺口为 0;OpenAPI 0.10.0 覆盖全部 Go 路由;legacy 导入工具 + cutover runbook + 路由契约矩阵 + 校验脚本。
### M6 门户工作台与扩展治理
门户资产目录、Prompt 查看/搜索/收藏、个人审计/Token/成本统计、接入说明、模型访问申请与管理员审批;事实核验(复用 Provider 凭据 + 作用域策略 + 事件契约);已发布应用门户单轮入口 + 服务端托管会话(独立 AEAD purpose 加密凭证、串行租约、SHA-256 哈希链、最多 200 条);Compose 同时交付 API/管理端 8081/门户端 8082。
### M7 旗舰版资源市场(本次新增)
三类可发布资产 **MCP 服务器 / Skills / 数字员工** + 共享分类与标签 + 市场安装(工作区绑定 + 权限):
- 数据层:6 张表(`marketplace_categories` / `mcp_servers` / `skills` / `digital_employees` / `digital_employee_runs` / `marketplace_installations`)
- 后端:管理端 CRUD + 在线编辑/发布(`admin_marketplace.go`);统一市场目录合并查询(`marketplace.go`);运行时渲染 skill、列出/调用 MCP 工具、运行数字员工(含知识检索 + 工具执行 + 访问控制,`runtime_marketplace.go`);MCP 客户端(SSE + JSON-RPC 工具发现/调用/缓存,`mcp_client.go`)
- 前端:管理端 4 页(市场总览 / MCP 服务器 / Skills / 数字员工)+ 门户资源市场页
- 测试:`TestMarketplaceLifecycle` / `TestWorkbenchPostgreSQLLifecycle` 连真实 PostgreSQL 通过
---
## 三、旗舰版(Ultra)功能矩阵对照
状态图例:**✅ 已完成** | **⚠️ 部分覆盖** | **❌ 未实现**
### 管理平台
| 功能模块 | 功能 | 社区 Free | 专业 Pro | 旗舰 Ultra | 当前状态 |
|---|---|---|---|---|---|
| 管理平台 | AI 助手:通过 AI 助手查看平台信息和权限控制 | — | ✓ | ✓ | ❌ |
| 管理平台 | 收藏:常用功能收藏 | ✓ | ✓ | ✓ | ❌ |
| 管理平台 | 概览:平台总览与基础看板 | — | ✓ | ✓ | ⚠️ 仅有 24h 监控汇总(`monitoring/overview`),非完整看板 |
| 管理平台 | 数据报表:token/工具/渠道/审批授权/安全事件汇总统计 | — | ✓ | ✓ | ❌(仅有 usage/stats 单维) |
| 资源市场 | 自定义 MCP/Skills/数字员工资源 | ✓ | ✓ | ✓ | ✅ M7 |
| 资源市场 | 在线编辑和发布 MCP/Skills/数字员工 | 不支持 | ✓ | ✓ | ✅ 管理端 CRUD + publish 路由 |
| 资源市场 | 资源分类管理和标签管理 | ✓ | ✓ | ✓ | ✅ `marketplace_categories` |
| 配置管理 | 平台配置敏感参数,skill/mcp 运行时动态注入环境变量 | — | ✓ | ✓ | ❌ |
| 智能体管理 | 智能体节点:运行监控/动态创建/裸机安装/节点池分配/公有私有池路由 | — | ✓ | ✓ | ❌ |
| 智能体管理 | LLMTrace:会话中大模型调用、工具调用执行性能跟踪 | — | ✓ | ✓ | ❌ |
| 智能体管理 | 智能体会话:会话列表,区分普通会话与数字员工会话 | — | ✓ | ✓ | ❌(有应用托管会话,非会话列表体系) |
| 模型管理 | 对接国内外主流大模型供应商 | ✓ | ✓ | ✓ | ✅ providers + 模型目录同步 |
| 模型管理 | 供应商中添加配置大模型 | ✓ | ✓ | ✓ | ✅ |
| 模型管理 | 对接本地模型(Ollama/vLLM) | 不支持 | ✓ | ✓ | ✅ 走 OpenAI 兼容通用 provider |
| 模型管理 | 不同大模型 Token 配额、使用量统计 | 不支持 | ✓ | ✓ | ✅ quota + usage 聚合 |
| 模型管理 | 大模型使用权限分级管控(用户/角色) | ✓ | ✓ | ✓ | ✅ RBAC scope |
| 知识库 | 外部文档导入、自动切分、向量化、语义匹配召回 | — | ✓ | ✓ | ⚠️ 导入/切分/检索 ✅;向量化+语义匹配 ❌(现为 FTS+中文分词) |
| 记忆管理 | 记忆集合:个人/部门/全局多层记忆,提炼与语义匹配召回 | — | ✓ | ✓ | ❌ |
| 记忆管理 | 根据调用自动裁剪衰减片段 | — | ✓ | ✓ | ❌ |
| 记忆管理 | 记忆授权:提炼内容/沉淀经验授权给其他用户 | — | ✓ | ✓ | ❌ |
| 权限管理 | 完整 RBAC 角色权限管理 | 不支持 | ✓ | ✓ | ✅ |
| 权限管理 | 用户/部门/角色管理 | ✓ | ✓ | ✓ | ✅ |
| 权限管理 | 系统历史操作审计日志 | — | ✓ | ✓ | ✅ `admin/audit-events` |
| 权限管理 | 资源(mcp/skills/数字员工)权限管控、资源授权 | ✓ | ✓ | ✓ | ✅ marketplace 访问控制 |
| 权限管理 | 资源/大模型/渠道使用申请审批 | — | ✓ | ✓ | ⚠️ 仅模型申请审批(model-requests);资源安装/渠道无审批流 |
| API 集成 | API Key 调用大模型/mcp/skills 组合或数字员工 | — | ✓ | ✓ | ✅ API Key + runtime marketplace |
| 渠道管理 | Web 聊天界面 | ✓ | ✓ | ✓ | ✅ portal |
| 渠道管理 | 企业微信/个人微信/钉钉/飞书渠道 | ✓ | ✓ | ✓ | ❌ |
| 渠道管理 | 渠道权限管控、使用权限授权(用户/角色) | ✓ | ✓ | ✓ | ❌ |
| 渠道管理 | 多渠道治理、审计、使用量统计 | ✓ | ✓ | ✓ | ❌ |
| 安装部署 | x86_64 安装包 | ✓ | ✓ | ✓ | ⚠️ Docker Compose 交付,非传统安装包 |
| 安装部署 | ARM64 安装包 | — | ✓ | ✓ | ❌ |
| 部署方式 | 单机 / 冷备 / 集群 | 单机 | 单机/冷备 | 单机/冷备/集群 | ⚠️ outbox 支持多实例 SKIP LOCKED(集群一部分);冷备/完整集群方案未做 |
| 租户管理 | 单租户使用 | ✓ | ✓ | ✓ | ✅ |
| 租户管理 | 平台管理员多租户管理 | — | — | ✓ | ❌ |
| 安全策略 | 运行时安全:网络/工具命令执行安全校验审批/工具调用频率限制 | — | ✓ | ✓ | ⚠️ 工具 SSRF/拨号防护 ✅;命令执行审批、工具限流 ❌ |
| 安全策略 | 供应链安全:skill/mcp 资源安全扫描 | — | ✓ | ✓ | ❌ |
| 安全策略 | 数据安全:工具数据输入输出脱敏 + 大模型回答隐私敏感信息拦截替换 | — | ✓ | ✓ | ⚠️ 提示词输入脱敏 ✅;工具输出/回答拦截替换 ❌ |
| 站内消息 | 平台推送站内消息与动态 | — | ✓ | ✓ | ❌(通知 worker 仅 webhook) |
| 审批授权 | 资源/模型/渠道使用申请流程审批管理 | — | ✓ | ✓ | ⚠️ 仅模型申请 |
| 文件管理 | 平台文件资源与对象存储文件浏览管理 | — | ✓ | ✓ | ❌(MinIO/S3 不在基线) |
| 审计日志 | 系统全量历史操作审计日志查询 | — | ✓ | ✓ | ✅ |
| 企业报表 | 企业级运营数据报表统计分析 | — | ✓ | ✓ | ❌ |
| License | 平台 License 授权管理与有效期管控 | ✓ | ✓ | ✓ | ❌ |
### 工作台
| 功能 | 功能说明 | 当前状态 |
|---|---|---|
| 聊天 | 新建会话、授权大模型对话 | ✅ |
| 聊天 | 删除会话、会话重命名 | ✅ |
| 定时任务 | 创建定时任务(配置提示词/渠道/mcp/skills/数字员工/会话ID) | ❌ |
| 定时任务 | 启动/修改/立即执行/删除定时任务配置 | ❌ |
| 定时任务 | 查看定时任务执行历史 | ❌ |
| 个人渠道 | 配置个人微信/企业微信/钉钉/飞书 | ❌ |
| 个人渠道 | 个人微信/企业微信快速扫码对接 | ❌ |
| 个人渠道 | 绑定特定大模型执行对话 | ❌ |
| 我的资源 | 查看被授权资源(mcp/skills/数字员工)详细信息 | ✅ portal/marketplace |
| 我的资源 | 从被授权数字员工进入会话 | ⚠️ runtime 可跑,无会话列表入口 |
| 我的资源 | 通过权限申请从插件市场安装 MCP/Skills/数字员工 | ✅ marketplace install |
| 我的资源 | 查看被授权大模型使用量 | ✅ portal/stats |
| 我的资源 | 查看插件资源权限等级(可查看/仅使用/管理) | ⚠️ 有访问控制,三档等级未成体系 |
| 我的资源 | 申请大模型/token量/skill/mcp/数字员工/渠道权限 | ⚠️ 仅模型申请 |
| 配置管理 | 个人配置环境变量,供 skill/mcp 使用 | ❌ |
| 个人文件仓库 | 对话产生的报告/文件存入个人仓库 | ❌ |
| 安全策略 | 个人智能体安全策略(网络/工具命令校验审批/限流/脱敏/隐私拦截) | ❌ |
| 个人中心 | 账号信息、密码修改、登录记录查看 | ✅ |
| 消息通知 | 系统消息、审批待办、任务执行结果提醒 | ⚠️ webhook 投递 ✅;站内消息/待办/结果提醒 ❌ |
---
## 四、差距汇总
- **完全未实现(❌,约 20 项)**:AI 助手、收藏、数据报表/企业报表、配置管理(env 注入)、智能体管理三项(节点/LLMTrace/会话)、记忆管理三项、渠道管理全项、多租户、供应链安全扫描、站内消息、完整审批流、文件管理(对象存储)、License、定时任务全项、个人渠道、个人文件仓库、个人安全策略、ARM64。
- **部分覆盖需补齐(⚠️,约 10 项)**:平台概览看板、知识库向量化语义召回、资源/渠道审批、工具输出脱敏与大模型回答拦截替换、工具命令审批与工具限流、集群部署方案、站内消息/审批待办/任务结果、数字员工会话入口、资源权限等级三档、全类型权限申请。
---
## 五、后续里程碑规划(M8–M13)
| 里程碑 | 内容 | 依赖 |
|---|---|---|
| **M8 基础设施层** | 对象存储(MinIO)、向量化(pgvector)、定时任务调度器、站内消息 | — |
| **M9 智能体与可观测** | LLMTrace、智能体会话、智能体节点(监控/节点池/路由)、AI 助手 | M8 |
| **M10 记忆管理** | 多层记忆集合、语义召回、裁剪衰减、记忆授权 | M8(pgvector) |
| **M11 渠道与审批** | 渠道管理(企业微信/个人微信/钉钉/飞书)、个人渠道、完整审批流、资源权限等级 | M8 |
| **M12 数据安全与供应链** | 知识库向量化、输出脱敏/回答拦截、工具命令审批/限流、供应链扫描、个人安全策略 | M8(pgvector) |
| **M13 平台运营** | 数据报表/企业报表、完整看板、收藏、多租户、License、ARM64/集群部署 | M8 |
已建任务跟踪:`#4``#9`
---
## 六、验证情况(当前基线)
- `go build ./...` ✅、`go vet ./internal/workbench/`
- 全套单测(全部包)✅;资源市场单测(含 MCP 客户端)✅
- 集成测试 `TestMarketplaceLifecycle``TestWorkbenchPostgreSQLLifecycle` 连真实 PostgreSQL ✅
- 部署冒烟:healthz/readyz ✅、admin :8081 / portal :8082 302 ✅、22 迁移应用 ✅
## 七、部署与已知坑
- 活跃工作树:`/home/ben/ai-gateway-src/ai-gateway-go-deploy-0.10.0`;compose 项目名 `deploy`,workdir 在 `deploy/` 下。
- **重启恢复**:postgres/redis restart 策略为 `no`,重启后需进 `deploy/` 执行 `docker compose up -d`;若容器 `networks` 为空需 `--force-recreate`
- **旧 Python 网关**:`llm-gateway.service`(systemd)监听 8080 已 `systemctl disable`,不再开机抢占。
- **本机无 go 工具链**:编译/测试用 `docker run --rm -v $PWD:/src -w /src -e GOCACHE=/tmp/gocache golang:1.26.5-alpine sh -c 'go build ./...'`;集成测试加 `--network deploy_default` + `WORKBENCH_TEST_DATABASE_URL=postgres://gateway:gateway@postgres:5432/gateway?sslmode=disable`
- **上线门禁未过**:旧库脱敏快照迁移、影子观察、容量验收、切换/回退演练需真实生产数据与授权后方可执行。