feat(权限): 收敛普通账号写权限至本人凭证

将调度时刻、采集参数等实例级配置收归管理员,普通账号仅可维护本人 Cookie 与 User-Agent。
新增 config.writable_by 作为唯一写权限入口,set_setting 强制全局键落到 user_id=0,
消除「管理员改了只有自己生效」的静默缺陷。新增 tools/check_docs.py 文档自检,
smoke 断言扩至 215 项、check_live 扩至 122 项并支持普通账号越权验收,
忽略 backups/、data/*.bak* 与 legacy-v1/,版本升至 v1.3.0。
这个提交包含在:
2026-09-18 08:46:00 +08:00
父节点 df7db3582e
当前提交 1bf961f6b3
共修改 42 个文件,包含 2615 行新增和 854 行删除
+50 -11
查看文件
@@ -74,7 +74,7 @@ docker compose exec portal python manage.py stats
```
完整复现步骤与原理见
[部署与运维指南](DEPLOYMENT.md#windows-绑定挂载的坑容器打不开数据库)。
[部署与运维指南](DEPLOYMENT.md#115-windows-绑定挂载的坑容器打不开数据库)。
### Q:`database is locked` / `disk I/O error`
@@ -104,7 +104,7 @@ python -c "p='docker/entrypoint.sh';d=open(p,'rb').read();open(p,'wb').write(d.r
### Q:采集报 `cookie_expired` / `unauthorized`
Cookie 过期。重新获取(见 [用户手册 3.2](USER-GUIDE.md#32-拿-cookie-的两种办法)),
Cookie 过期。重新获取(见 [用户手册 4.2](USER-GUIDE.md#42-拿-cookie-的两种办法)),
填进「配置管理 → **我的云端凭证**」,保存后按区间补采。
> Cookie 通常是浏览器会话级,**关掉浏览器可能就失效**。从已登录浏览器复制时勾选「保持登录」。
@@ -350,7 +350,7 @@ docker run --rm -v workbuddy-portal_wb_data:/data:ro -v "$PWD/backup":/backup \
alpine:3.20 tar czf /backup/wb-data-$(date +%F).tar.gz -C /data .
```
恢复见 [部署指南 6.3](DEPLOYMENT.md#63-恢复)。
恢复见 [部署指南 8.3](DEPLOYMENT.md#83-恢复)。
**别把新库配旧 WAL 用**——会损坏数据(用上面已 checkpoint 的单文件备份最省心)。
### Q:数据库文件越来越大
@@ -372,10 +372,33 @@ docker compose exec portal python manage.py vacuum
`明文凭证已加密:settings[uid=1].cookie`;
3. 建 `captchas` 表、给各表补 `user_id` 列与索引。
### Q:从 1.2.0 升级到 1.3.0 要做什么
**同样零手工动作**,但会做一次**配置作用域收敛**,值得知道它改了什么:
| 做了什么 | 效果 |
|---|---|
| 把管理员个人名下的**调度与采集参数**提升到实例级(`user_id=0`) | 这些策略从此「一台部署一套」,对所有账号统一生效 |
| 清掉这些键在 `user_id<>0` 下的残留 | 不会再有「某个账号偷偷带着一份自己的旧调度时刻」 |
| 实例级不再保留 `cookie` / `user_agent` | 凭证只属于个人;实例级存凭证等于给所有账号发同一张身份 |
审计里会留一条 `promote_global_settings`,日志里能看到「配置作用域收敛」。
迁移**幂等**,可重复启动。
**升级后建议确认两件事:**
1. 各账号的 Cookie 仍然「已配置」(能解密)——「配置管理 → 我的云端凭证」;
2. 用一个普通账号登录,「任务管理」里的调度时刻应当是**只读**的,
点保存会被拒并点名越权项。
> 如果你的旧部署里**不同账号原本设了不同的采集时刻**,升级后会统一成管理员那一刻。
> 这是刻意的(一台部署一个时刻表,避免多个采集抢同一把写锁),
> 但需要提前知会使用者:他们之后要改时刻得找管理员。
看迁移结果:
```bash
docker compose logs portal | grep -i migrate
docker compose logs portal | grep -iE "迁移|migrat|收敛|promote"
docker compose exec portal python manage.py users
docker compose exec portal python manage.py stats
```
@@ -391,14 +414,25 @@ docker compose exec portal python manage.py stats
> 迁移用 `PRAGMA user_version` 记录版本,**幂等**:重复启动不会重复迁移。
> 改列这种操作现在也由 `init_db()` 自动完成,不再是「需要手工迁移」。
> 想确认当前库结构版本:
> ```bash
> docker compose exec portal python -c "
> from workbuddy_portal import db; c = db.connect()
> print('user_version =', c.execute('PRAGMA user_version').fetchone()[0])"
> ```
> 1.2.0 是 `2`,1.3.0 是 `3`。
### Q:日志在哪、怎么滚动
| 位置 | 内容 |
|---|---|
| `docker compose logs portal` | 容器 stdout(entrypoint + waitress) |
| 命名卷 `workbuddy-portal_wb_logs` 里的 `app.log` | 应用日志,滚动 2 MB × 3 |
| 页面「日志管理」 | 采集逐行日志 + 应用日志尾部 + 操作审计 |
| 位置 | 内容 | 谁能看 |
|---|---|---|
| `docker compose logs portal` | 容器 stdout(entrypoint + waitress) | 能登服务器的人 |
| 命名卷 `workbuddy-portal_wb_logs` 里的 `app.log` | 应用日志,滚动 2 MB × 3 | 能登服务器的人 |
| 页面「日志管理」 | 全实例采集逐行日志 + 应用日志尾部 + 操作审计 | **仅管理员** |
| 页面「任务管理 → 运行历史」 | **你自己**的采集记录(触发方式、耗时、条数) | 所有登录用户 |
> 普通账号看不到「日志管理」页(导航里不显示,直接敲 `/logs` 返回 403)。
> 自己排错先用「任务管理 → 运行历史」,需要逐行日志时找管理员。
### Q:想改采集的接口地址(走镜像/代理)
@@ -415,13 +449,18 @@ docker compose exec portal python manage.py stats
**五层,前两层必须跑绿**:
```bash
python tools/smoke.py # 离线回归 165 项(不需要起服务)
python tools/smoke.py # 离线回归 215 项(不需要起服务)
python tools/check_docs.py # 文档自检(改过任何 md 就跑)
python tools/demo_data.py # 可选:造一份合成示例库
python manage.py serve --port 8849 --no-scheduler # 另开终端
python tools/check_live.py --base http://127.0.0.1:8849 # 真实 HTTP 83 项
python tools/check_live.py --base http://127.0.0.1:8849 # 真实 HTTP 122 项
python tools/shots.py --base http://127.0.0.1:8849 --full # 界面截图 + JS 报错
```
> 对**示例库**跑 `check_live.py` 时记得加 `--db data/demo/usage.sqlite` ——
> 它的 `--db` 默认值是真实的 `data/usage.sqlite`,不传会从错误库里取验证码答案,
> 症状是「登录失败」加一串看不懂的断言失败。
两个新增参数值得一提:
- `check_live.py --db <路径>`:让它直接读库里的验证码答案,从而**自动过验证码**登录;