PersonalWebApplication/docs/photo-wall.md

104 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 照片墙 Photo Wall`#photo` 入口)
模拟软木板/墙面的互动照片展示页:上传本地照片,以宝丽来风格散落摆放,用胶带、
图钉、便签、贴纸、印章、手绘箭头自由装扮,可导出为一张 PNG。
**职责切分**:图片字节**按登录用户存服务器**(最多 10 张/人、严格私有、JWT 鉴权);
背景、装饰、以及每张图的排版覆盖**存浏览器 `localStorage`**(按服务器图片 id 关联)。
设计来源:`photo-wall-design.docx`(产品设计文档 v1.0);服务器存图设计见
[specs/2026-06-24-photo-wall-server-storage-design.md](superpowers/specs/2026-06-24-photo-wall-server-storage-design.md)。
本页是其 React 重写版。
## 入口与鉴权流
地址栏 `#photo` 是一个**写死的公开入口**,与开发者入口门(`dev_gate`**无关**
```
浏览器 #photo
└─ 直接显示登录页(复用 LoginPage但去掉顶部服务器负载栏 ← 不查后端 gate
登录页提交用户名 + 密码
└─ POST /api/web/login → 校验同一套 ccuser 账号 → 成功后进入照片墙
```
- `#photo` **不走** `POST /api/web/gate`(藏门口令),但照片墙本身仍被登录挡住——
用的是同一套 `ccuser` 后端账号(见 [auth-login-gate.md](auth-login-gate.md))。
- **已登录**用户访问 `#photo` 会跳过登录页,直接进墙。
- 墙内右上角「退出」= 清登录态 + 抹掉地址栏 `#photo`,回到导航页(`StartPage`)。
## 涉及文件
前端:
- `Client/src/App.tsx` — 监听 `hashchange``#photo` 短路 gate`wantsPhoto`
已登录 + `wantsPhoto` → 渲染 `PhotoWallPage`(传入会话 `token`),否则博客页。
- `Client/src/components/LoginPage.tsx` — 新增 `hideStats` 开关,隐藏顶部
`AnnouncementBar`(系统负载栏);把轮询的 `useSystemStats` 拆进只在显示时挂载的
`StatsBar` 子组件,隐藏时不发请求。
- `Client/src/hooks/useAuth.ts``Auth` 暴露 `token`JWT供图片接口鉴权。
- `Client/src/api/photos.ts` — 图片接口封装:`listPhotos`/`uploadPhoto`/`deletePhoto`/
`fetchPhotoObjectUrl`(带 `Authorization: Bearer` 头;读取取 blob 成 objectURL
- `Client/src/components/PhotoWallPage.tsx` — 照片墙组件本体。
- `Client/src/index.css` — 末尾 `.pw-` 命名空间的全部照片墙美术样式。
后端Rust
- `Server/src/auth.rs``issue_token(username)` 签发 HS256 JWT、`verify_token` 校验。
- `Server/src/routes/extract.rs``AuthUser` 提取器(`Authorization: Bearer` → 用户名,失败 401
- `Server/src/photos.rs``PhotoStore`:按用户隔离到 `PHOTO_DIR/<用户>/`,含路径安全、
类型/大小/数量限额≤10 张、单图 ≤8MiBsave 加锁防并发越额。
- `Server/src/routes/web.rs` — 图片接口(见下);登录改签带用户名的 JWTCORS 放行
`DELETE`/`Authorization`;图片路由 body 上限放宽至 16MB。
## 接口(均经 `AuthUser` 鉴权)
| 方法 | 路径 | 行为 |
|---|---|---|
| GET | `/api/web/photos` | 列出当前用户图片 id`{ items:[{id}], max:10 }` |
| POST | `/api/web/photos` | 上传body `{ dataUrl }`);满 10 张 409、超 8MiB 413、类型错 415 |
| GET | `/api/web/photos/{id}` | 流式返回字节(属主校验,非法/不存在 404 |
| DELETE | `/api/web/photos/{id}` | 删除 |
存储:`PHOTO_DIR`(默认 `photo-wall`,线上 `/opt/internet-project/photo-wall/`)下
每用户一子目录,每图随机十六进制文件名。`JWT_SECRET` 环境变量为签名密钥。
## 功能
- **照片**:宝丽来白边(底部加大留白)、随机旋转 ±15°、立体投影三种随机固定方式
(胶带 / 彩色图钉 / 双角胶带)。
- **交互**:拖拽移动、双击编辑标注、右键菜单(旋转 ±15°、放大/缩小、移到最前/最后、
旋转角度预设 0/15/30/45/90°、放大比例预设 0.5/0.75/1/1.5/2×、删除便签额外可改色
触摸端长按 = 右键。
- **装饰**便签5 色 + 折角、胶带条、12 预设贴纸 + 自定义文字、SVG 印章、手绘箭头
(选中后在画布拖动绘制)。
- **背景**:软木板 / 黑板 / 白墙 / 牛皮纸,工具栏循环切换。
- **上传**:按钮选择 / 拖入画布 / 剪贴板粘贴(`FileReader` 读 Base64 → 传服务器);
满 10 张前端拦下、服务器 409 兜底。
- **特殊文件夹视频(规划)**:特殊文件夹(`#<文件夹名>`)将支持上传图片或视频;视频上传后先显示
「AI 正在压缩视频...」占位卡,服务器用 `ffmpeg` 压缩成 3M 带宽友好的 MP4 后自动变为
播放器。个人 `#photo` 暂不开放视频上传。不提供视频下载按钮。
- **历史**:撤销/重做 Ctrl+Z / Ctrl+Y最多 30 步。
- **持久化**:图片存服务器;**排版/装饰**操作后防抖 1s 自动写 `localStorage`key
`photo-wall-state``{ bgIndex, decorations, photoLayout }``photoLayout` 按服务器
图片 id 关联位置/旋转/缩放/层级/标注/宽度);工具栏显示「保存中…/已保存」。
无本地排版的图片首次加载时自动散落摆放。读到旧结构(`{ bgIndex, els }`,照片带
base64时只取其装饰部分旧照片丢弃。
- **导出**`html2canvas`CDN 懒加载)截图画布为 `photo-wall-<时间戳>.png`
## 实现注记
- **拖拽性能**:拖拽过程中只直接改 DOM 的 `transform`GPU 合成),**不触发 React
重渲染**;松手后才一次性 commit 进状态并记一步历史。故拖拽中不得 `setState`
- 元素统一 Pointer Events 处理鼠标 + 触摸;`touch-action: none` 防止滚动抢手势。
- 「保存中…」由各改动入口mutate / undo / redo / 拖拽 commit / 换背景)打标,
防抖 effect 仅负责落盘并置「已保存」——避免在 effect 内同步 `setState` 引发级联渲染。
## 线上访问
`http://8.130.143.54:8548/#photo`。服务器存图引入了后端改动 → 走**全量重部署**
(重编 Rust → 传二进制 → `restart_rust.sh` 重启),非 frontend-only。需在服务器
`weather.env``JWT_SECRET`;首次上传自动建 `photo-wall/<用户>/`。**因 token 格式
从旧的时间戳哈希变为 JWT上线后用户需重新登录一次。**
特殊文件夹视频媒体见
[specs/2026-06-29-special-folder-video-media-design.md](superpowers/specs/2026-06-29-special-folder-video-media-design.md)
实现后依赖服务器安装 `ffmpeg`,后端改动需全量重部署。