多用户之前是「知道牧场名就能玩」:?farm=boss 直接进别人家里。 现在网页端用用户名登录(用户选择:不设密码、一账号一个牧场, 用户名即身份——互信小圈子模型,防的是随手改 URL,不是蓄意攻击)。 * 新增 farm/server/auth.py:HMAC-SHA256 签名的 HttpOnly Cookie (sf_auth,30 天,恒时比较防篡改)。密钥存 <save_dir>/secret.key, 重启不掉线,也不用建表存 token。 * app.py 三个认 ?farm= 的地方(session_of / SSE / POST /api/farms) 统一规则:有 Cookie 强制本人的牧场(?farm=/X-Farm-Id 被忽略), 无 Cookie 保持旧语义——bot/TUI/CLI 脚本零改动照常工作。 * 新路由:POST /api/login(全局 token 照常把它挡在门外)、 POST /api/logout、GET /api/me(刻意不 guard:首屏只问"登录没")。 * 网页:移除牧场名输入框,改为登录遮罩 + 用户徽章 + 退出按钮; 首屏先 whoami() 再决定连接还是弹登录。 测试 +13:Cookie 伪造/过期/跨密钥/垃圾串/非法用户名/密钥跨重启复用/ session_of 优先级(starlette Request 用 scope dict 直接构造,不需要 httpx)9 项纯逻辑档 + 4 项 TestClient 档。528 项 OK(skip 39)。 真机验收 16/16:登录/HttpOnly/?farm= 被忽略/动作落自己牧场/篡改 退回未登录/退出/非法名 400;跨重启 Cookie 仍有效且进度保留。
58 lines
4.2 KiB
Markdown
58 lines
4.2 KiB
Markdown
# SimpleFarm 项目长期约定
|
||
|
||
## 是什么与运行
|
||
终端农场游戏 + 多人服务器(HTTP + SSE 给 AI/脚本;TUI/网页纯客户端)。
|
||
uv 管理:所有命令带 `uv run `;依赖只改 pyproject(uv add/remove),uv.lock 要提交;
|
||
requirements.txt 是导出产物只读。门禁(改动后必跑):
|
||
`uv run ruff format . && uv run ruff check . && uv run pyright && uv run python -m unittest discover -s tests`
|
||
(现 528 项;TestClient/真机档 39 项因无 httpx 自动 skip;`ruff check .` 会扫 tests,
|
||
测试文件要自己保持 lint 干净)。性能复核 `uv run python tools/bench.py --repeat 5`。
|
||
|
||
## 玩法与存档
|
||
- 一天 40 分钟(DEFAULT_TICK_SECONDS=2400);**没有手动快进一天**的入口,别再加。
|
||
- 天气 5 种决定水分蒸发,`state.forecast` 明天提前可见;收获进 `inventory` 谷仓、
|
||
`sell` 才换钱;建筑每天在 advance_day 里**先于生长**执行。
|
||
- 存档 SQLite `saves/farm.db` 一牧场一行:标量列 + 整份 payload JSON(加字段改
|
||
`_raw_state()`,不改表结构);SAVE_VERSION=2 / MIN=1 向后兼容;老 JSON 启动时自动
|
||
迁移(原文件 *.json.migrated 留底)。`StorageError = sqlite3.Error`,别写 except OSError。
|
||
WAL 是持久属性,建库设一次即可。测试钩子:`advance_day(weather="sunny")` 钉死天气。
|
||
|
||
## 架构约定
|
||
- 分层:engine 不认识网络;`server/actions.py` 是动作唯一登记表;RemoteFarm 与
|
||
FarmEngine 同名同语义(加动作两边都补)。StateView.apply():带 plots=整体替换、
|
||
不带=合并字段。farm/keys.py 是按键名唯一来源(全小写),tui.py 不许手写字符串字面量。
|
||
- session 的 `dirty`(推送节流,250ms 清)与 `unsaved`(自动存档依据,只有 save() 清)
|
||
必须分开——合并会让 autosave 瞎掉。网页禁用 window.prompt/confirm(嵌入预览里被
|
||
静默屏蔽),交互一律页面内弹层。
|
||
- **用户名登录(2026-09-23)**:仅用户名、一账号一牧场(用户名即 farm_id)。
|
||
`auth.py` 签发 HMAC HttpOnly Cookie `sf_auth`(30 天),密钥 `<save_dir>/secret.key`
|
||
(重启不掉线)。app.py 三个认 ?farm= 的地方(session_of / SSE / POST /api/farms)
|
||
都是**有 Cookie 强制本人牧场、无 Cookie 保持 ?farm= 旧语义**(脚本/AI 兼容)。
|
||
动登录逻辑别破坏这条边界;/api/me 刻意不 guard。
|
||
- SSE `/api/stream` 是唯一实时通道(单向);写操作一律 POST /api/actions;停机必须
|
||
hub.close_all()。uvicorn+zttp HTTP/2:uvicorn 0.53 不内置 h2,必须 `http="zttp"`;
|
||
信号接管=子类覆盖 Server.handle_exit(不调 super());as_asgi() 是 pyright 类型桥。
|
||
- app.py 结构:create_app 只装配,路由全在模块级 `_register_*`;轻量路径只走
|
||
brief_source()(BRIEF_KEYS 白名单要同步);>60 行的函数应视为信号。
|
||
- 守护进程:路径只有文件名时用 ensure_parent_dir()(别手写 makedirs(dirname));
|
||
pid 由服务进程自己写;端口自检用 port_busy()(判断「能连上」而非「能 bind」)。
|
||
|
||
## 本机环境(coreutils 残缺禁管道 / LF / 句柄等详见用户级记忆 ~/.workbuddy/MEMORY.md)
|
||
- `RemoteFarm.snapshot()` 是本地缓存不发请求;realtime=False 的脚本必须自己 refresh(),
|
||
否则轮询读到旧数据像卡死。
|
||
- 本机 HTTP 一律走 farm/net.py 的 open_url()(环境有 HTTP_PROXY 没 no_proxy)。
|
||
- 幂等补丁脚本注意:`new == ""` 时 `if new in text` 恒真会误报 ALREADY 漏改;
|
||
删除类编辑直接用 Edit 工具。
|
||
- 批量改同一文件用幂等补丁脚本(read_bytes/write_bytes + APPLIED/ALREADY/MISS);
|
||
同一文件一条消息里多次 Edit 会被静默覆盖。
|
||
|
||
## 远端与推送
|
||
- https://gitea.4i.hk/hhhge/SimpleFarm(public,main)。推送:
|
||
`GCM_INTERACTIVE=always git push origin main`(缺它 GCM 拒交互,报
|
||
"Failed to authenticate");还不行加 GCM_TRACE=1。沙箱会丢
|
||
.git/refs/remotes 写入使 status 显示 [gone]——假象,验证推送看 `git ls-remote`。
|
||
|
||
## 文档
|
||
README.md 是入口文档(接口表、SSE 协议、设计取舍);当天记录在
|
||
.workbuddy/memory/YYYY-MM-DD.md。
|