PersonalWebApplication/docs/photo-wall.md

6.5 KiB
Raw Blame History

照片墙 Photo Wall#photo 入口)

模拟软木板/墙面的互动照片展示页:上传本地照片,以宝丽来风格散落摆放,用胶带、 图钉、便签、贴纸、印章、手绘箭头自由装扮,可导出为一张 PNG。

职责切分:图片字节按登录用户存服务器(最多 10 张/人、严格私有、JWT 鉴权); 背景、装饰、以及每张图的排版覆盖存浏览器 localStorage(按服务器图片 id 关联)。

设计来源:photo-wall-design.docx(产品设计文档 v1.0);服务器存图设计见 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)。
  • 已登录用户访问 #photo 会跳过登录页,直接进墙。
  • 墙内右上角「退出」= 清登录态 + 抹掉地址栏 #photo,回到导航页(StartPage)。

涉及文件

前端:

  • Client/src/App.tsx — 监听 hashchange#photo 短路 gatewantsPhoto 已登录 + wantsPhoto → 渲染 PhotoWallPage(传入会话 token),否则博客页。
  • Client/src/components/LoginPage.tsx — 新增 hideStats 开关,隐藏顶部 AnnouncementBar(系统负载栏);把轮询的 useSystemStats 拆进只在显示时挂载的 StatsBar 子组件,隐藏时不发请求。
  • Client/src/hooks/useAuth.tsAuth 暴露 tokenJWT供图片接口鉴权。
  • 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.rsissue_token(username) 签发 HS256 JWT、verify_token 校验。
  • Server/src/routes/extract.rsAuthUser 提取器(Authorization: Bearer → 用户名,失败 401
  • Server/src/photos.rsPhotoStore:按用户隔离到 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 自动写 localStoragekey photo-wall-state{ bgIndex, decorations, photoLayout }photoLayout 按服务器 图片 id 关联位置/旋转/缩放/层级/标注/宽度);工具栏显示「保存中…/已保存」。 无本地排版的图片首次加载时自动散落摆放。读到旧结构({ bgIndex, els },照片带 base64时只取其装饰部分旧照片丢弃。
  • 导出html2canvasCDN 懒加载)截图画布为 photo-wall-<时间戳>.png

实现注记

  • 拖拽性能:拖拽过程中只直接改 DOM 的 transformGPU 合成),不触发 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.envJWT_SECRET;首次上传自动建 photo-wall/<用户>/因 token 格式 从旧的时间戳哈希变为 JWT上线后用户需重新登录一次。

特殊文件夹视频媒体见 specs/2026-06-29-special-folder-video-media-design.md 实现后依赖服务器安装 ffmpeg,后端改动需全量重部署。