本文只讨论技术实现。用到的接口是学校面向小程序开放的只读查询接口,不需要登录、不涉及个人信息。文中的实测数据都来自自己的设备,不包含任何同学的信息。
起因
宿舍电费是预付费式的:余额扣完就直接断电,而且断电那一刻你才会知道。GDPC比较神的地方在于晚上要是电费不够,是无法充值的。在炎热的夏天,没有空调,我觉得没人能安稳地睡觉
想查余额,得打开学校的小程序、点进宿舍电费、再一层层翻到自己的房间。而且GDPC还没有电费不足警告。
后来发现学校小程序里那个查询请求,本身不需要登录态:只要构造对请求,任何人都能拿到全校所有房间的余额。这就有了折腾的空间。
于是有了两个客户端:
Windows 托盘程序
常驻右下角,图标颜色就是电量状态,低电量弹通知。单文件 54 KB。
Android 应用「电量哨兵」
手机上随时看,低于阈值推送通知,还带 7 天余额曲线。APK 1.7 MB。
两个程序都不含任何服务器:数据由你自己的设备直接向学校接口查询。
数据从哪来?
接口长这样(这是整个项目的地基):
请求体只有一个 implType=CGCOMMON0001,返回的是全校两千多个房间的列表,包含每个房间的 powerBalance 字段,整个响应约 940 KB。
页面原始请求里还带了一个 token=<TOKEN> 参数(学校把令牌直接渲染进了页面里的每个链接)。实测这个参数被服务端完全忽略,所以两个客户端都不携带它。
实测:任何带 Origin 头的请求都会被 403
这是整个项目里最关键的一条限制,也是我踩了最久的坑。同一台机器、同样的时间、只改请求头,结果完全不同:
| 请求特征 | 结果 |
|---|---|
带 Origin 头(浏览器一定会带) | 403 |
只带 User-Agent + X-Requested-With: XMLHttpRequest | 200 |
响应头里有没有 Access-Control-Allow-Origin | 没有 |
结论:
- 浏览器前端不可能直接调这个接口——请求一旦带上
Origin就是 403 - 就算绕过了 403,响应也没有 CORS 头,浏览器依然不允许前端读取内容
所以这个接口只有非浏览器客户端能用。学校小程序之所以没问题,是因为它的请求由微信宿主发出,不受浏览器同源策略约束。
这一条直接决定了我后面所有方案的形态:要么写桌面/手机客户端,要么自己架一台服务器去查。
第一版:Windows 托盘程序
电脑基本一直开着,所以托盘程序是最省事的形态。


它的几个设计取舍:
- 单文件 54 KB,零依赖。用 Windows 自带的 .NET Framework 4.8 编译器构建,不需要安装 .NET SDK 或运行时,Win10/11 直接能跑。绿色程序,拷走就能用。
- 不预置任何房间。首次启动让你从下拉列表里自己选「校区 → 楼栋 → 房间」,避免把某个人的宿舍信息编译进 exe 里。
- 图标颜色即状态:绿=充足、黄=接近阈值、红=需充值、灰=查询失败。不用点开,瞄一眼就知道。
- 支持开机自启,也有
--uninstall-autostart一键取消。 - 一堆无界面参数用于自检:
--check(结果写文件,便于脚本化验证)、--selftest、--test-alert(预览告警弹窗样式)、--dump-ui(把弹窗渲染成位图并导出控件布局)、--dump-settings(无人值守跑一遍设置窗口的下拉联动与保存往返)。
最后两个参数是我特意加的:在没有人盯着屏幕的情况下,也能验证界面逻辑是对的。这对一个”改完只能靠自己点几下确认”的小工具来说,价值比想象中大。
中途差点做成的网站,以及为什么放弃
第二个客户端的形态我纠结了很久。最自然的想法是做个网站:任何设备打开浏览器就能看,还能顺手做 PWA 推送,连 App 都不用装。
技术上是可行的——只要有一台服务器代替浏览器去请求接口(避开 Origin 限制),再把结果转给前端就行。我连部署方案都选好了,EdgeOne 的 Edge Functions + KV,成本几乎为零。
然后在算流量的时候停下了:
1 | 5 分钟轮询一次 = 288 次/天 |
真正的转折点不是流量,而是风险的归属:
这个方案要求一台”第三方 IP 的服务器”持续高频请求学校的接口。如果学校因此把 WAF 规则收紧——比如加上频率限制、封禁非校园网 IP。那就不好玩了
技术可行 ≠ 应该做。
第二版:Android 应用
放弃网站方案后,问题回到「怎么让手机上也能看」。想通了一件事:
既然每个同学都有自己的手机和流量,为什么还要一台中心服务器?让手机自己去查就好了。

这一个念头把所有服务端依赖都消掉了:
省掉的
- 服务器、域名、备案
- 推送服务与密钥(VAPID / FCM)
- 每月流量费
- 单点故障
不太优美的地方
- iOS 用不了
- 后台查询受系统省电策略影响
应用本身(Kotlin,minSdk 24 / targetSdk 34,没有任何第三方库,只用了官方的 androidx.work):
- 后台定时查询,间隔可选 15 / 30 / 60 / 120 / 240 分钟
- 低于阈值发系统通知,可以选「进入预警区也提醒」和「充值恢复后提醒一次」
- 桌面图标随电量变色(四个状态各一个图标)
- 桌面小组件:数字 + 状态 + 更新时间,颜色跟着状态走
- 7 天余额曲线:手绘的自定义 View,图上还画出提醒阈值和预警线,一眼能看出还剩多少余量
- 运行自检页:上次成功时间、后台执行次数、通知权限、图标切换状态、历史采样数
- 深色模式、卡片式界面


