PersonalWebApplication/docs/superpowers/specs/2026-06-24-special-folders-design.md
2026-06-24 21:52:30 +08:00

8.9 KiB
Raw Permalink Blame History

设计:特殊文件夹(共享公共相册,按文件夹白名单)

在已有照片墙(#photo 个人墙,按用户存图)之上,增加一类共享的、命名的公共相册 通过地址栏 #<文件夹名> 进入,名单内的特定用户登录后可共同上传/浏览/删除, 复用照片墙的互动界面,无单文件夹张数上限,但总量超 1 GiB 时给管理员页面提示。

前置阅读:photo-wall.md(照片墙服务器存图)、 photo-wall-server-storage-design.mdauth-login-gate.md(登录 + dev_gate 入口门)。

状态:需求文档,待实现(留作后续 writing-plans 的输入)。

1. 目标与关键决策

维度 决策
内容模型 共享:一个特殊文件夹 = 一份公共相册,所有有权用户看到同一份内容
写权限 名单内任何登录用户可上传/删除(协作式:谁删大家都没了)
访问控制 按文件夹的用户白名单,名单只存服务器配置文件
界面 复用 PhotoWallPage(拖拽/装饰/导出),去掉 10 张上限
张数上限
配额 1 GiB 软阈值:不拦上传,超了在页面给管理员看警告
管理员 写死用户名 cc(可访问所有文件夹 + 看配额警告)
文件夹注册 服务器配置文件 special_folders.txt,前端不写死,加一行即生效
登录 同一套 ccuser(复用现有登录 + JWT

非目标YAGNI每文件夹独立配额数值、上传者归属/审计、回收站、文件夹的网页管理后台、 图片压缩。需要时再加。

2. 入口与路由

地址栏 #<名字>,前端 App.tsxhashchange 时依次判断:

hash === 'photo'                  → 个人照片墙(已实现)
否则 POST /api/web/folder {name}  → ok? 是 → 特殊文件夹流程
否则 POST /api/web/gate {hash}    → ok? 是 → 开发者登录页(已实现)
都不是                            → 起始页
  • POST /api/web/folder { name }登录前的存在性判断(仿 /api/web/gate 只回 { ok: bool }——该名字是否是注册的特殊文件夹。不返回白名单(不泄露成员)。 命中 → 显示登录页(复用 LoginPagehideStats,与 #photo 一致)。
  • 真正的访问控制在登录后的数据接口上做(见 §4不在白名单 → 403,前端显示 「无权访问该相册」。
  • 登录成功 → 渲染特殊文件夹版照片墙(见 §6。退出 → 清登录态 + 抹掉 hash → 起始页。

前端需要记住"当前进入的是哪个特殊文件夹"hash 即文件夹名),传给数据接口与组件。

3. 注册表与存储

3.1 注册表(配置文件)

  • 文件 special_folders.txt(路径由 SPECIAL_FOLDERS_FILE 覆盖,默认仓库/部署目录下), 线上放 /opt/internet-project/special_folders.txt
  • 格式:每行 文件夹名<空白>允许用户(逗号分隔)# 后为注释;空行忽略。
# 文件夹名   允许用户(逗号分隔)
family      cc,alice,bob
trip2026    cc,carol
  • 后端解析为 Map<文件夹名, Set<用户名>>每次校验时读文件(文件很小,开销可忽略), 这样新增/改名单即时生效、无需重启——与 dev_gate 文件模式同理。
  • 文件夹名合法性:复用用户名级别的字符集白名单 [A-Za-z0-9_-]、长度 164既作目录名安全 又避免与 #photo 等保留字冲突——photo 不应出现在注册表里)。

3.2 图片存储

  • 独立根目录 SPECIAL_DIR(默认 special-folders,线上 /opt/internet-project/special-folders/ 与个人墙的 PHOTO_DIR/<用户>/ 物理隔开,杜绝文件夹名与用户名撞车。
  • 每文件夹一子目录 SPECIAL_DIR/<文件夹名>/,每图随机 hex 文件名(复用 photos.rsvalid_idparse_data_url、随机文件名、路径安全逻辑)。无张数上限
  • 单图大小上限沿用 MAX_BYTES = 8 MiB

4. 后端接口

复用 §3.2 的存储原语,新增一个"按文件夹(而非按用户)"的存储视图与一组接口。 所有数据接口经 AuthUser(已实现的 JWT 提取器)。

方法 路径 鉴权 行为
POST /api/web/folder 无(登录前) body {name};该名是否注册 → {ok}
GET /api/web/folder/{name}/photos AuthUser + 白名单 { items:[{id}], totalBytes, overQuota }
POST /api/web/folder/{name}/photos AuthUser + 白名单 上传 {dataUrl},无张数上限
GET /api/web/folder/{name}/photos/{id} AuthUser + 白名单 流式返回字节
DELETE /api/web/folder/{name}/photos/{id} AuthUser + 白名单 删除

访问控制中间逻辑(每个 {name} 数据接口入口):

  1. name 不在注册表 → 404
  2. 登录用户既不在该文件夹白名单、又不是管理员 cc403
  3. 通过 → 执行。

错误码:未注册 404、无权 403、单图超 8MiB 413、类型不在白名单 415{id} 非法/不存在 404、IO 500。上传不因配额被拒(软阈值)。

配额字段GET list 返回):

  • totalBytes:该文件夹当前总字节(遍历目录累加文件大小)。
  • overQuotatotalBytes > 1 GiB1024^3)即 true

5. 管理员配额提示

  • 管理员 = 写死用户名 cc(实现上一个常量/小名单,便于日后扩展为可配置)。
  • list 接口对所有有权用户都返回 totalBytes/overQuota前端只在登录者是 ccoverQuota 为真时,于特殊文件夹墙工具栏显示一条警告条: 「该相册已超 1GB当前 <人类可读大小>),请清理」。其他用户不展示、也不感知。
  • 软阈值:超额后仍允许上传,仅提示。

6. 前端:复用照片墙

特殊文件夹版与 #photo 个人墙共用 PhotoWallPage 的交互(拖拽/双击编辑/右键菜单/ 装饰/导出/撤销重做),差异点:

  • 数据源:图片走 /api/web/folder/{name}/photos 系列接口(按文件夹),而非按用户。 建议把图片接口抽象成一个"源"参数(个人墙 = 用户源,特殊文件夹 = folder:<name> 源), 让 PhotoWallPage 通过 props 接收"如何 list/upload/get/delete",避免组件内写死。
  • 无 10 张上限:上传不做客户端张数拦截(个人墙保留 10 上限)。
  • 排版/装饰各人各存浏览器:图片共享,但每个浏览器的摆放/装饰是各自的。localStorage key 按文件夹区分photo-wall-state:folder:<name>(个人墙仍用 photo-wall-state 互不串。photoLayout 仍按服务器图片 id 关联。
  • 删除是共享副作用:任何有权用户删图 → 服务器文件没了 → 其他人下次加载即不见 (各自浏览器里该 id 的排版条目成为孤儿,加载时被忽略,无害)。
  • 管理员警告条:见 §5。
  • 403 处理:进入后若数据接口返回 403显示「无权访问该相册」并提供返回。

7. 部署与配置

  • 后端改动 → 全量重部署(同照片墙服务器存图流程)。
  • 新增运行时配置:
    • SPECIAL_DIR(可选,默认 special-folders)。
    • SPECIAL_FOLDERS_FILE(可选,默认 special_folders.txt)——线上放 /opt/internet-project/special_folders.txt,内容即文件夹→用户名单。
  • 新增一个特殊文件夹的运维动作:在 special_folders.txt 加一行(文件夹名 + 允许用户), 即时生效;首次上传自动建 special-folders/<名字>/ 目录。不改代码、不重部署前端。

8. 复用与新增一览

  • 复用auth.rsJWTroutes/extract.rsAuthUser)、photos.rsvalid_id/parse_data_url/随机文件名/路径安全、前端 PhotoWallPage/api/photos.ts 思路。
  • 新增(后端):特殊文件夹注册表解析(读 special_folders.txt)、按文件夹的存储视图与 配额统计、/api/web/folder 存在性接口、4 个 /api/web/folder/{name}/photos* 接口、 访问控制(白名单 + 管理员放行)。
  • 新增/改动(前端):App.tsx 入口路由加"特殊文件夹"分支、按文件夹的图片 API、 PhotoWallPage 接受"数据源 + 是否限额 + 是否管理员 + 配额状态"等 props、管理员警告条、 按文件夹的 localStorage key。

9. 待实现时需在计划里钉死的点

  • PhotoWallPage 的"数据源"抽象接口签名list/upload/get/delete 的统一形状)。
  • 注册表文件解析的容错(坏行跳过、重复文件夹名取首条还是报错)。
  • totalBytes 统计的性能(目录文件数大时遍历开销)——当前规模可接受,必要时缓存。
  • 管理员常量的位置与日后可配置化的留口。