Files
dorm-power-monitor/go
fallensigh 57b63316f3 feat: 精确耗电计算(接入充值记录接口)
- 新增 queryISIMSRoomBuyRecord 充值记录获取 + 10分钟缓存

- 日耗电 = 剩余变化 + 当天充值电量(电价0.535元/度)

- 月度汇总增加充值次数/电量/金额

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-08-23 21:43:30 +08:00
..
2026-08-23 21:29:33 +08:00
2026-08-23 21:29:33 +08:00
2026-08-23 21:29:33 +08:00
2026-08-23 21:29:33 +08:00

宿舍电费监控

兰州大学宿舍电费实时监控与每日耗电统计 Web 服务。

基于 FasterLZU 登录接口 + 小富婆xiaofubao一码通电费查询接口构建。纯 Go 标准库实现零第三方依赖单二进制部署Docker 镜像仅 6.2 MB


功能特性

  • 实时电费Web 页面实时显示当前剩余电量、预估金额、房间信息30 秒自动刷新,可手动刷新
  • 每日耗电统计:每天 24:0000:00自动查询并记录电费日耗电 = 前一天剩余 - 当天剩余7 天柱状图 + 历史记录表
  • 失败重试:定时记录失败自动重试 5 次(间隔 60 秒),次日 00:00-00:05 窗口内可补记
  • 会话缓存:复用 application 域 shiroJID 会话(约 7 天有效),命中缓存时仅 2 个请求即可查电费;失效自动重新登录
  • 数据持久化:历史记录与会话缓存写入 JSON 文件,挂载 Docker 卷持久保存

架构

浏览器 (HTML+CSS+JS)
    │  http://localhost:8000
    ▼
Go Web 服务 (单二进制, go:embed 内嵌前端)
    ├── /api/electric  实时电费查询
    ├── /api/history   历史记录 + 耗电统计
    └── 定时任务       每天 00:00 记录失败重试5次
        │
        ├── FasterLZU 登录 (appservice.lzu.edu.cn, AES-CBC 加密)
        │     └── getSt → ST 票据
        │
        └── xiaofubao 链路 (6 步)
              getCodeV2 → getUserByCodeV2 → getUserCode
              → getUser4Authorize → queryBind → queryISIMSRoomSurplus

数据流

[查询请求]
    ├─ 缓存命中? ──是──→ 直接 queryISIMSRoomSurplus (2 个请求)
    └─ 否 ──→ FasterLZU 登录 → getSt → xiaofubao 6 步 → 回写缓存
                │
                ▼
    [结果] { room, surplus, amount, status }
                │
                ├─ 定时任务 → 写入 electric_history.json
                └─ API 响应 → 浏览器渲染

目录结构

final/web/go/
├── main.go            # HTTP 服务器 + 定时任务 + go:embed 静态文件
├── electric.go        # xiaofubao 6 步链路 + shiroJID 缓存
├── lzuapi.go          # FasterLZU 登录客户端 (AES 加密)
├── crypto.go          # AES-CBC 加密/解密 + MD5 签名
├── history.go         # 每日记录 JSON 存储 + 耗电统计
├── static/            # 前端 (go:embed 嵌入二进制)
│   ├── index.html
│   ├── style.css
│   └── app.js
├── Dockerfile         # 多阶段构建 → 6.2MB 镜像
├── docker-compose.yml
├── .env.example       # 环境配置模板
└── data/              # 运行时数据 (挂载卷, 不入库)
    ├── electric_cache.json     # shiroJID 会话缓存
    └── electric_history.json   # 每日电费记录

快速开始

方式一Docker Compose推荐

cd final/web/go

# 1. 准备配置
cp .env.example .env
# 编辑 .env填入真实账号密码
#   LZU_USERNAME=你的学号
#   LZU_PASSWORD=你的密码

# 2. 构建并启动
docker compose up -d --build

# 3. 访问
#   http://localhost:8000

方式二docker run

docker build -t electric-monitor .
docker run -d --name electric-monitor \
  -p 8000:8000 \
  -e LZU_USERNAME=学号 \
  -e LZU_PASSWORD=密码 \
  -v $(pwd)/data:/app/data \
  electric-monitor

方式三WSL/本机直接运行

cd final/web/go
# 直接编译运行(需 Go 1.18+
go build -o electric-monitor .
LZU_USERNAME=学号 LZU_PASSWORD=密码 ./electric-monitor