使用门槛刻意压得很低:选好房间、填一个阈值就能用,其余都是默认值。
Android 上的五个坑
这部分是我觉得最值得记下来的。**它们都不是”写错了一行代码”,而是”对运行环境的假设错了”**。
Android 的 WorkManager 周期任务最短间隔就是 15 分钟,这是系统硬限制。而且更要命的是:
它只保证”大概在这个频率附近运行”,不保证到点必查。
Doze 模式、应用待机分组、厂商省电策略都会推迟甚至暂停这些任务。如果一个提醒应用假装自己能”准点提醒”,用户就会在真正需要它的那天发现它没响。
所以我的做法是:不假装。主界面明确写着”受 Android 后台限制,本应用无法保证到点必查”,同时给一个自检页,让用户能自己看到”上次成功是什么时候、后台任务实际跑了几次”。看得见,才不会误判。
Android 没有官方的”动态改图标”接口。”变色图标”是靠 manifest 里四个 activity-alias(每个别名一套图标),运行时启用其中一个、禁用其余三个实现的。
结果图标永远不变色,而桌面小组件一切正常。原因很隐蔽:
1 | manifest 里别名写的是相对名 .LauncherOk |
debug 构建带了 applicationIdSuffix = ".debug",两个名字就不一样了。对着一个不存在的组件调 setComponentEnabledSetting 会抛异常——而我把异常静默吞掉了,于是表现就是”什么都没发生”。
更阴的是:release 构建下两个名字恰好相同,这段代码会”碰巧能用”。所以我修的时候没有只改字符串,而是从 PackageManager 查真实组件名(顺带必须带 GET_DISABLED_COMPONENTS,否则只能看到当前启用的那一个),并且把结果写进自检页——现在失败会明明白白显示”找不到图标别名组件”,而不是装作没事。
我一开始在小组件上显示”刚刚更新 / N 分钟前”,结果它**永远显示”刚刚更新”**。
原因不是时区也不是格式化,而是重绘时机:小组件只在①轮询成功后、②系统每 30 分钟的 tick 时重绘,而①几乎总是紧跟在轮询成功之后——那一刻算出来的差值永远是 0 分钟。它根本没有机会显示”3 小时前”,因为没有谁会在 3 小时后叫它重画。
同一个逻辑,在”每次打开都重算”的网页里完全正确,搬到”事件驱动重绘”的小组件里就必然错。改成绝对时间(09:30 更新 / 昨天 21:30 更新)之后,它不需要重绘也永远真实。
调试时发现下拉框的选中行偶尔闪出品红色高亮,复选框的勾是系统蓝。
我第一反应是”主题强调色被厂商改了”,于是在自己的主题里把 colorAccent、colorActivatedHighlight 等全部写死——没用。真正的原因是厂商不只改主题属性,还会直接替换框架的布局与 drawable 资源,那些资源引用的是厂商私有颜色,覆盖 AOSP 属性自然管不到。
而且高亮可能来自两层:
1 | ② ListView 自己的选中高亮层 ← 主题的 listSelector,画在行的【背后】 |
我第一版只换掉了 ①,但把行背景设成了透明——等于把 ② 那层露出来给它画。最终的解法是自绘行布局 + 不透明背景:不管粉色来自哪一层都被盖住,也不需要先猜出厂商的替换机制。


应用里有个「允许自启动」按钮,跳转到系统的自启动管理页。
这个页面属于别的应用(com.huawei.systemmanager 之类),而 targetSdk ≥ 30 时受软件包可见性限制:不在 manifest 里用 <queries> 声明这些包名,resolveActivity 对它们一律返回 null。
结果是:点按钮什么都不发生,也没有任何报错。这种”静默失效”最耗时间——你甚至不知道该从哪里查。
修法除了声明 <queries>,我还加了一段脚本核对”代码里的候选清单”与”manifest 声明”完全一致:这两个清单一旦不一致就是静默失效,靠人眼核对迟早出错。
版本与下载
两个程序互相独立,看你手边是什么设备,各取所需。
电脑端:Windows 托盘程序
- 形态:单个
PowerFeeTray.exe,54 KB,绿色免安装,拷走就能用 - 环境要求:Windows 10 / 11。用系统自带的 .NET Framework 4.8 运行,不需要安装运行时或 SDK
- 当前版本:v1.0.0
- 能做什么:托盘图标按电量变色、低电量弹窗提醒、开机自启、三级下拉选房间,
另有--check/--selftest/--dump-ui等无界面自检参数,便于脚本化验证
手机端:Android 应用「电量哨兵」
查询 + 房间三级选择 + 阈值设置 + 系统通知 + 动态图标 + 桌面小组件 + 运行自检页。
新增 7 天余额曲线、深色模式、卡片式界面、「允许自启动」一键跳转;修复图标不变色、下拉框粉色高亮、换宿舍后数据未重置等问题。
- 环境要求:Android 7.0 及以上;iPhone 装不了(APK 无法安装)
- 体积:约 1.7 MB,没有任何第三方依赖
- 首次使用三件事:允许「未知来源」安装 → 授予通知权限 →
在系统设置里允许自启动、并加入电池优化白名单(小米 / 华为 / 荣耀 / OPPO / vivo 尤其严格)
还没做好的事
诚实地列一下目前的问题:
最后
这两个程序本身都不复杂,加起来也就几千行。
现在两个客户端都在正常跑着。如果你也住宿舍、也有这样一个接口,希望这篇里踩过的坑能让你少走一点弯路。