一个可以查询Uclean洗衣机状态的系统
- Python 36%
- JavaScript 29.3%
- CSS 17.8%
- HTML 16.6%
- Dockerfile 0.3%
|
|
||
|---|---|---|
| .forgejo/workflows | ||
| resources | ||
| tests | ||
| web | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| main.py | ||
| pyproject.toml | ||
| README.md | ||
| requirements.txt | ||
| util.py | ||
| uv.lock | ||
UClean
UClean 是一个面向宿舍、楼层或小型公共空间的 U 净洗衣机状态看板。它保留最有用的能力:按房间查看洗衣机是否空闲、自动刷新状态、在空闲时提醒,并提供一个受保护的后台用于维护登录 token。
当前版本:1.0.0
功能
- 房间首页:输入房间 Code 或从已配置房间进入状态页。
- 状态页:展示空闲、运行中、异常、更新时间、数据源和下次刷新时间。
- 自动刷新:前端轮询状态接口,后端按房间限频,避免频繁请求 U 净 API。
- 空闲提醒:用户主动开启跟踪后,检测到洗衣机空闲会播放提示音并弹出通知。
- 后台维护:管理员可通过手机号验证码更新 token,也可手动写入 token。
- 安全基础:后台口令、CSRF 校验、敏感操作二次解锁、接口限流、审计日志、基础安全响应头。
- PWA:支持添加到主屏幕,静态资源离线缓存。
技术栈
- Python 3.12
- Flask 3
- Redis
- 原生 Jinja 模板、CSS、JavaScript
- Docker / Docker Compose
目录
.
├── main.py # Flask 应用与路由
├── util.py # 配置读写与 HTTP 请求封装
├── config.json # 本地房间与 token 配置
├── web/templates/ # 页面模板
├── web/static/ # CSS、JS、PWA 资源
├── tests/ # pytest 测试
├── Dockerfile
├── docker-compose.yml
├── pyproject.toml
└── requirements.txt
快速开始
1. 安装依赖
推荐使用 uv:
uv sync --group test
也可以使用 pip:
pip install -r requirements.txt
pip install pytest
2. 配置房间
创建或编辑 config.json:
{
"token": "",
"groups": {
"703": {
"title": "703",
"washer": {
"name": "703 洗衣机",
"qrCode": "https://q.ujing.com.cn/ucqrc/index.html?cd=..."
}
}
}
}
说明:
groups的 key 是访问路径里的房间 Code,例如703对应/703。title是页面展示名称。washer.name是洗衣机展示名称。washer.qrCode填 U 净二维码内容字符串,不需要解析二维码图片。- 初次部署时
token可以留空,之后在/admin里登录并自动写入。
3. 启动 Redis
本机 Redis:
redis-server
Docker:
docker run --name uclean-redis -p 6379:6379 -d redis:7
4. 启动服务
uv run python main.py
或:
python main.py
默认地址:
- 首页:
http://127.0.0.1:5000/ - 房间页:
http://127.0.0.1:5000/703 - 后台:
http://127.0.0.1:5000/admin - 健康检查:
http://127.0.0.1:5000/healthz
后台设置
后台默认关闭。设置管理员口令后,/admin 才会开放。
开发环境可以直接设置明文口令:
export ADMIN_PASSWORD="换成一个足够长的口令"
export SECRET_KEY="换成一个随机长字符串"
生产环境推荐使用哈希口令:
python -c "from werkzeug.security import generate_password_hash; print(generate_password_hash('你的管理员口令'))"
export ADMIN_PASSWORD_HASH="上一步输出的完整哈希"
unset ADMIN_PASSWORD
后台支持三类操作:
- 检查当前 token 是否可请求 U 净 API。
- 通过手机号验证码登录并更新 token。
- 手动写入已获取到的 token。
涉及短信发送、登录和 token 写入的操作需要先在后台二次解锁。
环境变量
| 变量 | 默认值 | 说明 |
|---|---|---|
REDIS_URL |
redis://127.0.0.1:6379/0 |
Redis 连接地址 |
MIN_FETCH_INTERVAL_SECONDS |
60 |
每个房间请求 U 净 API 的最小间隔 |
DATA_TTL_SECONDS |
3600 |
状态缓存保留时间 |
ADMIN_PASSWORD |
空 | 管理员明文口令,开发环境可用 |
ADMIN_PASSWORD_HASH |
空 | 管理员哈希口令,优先级高于 ADMIN_PASSWORD |
SECRET_KEY |
随机生成 | Flask 会话签名密钥,生产环境必须固定设置 |
SECURE_COOKIES |
0 |
HTTPS 部署时建议设为 1 |
SESSION_MAX_AGE_SECONDS |
21600 |
管理员会话最长有效期 |
ADMIN_REAUTH_SECONDS |
300 |
敏感操作解锁有效期 |
ADMIN_RATE_LIMIT_WINDOW_SECONDS |
300 |
后台限流窗口 |
ADMIN_PASSWORD_LIMIT |
10 |
管理员口令尝试次数上限 |
ADMIN_SET_TOKEN_LIMIT |
20 |
手动写入 token 次数上限 |
ADMIN_CHECK_TOKEN_LIMIT |
30 |
检查 token 次数上限 |
ADMIN_SEND_CAPTCHA_LIMIT |
3 |
单手机号发送验证码次数上限 |
ADMIN_LOGIN_LIMIT |
5 |
单手机号登录次数上限 |
ADMIN_IP_SEND_CAPTCHA_LIMIT |
5 |
单 IP 发送验证码次数上限 |
ADMIN_IP_LOGIN_LIMIT |
10 |
单 IP 登录次数上限 |
ADMIN_LOCKOUT_MAX_SECONDS |
900 |
管理员口令错误后的最大锁定秒数 |
ADMIN_LOCKOUT_RESET_SECONDS |
3600 |
管理员失败计数重置时间 |
TRUST_PROXY |
0 |
反向代理后方部署时,设为 1 才信任 X-Forwarded-For |
INSECURE_SKIP_TLS_VERIFY |
0 |
仅排障使用,设为 1 会跳过请求 U 净 API 时的 TLS 校验 |
Docker Compose 部署
准备环境变量:
export ADMIN_PASSWORD_HASH="你的管理员口令哈希"
export SECRET_KEY="随机长字符串"
启动:
docker compose up -d --build
查看:
docker compose ps
停止:
docker compose down
API
GET /api/meta
返回系统账号连接状态。
GET /api/<code>/washers
读取指定房间的洗衣机状态。接口优先使用 Redis 缓存,并按房间限制刷新频率。
常见返回码:
0:成功。2:尚未配置 token。3:token 已失效。4:房间配置有误。5:U 净接口异常。7:Redis 不可用。
GET /api/<code>/washers?force=1
尝试强制刷新。即使传入 force=1,仍会受后端限频保护。
GET /healthz
健康检查。Redis 可用时返回 200,不可用时返回 500。
测试
uv run pytest
或:
pytest
PWA
- iOS Safari:打开站点后,通过分享菜单添加到主屏幕。
- Android Chrome:打开站点后,通过浏览器菜单安装应用或添加到主屏幕。
静态资源由 web/static/sw.js 缓存。更新前端资源后需要提升 CACHE_NAME,否则老用户可能继续看到旧样式。