企业级 Claude Code 透明代理 — 统一管控 LLM API Key,精确追踪 token 用量,零侵入接入。
┌─────────────┐ HTTP/SSE ┌───────────────────────────┐ HTTPS ┌──────────────────┐
│ Claude Code │──────────────▶│ cproxy 127.0.0.1:8080 │──────────▶│ sproxy :9000 │──▶ Anthropic API
│ (code agent)│ │ 注入用户 JWT │ │ 验证身份 · 配额 │
└─────────────┘ └───────────────────────────┘ │ 统计用量 · 转发 │
└──────────────────┘
两个组件,解决一个问题:多人共用 LLM API Key 时,谁用了多少、超没超额、钱花哪了——一目了然。
| 分类 | 功能 |
|---|---|
| 零侵入接入 | 用户只需设置两个环境变量,无需修改 Claude Code 配置 |
| JWT 认证 | 每位用户独立 JWT,access token 24h,refresh token 7天,支持自动刷新 |
| LDAP / AD 集成 | 一行配置接入企业 LDAP / Active Directory,首次登录自动 JIT 创建用户,支持 LDAPS |
| 多 Provider 支持 | 透传 Anthropic / OpenAI / Ollama 请求,按路径自动路由,精确解析各 provider token 用量 |
| 数据导出 | sproxy admin export --format csv|json CLI 导出或 GET /api/admin/export 流式下载;sproxy admin backup 一键备份 SQLite |
| Token 统计 | 同步/流式(SSE)请求均精确统计 input/output tokens,不缓冲不延迟 |
| 费用估算 | 按模型定价配置,Dashboard 实时显示 USD 消耗 |
| 用户配额 | 按分组设置每日/每月 token 上限,超额返回 429 |
| 速率限制 | 每用户每分钟请求数(RPM)限制,滑动窗口算法 |
| 负载均衡 | cproxy↔sproxy、sproxy↔LLM 两级负载均衡,加权随机策略 |
| 健康检查 | 主动(GET /health)+ 被动(连续失败熔断)双重检查 |
| 集群模式 | primary + worker 多节点,路由表自动下发给 cproxy |
| Web Dashboard | Go 模板 + Tailwind CSS,内嵌二进制,无需前端构建 |
| Admin CLI | 命令行管理用户、分组、配额、统计 |
| Prometheus 指标 | GET /metrics,标准文本格式,可接 Grafana;含 DB 文件大小、配额缓存命中率、心跳延迟 |
| Webhook 告警 | 节点故障、配额超限等事件推送到 Slack/飞书/企业微信 |
| OpenTelemetry 追踪 | 可选启用,支持 gRPC/HTTP/stdout exporter;自动对接 Jaeger、Grafana Tempo 等后端 |
| 登录频率限制 | 每 IP 5 次失败后锁定 5 分钟,防暴力破解 |
| 管理审计日志 | 所有用户/分组增删改操作记录到 audit_logs,可在 Dashboard 查看 |
| Token 自动刷新 | cproxy 自动检测 token 过期,5s 内向 sproxy 换取新 token |
- Go 1.21+(仅编译时需要,二进制无其他依赖)
- 一台可被开发者访问的服务器(部署 sproxy)
- Anthropic API Key
git clone https://github.com/l17728/pairproxy.git
cd pairproxy
make build
# 输出:bin/cproxy bin/sproxy跨平台发布包(Linux/macOS/Windows × amd64/arm64):
make release
# 输出:dist/pairproxy-linux-amd64.tar.gz 等# 生成 admin 密码 hash
./bin/sproxy hash-password
# 输入密码后得到 $2a$10$... 格式的 hash
# 准备环境变量
export ANTHROPIC_API_KEY_1="sk-ant-api03-..."
export JWT_SECRET="$(openssl rand -hex 32)"
export ADMIN_PASSWORD_HASH='$2a$10$...' # 上一步输出的 hash
# 复制并编辑配置
cp config/sproxy.yaml.example sproxy.yaml
# 至少修改:llm.targets[0].url、database.path
# 启动
./bin/sproxy start --config sproxy.yaml
# INFO sproxy listening addr=0.0.0.0:9000
# INFO dashboard enabled path=/dashboard/创建用户和分组(服务启动后操作):
# 创建分组(设置每日 100万 token 上限,每分钟 60 次请求)
./bin/sproxy admin group add engineering \
--daily-limit 1000000 --monthly-limit 20000000 --rpm 60
# 创建用户
./bin/sproxy admin user add alice --group engineering
# Password: ****# 下载 cproxy 二进制(或自行编译)
# 登录,获取 JWT
cproxy login --server http://proxy.company.com:9000
# Username: alice
# Password: ****
# ✓ Login successful. Token saved to ~/.config/pairproxy/token.json
# 启动本地代理
cproxy start
# cproxy listening on http://127.0.0.1:8080# 在 shell profile 中添加(或在 IDE 中设置环境变量)
export ANTHROPIC_BASE_URL=http://127.0.0.1:8080
export ANTHROPIC_API_KEY=any-placeholder # 值任意填写,cproxy 会用 JWT 替换真实认证头说明:
ANTHROPIC_API_KEY仅用于满足 Claude Code SDK 的非空校验,其值不会被发送给 Anthropic。cproxy 会将此 header 替换为用户 JWT,sproxy 再将其替换为真实 API Key。
之后正常使用 Claude Code,所有请求自动经过代理统计。
Claude Code 发出:
POST http://127.0.0.1:8080/v1/messages
Authorization: Bearer any-placeholder ← 假 key
cproxy 转发给 sproxy:
POST http://proxy.company.com:9000/v1/messages
X-PairProxy-Auth: eyJhbGc... ← 用户 JWT(替换原 Authorization)
sproxy 转发给 Anthropic:
POST https://api.anthropic.com/v1/messages
Authorization: Bearer sk-ant-REAL-KEY ← 真实 API Key(替换 X-PairProxy-Auth)
用户永远看不到真实 API Key。
cproxy 启动时读取 ~/.pairproxy/token.json,并在请求前检查 token 有效期:
token 有效期 > refresh_threshold(默认30分钟)
→ 直接使用当前 access token
token 有效期 ≤ refresh_threshold
→ 用 refresh_token 向 sproxy 换取新的 access token
→ 新 token 保存到 token.json
refresh_token 也已过期(> 7天未使用)
→ 返回 401,提示用户重新执行 cproxy login
access token 有效期 24h,refresh token 7天,正常使用下用户无需手动登录。
sproxy 使用 TeeResponseWriter 包装响应流:
Anthropic SSE 流
│
├── 同步写给 Claude Code(零缓冲,不增加延迟)
│
└── 同时解析 SSE 事件行
message_start → 记录 input_tokens
message_delta → 记录 output_tokens
message_stop → 异步写入 SQLite usage_logs
cproxy 启动
│
└── 连接 sproxy primary(cfg.sproxy.primary)
│
sproxy primary 在响应头中注入路由表:
X-Routing-Version: 3
X-Routing-Update: base64(JSON{entries:[sp1,sp2,sp3]})
│
cproxy 解析路由头,更新本地 Balancer
│
后续请求自动负载均衡到 sp1/sp2/sp3
访问 http://<sproxy-host>:9000/dashboard/,使用 admin 密码登录。
| 页面 | 内容 |
|---|---|
| 概览 | 今日 token 总量、请求次数、成功率、活跃用户数、估算费用;最近请求列表 |
| 用户 | 用量排行、创建用户、启用/禁用、重置密码 |
| 分组 | 分组管理、设置每日/每月 token 上限和 RPM |
| 日志 | 最近 N 条请求记录,支持按用户 ID 过滤 |
| 审计 | 管理员操作审计日志(用户/分组增删改操作) |
# 用户管理
sproxy admin user add <username> [--group <group>]
sproxy admin user list [--group <group>]
sproxy admin user disable <username>
sproxy admin user enable <username>
sproxy admin user reset-password <username>
# 分组管理
sproxy admin group add <name> [--daily-limit <n>] [--monthly-limit <n>] [--rpm <n>]
sproxy admin group list
sproxy admin group set-quota <name> [--daily <n>] [--monthly <n>] [--rpm <n>]
# 统计查询
sproxy admin stats [--user <username>] [--days <n>]
# Token 管理
sproxy admin token revoke <username> # 强制下线用户
# 工具
sproxy hash-password [--password <pwd>] # 生成 bcrypt hash
sproxy version # 查看版本信息# cproxy 命令
cproxy login --server <url> # 登录并保存 token
cproxy start [--config <path>]# 启动本地代理
cproxy status # 查看 token 状态
cproxy logout # 登出并撤销 refresh token
cproxy version # 查看版本信息listen:
host: "127.0.0.1"
port: 8080
sproxy:
primary: "http://proxy.company.com:9000" # 必填
lb_strategy: "round_robin"
health_check_interval: 30s
request_timeout: 300s
auth:
refresh_threshold: 30m # token 过期前多久自动刷新
log:
level: "info"listen:
host: "0.0.0.0"
port: 9000
llm:
lb_strategy: "round_robin"
request_timeout: 300s
targets:
- url: "https://api.anthropic.com"
api_key: "${ANTHROPIC_API_KEY_1}" # 从环境变量读取
weight: 1
database:
path: "/var/lib/pairproxy/pairproxy.db"
write_buffer_size: 200
flush_interval: 5s
auth:
jwt_secret: "${JWT_SECRET}" # 必填,建议从环境变量注入
access_token_ttl: 24h
refresh_token_ttl: 168h
admin:
password_hash: "${ADMIN_PASSWORD_HASH}"
cluster:
role: "primary" # primary | worker
self_addr: "http://sp1.company.com:9000"
self_weight: 50
alert_webhook: "" # Slack/飞书 webhook,留空不发告警
dashboard:
enabled: true
pricing:
default_input_per_1k: 0.003
default_output_per_1k: 0.015
models:
claude-sonnet-4-5:
input_per_1k: 0.003
output_per_1k: 0.015
claude-opus-4-5:
input_per_1k: 0.015
output_per_1k: 0.075
log:
level: "info"完整注释版见 config/sproxy.yaml.example 和 config/cproxy.yaml.example。
┌───────────────────────────────┐
[开发者 A] cproxy ──┤ │
[开发者 B] cproxy ──┤ sp-1 primary :9000 │──▶ Anthropic API
[开发者 C] cproxy ──┤ sp-2 worker :9000 │──▶ Anthropic API
│ sp-3 worker :9000 │──▶ Anthropic API
└──────────────┬────────────────┘
│
Web Dashboard
http://sp-1:9000/dashboard/
worker 节点额外配置:
cluster:
role: "worker"
primary: "http://sp1.company.com:9000" # primary 地址
self_addr: "http://sp2.company.com:9000"
self_weight: 50
dashboard:
enabled: false # worker 不开 Dashboardworker 节点通过心跳向 primary 注册,primary 将路由表下发给所有 cproxy,实现自动感知和负载均衡。
# 1. 准备配置文件和密钥
cp config/sproxy.yaml.example sproxy.yaml # 编辑填入实际地址
cp .env.example .env # 编辑填入 API Key / JWT Secret
# 2. 启动
docker compose up -d
# 3. 查看日志
docker compose logs -f sproxy# 构建 sproxy(注入版本信息)
docker build \
--build-arg VERSION=$(git describe --tags --always) \
--build-arg COMMIT=$(git rev-parse --short HEAD) \
--build-arg BUILT=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
-t pairproxy/sproxy .
# 运行
docker run -d \
--name sproxy \
-p 9000:9000 \
--env-file .env \
-v $(pwd)/sproxy.yaml:/etc/pairproxy/sproxy.yaml:ro \
-v sproxy-data:/var/lib/pairproxy \
pairproxy/sproxy最终镜像基于 gcr.io/distroless/static-debian12:含 CA 证书(HTTPS 请求 Anthropic 需要)、无 shell、以 UID 65532 非 root 用户运行。二进制为全静态编译(CGO_ENABLED=0),镜像约 15 MB。
| 文件 | 说明 |
|---|---|
Dockerfile |
多阶段构建,--build-arg BINARY=cproxy|sproxy |
.dockerignore |
排除编译产物、DB 文件、.git 等 |
docker-compose.yml |
单机部署,含 worker 节点注释模板 |
.env.example |
环境变量模板(复制为 .env 后填写真实值) |
# 1. 复制二进制
sudo cp bin/sproxy /usr/local/bin/sproxy
sudo chmod +x /usr/local/bin/sproxy
# 2. 创建最小权限系统用户
sudo useradd --system --no-create-home --shell /usr/sbin/nologin pairproxy
# 3. 创建目录
sudo mkdir -p /etc/pairproxy /var/lib/pairproxy
sudo chown pairproxy:pairproxy /var/lib/pairproxy
sudo chmod 750 /var/lib/pairproxy
# 4. 复制配置文件
sudo cp config/sproxy.yaml.example /etc/pairproxy/sproxy.yaml
sudo chown root:pairproxy /etc/pairproxy/sproxy.yaml
sudo chmod 640 /etc/pairproxy/sproxy.yaml
# 编辑 sproxy.yaml,填写实际地址和路径
# 5. 创建环境变量文件(存放密钥,不进配置文件)
sudo install -m 640 -o root -g pairproxy /dev/null /etc/pairproxy/sproxy.env
sudo tee /etc/pairproxy/sproxy.env <<'EOF'
ANTHROPIC_API_KEY_1=sk-ant-api03-...
JWT_SECRET=your-32-char-random-secret
ADMIN_PASSWORD_HASH=$2a$10$...
EOF
# 6. 安装 service 文件并启动
sudo cp config/sproxy.service /etc/systemd/system/sproxy.service
sudo systemctl daemon-reload
sudo systemctl enable --now sproxysudo systemctl status sproxy # 查看运行状态
sudo journalctl -u sproxy -f # 实时查看日志
sudo journalctl -u sproxy --since today # 今日日志
sudo systemctl reload sproxy # 热重载(发送 SIGHUP,仅重载 log.level)
sudo systemctl restart sproxy # 重启服务(完整配置重载)service 文件默认启用了以下安全加固:NoNewPrivileges、ProtectSystem=strict、PrivateTmp、MemoryDenyWriteExecute、系统调用白名单过滤、禁止 core dump(防止内存中密钥落盘)。
完整 service 文件见 config/sproxy.service,worker 节点见 config/sproxy-worker.service。
GET http://<sproxy-host>:9000/metrics
# HELP pairproxy_tokens_today Total tokens today
pairproxy_tokens_today{type="input"} 1234567
pairproxy_tokens_today{type="output"} 345678
# HELP pairproxy_requests_today Total requests today
pairproxy_requests_today{type="total"} 1000
pairproxy_requests_today{type="error"} 5
# HELP pairproxy_active_users_today Active users today
pairproxy_active_users_today 12
# HELP pairproxy_cost_usd_today Estimated cost today (USD)
pairproxy_cost_usd_today 4.2100
指标每 30 秒缓存一次,避免频繁查询 DB。
make build # 编译当前平台二进制到 bin/
make test # 运行全部测试
make test-race # 竞态检测
make test-cover # 生成覆盖率报告
make release # 跨平台发布包(dist/)
make vet fmt lint # 代码检查
make bcrypt-hash # 生成 admin 密码 hash
make clean # 清理 bin/确认所有改动已合并到 main 后,推送一个符合语义版本的 tag,CI 自动完成剩余全部工作:
git tag v1.2.3 && git push origin v1.2.3GitHub Actions release.yml 随后自动执行:
- 交叉编译 5 个平台的二进制(Linux/macOS/Windows × amd64/arm64)
- 生成
SHA256SUMS.txt校验文件 - 创建 GitHub Release,附上所有产物和自动生成的 release notes
- 构建 多架构 Docker 镜像(
linux/amd64+linux/arm64)并推送到ghcr.io/l17728/pairproxy,标签包含v1.2.3、1.2、1、latest
pairproxy/
├── cmd/
│ ├── cproxy/main.go # cproxy CLI 入口
│ └── sproxy/main.go # sproxy CLI 入口 + admin 子命令
├── internal/
│ ├── auth/ # JWT 管理、bcrypt、本地 token 文件
│ ├── proxy/ # cproxy/sproxy 核心 HTTP 处理器 + 中间件
│ ├── tap/ # TeeResponseWriter + Anthropic SSE 解析器
│ ├── lb/ # Balancer 接口、加权随机、健康检查
│ ├── cluster/ # 路由表、PeerRegistry、Reporter
│ ├── quota/ # 配额检查、速率限制、中间件
│ ├── db/ # SQLite + GORM 模型、UserRepo、UsageRepo
│ ├── api/ # AuthHandler、AdminHandler、ClusterHandler
│ ├── dashboard/ # Web Dashboard(Go 模板 + embed)
│ ├── metrics/ # Prometheus 格式 /metrics 端点
│ ├── alert/ # Webhook 告警通知器
│ ├── config/ # YAML 配置加载(支持 ${ENV_VAR} 展开)
│ └── version/ # 版本信息
├── config/
│ ├── cproxy.yaml.example
│ ├── sproxy.yaml.example
│ └── sproxy-worker.yaml.example
├── Makefile
└── go.mod # module: github.com/l17728/pairproxy
| 文档 | 说明 |
|---|---|
| docs/manual.md | 完整用户手册(部署、配置、CLI 命令、Dashboard 使用) |
| docs/API.md | REST API 参考(Auth、Admin、Stats 端点) |
| docs/CLUSTER_DESIGN.md | 多节点集群架构设计(路由分发、心跳、故障恢复) |
| docs/SECURITY.md | 安全模型说明(JWT 防护、集群 API 鉴权、TLS 配置、密钥轮换) |
| docs/TROUBLESHOOTING.md | 故障排查手册(启动失败、JWT 错误、配额超限、集群问题、性能调优) |
| docs/UPGRADE.md | 版本升级指南(Schema 变更、回滚方法、不兼容变更清单) |
| docs/PERFORMANCE.md | 性能调优指南(缓冲区、WAL 模式、连接池、缓存调优) |
| 组件 | 选型 | 理由 |
|---|---|---|
| HTTP 反向代理 | net/http/httputil.ReverseProxy |
标准库,SSE 天然支持 |
| 数据库 | SQLite + GORM (glebarez/sqlite) |
纯 Go,无 CGO,单文件部署 |
| CLI | cobra |
标准 Go CLI 框架 |
| 日志 | zap |
结构化日志,高性能 |
| 密码 | bcrypt (golang.org/x/crypto) |
行业标准 |
| 前端 | Go HTML 模板 + Tailwind CSS CDN | 无需构建,内嵌二进制 |