Files
zhanlu_proxy/README.md
T
m1saka fa9640d919
build / build (push) Successful in 2m34s
Add token usage stats and consolidate credentials in SQLite
- New internal/stats (types) and internal/store (SQLite owner: requests + credentials tables, WAL); store implements stats.Recorder.
- Stream (SSE tee) and non-stream chat paths parse upstream usage incl. cached_tokens and record per-request; add /api/stats, /api/stats/reset, /admin/stats HTML with cache hit rate.
- Drop credentials.json: remove auth file I/O and ZHANLU_CREDENTIALS_FILE; credential precedence is env vars > db row.
2026-08-19 15:52:00 +08:00

265 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Zhanlu Proxy
一个本地 Go 代理服务,用于读取湛卢(v1.4.2)插件凭据,按插件认证规则换取模型 API Key,并暴露 OpenAI 兼容接口。
当前实现包含:
- 登录页支持插件默认的移动云手机号验证码登录:验证码校验后按插件流程调用 `/api/acepilot/zhanlu/v1/login` 获取用户资料,再通过 SM2 签名调用 `/user/api/v2/external/key/get-or-create` 换取模型 API Key,凭据自动保存到本地 JSON。
- OpenAI 兼容接口:`/v1/models`(优先从 `/gateway/v1/model/info` 拉取模型列表)、`/v1/chat/completions`(携带 `Authorization: Bearer <apiKey>` 请求 `{modelBaseUrl}/chat/completions`)。
- 湛卢登录签名逻辑:RSA `authorization`、SHA-256 query hash、HMAC-SHA1 `Signature`,用于 v1.4.2 的 v1/login 认证。
- 模型 API Key 换取签名逻辑:SM3 摘要 + SM2 签名(`X-Auth-Signature`/`X-Auth-Timestamp`/`X-Auth-Nonce`)。
- 上游 SSE 直接透传为 OpenAI SSE;非流式请求在本地聚合为 OpenAI Chat Completion JSON。
- OpenAI 函数/工具调用:支持 `tools``tool_choice`、流式 `delta.tool_calls`、非流式 `message.tool_calls` 以及 `role: tool` 结果续传。
- Token 消耗统计:流式与非流式请求均解析上游 `usage`,按模型/按日/最近明细写入本地 SQLite(`zhanlu.db`),凭据也一并持久化在同一个库中。管理页提供 `GET /admin/stats` 可视化与 `GET /api/stats` JSON 接口。
## 运行
```powershell
go run ./cmd/zhanlu-proxy
```
默认监听:
```text
http://127.0.0.1:8080
```
打开首页会自动跳转到管理登录页:
```text
http://127.0.0.1:8080/
```
## systemd 服务示例
假设二进制文件放在:
```text
/opt/zhanlu-proxy/zhanlu-proxy
```
数据库放在:
```text
/opt/zhanlu-proxy/zhanlu.db
```
创建环境变量文件 `/etc/zhanlu-proxy/zhanlu-proxy.env`
```env
ZHANLU_LISTEN_ADDR=:8080
ZHANLU_DB_FILE=/opt/zhanlu-proxy/zhanlu.db
ZHANLU_MOBILE_LOGIN_BASE_URL=https://ecloud.10086.cn
ZHANLU_MOBILE_MODEL_BASE_URL=https://ecloud.10086.cn/api/query/aigateway
ZHANLU_UPSTREAM_TIMEOUT=300s
ZHANLU_LOGIN_PASSWORD=change-this-login-password
OPENAI_COMPAT_API_KEY=change-this-local-secret
```
创建 systemd service 文件 `/etc/systemd/system/zhanlu-proxy.service`
```ini
[Unit]
Description=Zhanlu OpenAI-compatible proxy
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/opt/zhanlu-proxy
EnvironmentFile=/etc/zhanlu-proxy/zhanlu-proxy.env
ExecStart=/opt/zhanlu-proxy/zhanlu-proxy
Restart=always
RestartSec=3
User=zhanlu-proxy
Group=zhanlu-proxy
[Install]
WantedBy=multi-user.target
```
启用并启动服务:
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now zhanlu-proxy
sudo systemctl status zhanlu-proxy
```
查看日志:
```bash
journalctl -u zhanlu-proxy -f
```
## 登录与凭据
### 移动云手机号验证码登录
打开 `/login` 后输入手机号并点击“获取验证码”。实现按插件默认登录分支工作:
如果设置了 `ZHANLU_LOGIN_PASSWORD``/login` 只负责管理密码登录。密码正确后服务会设置 HttpOnly 会话 Cookie,并跳转到 `/admin/login``/admin/login` 才是手机号验证码登录湛卢的页面,之后才能查看凭据状态、获取短信验证码、保存凭据或使用备用 SSO 管理接口。
- 生成 16 位一次性 `secret`
- 使用插件内置 RSA 公钥加密手机号和 `secret`
- 调用公网接口 `/api/query/acepilot-h5/manager/code/getAuthCode` 发送验证码。
- 输入验证码后调用 `/api/query/acepilot-h5/manager/code/checkCode`
- 使用本次 `secret` AES 解密响应中的 `ak``sk``license`,得到 `AccessKey``SecretKey``Token`
- 按插件 v1.4.2 流程调用 `/api/acepilot/zhanlu/v1/login`RSA+HmacSHA1 签名 URL + `plugin_type=zhanlu_ide` 请求头)获取用户资料(email/组织/团队)。
- 用 SM2 私钥签名调用 `{mobileModelBaseUrl}/user/api/v2/external/key/get-or-create` 换取模型 `apiKey`
- 凭据(含 `apiKey``modelBaseUrl`、email 等)会写入本地 SQLite 数据库(`zhanlu.db`),后续 OpenAI 兼容接口自动使用。
默认数据库保存在当前执行目录:
```text
zhanlu.db
```
可以通过环境变量覆盖:
```powershell
$env:ZHANLU_DB_FILE="E:\path\to\zhanlu.db"
```
凭据仅持久化在数据库中,不再使用 JSON 文件。
手机号验证码登录使用 `ZHANLU_MOBILE_LOGIN_BASE_URL`,默认公网地址来自插件配置(兼容旧环境变量 `ZHANLU_SERVER_BASE_URL`):
```powershell
$env:ZHANLU_MOBILE_LOGIN_BASE_URL="https://ecloud.10086.cn"
$env:ZHANLU_MOBILE_MODEL_BASE_URL="https://ecloud.10086.cn/api/query/aigateway"
```
灵犀(内网)SSO 的 `/auth/start``/auth/callback` 仍保留为备用接口,但不是 `/login` 的默认主流程。
## OpenAI 兼容接口
### 健康检查
```powershell
curl http://127.0.0.1:8080/healthz
```
### 模型列表
```powershell
curl http://127.0.0.1:8080/v1/models
```
### Chat Completions
非流式:
```powershell
curl http://127.0.0.1:8080/v1/chat/completions `
-H "Content-Type: application/json" `
-d '{"model":"zhanlu/auto","messages":[{"role":"user","content":"hello"}],"stream":false}'
```
流式:
```powershell
curl -N http://127.0.0.1:8080/v1/chat/completions `
-H "Content-Type: application/json" `
-d '{"model":"zhanlu/auto","messages":[{"role":"user","content":"hello"}],"stream":true}'
```
### 工具调用
请求中的 `tools``tool_choice` 会传给湛卢模型。流式响应返回增量 `delta.tool_calls`;非流式响应会把分片聚合为完整的 `message.tool_calls`,并保留 `finish_reason: "tool_calls"`。执行工具后,将 assistant 的 `tool_calls``role: "tool"` 结果放回 `messages` 再发起请求即可得到最终回答。
如果设置了本地 OpenAI 兼容 API Key,需要带 `Authorization`
```powershell
$env:OPENAI_COMPAT_API_KEY="local-secret"
```
```powershell
curl http://127.0.0.1:8080/v1/models `
-H "Authorization: Bearer local-secret"
```
## 配置项
| 环境变量 | 默认值 | 说明 |
| --- | --- | --- |
| `ZHANLU_LISTEN_ADDR` | `:8080` | 本地监听地址 |
| `ZHANLU_MOBILE_LOGIN_BASE_URL` | `https://ecloud.10086.cn` | 移动云公网登录 Base URL(兼容旧变量 `ZHANLU_SERVER_BASE_URL` |
| `ZHANLU_MOBILE_MODEL_BASE_URL` | `https://ecloud.10086.cn/api/query/aigateway` | 移动云公网模型网关 Base URL |
| `ZHANLU_UPSTREAM_PATH` | `/chat/completions` | 模型网关聊天接口路径 |
| `ZHANLU_DB_FILE` | `zhanlu.db` | 本地 SQLite 数据库路径,凭据与 token 统计均存于此,默认当前执行目录 |
| `ZHANLU_STATS_DISABLED` | `false` | 设为 `true` 关闭 token 用量记录(仅停止写入统计,凭据存储不受影响) |
| `ZHANLU_ACCESS_KEY` | 空 | 直接从环境变量提供 AccessKey |
| `ZHANLU_SECRET_KEY` | 空 | 直接从环境变量提供 SecretKey |
| `ZHANLU_TOKEN` | 空 | 直接从环境变量提供 Token |
| `ZHANLU_API_KEY` | 空 | 直接从环境变量提供已换取的模型 API Key |
| `ZHANLU_SSO_BASE_URL` | `http://4c.hq.cmcc` | 灵犀内网 SSO 备用页面 Base URL,非默认手机号登录流程 |
| `ZHANLU_SSO_EXCHANGE_URL` | `http://rdcloud.4c.hq.cmcc/cmdevops-aiplus-agent-gateway/api/acepilot/zhanlu/authToken` | 灵犀 SSO 换取用户资料的备用接口 |
| `ZHANLU_TOKEN_DECRYPT_KEY` | `3jw7woww2rvhla6k` | 解密 SSO 返回用户资料字段的 AES key(插件内置默认值) |
| `ZHANLU_PUBLIC_KEY_PEM` | 插件内置签名公钥 | 签名 URL 中 `authorization` 使用的 RSA 公钥,通常不需要设置 |
| `ZHANLU_PHONE_PUBLIC_KEY_PEM` | 插件内置手机号登录公钥 | 手机号验证码登录加密手机号和一次性 secret 使用的 RSA 公钥,通常不需要设置 |
| `ZHANLU_APIKEY_AUTH_SM2_PRIVATE_KEY` | 插件内置 SM2 私钥 | 换取模型 API Key 时 `X-Auth-Signature` 使用的 SM2 私钥,通常不需要设置 |
| `ZHANLU_PLUGIN_VERSION` | `1.4.2` | 请求头 `plugin_version` |
| `ZHANLU_UPSTREAM_TIMEOUT` | `300s` | 上游请求超时 |
| `ZHANLU_STREAM_IDLE_TIMEOUT` | `300s` | 预留的流式空闲超时配置 |
| `ZHANLU_LOGIN_PASSWORD` | 空 | `/login` 管理页面密码;设置后登录成功跳转到 `/admin/login` 管理湛卢凭据 |
| `ZHANLU_DEBUG` | `false` | 调试模式,错误信息更详细但会脱敏敏感 query |
| `OPENAI_COMPAT_API_KEY` | 空 | 本地 OpenAI 兼容接口鉴权 key |
## 凭据优先级
服务启动时按以下优先级加载凭据:
1. 环境变量 `ZHANLU_ACCESS_KEY``ZHANLU_SECRET_KEY``ZHANLU_TOKEN``ZHANLU_API_KEY`
2. `ZHANLU_DB_FILE` 数据库中持久化的凭据行。
登录页面保存后,运行中的服务会立即使用新凭据并写入数据库。若环境中只有 AK/SK/Token 而没有 `apiKey`,首次调用聊天接口时会自动按插件流程换取 API Key 并回写数据库。
## Token 消耗统计
代理在 `/v1/chat/completions` 完成后解析上游 `usage`(流式路径在透传 SSE 的同时旁路解析末块 `usage`,非流式路径在聚合时解析),将以下维度写入 `ZHANLU_DB_FILE`
- 时间、模型、流式/非流式
- `prompt_tokens` / `completion_tokens` / `total_tokens` / `reasoning_tokens`(思考模型)
- `cached_tokens`(来自 `prompt_tokens_details.cached_tokens`,提示缓存命中的 token 数)与缓存命中率
- 请求状态(`success` / `upstream_error`)与耗时
缓存命中率为 `cached_tokens / prompt_tokens`,仅在模型/网关支持 prompt 缓存且上游返回 `cached_tokens` 时非零。
管理页(需先登录管理页面)提供:
- `GET /admin/stats`:可视化页面,展示总览、按模型、按日柱状、最近请求明细,带重置按钮。
- `GET /api/stats`JSON 接口,支持 `since`/`until`RFC3339)、`model``limit` 查询参数。
- `POST /api/stats/reset`:清空统计(凭据不受影响)。
设置 `ZHANLU_STATS_DISABLED=true` 可停止写入统计。
## 安全说明
- `zhanlu.db` 数据库包含明文 `AccessKey``SecretKey``Token``apiKey`,请不要提交到仓库。
- 默认保存在当前执行目录的 `zhanlu.db`(建议通过 `ZHANLU_DB_FILE` 指向受保护路径)。
- 建议设置 `ZHANLU_LOGIN_PASSWORD`,避免公网暴露的 `/admin/login` 被直接访问。
- 错误响应默认不会返回签名 URL,避免泄露 `AccessKey``authorization``Signature`
- `ZHANLU_DEBUG=true` 时会返回更详细错误,但仍会对敏感 query 参数脱敏。
## 已知限制
- 灵犀内网 SSO`/auth/start``/auth/callback`)为备用接口,主流程是移动云手机号验证码登录。
- 手机号验证码接口可能有风控或频率限制;请按正常登录频率使用。
- 模型网关对 OpenAI `stream:false` 请求由代理负责聚合流式响应。
## 验证
```powershell
go test ./...
go build ./cmd/zhanlu-proxy
```
本地端点验证:
```powershell
go run ./cmd/zhanlu-proxy
curl http://127.0.0.1:8080/healthz
curl http://127.0.0.1:8080/v1/models
```