兰 亭 墨 苑
期货 · 量化 · AI · 终身学习
首页
归档
编辑文章
标题 *
URL 别名 *
内容 *
(支持 Markdown 格式)
# 套餐配额查询功能实现详解 > **日期**: 2026-06-30 > **作者**: 雨轩 > **标签**: 配额查询, 火山引擎, 智谱, web-panel, cookie, 技术实现 --- ## 一、背景与需求 在 Nanobot & Hermes Agent 升级完成后,我们将模型 Provider 切换到了火山引擎 Ark Coding Plan(字节跳动方舟编码套餐)。由于该套餐有月度配额限制,需要实时监控用量,避免超限导致服务中断。 此外,智谱 GLM 也有独立的套餐配额(Pro 版,含 Token 限额和 MCP 调用次数限额),同样需要可视化的监控手段。 初始需求: 1. 查询火山引擎 Coding Plan 的会话级/周度/月度三级配额 2. 查询智谱 GLM 的 Token 配额和 MCP 调用次数 3. 在 Web 管理面板(`hms.want.biz`)上以环形进度条和卡片形式展示 4. 通过 Hermes 和 Nanobot 的技能系统,支持在微信/钉钉中直接查询 --- ## 二、整体架构 ### 2.1 数据流 ``` 火山引擎控制台 API ──→ web-panel (server.py) ──→ index.html 前端 │ │ │ ├── /api/coding-quota (供前端调用) │ ├── /api/glm-quota (供前端调用) │ ├── /api/coding-cookies (存储 Cookie) │ ├── /api/glm-auth (存储智谱认证) │ └── localhost:9100 │ 智谱控制台 API ──────→ check.py 脚本 ──→ Hermes/Nanobot 技能 ``` 整个系统分为四层: **第一层:数据源** — 火山引擎和智谱的控制台 API。这两个 API 都需要认证信息(Cookie / JWT Token),不公开开放。 **第二层:代理服务** — `web-panel/server.py` 是运行在 localhost:9100 的 FastAPI 服务,作为中间代理层。前端不直接调用第三方 API,而是通过 web-panel 中转,这样认证信息只需要在服务端保存,不暴露给客户端。 **第三层:前端展示** — `static/index.html` 是单页 Web 应用,通过调用 web-panel 的 API 展示配额数据,支持双标签切换、环形进度条、倒计时显示。 **第四层:技能系统** — Hermes 和 Nanobot 的技能脚本(`check.py`),可以直接在命令行或通过微信/钉钉调用查询。 ### 2.2 认证机制 | 平台 | 认证方式 | 存储文件 | 更新方式 | |:----|:---------|:---------|:---------| | 火山引擎 | Cookie(5个字段)+ CSRF Token | `.coding_cookies` + `.coding_csrf` | 粘贴 Cookie 字符串或 JSON | | 智谱 GLM | Authorization JWT + 组织/项目 ID | `.glm_auth` | 粘贴 Cookie JSON 或手动填写 | 所有认证信息存储在 `/root/.hermes/web-panel/` 目录下,与 web-panel 的 token 文件同目录。 --- ## 三、火山引擎 Coding Plan 配额实现 ### 3.1 发现 API 火山引擎控制台的配额数据通过内部 API `https://console.volcengine.com/api/top/ark/cn-beijing/2024-01-01/GetCodingPlanUsage` 返回。该 API 需要: 1. **Cookie** — 包含登录会话的 JWT(`digest`)、用户 ID(`AccountID`)、设备标识(`volcfe-uuid`)、语言(`user_locale`)、CSRF 防护(`csrfToken`) 2. **x-csrf-token 请求头** — 与 Cookie 中的 `csrfToken` 值一致,双重认证防 CSRF 攻击 通过 F12 开发者工具找到该 API 后,提取了必要的认证字段。最初复制了 32 个 Cookie 字段,经过逐一测试精简到 **5 个必需字段**。同时发现 `userInfo` 字段虽然包含用户信息,但查询配额时并非必要。 ### 3.2 Cookie 管理 Cookie 管理经历了多次迭代: **v1 — 完整 Cookie 字符串** 直接将 32 个字段的完整 Cookie 字符串存入文件。缺点:字段过多,容易混淆,且包含大量埋点/统计类无关字段。 **v2 — 精简为 5 个必需字段** 通过测试确认只需要 `user_locale`、`volcfe-uuid`、`digest`、`AccountID`、`csrfToken` 五个字段。其他 27 个字段(`monitor_*`、`__spti`、`acw_tc` 等)均为统计/埋点用途,可以丢弃。 **v3 — 前端自动提取** 用户在粘贴 Cookie 时,前端 JS 自动从完整字符串中提取 5 个必需字段,丢弃其余字段。同时增加去重逻辑,避免 `user_locale` 等字段出现两次导致计数异常。 **v4 — Cookie JSON 解析** 支持浏览器插件导出的 JSON 格式 Cookie 数组(`[{"name":"digest","value":"eyJ..."},...]`),用户可以从 ExportCookies 类插件导出后直接粘贴,前端自动解析并提取必需字段。 ### 3.3 CSRF Token 的存储问题 这是一个关键的坑点。火山引擎的 API 需要双重认证: ``` Cookie: digest=xxx...; csrfToken=yyy... x-csrf-token: yyy... ← 与 Cookie 中的 csrfToken 值相同 ``` 最初我们把 Cookie 和 CSRF token 分开存储在两个文件: | 文件 | 内容 | |:-----|:------| | `.coding_cookies` | 5 个 Cookie 字段的字符串 | | `.coding_csrf` | CSRF token 值 | 但前端 `saveCookies()` 函数只保存了 Cookie 字符串到 `.coding_cookies`,**没有同时更新 `.coding_csrf`**。导致用户更新 Cookie 后,CSRF token 仍然是旧值,查询始终返回 `InvalidCSRFToken` 错误。 修复方案:后端 `/api/coding-cookies` 接口在收到 Cookie 字符串后,自动从中提取 `csrfToken` 字段并写入 `.coding_csrf` 文件,无需前端额外处理。 ### 3.4 过期管理 Cookie 中的 `digest` 字段是一个 JWT(JSON Web Token),解码后可以看到 `exp`(过期时间)字段。web-panel 后端自动解析该时间: ```python b64 = p.split("=", 1)[1].split(".")[1] b64 += "=" * (4 - len(b64) % 4) payload = json.loads(base64.urlsafe_b64decode(b64)) exp = payload.get("exp", 0) ``` 前端根据剩余时间显示不同颜色: - 🟢 >24小时:绿色 - 🟡 1~24小时:黄色 - 🔴 <1小时:红色 ### 3.5 返回数据结构 火山引擎 API 返回的三级配额: ```json { "Status": "Running", "UpdateTimestamp": 1782723231, "QuotaUsage": [ {"Level": "session", "Percent": 2.1, "ResetTimestamp": 1782738375}, {"Level": "weekly", "Percent": 38.2, "ResetTimestamp": 1783267200}, {"Level": "monthly", "Percent": 46.7, "ResetTimestamp": 1783439999} ] } ``` - `session` — 会话级配额,约几小时重置一次 - `weekly` — 周度配额,每周重置 - `monthly` — 月度配额,每月重置,**最需要关注** --- ## 四、智谱 GLM 配额实现 ### 4.1 发现 API 智谱控制台的配额 API 为 `https://bigmodel.cn/api/monitor/usage/quota/limit`。认证方式与火山引擎不同: 1. **Authorization 请求头** — JWT 格式的登录令牌,对应 Cookie 中的 `bigmodel_token_production` 2. **bigmodel-organization 请求头** — 组织 ID 3. **bigmodel-project 请求头** — 项目 ID ### 4.2 认证信息管理 智谱的认证信息通过 JSON 文件存储: ```json { "token": "eyJ...", "org": "org-xxx...", "project": "proj_xxx..." } ``` 前端展示时自动回填到输入框,用户修改后保存。支持两种更新方式: - **方式一**:粘贴 Cookie JSON,自动提取 `bigmodel_token_production` 的值填入 token 字段 - **方式二**:手动填写 token/org/project 三个输入框 ### 4.3 配额字段映射 智谱 API 返回的字段命名和含义不够直观。通过对比智谱控制台页面的实际显示,我们做了以下映射: | API 字段 | 控制台显示 | 我们的标签 | |:---------|:----------|:-----------| | `TIME_LIMIT, unit=5, number=1` | MCP 每月额度 | 🤖 **MCP 月度额度(次数)** | | `TOKENS_LIMIT, unit=3, number=5` | 每 5 小时使用额度 | ⏱️ **每 5 小时额度(Token)** | | `TOKENS_LIMIT, unit=6, number=1` | 每周使用额度 | 📅 **每周额度(Token)** | 每次遇到新的 `unit`/`number` 组合时,都需要查看控制台页面确认对应关系。 --- ## 五、前端实现 ### 5.1 双标签切换 配额页面分为两个标签页,通过 JS 控制显示/隐藏: ```javascript function switchQuotaTab(tab) { document.getElementById('quotaCoding').style.display = tab === 'coding' ? 'block' : 'none'; document.getElementById('quotaGlm').style.display = tab === 'glm' ? 'block' : 'none'; // 切换按钮样式 } ``` 火山引擎标签默认激活,智谱标签为次要。 ### 5.2 环形进度条 使用 SVG 绘制环形进度条: ```svg <path d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 ..." stroke-dasharray="46.7, 100" /> ``` `stroke-dasharray` 的第一个值等于百分比,第二个值固定为 100。SVG 的 `transform: rotate(-90deg)` 将起点从右侧移到顶部。 ### 5.3 手机自适应 手机屏幕上三列环图会溢出,通过检测容器宽度动态调整列数: ```javascript const cw = document.getElementById('page-quota').clientWidth; const cols = cw < 400 ? '1fr' : cw < 600 ? 'repeat(2,1fr)' : 'repeat(3,1fr)'; ``` - 窄屏(<400px):单列居中 - 中屏(400~600px):两列 - 宽屏(>600px):三列 ### 5.4 认证回填 两个标签页都实现了认证信息的回填。加载时自动调用 API 获取已保存的值并填入输入框: ```javascript // 火山引擎 const st = await api('coding-cookie-status'); if (st.cookies) { const pairs = essential.map(name => `${name}=${st.cookies[name]}`); document.getElementById('cookieInput').value = pairs.join('; '); } // 智谱 GLM const st = await api('glm-auth-status'); if (st.set) { document.getElementById('glmToken').value = st.token; document.getElementById('glmOrg').value = st.org; document.getElementById('glmProject').value = st.project; } ``` --- ## 六、技能系统集成 ### 6.1 Hermes 技能 Hermes 技能目录在 `/root/.hermes/skills/ark-quota-check/`,包含: - `SKILL.md` — 技能描述,定义了触发词("查一下配额"、"查一下额度") - `scripts/check.py` — Python 脚本,调用 web-panel API 获取两个平台的数据并格式化输出 ### 6.2 Nanobot 技能 Nanobot 技能目录在 `/home/nanobot/.nanobot/skills/coding-quota/`,同样的 `SKILL.md` + `check.py` 结构。被 weclaw 的 `n`/`nb` 别名调用。 ### 6.3 输出格式 两个技能使用相同的格式化函数,输出一致性: ``` 🔥 火山引擎 Coding Plan ------------------------------ 🟢 会话级: 3.0% (12h 48m后重置) 🟡 周度: 38.2% (5d 04h 后重置) 🔴 月度: 46.7% (7d 04h 后重置) 状态: ✅ 运行中 🧪 智谱 GLM (Pro) ------------------------------ 🤖 MCP 月度额度 (次数): 剩余 1000/1000 (0%) 🟢 每5小时额度 (Token): 0% 🟢 每周额度 (Token): 17% (5d 06h 后重置) 等级: pro ``` ### 6.4 调用链 微信 → weclaw → 用户说 "h 查一下额度" → Hermes 匹配 `ark-quota-check` 技能 → 执行 `check.py` → 调用 web-panel API(localhost:9100) → 返回格式化结果 → 微信回复 --- ## 七、过程中遇到的坑 ### 7.1 CSRF Token 不同步 **症状**:保存 Cookie 后查询仍然返回 `InvalidCSRFToken` **原因**:Cookie 中的 `csrfToken` 已更新,但 `.coding_csrf` 文件仍是旧值 **修复**:后端自动从 Cookie 字符串提取 csrfToken 并写入独立文件 ### 7.2 Cookie 字段重复 **症状**:保存时显示 "8/5 字段" **原因**:原始 Cookie 字符串中 `user_locale`、`AccountID` 等字段出现两次 **修复**:前端提取时按字段名去重 ### 7.3 手机端布局溢出 **症状**:三个环图在手机上右侧冲出框外 **原因**:`grid-template-columns: repeat(3, 1fr)` 在窄屏上不适用 **修复**:动态检测容器宽度,自动切换单列/双列/三列 ### 7.4 认证字段不回填 **症状**:关闭页面再打开,输入框为空 **原因**:没有在页面加载时从 API 获取已保存的认证信息 **修复**:`loadQuota()` 时自动请求 `coding-cookie-status` 和 `glm-auth-status` 接口,回填输入框 ### 7.5 窗口期显示问题 **症状**:GLM 的 "每5小时额度" 有时显示下次重置时间,有时不显示 **原因**:API 返回数据中 `nextResetTime` 字段对部分配额项缺失 **修复**:前端增加 `if (q.nextResetTime)` 判断,缺失时不显示重置时间 --- ## 八、总结 整个配额查询功能从需求提出到实现完成,经过了多次迭代。核心经验: 1. **认证信息管理是重中之重** — 两个平台使用不同的认证方式(Cookie vs JWT),需要分开存储和管理。CSRF 双重认证是常见的坑点,需要特别注意两个 token 的同步。 2. **前端体验要闭环** — 保存→验证→回填,每个环节都需要用户能感知到。尤其是认证信息的回填,否则用户每次打开页面都看到空的输入框,会怀疑是否已保存。 3. **Cookie 有生命周期** — 火山引擎的 digest(约 2 天过期)和 csrfToken(可能更短)有不同的过期时间。需要在面板上明确显示过期倒计时,并在过期后引导用户更新。 4. **三方 API 字段映射需要实测** — 智谱 API 返回的 `unit`/`number` 组合需要对照控制台页面逐一确认,不能凭猜测命名。 功能覆盖三个渠道: - 🖥️ **Web 面板**(hms.want.biz):可视化图表,双标签切换 - 🧠 **Hermes**(weclaw `h`):技能查询 - 🤖 **Nanobot**(weclaw `n`):技能查询 --- *雨轩于听雨轩 🌧️🏠*
配图 (可多选)
选择新图片文件或拖拽到此处
标签
更新文章
删除文章