PersonalWebApplication/docs/superpowers/specs/2026-06-24-special-folders-design.md

160 lines
9.2 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` 个人墙,按用户存图)之上,增加一类**共享的、命名的公共相册**
> 通过地址栏 `#<文件夹名>` 进入,名单内的特定用户登录后可共同上传/浏览/删除,
> 复用照片墙的互动界面,无单文件夹张数上限,但总量超 1 GiB 时给管理员页面提示。
>
> 前置阅读:[photo-wall.md](../../photo-wall.md)(照片墙服务器存图)、
> [photo-wall-server-storage-design.md](2026-06-24-photo-wall-server-storage-design.md)、
> [auth-login-gate.md](../../auth-login-gate.md)(登录 + dev_gate 入口门)。
>
> 状态:需求文档,**待实现**(留作后续 writing-plans 的输入)。
## 1. 目标与关键决策
| 维度 | 决策 |
|---|---|
| 内容模型 | **共享**:一个特殊文件夹 = 一份公共相册,所有有权用户看到同一份内容 |
| 写权限 | 名单内**任何登录用户**可上传/删除(协作式:谁删大家都没了) |
| 访问控制 | **按文件夹的用户白名单**,名单只存服务器配置文件 |
| 界面 | **复用 `PhotoWallPage`**(拖拽/装饰/导出),去掉 10 张上限 |
| 张数上限 | 无 |
| 配额 | **1 GiB 软阈值**:不拦上传,超了在页面给管理员看警告 |
| 管理员 | 写死用户名 `cc`(可访问所有文件夹 + 看配额警告) |
| 文件夹注册 | 服务器配置文件 `special_folders.txt`,前端不写死,加一行即生效 |
| 登录 | 同一套 `ccuser`(复用现有登录 + JWT |
非目标YAGNI每文件夹独立配额数值、上传者归属/审计、回收站、文件夹的网页管理后台、
图片压缩。需要时再加。
## 2. 入口与路由
地址栏 `#<名字>`,前端 `App.tsx``hashchange` 时依次判断:
```
hash === 'photo' → 个人照片墙(已实现)
否则 POST /api/web/folder {name} → ok? 是 → 特殊文件夹流程
否则 POST /api/web/gate {hash} → ok? 是 → 开发者登录页(已实现)
都不是 → 起始页
```
- `POST /api/web/folder { name }` 是**登录前**的存在性判断(仿 `/api/web/gate`
只回 `{ ok: bool }`——该名字是否是注册的特殊文件夹。**不返回白名单**(不泄露成员)。
命中 → 显示登录页(复用 `LoginPage``hideStats`,与 `#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.rs`
`valid_id`、`parse_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. 登录用户既不在该文件夹白名单、又不是管理员 `cc``403`
3. 通过 → 执行。
**错误码**:未注册 `404`、无权 `403`、单图超 8MiB `413`、类型不在白名单 `415`
`{id}` 非法/不存在 `404`、IO `500`。上传**不因配额被拒**(软阈值)。
**配额字段**GET list 返回):
- `totalBytes`:该文件夹当前总字节(遍历目录累加文件大小)。
- `overQuota``totalBytes > 1 GiB``1024^3`)即 `true`
## 5. 管理员配额提示
- 管理员 = 写死用户名 `cc`(实现上一个常量/小名单,便于日后扩展为可配置)。
- list 接口对所有有权用户都返回 `totalBytes`/`overQuota`**前端只在登录者是 `cc`
`overQuota` 为真时**,于特殊文件夹墙工具栏显示一条警告条:
「该相册已超 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.rs`JWT、`routes/extract.rs``AuthUser`)、`photos.rs` 的
`valid_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` 统计的性能(目录文件数大时遍历开销)——当前规模可接受,必要时缓存。
- 管理员常量的位置与日后可配置化的留口。
## 10. 后续扩展:视频媒体
特殊文件夹的视频上传、异步转码、媒体接口和 `mediaLayout` 兼容策略由
[2026-06-29-special-folder-video-media-design.md](2026-06-29-special-folder-video-media-design.md)
接续设计。个人 `#photo` 不在该扩展范围内。