一个托盘装两家账:DeepSeek 余额与 GLM 套餐额度的双数据源设计
先说一个有点荒诞的起点:我每天要开两个Admin看"还剩多少钱"。一个是 DeepSeek 开放平台的余额页,一个是智谱的套餐用量页。两边都只是看一眼数字,但加起来一天要三十秒,一周就是三分半,一个月十几分钟——纯粹的时间损耗,没有任何产出。
于是做了个 Windows 托盘小工具,把两家账并到同一个图标里。这篇文章讲数据层怎么接的,以及三个真正卡住我的坑。
一、两种"钱"根本不是一回事
动手之前先把这个想清楚,否则字段设计一定跑偏:
| DeepSeek | GLM Coding Plan | |
|---|---|---|
| 计费方式 | 按量付费,充多少用多少 | 订阅套餐,按月付费 |
| 关键数字 | 账户余额(元) | 额度剩余百分比 |
| 用完的后果 | 服务中断,充值即恢复 | 等周期刷新,不扣钱 |
| 刷新机制 | 无 | 5 小时滚动 + 每周固定 |
| 要不要换算成钱 | 要 | 不要 |
最容易被带偏的是最后一行。GLM 的套餐额度不能折算成金额——它不是"你还有 37 块钱的额度",而是"这个 5 小时窗口里你还能用 62%"。硬要标价反而误导:同样是 62%,在 Lite 套餐和 Max 套餐背后是完全不同的算力。
所以界面分成两个页签,两套不同的表达:DeepSeek 页用金额,GLM 页用百分比。不做统一抽象,因为它们在语义上确实不同。
二、DeepSeek:官方接口 + 一份不体面的备胎
DeepSeek 这边有两条路:
余额走官方公开接口,稳定可靠:
GET https://api.deepseek.com/user/balance
Authorization: Bearer sk-xxx
今日消耗就没这么体面了。官方没有公开的用量查询 API,只能调网页Admin自己用的内部接口:
GET https://platform.deepseek.com/api/v0/usage/amount?month=&year=
这个接口要的是网页登录态的 token,不是 API Key。两者是两套凭证,混用一律 401。
考虑到内部接口随时可能改版,做了双轨降级:
- 能拿到登录态 → 调内部接口,界面标「接口精确」
- 拿不到 → 用余额下降的累计差值估算,标「余额差值估算」
第二条不是凑数。本地记账时要注意一个细节:充值会让余额上涨,不能直接算差值。逻辑是每次拉取余额时和上次比较,只有下降才算消耗,上涨视为充值并重置基准。
三、GLM:一个接口,三种窗口
GLM 的接口反而更规整——一个端点把三档额度一起返回:
GET https://open.bigmodel.cn/api/monitor/usage/quota/limit
Authorization: <完整 API Key> ← 注意这里,后面细说
国内 open.bigmodel.cn,国际 api.z.ai,路径和返回结构完全一致,同一个 Key 两边都能查,主要差异是文案语言。
响应长这样(截取关键部分):
{
"code": 200,
"data": {
"level": "pro",
"limits": [
{ "type": "TOKENS_LIMIT", "unit": 3, "number": 5, "percentage": 38,
"nextResetTime": 1781345721976 },
{ "type": "TOKENS_LIMIT", "unit": 6, "number": 1, "percentage": 55,
"nextResetTime": 1781928310123 },
{ "type": "TIME_LIMIT", "unit": 5, "number": 1,
"usage": 1000, "currentValue": 120, "percentage": 12,
"nextResetTime": 1783915420110 }
]
}
}
limits 是个混合数组,必须靠 unit 才能判断它是什么窗口:
| type | unit | 含义 | 界面上写成 |
|---|---|---|---|
| TOKENS_LIMIT | 3 | 小时维度 | 5 小时额度 |
| TOKENS_LIMIT | 6 | 周维度 | 1 周额度 |
| TIME_LIMIT | 5 | 月维度 | MCP 工具额度(月) |
这里有两个细节值得记:
unit=6的周窗口部分账号不返回。前端不能假设它一定存在,缺了就隐藏整块,不能显示 0%- 两个类型的百分比字段含义不同:
TOKENS_LIMIT只给percentage(已用百分比),TIME_LIMIT额外给usage/currentValue,可以自己算
四、三个真正卡住我的坑
坑一:Authorization 到底要不要 Bearer
我在两份不同的技术文档里看到了完全相反的说法:
A 文档:authorization header 直接传完整 API key,不要加 Bearer,该接口加 Bearer 会鉴权失败。
B 文档:Authorization: Bearer <key>
互相矛盾,谁也没说清适用条件。我没有账号可以一条条试,最后干脆让代码两种都试:
foreach (bool bearer in new[] { false, true })
{
var (ok, body) = await RequestAsync(host, apiKey, bearer, ct);
if (!ok) continue; // 401 就换另一种
var snap = Parse(body, host);
if (snap is null) continue;
_needBearer = bearer; // 成功的方式缓存下来
return snap;
}
第一次连上之后把成功的方式记在静态字段里,后续不再重复探测。日志里会打印最终用了哪种,方便下次接口变更时排查。
结论:这类"文档打架"的情况,与其猜,不如花十行代码把两种都试一遍,然后缓存结果。
坑二:刷新时间绝对不能用本地规则算
这是最危险的一个坑,因为它不会报错,只会安静地给你错误的数字。
GLM 官方规则是:
- 5 小时额度:从每次请求消耗的时刻起,滚动 5 小时后重置
- 周额度:按下单时间起每 7 天刷新
注意"滚动"两个字。它不是整点对齐——不是"每天 9 点、14 点、19 点重置",而是你最后一次消耗往后推 5 小时。这意味着根本不存在一个可以用本地时钟推算的重置时刻。
好在这个接口直接给了 nextResetTime(毫秒时间戳)。直接用它,一行本地推算都不要写。
long resetMs = ReadLong(item, "nextResetTime");
var resetAt = resetMs > 0
? DateTimeOffset.FromUnixTimeMilliseconds(resetMs).LocalDateTime
: (DateTime?)null;
而且 nextResetTime 是可选字段,缺失时只能隐藏"X 小时后重置"这一行,不能退回去猜。
如果一开始按"每日 9/14/19 点重置"的直觉去实现,界面上会显示一个看起来很合理的倒计时,但它是错的——而且错得很隐蔽,因为数字每天都在变,你不会怀疑它。
坑三:托盘 tooltip 只有 63 个字符
NotifyIcon.Text 有硬上限 63 个字符,超了会被系统截断(甚至有些 Windows 版本会直接抛异常)。
本来想要的悬停文案是:
DeepSeek 余额 ¥233.37 · 今日 ¥0.79 | GLM 5h 剩 62% 周剩 45% | 当前低谷 距高峰 1 天 20 小时
一算 60 多个字符,加上中文占位,直接爆掉。
只能压。最后压成:
DS ¥233.4 | GLM 5h 62% 周45% | 低谷→09:00
37 个字符,留出余量。压缩策略:
DeepSeek→DS(上下文里没有歧义)剩余 62%→62%(剩余是默认语义,耗尽时才需要强调)当前低谷 距高峰 1 天 20 小时→低谷→09:00(精确时间比模糊时长更有用)- 两位小数 → 一位(托盘又不是账本)
关键取舍:tooltip 的定位是"扫一眼知道要不要点开",不是"完整信息"。完整信息留给浮窗。
五、还有一个设计上的自我推翻
进度条一开始画的是「已用比例」。结果界面上出现"剩余 62%"配一条 38% 填充的绿条——数字和条方向相反,一眼就会看反。
改成填充「剩余比例」后,62% 就配 62% 的条,视觉和数字对齐。虽然从"油表"的角度说,显示已用更符合直觉,但在百分比数字就在旁边的情况下,一致性比隐喻重要。
六、结果
界面是两个页签,鼠标点一下切换,托盘右键也能直接打开指定那页。
DeepSeek 页:余额、赠送/充值拆分、今日消耗、请求数、Token 用量、缓存命中率,以及一块峰谷时段轴——高峰时段(北京时间 09:00–12:00 / 14:00–18:00)价格是低谷的两倍,这个信息值得常驻可见。
GLM 页:套餐等级、三档额度的剩余百分比、进度条、下次刷新时间、已用/总量。
Tech stack很朴素:C# / WinForms + WebView2 渲染界面,前端纯 HTML/CSS/JS,没有框架。选 WebView2 是因为做图表和动画比 WinForms 自绘省事太多,而且改样式不用重新编译——Web 目录和 exe 放在一起,改完刷新就生效。
七、没能解决的事
精确用量依然依赖网页登录态。 我做了内嵌浏览器自动拦截 Authorization 头,但这条路本质上是在蹭内部接口,随时可能失效。所以双轨降级不是可选项,是必需品。
峰谷时段的法定节假日无法自动判断。 官方规则说节假日全天低谷,但没有公开的节假日日历接口,只能在配置文件里手动加日期。这是一个已知的、只能靠人工维护的缺口。
桌面应用没有 Web 地址。 想把它发到作品区,卡在"作品必须有 HTTPS 可达地址 + 同源 OAuth 回调"这条上——这个工具连服务器都没有。这是平台模型和桌面工具的天然错位,暂时无解,只能先发在综合Discussion。
代码在 gitee.com/aowind/DeepSeekPulse,MIT 之外的授权没想好,先放着。GLM 那部分接口解析逻辑欢迎拿去用——尤其是 unit 映射和"别本地算刷新时间"这两条,能省你至少半天。