实现完整的 Claude API 代理服务,支持: - 透传 /v1/* 路径到上游 API - 流式 (SSE) 和非流式响应 - JSONL 格式请求日志 - /health 健康检查端点 - Docker 多阶段构建 - Gitea Actions CI/CD 工作流 安全加固: - 容器以非 root 用户运行 - 添加 Docker HEALTHCHECK - 实现优雅关闭处理 (SIGTERM/SIGINT) Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
243 lines
8.9 KiB
Markdown
243 lines
8.9 KiB
Markdown
---
|
||
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: 低影响或过度优化
|