# 软件设计说明书(SDD) ## 自建多模态 AI 网关系统(AI Gateway System) | 项目 | 内容 | |---|---| | 文档版本 | V1.0 | | 编写日期 | 2026-08-23 | | 对应需求文档 | 01-软件需求文档-SRS.md V1.0 | --- ## 1. 设计概述 ### 1.1 设计目标 在满足 SRS 中全部功能需求与非功能需求的前提下,采用**成熟开源组件 + 容器化编排**的方式快速搭建系统,降低自研成本,优先保证 Failover 可靠性与部署可维护性。 ### 1.2 设计原则 - **不重复造轮子**:网关、前端均采用成熟开源方案(One-API + LobeChat),仅做配置层工作 - **松耦合**:前端、网关、各 AI 渠道之间通过标准 OpenAI 兼容接口通信,任一组件可替换 - **配置驱动**:渠道增删、优先级调整通过后台界面完成,不需改代码重新部署 - **最小暴露面**:仅前端端口对公网开放,管理端口和数据库端口仅内网可访问 --- ## 2. 系统架构设计 ### 2.1 总体架构图 ``` ┌───────────────────────────────────────────────────────────────┐ │ 公司内网(用户侧) │ │ 浏览器 / 移动端浏览器 │ └──────────────────────────┬────────────────────────────────────┘ │ HTTPS (443) ▼ ┌───────────────────────────────────────────────────────────────┐ │ 甲骨文服务器(129.146.203.203,公网反代层) │ │ frp / nginx 反向代理端口 → NAS(对外统一入口) │ └──────────────────────────┬────────────────────────────────────┘ │ 反代 / 隧道(frp) ▼ ┌───────────────────────────────────────────────────────────────┐ │ NAS 本机(Docker 宿主机) │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Nginx(反向代理层) │ │ │ │ - HTTPS 终止 / 证书管理 │ │ │ │ - 请求转发至 LobeChat │ │ │ │ - 流式响应透传(proxy_buffering off) │ │ │ └───────────────────────┬─────────────────────────────────┘ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ LobeChat(前端应用层) 端口 3210 │ │ │ │ - 对话界面渲染 │ │ │ │ - 图片上传处理 │ │ │ │ - Access Code 鉴权 │ │ │ │ - 对话历史管理(IndexedDB / PostgreSQL) │ │ │ └───────────────────────┬─────────────────────────────────┘ │ │ │ OpenAI 兼容协议 │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ One-API(网关聚合层) 端口 3000(仅内网) │ │ │ │ - 渠道管理 / 优先级路由 │ │ │ │ - 模型名映射 │ │ │ │ - 失败重试 / 自动禁用 / 冷却恢复 │ │ │ │ - 调用日志 / 用量统计 │ │ │ └───────────────────────┬─────────────────────────────────┘ │ │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ MySQL(网关数据存储) │ │ │ │ - 渠道配置表 / Token 表 / 调用日志表 │ │ │ └─────────────────────────────────────────────────────────┘ │ └──────────────────────────┬────────────────────────────────────┘ │ 各家官方 HTTPS API ┌───────────┬───────────┬───────────┬───────────┐ ▼ ▼ ▼ ▼ ▼ Gemini NVIDIA NIM Groq OpenRouter Mistral ``` ### 2.2 分层说明 | 层级 | 组件 | 职责 | |---|---|---| | 反代层 | 甲骨文服务器 | frp/nginx 反向代理端口,对外统一访问入口 | | 接入层 | Nginx | HTTPS 终止、反向代理、访问入口统一(NAS 本机) | | 表现层 | LobeChat | 用户交互、对话渲染、多模态输入采集 | | 网关层 | One-API | 协议转换、渠道路由、故障转移、用量统计 | | 数据层 | MySQL | 网关配置与日志持久化 | | 外部服务层 | 5 家 AI API | 实际推理能力提供方 | ### 2.3 部署视图 单机 Docker Compose 部署于 **NAS 本机**,各服务以容器形式运行在同一 Docker 网络(`ai-network`)内,服务间通过容器名互相寻址,仅 Nginx 的 80/443 端口对局域网暴露;对外由**甲骨文服务器(129.146.203.203)**通过 frp/nginx 反向代理该端口,供公司内网公网访问。 --- ## 3. 模块设计 ### 3.1 Nginx 反向代理模块 **职责** - 在 NAS 本机暴露 443(HTTPS)与 80(HTTP 跳转 443);公网访问由甲骨文服务器反向代理到本机 443 端口 - 将请求转发至 `lobe-chat:3210` - 关闭代理缓冲以支持流式输出(SSE) **关键配置片段** ```nginx server { listen 443 ssl; server_name ; ssl_certificate /etc/nginx/certs/fullchain.pem; ssl_certificate_key /etc/nginx/certs/privkey.pem; location / { proxy_pass http://lobe-chat:3210; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_buffering off; proxy_cache off; } } server { listen 80; server_name ; return 301 https://$host$request_uri; } ``` ### 3.2 LobeChat 前端模块 **职责** - 渲染对话界面、处理用户输入(含图片) - 通过 `OPENAI_PROXY_URL` 将请求发送至 One-API,而非直连任一具体厂商 - 校验 `ACCESS_CODE` 完成访问控制 **关键环境变量设计** | 变量名 | 说明 | 示例值 | |---|---|---| | `OPENAI_API_KEY` | 填 One-API 生成的聚合 Token(非各厂商原始 Key) | `sk-xxxxx` | | `OPENAI_PROXY_URL` | 指向 One-API 的 v1 路径 | `http://one-api:3000/v1` | | `ACCESS_CODE` | 前端访问密码 | 自定义 | | `DEFAULT_LANG` | 默认语言 | `zh-CN` | | `OPENAI_MODEL_LIST` | 控制模型下拉菜单显示项 | 见 3.3 模型映射设计 | **数据存储**:本期采用 Client-Side(IndexedDB),后续如需多端同步再切换 Server-Side(PostgreSQL)。 ### 3.3 One-API 网关模块 **职责** - 维护渠道(Channel)配置表:Base URL、Key、支持模型、优先级、权重 - 维护 Token 表:对外暴露给 LobeChat 使用的聚合密钥 - 请求路由:按优先级+权重选择渠道,失败时按策略重试下一渠道 - 日志记录:每次请求实际路由渠道、耗时、成功/失败状态 **渠道设计表** | 渠道名 | 类型 | Base URL | 优先级 | 支持视觉 | |---|---|---|---|---| | nvidia-nim | 自定义(OpenAI兼容) | `integrate.api.nvidia.com/v1` | 1 | 部分模型 | | gemini | Google Gemini | `generativelanguage.googleapis.com/v1beta` | 1 | 是 | | openrouter | 自定义(OpenAI兼容) | `openrouter.ai/api/v1` | 2 | 部分模型(:free) | | groq | 自定义(OpenAI兼容) | `api.groq.com/openai/v1` | 2 | 有限 | | mistral | 自定义(OpenAI兼容) | `api.mistral.ai/v1` | 3 | Pixtral系列 | > 优先级数字越小越优先;同优先级内按权重轮询分摊负载。 **模型映射设计** 对外统一暴露两个聚合模型名,前端只需感知这两个名字: | 对外模型名 | 用途 | 关联渠道 | |---|---|---| | `auto-vision` | 图文混合问答 | Gemini、NVIDIA(视觉模型)、OpenRouter(视觉模型)、Mistral(Pixtral) | | `auto-text` | 纯文本问答 | 上述全部 + Groq | **故障转移策略** 1. 请求到达 One-API,按 `auto-vision` 或 `auto-text` 匹配渠道池 2. 按优先级从高到低尝试,同优先级内按权重随机/轮询选择 3. 若渠道返回 429(限流)/5xx(服务错误)/超时,标记该渠道本次失败,自动尝试下一渠道 4. 连续失败次数超过阈值(如 5 次/5 分钟),该渠道进入"冷却禁用"状态,冷却期(如 10 分钟)后自动恢复参与路由 5. 全部渠道均失败时,向前端返回明确错误提示(而非静默失败) ### 3.4 MySQL 数据模块 **职责**:持久化 One-API 的渠道配置、Token、调用日志 **核心表设计(概念级,实际以 One-API 内建 schema 为准)** | 表 | 关键字段 | 说明 | |---|---|---| | channels | id, name, base_url, key, priority, weight, models, status | 渠道配置 | | tokens | id, key, name, quota, status | 对外令牌 | | logs | id, channel_id, model, status_code, latency, created_at | 调用日志 | --- ## 4. 接口设计 ### 4.1 前端 ↔ 网关接口 遵循 OpenAI `/v1/chat/completions` 协议,支持多模态 `content` 数组格式: ```json { "model": "auto-vision", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "这张图片里有什么?" }, { "type": "image_url", "image_url": { "url": "data:image/jpeg;base64,..." } } ] } ], "stream": true } ``` ### 4.2 网关 ↔ 各 AI 渠道接口 | 渠道 | 协议特点 | 转换要点 | |---|---|---| | Gemini | 原生协议与 OpenAI 不同(`contents`/`parts`结构) | One-API 内建适配器自动转换 | | NVIDIA NIM | OpenAI 兼容 | 直接透传,仅替换 Base URL 和 Key | | Groq | OpenAI 兼容 | 直接透传 | | OpenRouter | OpenAI 兼容 | 直接透传,模型名需带 `:free` 后缀 | | Mistral | OpenAI 兼容 | 直接透传,视觉模型指定 `pixtral-*` | --- ## 5. 安全设计 | 风险点 | 设计对策 | |---|---| | 后台管理端口暴露 | One-API 的 3000 端口不映射到公网,仅容器内网可访问;Oracle 反代仅转发前端端口,管理端口走 SSH 隧道或 VPN | | API Key 泄露 | 所有 Key 通过 `.env` 文件注入容器环境变量,`.env` 加入 `.gitignore`,不提交版本库 | | 未授权访问前端 | LobeChat `ACCESS_CODE` 强制校验 | | 传输层安全 | Nginx 强制 HTTPS,HTTP 请求 301 跳转 | | 公网入口暴露 | 对外仅暴露甲骨文服务器反代端口,NAS 本机不直接暴露公网 | | 密钥被盗用后的止损 | One-API 后台可随时吊销/轮换 Token,不影响各渠道原始 Key | --- ## 6. 部署设计 ### 6.1 目录结构 ``` ~/ai-gateway/ ├── docker-compose.yml ├── .env # 敏感变量,不提交版本库 ├── nginx.conf ├── certs/ # HTTPS 证书 ├── one-api-data/ # One-API 持久化数据 └── mysql-data/ # MySQL 持久化数据 ``` ### 6.2 Docker Compose 设计 ```yaml version: '3.8' services: one-api: image: justsong/one-api:latest container_name: one-api restart: always ports: - "3000:3000" # 建议后续改为仅内网映射 volumes: - ./one-api-data:/data environment: - SQL_DSN=root:${MYSQL_ROOT_PASSWORD}@tcp(mysql:3306)/oneapi - TZ=Asia/Shanghai depends_on: - mysql networks: - ai-network mysql: image: mysql:8.0 container_name: one-api-mysql restart: always environment: - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} - MYSQL_DATABASE=oneapi volumes: - ./mysql-data:/var/lib/mysql networks: - ai-network lobe-chat: image: lobehub/lobe-chat:latest container_name: lobe-chat restart: always ports: - "3210:3210" environment: - OPENAI_API_KEY=${ONE_API_TOKEN} - OPENAI_PROXY_URL=http://one-api:3000/v1 - ACCESS_CODE=${LOBE_ACCESS_CODE} - DEFAULT_LANG=zh-CN depends_on: - one-api networks: - ai-network nginx: image: nginx:alpine container_name: nginx-proxy restart: always ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./certs:/etc/nginx/certs:ro depends_on: - lobe-chat networks: - ai-network networks: ai-network: driver: bridge ``` ### 6.3 部署步骤 1. NAS 安装 Docker + Docker Compose(ContainerManager) 2. 按 6.1 结构创建目录,放置 `docker-compose.yml`、`.env`、`nginx.conf` 3. 申请 HTTPS 证书(Let's Encrypt/certbot)放入 `certs/` 4. 执行 `docker-compose up -d` 启动全部服务 5. 访问 `http://:3000` 完成 One-API 初始化(设置管理员密码) 6. 后台按 3.3 节渠道设计表逐个添加渠道,设置优先级 7. 创建对外 Token,写入 `.env` 的 `ONE_API_TOKEN` 8. `docker-compose restart lobe-chat` 使配置生效 9. 在甲骨文服务器配置 frp(或 nginx 反代)将公网端口转发至 NAS 的 Nginx 443 端口 10. 访问 `https://<域名>`(经甲骨文反代)验证前端可正常对话、上传图片、触发 Failover ### 6.4 运维监控设计 | 监控项 | 方式 | |---|---| | 渠道可用性 | One-API 后台"渠道"页面查看状态与最近调用日志 | | 免费额度消耗 | 各厂商官方控制台 + One-API 用量统计页面双重核对 | | 容器健康状态 | `docker-compose ps` / 可选接入 Prometheus+Grafana(后续扩展) | | 证书到期提醒 | certbot 自动续期 + 到期前邮件提醒(后续扩展) | --- ## 7. 设计与需求追溯表 | SRS 需求编号 | 对应设计章节 | |---|---| | FR-1 统一对话界面 | 3.2 LobeChat 前端模块 | | FR-2 多渠道聚合与路由 | 3.3 One-API 网关模块 | | FR-3 多模态能力 | 3.3 模型映射设计 | | FR-4 渠道管理 | 3.3 / 3.4 | | FR-5 安全与访问控制 | 第 5 章 安全设计 | | 非功能-可用性 | 3.3 故障转移策略 | | 非功能-可维护性 | 6.4 运维监控设计 | --- ## 8. 待确认事项(2026-08-23 更新:已实际部署,多数事项已给出默认结论) 1. ~~是否需要 Server-Side 模式支持多端对话同步~~ **默认结论**:本期保持 Client-Side(IndexedDB),不额外部署 PostgreSQL;仅单用户/单浏览器使用,暂无多端同步需求。如后续需要,再单独排期 2. ~~后台管理端口是否需要通过 VPN/SSH 隧道访问~~ **已实施(2026-08-23 修正)**:NAS sshd 关闭了 `AllowTcpForwarding`,SSH 隧道方案实测不可行;改为 One-API 绑定 NAS 局域网 IP `192.168.50.64:3001`,不接入 frp 隧道、不映射公网,同局域网内浏览器直接访问 `http://192.168.50.64:3001` 3. ~~是否需要接入 Prometheus/Grafana~~ **默认结论**:本期不接入,沿用 SDD 6.4 既定的轻量监控(`docker compose ps` + One-API 后台渠道页面);用量不大,暂不需要额外可视化监控栈 4. **域名与证书**:尚未有正式域名,当前用**临时自签名证书**(`certs/fullchain.pem`/`privkey.pem`,CN=nexusai.local)跑通链路,浏览器会提示"不安全"但功能不受影响;若紫川后续购买/已有域名,替换证书为 Let's Encrypt 即可,无需改动其他组件——**此项仍待紫川决定是否购买域名** 5. ~~NAS 到各海外 API 官方域名的直连稳定性验证~~ **已完成(2026-08-23)**:五渠道 TCP/TLS 握手均 2 秒内完成,NAS 出站直连稳定,无需额外代理 6. ~~甲骨文反代方式确认~~ **已完成(2026-08-23)**:采用 frp 隧道(与 FAM 项目一致),复用 Oracle 上已运行的 `frps`,NAS `frpc.toml` 新增 `nexusai` 代理(remotePort **3210**(专属端口,非默认 443,与 gitea:3000/wordpress:8500/fam-core:8000 同一约定)→ NAS 8443),对外访问地址为 `https://129.146.203.203:3210`