5.5 KiB
5.5 KiB
设计:照片墙服务器存图(按用户、最多 10 张)
把照片墙从「纯前端 localStorage 存图」升级为「图片存服务器、按登录用户隔离」。 排版与装饰仍存浏览器。前置阅读:photo-wall.md、 auth-login-gate.md。
1. 目标与职责切分
| 存哪 | 内容 | 跨设备 |
|---|---|---|
服务器(PHOTO_DIR/<用户>/,≤10 张/用户) |
图片字节本身 | ✅ 任意设备登录可见 |
| 浏览器 localStorage | 背景、装饰(便签/胶带/贴纸/印章/手绘)、每张图的排版覆盖(位置/旋转/缩放/层级/标注/宽度,按图片 id 关联) | ❌ 各浏览器独立 |
加载逻辑:登录后拉取本用户图片列表 → 每张取字节显示 → 若 localStorage 有该 id 的排版记录则套用,否则自动散落排版(沿用软木板随机摆放风格,按数量铺开避免重叠)。 图片严格私有:读取必须带登录凭证,后端校验身份后才返回字节。
2. 鉴权(JWT)
现状:登录返回的 token 是时间戳 SHA256,无任何接口校验。本设计赋予其真实含义。
auth::issue_token(username)改为签发 HS256 JWT(jsonwebtoken,已是依赖): claimssub = 用户名、exp = 现在 + 30 天,密钥取环境变量JWT_SECRET。- 未设
JWT_SECRET→ 用内置开发兜底密钥并打印警告(仅本地开发用;生产必须设)。
- 未设
- 新增
auth::verify_token(token) -> Option<String>(返回用户名)。 - 新增 axum 提取器
AuthUser(String):读Authorization: Bearer <jwt>,校验通过产出 用户名,否则401。受保护接口用它。 - 影响:token 格式变化,上线后现有登录态失效,需重新登录一次。
3. 后端接口与存储
存储布局:
- 根目录
PHOTO_DIR(默认photo-wall,相对进程 cwd;线上 cwd 为/opt/internet-project/,即/opt/internet-project/photo-wall/)。 - 每用户一子目录,目录名 = 用户名经字符集白名单
[A-Za-z0-9_-]校验后使用; 含非法字符的用户名拒绝(400)。 - 每图文件名
<16字节随机 hex>.<ext>,ext ∈ {jpg,png,gif,webp}。 无独立元数据存储:列目录即得列表(无需数据库)。 - 图片
id即完整文件名(含扩展名,如a1b2…f9.jpg)。GET /:id据扩展名 决定Content-Type。id仅允许[0-9a-f]+\.(jpg|png|gif|webp),否则404。
接口(全部经 AuthUser,挂在 web 路由):
| 方法 | 路径 | 行为 | 返回 |
|---|---|---|---|
| GET | /api/web/photos |
列出当前用户图片 | { items: [{ id }], max: 10 } |
| POST | /api/web/photos |
上传一张(body { dataUrl } base64 data URL) |
201 { id } |
| GET | /api/web/photos/:id |
流式返回字节(属主校验,文件须在该用户目录) | 200 + Content-Type |
| DELETE | /api/web/photos/:id |
删除一张 | 204 |
约束与错误:
- 满 10 张再传 →
409(前端也禁用上传按钮)。 - 单图解码后 > 8 MB →
413。 - 类型不在白名单 →
415。 :id含路径分隔/..或文件不存在 →404(防目录穿越;id 仅允许 hex+扩展名)。- 放宽对应路由的 axum
DefaultBodyLimit至 ~16 MB(容纳 base64)。
4. 前端数据流
useAuth:在Auth接口里暴露token(当前已存 session,仅未对外返回)。App.tsx把auth.token传给PhotoWallPage。- 新增
Client/src/api/photos.ts:listPhotos(token)、uploadPhoto(token, dataUrl)、deletePhoto(token, id)、fetchPhotoObjectUrl(token, id)(带Bearer头 fetch →blob→URL.createObjectURL)。 PhotoWallPage:- 挂载时:
listPhotos→ 并发取每张 objectUrl → 建照片元素;按图片 id 从 localStorage 合并排版,缺失则自动散落(需图片自然宽高算比例,载入后取naturalWidth/Height)。 - 持久化结构改为
{ bgIndex, decorations: El[], photoLayout: { [id]: {x,y,rot,scale,z,caption,w} } }——照片字节不再进 localStorage。读到旧结构(照片带 base64)时只取其装饰部分。 - 上传:选图 → 读 dataURL →
uploadPhoto→ 成功后插入照片元素(自动摆放)。满 10 禁用。 - 删除(右键):
deletePhoto→ 移除元素 + 清该 id 本地排版 +revokeObjectURL。 - 卸载时释放所有 objectUrl。
- 挂载时:
- 右键菜单改进(用户反馈:当前调整幅度太小):
- 默认步进调大:旋转 ±15°、照片放大/缩小 ±30px、其它元素 scale ±0.25。
- 新增「旋转角度」子项:预设角度(如 0/±15/±30/±45/90)直接设定。
- 新增「放大比例」子项:预设比例(如 0.5×/0.75×/1×/1.5×/2×)直接设定。
5. 部署与运维
- 这是后端改动 → 全量重部署:重编 Rust → 传二进制 →
restart_rust.sh重启 (非 frontend-only)。 - 服务器
weather.env增加JWT_SECRET=<随机串>;可选PHOTO_DIR(默认即对)。 - 首次上传自动创建
photo-wall/<用户>/。 - CORS(仅影响 dev 跨源):在
routes/web.rs的CorsLayer增加DELETE方法与Authorization请求头;生产同源不受影响。 - 上线后提示用户重新登录一次。
6. 不做(YAGNI)
- 服务器端图片压缩/缩放(小规模,35GB 盘够用;前端显示尺寸小,将来需要再加)。
- 排版/装饰上云(明确只存浏览器)。
- 图片重命名/相册/分享链接等。