配置项

环境变量 默认值 说明
HOST 0.0.0.0 监听地址
PORT 8000 监听端口
LZU_USERNAME FasterLZU 登录账号(学号)必填
LZU_PASSWORD FasterLZU 登录密码 必填
LZU_SERVICE_ID 29615 宿舍电费服务 ID
XF_PLATFORM QHIT_EDU xiaofubao 平台标识
XF_AUTH_APPID 2606161276416311297 xiaofubao 应用 ID
XF_SCHOOL_CODE 10730 学校代码(兰州大学)
XF_YM_APPID 1810181825222034 一码通应用 ID
LZU_AES_KEY AES 加密密钥16字节由环境注入必填
LZU_MD5_KEY MD5 签名密钥(由环境注入)必填
CACHE_FILE data/electric_cache.json 会话缓存路径
HISTORY_FILE data/electric_history.json 历史记录路径
XF_CACHE_TTL 518400 (6天) 会话缓存有效期(秒)

API 接口

GET /api/electric — 实时电费

{
  "ok": true,
  "room": "兰州大学榆中本科生29号公寓3层29-321",
  "surplus": 72.57,
  "amount": 38.82,
  "status": "正常用电",
  "source": "cache",
  "time": 1787490436
}
字段 说明
ok 查询是否成功
room 房间全名
surplus 剩余电量(度)
amount 预估金额(元)
status 用电状态
source cache=缓存会话 / login=重新登录
time 查询时间Unix 秒)

GET /api/history — 历史记录与耗电统计(支持按月)

GET /api/history            # 全部记录
GET /api/history?month=2026-08   # 指定月份 (YYYY-MM)
{
  "ok": true,
  "month": "2026-08",
  "months": ["2026-08", "2026-07"],
  "records": [
    {"date": "2026-08-01", "surplus": 83.5, "amount": 44, "usage": 0.7}
  ],
  "daily": [
    {"date": "2026-08-01", "surplus": 83.5, "amount": 44, "usage": 0.7}
  ],
  "summary": {
    "month": "2026-08",
    "record_count": 5,
    "start_surplus": 83.5,
    "end_surplus": 72.5,
    "total_usage": 11.7,
    "avg_usage": 2.34,
    "recharge_days": 0,
    "max_usage": 8.9
  },
  "all_count": 7
}
字段 说明
month 当前查询月份(空=全部)
months 所有有记录的月份(降序,供前端选择器)
records / daily 该月每日统计(升序),usage=日耗电null=无基线)
summary 月度汇总:记录天数 / 月初月末电量 / 月总耗电 / 日均 / 疑似充值天数 / 单日最大耗电
all_count 全部记录数

跨月基线:指定月份时,月内第一天的耗电以上月末记录为基线计算(跨月衔接正确)。

定时任务说明

  • 触发时机:每天 00:00-00:05 窗口内Asia/Shanghai 时区)
  • 执行逻辑:查询当前电费 → 写入当日记录(source: auto
  • 失败重试:最多 5 次,间隔 60 秒;窗口内每分钟补记一次直至成功
  • 启动基线:服务启动时若今日无记录,立即查询建立基线(保证当日耗电可计算)

会话与缓存机制

  • 认证核心是 shiroJID HttpOnly cookie非 localStorage分 webapp 域7 天)与 application 域(可复用)
  • 缓存 application 域 shiroJID + 房间编码后,查询仅需 2 个请求queryBind + queryISIMSRoomSurplus
  • 缓存约 6 天后自动失效,触发重新登录并回写缓存
  • ST 票据getSt与 getUserCode 的 code 均为一次性,每次登录重新生成

内存占用

实测数据Docker 容器内运行)

指标 实测值 说明
容器 MEM USAGE 1.4 - 6.6 MiB docker stats 实测(缓存命中时最低)
VmRSS物理内存 11.4 MB 宿主机 /proc/<pid>/status
RssAnon堆/匿名页) 6.0 MB Go 堆 + 运行时数据
RssFile二进制/库) 5.4 MB 可执行文件映射(多进程共享)
VmSize虚拟地址 1.23 GB Go 运行时保留的虚拟地址空间,非实际占用
线程数 8 Go runtime 调度器 + 主线程
镜像大小 20.6 MB alpine 基础层 + 7.7MB 二进制
二进制大小 7.7 MB CGO_ENABLED=0 纯静态

