--- title: 'Claude API 透传代理服务' slug: 'claude-api-proxy' created: '2026-03-20T04:30:00.000Z' status: 'completed' stepsCompleted: [1, 2, 3, 4, 5, 6] tech_stack: ['Node.js', 'Express', 'TypeScript', 'axios', 'Docker'] files_to_modify: - 'package.json' - 'tsconfig.json' - 'src/index.ts' - 'src/proxy.ts' - 'src/health.ts' - 'src/logger.ts' - '.env.example' - 'Dockerfile' - '.dockerignore' - '.gitea/workflows/release.yaml' code_patterns: - '流式响应: axios stream + pipe' - '错误处理: axios error + 统一 JSON 格式' - '日志: JSONL 格式存储到 /tmp/proxy.jsonl' - 'Docker: 多阶段构建 + 生产依赖分离' - 'CI/CD: Gitea Actions + 阿里云镜像仓库' test_patterns: [] --- # Tech-Spec: Claude API 透传代理服务 **Created:** 2026-03-20T04:30:00.000Z ## Overview ### Problem Statement D8D 项目有一个 API Key 池,目前直接请求 Claude 官方 API。但官方 API 有 IP 并发限制,单个 IP 的并发数有限。为了绕过这个限制,需要启动多个代理容器实例,每个实例有不同的出口 IP,从而分散请求。 ### Solution 创建一个纯透传的 Claude API 代理服务: - 使用 Express.js 构建轻量级代理服务 - 接收什么就转发什么,不做任何业务逻辑处理 - 支持流式(streaming)和非流式两种输出模式 - 构建为 Docker 镜像,部署到阿里云 ECI (t5-lc2m1.nano: 0.5 CPU, 1G 内存) - 通过 K8S 管理 Pod 和 Ingress 配置 ### Scope **In Scope:** - Express.js 代理服务(透传 /v1/messages 端点) - 支持流式和非流式响应 - Dockerfile 构建配置 - Gitea workflow 自动构建镜像 - K8S 部署配置参考 **Out of Scope:** - 用户认证和授权 - 余额检查和计费 - API Key 池管理(由 D8D 主项目处理) - 复杂的日志和监控 ## Context for Development ### Codebase Patterns 参考项目:`/mnt/code-fast/d8d-worktrees/merge-epics` **代理转发参考:** - `src/server/api/ai/claude/v1/messages/post.ts` - Claude API 调用实现 - 使用 axios 进行 HTTP 请求 - 流式响应使用 TransformStream 处理 **K8S 部署参考:** - `src/server/utils/k8s.ts` - K8S 客户端封装 - 使用阿里云 ECI (virtual-kubelet) - 创建 Deployment、Service、Ingress **Docker 构建参考:** - `Dockerfile` - 多阶段构建 - `.gitea/workflows/release.yaml` - Gitea Actions 构建流程 ### Files to Reference | File | Purpose | | ---- | ------- | | `/mnt/code-fast/d8d-worktrees/merge-epics/src/server/api/ai/claude/v1/messages/post.ts` | Claude API 转发逻辑参考 | | `/mnt/code-fast/d8d-worktrees/merge-epics/src/server/utils/k8s.ts` | K8S 客户端实现参考 | | `/mnt/code-fast/d8d-worktrees/merge-epics/Dockerfile` | Docker 构建参考 | | `/mnt/code-fast/d8d-worktrees/merge-epics/.gitea/workflows/release.yaml` | Gitea workflow 参考 | ### Technical Decisions - **框架选择**: Express.js(用户指定,轻量简单) - **语言**: TypeScript(类型安全) - **HTTP 客户端**: axios(支持流式响应) - **容器规格**: 阿里云 ECI t5-lc2m1.nano (0.5 CPU, 1G 内存) - **镜像仓库**: registry.cn-beijing.aliyuncs.com/d8dcloud/ - **端口**: 3000 - **API URL**: 环境变量 `ANTHROPIC_BASE_URL`,本地读取 `.env`,容器通过 K8S 注入 - **智谱端点**: `https://open.bigmodel.cn/api/anthropic` - **路径透传**: 完全透传 `/v1/*` 路径到上游 API - **健康检查**: 添加 `/health` 端点供 K8S 探针使用 - **Headers 透传**: `x-api-key`、`authorization`、`anthropic-version`、`content-type`、`accept` - **超时设置**: 不设置 axios timeout,让上游 API 自己处理 - **日志格式**: JSONL 格式存储到 `/tmp/proxy.jsonl`,容器重启自动清理 - **日志内容**: 只记录元信息(time, method, path, status, duration, stream, error),不记录请求/响应 body - **请求体大小**: Express body-parser 设置 `limit: '50mb'` - **包管理器**: pnpm(与参考项目一致) - **镜像名称**: `registry.cn-beijing.aliyuncs.com/d8dcloud/claude-api-proxy:${VERSION}` - **HTTPS**: 依赖 K8S Ingress 处理 TLS,代理本身不需要 HTTPS - **流式错误处理**: 流式传输中若上游报错,关闭客户端连接,记录错误日志 - **启动命令**: `node dist/index.js` ## Implementation Plan ### Tasks **Task 1: 项目初始化** - 创建 `package.json` 配置 Express、axios、TypeScript 依赖 - 创建 `tsconfig.json` 配置 TypeScript 编译选项 - 创建 `.env.example` 环境变量示例 **Task 2: 核心代理服务** - 创建 `src/index.ts` Express 入口,监听端口 3000 - 创建 `src/proxy.ts` 代理逻辑: - 捕获所有 `/v1/*` 路由 - 透传 headers (x-api-key, authorization, anthropic-version, content-type, accept) - 透传 body 到 `ANTHROPIC_BASE_URL` - 流式响应:设置 SSE headers,使用 axios stream + pipe - 非流式响应:直接转发 JSON - **不设置 axios timeout**,让上游 API 自己处理 - 监听 `req.on('close')` 处理客户端断开 - **流式错误处理**:上游流式报错时关闭客户端连接,记录日志 - 设置 Express body-parser `limit: '50mb'` **Task 3: 日志服务** - 创建 `src/logger.ts` 日志工具 - JSONL 格式写入 `/tmp/proxy.jsonl` - 记录字段:time, method, path, status, duration, stream?, error? - **不记录请求/响应 body**(避免敏感数据泄露) **Task 4: 健康检查** - 创建 `src/health.ts` 添加 `/health` 端点 - 返回 `{ status: 'ok', timestamp: ISO时间 }` **Task 5: Docker 构建配置** - 创建 `Dockerfile` 多阶段构建 - 构建阶段:pnpm 安装依赖 + TypeScript 编译 - 运行阶段:只包含生产依赖 + 编译产物 - 暴露端口 3000 - 启动命令:`CMD ["node", "dist/index.js"]` - 创建 `.dockerignore` 排除 node_modules、.git、.env 等 **Task 6: CI/CD 工作流** - 创建 `.gitea/workflows/release.yaml` - 触发条件:push 到 `release/*` 分支 - 步骤:提取版本号 → 登录阿里云镜像仓库 → 构建推送镜像 - 镜像名称:`registry.cn-beijing.aliyuncs.com/d8dcloud/claude-api-proxy:${VERSION}` ### Acceptance Criteria **AC1: 非流式请求转发** - Given: 客户端发送 POST `/v1/messages` 请求 (stream: false) - When: 代理收到请求 - Then: 请求被转发到 `ANTHROPIC_BASE_URL/v1/messages`,响应完整返回 **AC2: 流式请求转发** - Given: 客户端发送 POST `/v1/messages` 请求 (stream: true) - When: 代理收到请求 - Then: 设置 SSE headers,流式转发响应数据 - And: 若流式过程中上游报错,关闭客户端连接并记录日志 **AC3: 健康检查** - Given: 客户端请求 GET `/health` - When: 代理收到请求 - Then: 返回 `{ status: 'ok', timestamp: '...' }` 状态码 200 **AC4: Docker 镜像构建** - Given: 执行 `docker build` - When: 构建完成 - Then: 镜像大小 < 200MB,能正常启动并监听 3000 端口 **AC5: Headers 透传** - Given: 请求包含 `x-api-key`、`authorization`、`anthropic-version` headers - When: 代理转发请求 - Then: 这些 headers 被完整传递到上游 API **AC6: 错误透传** - Given: 上游 API 返回错误响应(如 400/401/429/500) - When: 代理收到错误响应 - Then: 透传错误状态码和响应 body 给客户端 ## Additional Context ### Dependencies **生产依赖:** - express - Web 框架 - axios - HTTP 客户端(支持流式) - dotenv - 环境变量加载 **开发依赖:** - typescript - TypeScript 编译器 - @types/express - Express 类型定义 - @types/node - Node.js 类型定义 - tsx - TypeScript 执行器(支持热重载) - tsc-alias - TypeScript 路径别名处理 ### Testing Strategy - 手动测试流式和非流式响应 - 使用 curl 测试代理功能 ### Notes - 代理服务需要完全透传,包括 headers 和 body - 流式响应需要正确处理 SSE (Server-Sent Events) 格式 - API Key 通过请求头 x-api-key 或 Authorization 传递 - **超时策略**:不设置 axios timeout,AI 请求可能耗时很长 - **日志清理**:存储在 /tmp 目录,容器重启自动清空,不会累积 - **安全考虑**:日志不记录请求/响应 body,避免敏感数据泄露 - **HTTPS/TLS**:代理本身不处理 HTTPS,完全依赖 K8S Ingress 处理 TLS - **流式错误处理**:流式传输中上游报错时,直接关闭客户端连接 - **镜像命名**:`registry.cn-beijing.aliyuncs.com/d8dcloud/claude-api-proxy:${VERSION}` ## Review Notes - 对抗性审查已完成 (2026-03-20) - 发现总数: 12 个 (2 严重, 4 高, 4 中, 2 低) - 修复数量: 3 个 (F1, F3, F4) - 跳过数量: 9 个 (经专家讨论确认合理) - 解决方案: 派对模式专家讨论 (Winston, Amelia, Barry, Quinn) **已修复:** - F1: Docker 添加 USER node 和 HEALTHCHECK - F3: 添加 SIGTERM/SIGINT 优雅关闭处理 - F4: 流式错误处理添加 writableEnded 检查 **已跳过 (合理原因):** - F2: 同步日志对轻量代理可接受 - F5: 透传代理不应验证请求体 - F6-F12: 低影响或过度优化