内存构成分析

11.4 MB VmRSS 总物理内存
├── ~5.5 MB  二进制镜像映射(.text/.rodata/embed 静态资源,跨进程共享)
├── ~2 MB    Go runtime 基础GC、调度器、栈
├── ~2 MB    HTTP 连接缓冲 + 协程栈
├── ~1 MB    内嵌静态资源index.html + style.css + app.js ≈ 9.5KB 实际极小)
└── ~1 MB    零散堆分配JSON 解析、字符串、缓存结构)

代码级内存分析Go 视角)

固定成本(常驻):

  • Go runtime 基础GC、调度器、内存池mcache/arena~10-20MB RSS与业务无关是本服务内存的主要构成
  • go:embed 静态资源9.4KB 内嵌二进制,运行时零额外占用(serveStatic 每次请求复制 ~4KB 瞬时)
  • 配置字符串~几百字节,静态

增长项(唯一长期增长):

  • historyCache 每日 +1 条记录(~200B/条)→ ~70KB/年。10 年才 ~700KB可忽略。历史文件同理/api/history 响应随记录数线性增长

瞬态分配GC 回收):

  • 完整登录8 个 http.Client(共享 DefaultTransport 连接池,非泄漏)+ 8 个小 JSON 响应体,~几十 KB
  • 缓存命中1 个 client + 1 个响应体,~几 KB
  • JSON 编解码:json.NewEncoder 流式写出,无整包缓冲

goroutine 生命周期:

  • schedulerLoop常驻无限循环30s 检查一次),设计如此,非泄漏
  • 启动记录协程:成功或 5 次重试后退出(最长 ~5 分钟)
  • HTTP 处理协程:连接关闭即回收

已实施的内存/稳定性优化

优化项 说明
HTTP Server 超时 ReadHeaderTimeout=10s / ReadTimeout=30s / WriteTimeout=60s / IdleTimeout=90s,防止空闲/慢速连接堆积 goroutine
出站请求超时 所有 API 请求加 context.WithTimeout(20s),上游无响应时 20s 自动放弃,防止 queryLock 永久阻塞导致请求排队
移除 debugMain 修复编译阻断(原 -debug 分支引用未定义函数)

与 Python 版对比

指标 Python (requests + Flask) Go (本版)
运行时内存(常驻) 30-60 MB解释器+依赖) ~11 MB
启动速度 1-3 秒 <1 秒
部署体积 需 Python + pip 依赖 + venv 单二进制 7.7MB / Docker 6.2MB
依赖数量 requests/Flask 等 ≥5 个 0 个
内存泄漏风险 Python 引用计数/循环引用) Go GC + 自动回收)

优化建议

  • 本服务内存占用已极低1.4-6.6MiB无需进一步优化
  • 若需长期运行多年:可考虑历史记录裁剪(保留最近 N 年),仅影响 ~700KB/10年 级别
  • GOGC/GOMEMLIMIT 环境变量可微调 GC 频率(默认即可)

安全说明

  • 账号密码、加密密钥通过环境变量注入,不进镜像层、不入代码仓库
  • .env 已被 .dockerignore 排除,切勿提交到公开仓库
  • 服务默认监听 0.0.0.0:8000,如仅本机使用建议 HOST=127.0.0.1
  • 会话缓存与历史记录含个人数据,注意目录权限

常见问题

Q: 查询返回 ok: false A: 查看容器日志定位具体失败步骤(docker logs electric-monitor)。常见原因:账号密码错误、网络不通、服务端会话异常(会自动重试/重登)。

Q: 如何换绑定的房间? A: 房间来自 queryBind 接口返回的默认绑定,如更换宿舍需在 xiaofubao APP 中重新绑房后重启服务。

Q: 定时记录失败怎么办? A: 自动重试 5 次 + 次日补记。若持续失败,检查账号是否到期、网络是否可达。

版本历史

  • v1.0 (2026-08-23): Go 版完整实现Docker 部署,实时监控 + 每日耗电统计
  • v1.0.1 (2026-08-23): 内存/稳定性优化——HTTP Server 超时、出站请求 context 超时、修复 debugMain 编译问题;补充内存分析文档
  • v1.1 (2026-08-23): 按月查询——月份选择器导航、月度汇总(总耗电/日均/疑似充值/月初月末电量)、跨月耗电基线衔接