Files
my_site/docs/api-reference.md

755 lines
35 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.
# API 接口文档
本文档用于快速了解 `yoyuzh.xyz` 当前后端 API 的职责、鉴权方式和主要接口分组。
## 1. 基本约定
### 基础路径
- 后端接口统一以 `/api` 开头
- 本地开发默认地址:`http://localhost:8080`
### 返回格式
大部分接口返回统一结构:
```json
{
"code": 0,
"msg": "success",
"data": {}
}
```
常见含义:
- `code = 0`:成功
- `code = 1000`:参数校验失败
- `code = 1001`:未登录
- `code = 1002`:权限不足
- `code = 1003`:业务对象不存在、邀请码错误、取件码失效等业务失败
### 鉴权方式
- 采用 `Authorization: Bearer <accessToken>`
- `refreshToken` 通过 `/api/auth/refresh` 换取新的登录态
- 当前实现为“按客户端类型拆分会话”
- 桌面端与移动端可以同时在线
- 同一端类型再次登录后,该端旧 access token 会失效
- `/api/auth/register``/api/auth/login``/api/auth/refresh` 与开发环境 `/api/auth/dev-login` 支持可选请求头 `X-Yoyuzh-Client: desktop|mobile`
### 权限分层
- 公开接口:
- `/api/auth/**`
- `/api/transfer/**`
- `GET /api/files/share-links/{token}`
- 登录后接口:
- `/api/user/**`
- `/api/files/**`
- `/api/admin/**`
## 2. 认证模块
控制器:
- `backend/src/main/java/com/yoyuzh/auth/AuthController.java`
- `backend/src/main/java/com/yoyuzh/auth/DevAuthController.java`
- `backend/src/main/java/com/yoyuzh/auth/UserController.java`
### 2.1 注册
`POST /api/auth/register`
说明:
- 使用邀请码注册
- 注册成功后直接返回登录态
- 邀请码成功使用后会自动刷新
- 若请求未显式带 `X-Yoyuzh-Client`,后端默认按 `desktop` 处理
请求重点字段:
- `username`
- `email`
- `phoneNumber`
- `password`
- `confirmPassword`
- `inviteCode`
### 2.2 登录
`POST /api/auth/login`
请求字段:
- `username`
- `password`
返回字段:
- `token`
- `accessToken`
- `refreshToken`
- `user`
补充说明:
- 可选请求头 `X-Yoyuzh-Client` 用于声明当前登录来自桌面端还是移动端
- 同账号桌面端与移动端可同时保持登录,但同类型端再次登录会顶掉旧会话
### 2.3 刷新登录态
`POST /api/auth/refresh`
请求字段:
- `refreshToken`
说明:
- 刷新后会返回新的 access token 与 refresh token
- 当前系统会让旧 refresh token 失效
- 刷新会沿用该 refresh token 原本所属的客户端类型;请求头缺省时仍按 `desktop` 兜底
### 2.4 开发环境登录
`POST /api/auth/dev-login`
说明:
- 仅用于开发联调
- 是否可用取决于当前环境配置
- 同样支持可选请求头 `X-Yoyuzh-Client: desktop|mobile`
### 2.5 Android 客户端更新信息
`GET /api/app/android/latest`
说明:
- 公开接口,不需要登录
- 返回当前 Android 安装包下载地址、文件名和最新发布时间
- 后端会先读取文件桶中的 `android/releases/latest.json` 元数据,再返回当前 APK 对应的后端下载地址
- 安卓端原生壳应通过该接口检查更新
### 2.6 Android 客户端下载入口
`GET /api/app/android/download`
说明:
- 公开接口,不需要登录
- 该接口会直接回传当前最新 APK 的字节流,并通过 `Content-Disposition` 指定带版本号的文件名
- Web 端总览页应直接使用这个公开下载入口,而不是直接访问对象存储路径
### 2.7 获取用户资料
`GET /api/user/profile`
### 2.8 更新用户资料
`PUT /api/user/profile`
### 2.9 修改密码
`POST /api/user/password`
说明:
- 成功后会重新签发新的登录态
- 同时会顶掉旧设备会话
### 2.10 头像相关
- `POST /api/user/avatar/upload/initiate`
- `POST /api/user/avatar/upload`
- `POST /api/user/avatar/upload/complete`
- `GET /api/user/avatar/content`
说明:
- 支持初始化直传
- 支持代理上传
- 最终通过完成接口落库
## 3. 网盘模块
控制器:
- `backend/src/main/java/com/yoyuzh/files/core/FileController.java`
### 3.1 上传相关
- `POST /api/files/upload`
- `POST /api/files/upload/initiate`
- `POST /api/files/upload/complete`
说明:
- 兼容普通上传和 OSS 直传
- 前端会优先尝试“初始化上传 -> 直传/代理 -> 完成上传”
- `upload/initiate` 返回的 `storageName` 现在是一次上传对应的 opaque blob object key新文件会落到全局 `blobs/...` key而不是用户目录路径 key
- `upload/complete` 必须回传这个 opaque blob key后端会据此创建 `FileBlob` 并把新 `StoredFile` 绑定到该 blob
### 3.2 目录与列表
- `POST /api/files/mkdir`
- `GET /api/files/list`
- `GET /api/files/recent`
说明:
- `list` 支持 `path``page``size`
- 当前前端会在网盘页缓存目录内容和最后访问路径
### 3.3 下载
- `GET /api/files/download/{fileId}`
- `GET /api/files/download/{fileId}/url`
说明:
- 普通文件优先获取下载 URL
- 文件夹可走 ZIP 下载
- 私有 `apk/ipa` 下载会返回一个短时有效的 `https://api.yoyuzh.xyz/_dl/...` URL该 URL 由 Nginx 按签名和过期时间校验后代理到对象存储自定义下载域名,不是长期可复用的公开直链
### 3.4 文件操作
- `PATCH /api/files/{fileId}/rename`
- `PATCH /api/files/{fileId}/move`
- `POST /api/files/{fileId}/copy`
- `DELETE /api/files/{fileId}`
- `GET /api/files/recycle-bin`
- `POST /api/files/recycle-bin/{fileId}/restore`
说明:
- `move` 用于移动到目标路径
- `copy` 用于复制到目标路径
- 文件和文件夹都支持移动 / 复制
- 普通文件的 `move` / `rename` / `copy` 只改逻辑元数据;`copy` 会复用原有 `FileBlob`,不会复制底层对象
- `DELETE /api/files/{fileId}` 现在语义是“移入回收站”,不会立刻物理删除;删除的文件或整个目录树会保留 10 天
- `GET /api/files/recycle-bin` 返回当前用户回收站根条目分页列表,包含删除时间和预计清理时间
- `POST /api/files/recycle-bin/{fileId}/restore` 用于把某个回收站根条目恢复到原目录;若原位置已有同名文件,或当前剩余空间不足,则恢复失败
### 3.5 分享链接
- `POST /api/files/{fileId}/share-links`
- `GET /api/files/share-links/{token}`
- `POST /api/files/share-links/{token}/import`
说明:
- 已登录用户可为自己的文件或文件夹创建分享链接
- 公开访客可查看分享详情
- 登录用户可将分享内容导入自己的网盘
- 普通文件导入时会新建自己的 `StoredFile` 并复用源 `FileBlob`,不会再次写入物理文件
### 3.6 v2 上传会话
- `POST /api/v2/files/upload-sessions`
- `GET /api/v2/files/upload-sessions/{sessionId}`
- `DELETE /api/v2/files/upload-sessions/{sessionId}`
- `GET /api/v2/files/upload-sessions/{sessionId}/prepare`
- `POST /api/v2/files/upload-sessions/{sessionId}/content`
- `GET /api/v2/files/upload-sessions/{sessionId}/parts/{partIndex}/prepare`
- `PUT /api/v2/files/upload-sessions/{sessionId}/parts/{partIndex}`
- `POST /api/v2/files/upload-sessions/{sessionId}/complete`
说明:
- 需要登录,只允许操作当前用户自己的上传会话
- 会话响应返回 `sessionId``objectKey``directUpload``multipartUpload``uploadMode``path``filename``contentType``size``storagePolicyId``status``chunkSize``chunkCount``expiresAt``createdAt``updatedAt`,以及一个面向前端消费的 `strategy` 对象
- `uploadMode` 目前有三种:`PROXY``DIRECT_SINGLE``DIRECT_MULTIPART`
- 默认 S3 存储策略下,创建会话时会立即初始化 multipart upload并把 `directUpload=true``multipartUpload=true``uploadMode=DIRECT_MULTIPART` 返回给客户端;若默认策略 `directUpload=true``multipartUpload=false`,会返回 `DIRECT_SINGLE`;若 `directUpload=false`,则返回 `PROXY`
- `strategy` 会把当前会话下一步该调用的后端入口显式返回出来:`DIRECT_SINGLE` 返回 `prepareUrl` + `completeUrl``PROXY` 返回 `proxyContentUrl` + `proxyFormField=file` + `completeUrl``DIRECT_MULTIPART` 返回 `partPrepareUrlTemplate``partRecordUrlTemplate``completeUrl`
- `GET /{sessionId}/prepare` 仅用于 `DIRECT_SINGLE`,返回整文件直传所需的 `direct/uploadUrl/method/headers/storageName`
- `POST /{sessionId}/content` 仅用于 `PROXY`,以 multipart 表单上传整文件内容到当前 upload session 绑定的 `objectKey`
- `GET /parts/{partIndex}/prepare` 会返回当前分片的直传信息:`direct``uploadUrl``method``headers``storageName`
- `PUT /parts/{partIndex}` 请求体仍为 `{ "etag": "...", "size": 8388608 }`,只负责记录 part 元数据,不直接接收字节流
- `POST /complete` 会先按已记录的 part 元数据提交 multipart complete再复用旧上传完成链路写入 `FileBlob + StoredFile + FileEntity.VERSION`
- 后端每小时清理过期且未完成的会话;若会话已绑定 multipart upload会优先向对象存储发送 abort
- 当前前端网盘上传主链路已经消费这套 v2 接口:桌面/移动文件页和“存入网盘”入口都会按 `uploadMode + strategy` 自动选择代理上传、单请求直传或 multipart 分片上传
## 4. 快传模块
控制器:
- `backend/src/main/java/com/yoyuzh/transfer/TransferController.java`
### 4.1 创建会话
`POST /api/transfer/sessions`
说明:
- 在线快传会话允许未登录用户创建
- 离线快传会话仍要求发送端登录
- 请求体必须区分 `mode`
- `ONLINE`: 在线快传15 分钟有效,只能被接收一次
- `OFFLINE`: 离线快传7 天有效,文件会落到站点存储并可被重复接收
- 返回会话 ID、取件码、模式、过期时间和文件清单
### 4.2 通过取件码查找会话
`GET /api/transfer/sessions/lookup?pickupCode=xxxxxx`
说明:
- 接收端通过 6 位取件码查找会话
- 在线快传和离线快传都允许未登录用户查找
### 4.3 加入会话
`POST /api/transfer/sessions/{sessionId}/join`
说明:
- 在线快传会占用一次性会话
- 离线快传返回可下载文件清单,不需要建立 P2P 通道
- 在线快传和离线快传都允许未登录用户加入
### 4.4 信令交换
- `POST /api/transfer/sessions/{sessionId}/signals`
- `GET /api/transfer/sessions/{sessionId}/signals`
说明:
- 后端负责 WebRTC 信令交换
- 文件内容本身不经过后端
- 实际文件通过浏览器 DataChannel 进行 P2P 传输
- 该组接口仅用于 `ONLINE` 模式
### 4.5 查看我的离线快传记录
`GET /api/transfer/sessions/offline/mine`
说明:
- 需要登录
- 返回当前用户未过期的离线快传会话列表
- 每个会话包含取件码、有效期和文件清单,前端可据此重新展示二维码与分享链接
### 4.6 上传离线快传文件
`POST /api/transfer/sessions/{sessionId}/files/{fileId}/content`
说明:
- 需要发送端登录
- 发送端把离线文件内容上传到站点存储
- 线上环境会把离线文件落到对象存储
### 4.6 下载离线快传文件
`GET /api/transfer/sessions/{sessionId}/files/{fileId}/download`
说明:
- 不需要登录
- 离线文件在有效期内可以被重复下载
### 4.7 存入网盘
`POST /api/transfer/sessions/{sessionId}/files/{fileId}/import`
说明:
- 需要登录
- 把离线快传文件导入到当前用户网盘
## 5. 管理台模块
控制器:
- `backend/src/main/java/com/yoyuzh/admin/AdminController.java`
### 5.1 总览
`GET /api/admin/summary`
返回内容包括:
- 用户总数
- 文件总数
- 当前邀请码
- 今日请求次数
- 今日按小时请求折线图
- 最近 7 天每日上线人数和用户名单
- 当前离线快传占用与上限
补充说明:
- `requestTimeline` 现在只返回当天已经过去的小时,例如当天只到 `07:xx` 时只会返回 `00:00``07:00`
- `dailyActiveUsers` 固定返回最近 7 天,按日期升序排列;每项包含日期、展示标签、当天去重后的上线人数和用户名列表
- “上线”定义为用户成功通过 JWT 鉴权访问受保护接口后的当天首次记录
### 5.2 用户管理
- `GET /api/admin/users`
- `PATCH /api/admin/users/{userId}/role`
- `PATCH /api/admin/users/{userId}/status`
- `PUT /api/admin/users/{userId}/password`
- `POST /api/admin/users/{userId}/password/reset`
说明:
- 可调整用户角色
- 可封禁用户
- 可重置或直接设置密码
- 封禁/改密会使原登录态失效
### 5.3 文件管理
- `GET /api/admin/files`
- `DELETE /api/admin/files/{fileId}`
### 5.4 存储策略
- `GET /api/admin/storage-policies`
- `POST /api/admin/storage-policies`
- `PUT /api/admin/storage-policies/{policyId}`
- `PATCH /api/admin/storage-policies/{policyId}/status`
- `POST /api/admin/storage-policies/migrations`
说明:
- 需要管理员登录
- 返回当前存储策略列表和结构化能力声明
- 新增/编辑接口当前允许维护名称、类型、bucket/endpoint/region、前缀、凭证模式、最大对象大小、能力声明与启用状态
- `PATCH /status` 用于启用或停用非默认策略;默认策略不能被停用
- `POST /migrations` 需要管理员登录,请求体为 `sourcePolicyId``targetPolicyId` 与可选 `correlationId`;当前会创建一个 `STORAGE_POLICY_MIGRATION` 后台任务,返回值沿用 `/api/v2/tasks/{id}` 的任务响应形状
- 当前迁移任务会在“当前活动存储后端”内执行真实对象迁移:复制旧对象到新的 target-policy object key更新 `FileBlob``FileEntity.VERSION`,并在事务提交后清理旧对象;如果源/目标策略类型与当前运行时存储后端不匹配,任务会失败
- 当前仍不支持删除策略、切换默认策略或通过管理接口暴露实际凭证内容
- `capabilities.multipartUpload` 现在会反映默认策略是否支持 v2 上传会话 multipart当前默认 S3 策略为 `true`,本地策略为 `false`
## 6. 前端公开路由与接口关系
前端入口在:
- `front/src/App.tsx`
主要页面:
- `/login`
- `/overview`
- `/files`
- `/transfer`
- `/share/:token`
- `/admin/*`
接口关系:
- 登录页:调用 `/api/auth/login``/api/auth/register`
- 网盘页:调用 `/api/files/**`
- 快传页:调用 `/api/transfer/**`
- 分享页:调用 `/api/files/share-links/{token}` 和导入接口
- 管理台:调用 `/api/admin/**`
## 7. 建议阅读顺序
后续新窗口如果要接手后端功能,建议按这个顺序看:
1. `memory.md`
2. `docs/architecture.md`
3. `docs/api-reference.md`
4. `AGENTS.md`
5. `CLAUDE.md`
6. `backend/src/main/java/com/yoyuzh/config/SecurityConfig.java`
7. 对应业务模块的 `Controller + Service`
补充说明:
- 根目录 `.env` 现在是本地密钥和部署参数的统一入口
- 额外的交接背景可查看 `docs/agents/handoff.md`
## 2026-04-08 API v2 阶段 1 补充
`GET /api/v2/site/ping`
说明:
- 公开接口,不需要登录。
- 当前是 v2 API 的最小边界探针,返回结构为 `{ "code": 0, "msg": "success", "data": { "status": "ok", "apiVersion": "v2" } }`
- v2 错误响应开始使用独立 `ApiV2ErrorCode` 范围;旧 `/api/**` 接口暂不迁移。
- 前端访问 v2 接口时可通过 `apiV2Request()` 自动拼接 `/api/v2/**`,内部请求会携带 `X-Yoyuzh-Client-Id`
## 2026-04-08 文件实体模型二期第一小步
- 本阶段只新增后端实体和迁移映射,不新增对外 API。
-`/api/files/**`、分享、回收站、快传接口继续使用现有 DTO 和响应结构。
- `StoredFile.primaryEntity``portal_stored_file_entity` 目前只作为兼容迁移数据,后续阶段稳定后再切换新读写路径。
## 2026-04-08 文件实体模型二期第二小步
- 本阶段不新增对外 API`/api/files/**`、分享、回收站、快传导入等响应结构保持不变。
- 后端在旧接口内部开始双写实体模型:上传完成、外部导入、分享导入和网盘复制会继续写 `FileBlob`,同时创建或复用 `FileEntity.VERSION`,并写入 `StoredFile.primaryEntity``StoredFileEntity(PRIMARY)`
- 下载、分享详情、回收站、ZIP 下载仍读取 `StoredFile.blob`;后续阶段稳定后再切换到 `primaryEntity` 读取。
- 2026-04-08 阶段 3 第一小步 API 补充:新增受保护的 v2 上传会话接口族,`POST /api/v2/files/upload-sessions` 创建会话,`GET /api/v2/files/upload-sessions/{sessionId}` 查询当前用户自己的会话,`DELETE /api/v2/files/upload-sessions/{sessionId}` 取消会话。当前响应会返回 `sessionId``objectKey``multipartUpload`、路径、文件名、状态、分片大小、分片数量和时间字段。
- 2026-04-08 阶段 3 第二小步 API 补充:`POST /api/v2/files/upload-sessions/{sessionId}/complete` 用于把当前用户自己的上传会话提交完成。当前默认 S3 策略下,该接口会先完成 multipart 合并,再复用旧上传完成链路落库,成功后返回 `COMPLETED` 状态的 v2 会话响应。
- 2026-04-08 阶段 3 第三小步 API 补充:`PUT /api/v2/files/upload-sessions/{sessionId}/parts/{partIndex}` 请求体为 `{ "etag": "...", "size": 8388608 }`,用于记录当前用户上传会话的 part 元数据并返回 v2 会话响应;`GET /api/v2/files/upload-sessions/{sessionId}/parts/{partIndex}/prepare` 则返回该分片的直传地址和请求头。字节流仍直接上传到对象存储,不经过后端转发。
- 2026-04-08 阶段 3 第四小步 API 补充:本小步没有新增额外资源类型。后端新增上传会话过期清理任务,只处理未完成且已过期的会话,并把它们标记为 `EXPIRED`;若会话绑定了 multipart upload还会在清理时发起 abort。
- 2026-04-08 阶段 4 第一小步 API 补充:本小步没有新增存储策略管理 API。v2 上传会话响应新增 `storagePolicyId`,用于标识该会话绑定的默认存储策略;该字段现在也用于区分会话是否应按策略能力走 multipart 上传。
## 2026-04-08 阶段 5 文件搜索第一小步
`GET /api/v2/files/search`
说明:
- 需要登录,且只返回当前用户自己的未删除文件或目录。
- 返回 v2 envelope`data` 结构复用 `PageResponse<FileMetadataResponse>``items``total``page``size`
- 支持查询参数:`name``type``sizeGte``sizeLte``createdGte``createdLte``updatedGte``updatedLte``page``size`
- `type` 支持 `file``directory``folder``all`;时间参数使用 ISO 日期时间格式,例如 `2026-04-08T12:00:00`
- 当前搜索只基于 `StoredFile` 固定字段,不启用标签或 metadata 条件过滤;旧 `/api/files/list` 与上传下载分享接口保持不变。
## 2026-04-08 阶段 5 文件搜索第二小步
- 前端通过 `front/src/lib/file-search.ts` 接入 `GET /api/v2/files/search`,该 helper 会拼接 `name``type``sizeGte/sizeLte``createdGte/createdLte``updatedGte/updatedLte``page``size`,并复用 `apiV2Request()` 的生产端点、认证与 client id 头。
- `front/src/pages/Files.tsx` 的桌面端文件页新增独立搜索视图,搜索结果不写入 `getFilesListCacheKey(...)`,清空搜索后回到当前目录列表;移动端搜索尚未接入。
## 2026-04-08 阶段 5 分享二期后端最小骨架
`POST /api/v2/shares`
需要登录。
- 为当前用户自己的非目录文件创建分享。
- 请求字段:`fileId`,以及可选的 `password``expiresAt``maxDownloads``allowImport``allowDownload``shareName`
- 密码只保存 hash不在响应中返回。
`GET /api/v2/shares/{token}`
公开访问。
- 返回分享摘要。
- 如果分享设置了密码,在校验前不返回 `file` 详情。
`POST /api/v2/shares/{token}/verify-password`
公开访问。
- 校验分享密码,成功后返回可读分享摘要。
- 响应永不返回 `passwordHash`
`POST /api/v2/shares/{token}/import`
需要登录。
- 把分享文件导入当前用户网盘。
- 分享过期、密码错误或未提供、`allowImport=false``maxDownloads` 已耗尽时拒绝导入。
`GET /api/v2/shares/mine`
需要登录。
- 分页列出当前用户创建的分享。
`DELETE /api/v2/shares/{id}`
需要登录。
- 只删除当前用户自己的分享。
-`/api/files/share-links/**` 接口保留兼容;当前 `allowDownload` 已落库并返回,但还没有独立 v2 下载路由消费它。
## 2026-04-08 阶段 5 文件事件流最小闭环
`GET /api/v2/files/events?path=/`
说明:
- 需要登录,返回 `text/event-stream`
- 请求头支持 `X-Yoyuzh-Client-Id`
- 首次连接会先推送一个轻量 `READY` 事件
- 事件写入 `FileEvent` 表,字段包含 `userId``eventType``fileId``fromPath``toPath``clientId``payloadJson``createdAt`
- 当前后端已做同用户广播、路径前缀过滤和同 `clientId` 自身事件抑制
- 前端通过 `front/src/lib/file-events.ts` 以 fetch stream 订阅该 SSE复用鉴权与 `X-Yoyuzh-Client-Id` 请求头;桌面 `Files` 与移动 `MobileFiles` 收到变更事件后会失效当前目录缓存并刷新当前目录列表
## 2026-04-08 阶段 6 任务框架与 worker 后端最小骨架
`GET /api/v2/tasks`
需要登录。分页列出当前用户自己的后台任务。
`GET /api/v2/tasks/{id}`
需要登录。只返回当前用户自己的任务详情。
`DELETE /api/v2/tasks/{id}`
需要登录。取消当前用户自己的任务,`QUEUED` / `RUNNING` 会转为 `CANCELLED` 并写入 `finishedAt`,终态任务保持原样。
`POST /api/v2/tasks/{id}/retry`
需要登录。仅允许当前用户重试自己处于 `FAILED` 的后台任务。
补充说明:
- 成功后任务状态会重置为 `QUEUED`
- `finishedAt``errorMessage` 会被清空
- `publicStateJson.phase` 会重置为 `queued`
- `publicStateJson.attemptCount` 会重置为 `0`
- 公开 state 会按服务端保存的 `privateStateJson` 重建,因此失败执行时写入的瞬时字段不会保留
-`FAILED` 任务调用会返回 `400`
`POST /api/v2/tasks/archive`
需要登录。创建 `ARCHIVE` 类型的 `QUEUED` 任务;`fileId` 必须属于当前用户且未删除,`path` 必须匹配服务端派生逻辑路径,暂允许文件和目录;当前 worker 会生成 zip 并把归档结果回写到原文件同级目录。
`POST /api/v2/tasks/extract`
需要登录。创建 `EXTRACT` 类型的 `QUEUED` 任务;`fileId` 必须属于当前用户且未删除,`path` 必须匹配服务端派生逻辑路径,并拒绝目录和非压缩包类文件;当前 worker 只支持 zip-compatible 归档,会剥离共享根目录,并把解压结果恢复到原文件父目录。
`POST /api/v2/tasks/media-metadata`
需要登录。创建 `MEDIA_META` 类型的 `QUEUED` 任务;`fileId` 必须属于当前用户且未删除,`path` 必须匹配服务端派生逻辑路径并拒绝目录和非媒体类文件worker 会重新按 `userId + fileId` 加载文件,写入 `media:contentType``media:size`,对 ImageIO 可识别图片额外写 `media:width``media:height`。当前仍不做缩略图、视频时长或前端任务面板。
补充说明:
- worker 会定时领取少量 `QUEUED` 任务并切换为 `RUNNING`,完成后标记 `COMPLETED`,异常时标记 `FAILED` 并写入 `errorMessage`
- `publicStateJson.phase` 当前会经历 `queued -> running -> archiving/extracting/extracting-metadata -> completed/failed/cancelled` 这样的最小阶段流转。
- `publicStateJson` 还会暴露 `attemptCount/maxAttempts`;当前默认预算为 `ARCHIVE=4``EXTRACT=3``MEDIA_META=2`
- 任务进入 `RUNNING` 后,`publicStateJson` 会额外暴露 `workerOwner/heartbeatAt/leaseExpiresAt/startedAt`,用于描述当前 worker 的 lease 和 heartbeat终态或重排回队列后会移除运行态 owner/lease 字段。
- `ARCHIVE/EXTRACT` 任务还会在 `publicStateJson` 里暴露 `processedFileCount/totalFileCount``processedDirectoryCount/totalDirectoryCount` 与真实 `progressPercent``MEDIA_META` 会额外暴露 `metadataStage`
- 当 worker 命中失败时,任务会按失败分类写入 `failureCategory``TRANSIENT_INFRASTRUCTURE``RATE_LIMITED` 与部分 `UNKNOWN` 失败会按任务类型退避自动重排回 `QUEUED`,并在 `publicStateJson` 写入 `retryScheduled=true``nextRetryAt``retryDelaySeconds``lastFailureMessage``lastFailureAt``UNSUPPORTED_INPUT``DATA_STATE` 这类确定性失败不会自动重试。
- 已取消或其他终态任务不会被重新执行。
- 服务重启后,只有 lease 已过期或历史上没有 lease 的 `RUNNING` 任务会在启动完成时被重置回 `QUEUED`,避免多实例下误抢仍在运行的 worker。
- 创建成功后的任务 state 使用服务端文件信息,至少包含 `fileId``path``filename``directory``contentType``size`
- 桌面端 `Files` 页面会拉取最近 10 条任务、提供 `QUEUED/RUNNING` 取消按钮,并可为当前选中文件创建 `MEDIA_META` 任务;移动端与 archive/extract 的前端入口暂未接入。
## 2026-04-10 Redis Login-State Invalidation
- 新增可选 Redis 基础设施配置:
- `spring.data.redis.*`:连接参数。
- `app.redis.*`:业务 key prefix、TTL buffer、cache TTL 与命名空间。
-`app.redis.enabled=true` 时,认证链路会启用 Redis 驱动的登录态失效层:
- access token 按 `userId + clientType` 记录“在此时间点之前签发的 token 失效”。
- refresh token 按 hash 写入黑名单TTL 与剩余有效期对齐。
- `POST /api/auth/login``POST /api/auth/register``POST /api/auth/dev-login`:如果是同客户端重新签发登录态,旧 access token 会被写入 Redis 失效层,并继续保留原有 `sid` 会话匹配语义。
- `POST /api/user/password`、管理员封禁/改密/重置密码相关路径:会同时触发 access token Redis 失效标记与数据库 refresh token 撤销。
- `POST /api/auth/refresh`:旧 refresh token 在数据库撤销之外,还会同步写入 Redis 黑名单;先命中黑名单的 token 会被直接拒绝。
- 当 Redis 关闭时,系统会自动回退到原有的数据库 refresh token + `sid` 会话校验语义,不影响本地与 dev 启动。
## 2026-04-10 Redis Files Cache And Upload Runtime
- `GET /api/files/list`
- 对外语义不变,仍使用 `path``page``size` 参数返回当前用户目录分页结果。
-`app.redis.enabled=true` 时,后端会把热点目录页写入 Redis `files:list` cache并通过目录版本号在创建、删除、移动、复制、重命名、恢复、上传完成和导入后做精准失效。
- 搜索结果、回收站列表和后台任务列表不复用这套 key避免不同语义的分页结果互相污染。
- `GET /api/v2/files/upload-sessions/{sessionId}`
- 响应体新增 `runtime` 字段;当 Redis 运行态存在时返回实时上传快照,不存在时返回 `null`,不影响原有会话元数据字段。
- `runtime` 当前包含 `phase``uploadedBytes``uploadedPartCount``progressPercent``lastUpdatedAt``expiresAt`
- 该运行态由后端在会话创建、分片记录、代理上传、完成、取消、失败和过期时刷新,属于短生命周期缓存,不替代数据库里的最终状态。
- `POST /api/files/recycle-bin/{fileId}/restore`
- 外部接口不变,但 Redis 启用时后端会为同一 `fileId` 的恢复流程加分布式锁,避免多实例或并发请求重复恢复同一批条目。
## 2026-04-10 Redis Lightweight Broker First Landing
- 本批次没有新增对外 HTTP API用户可见接口仍沿用现有 `/api/files/**``/api/v2/tasks/**`
- 媒体文件通过网盘主链路落库后,后端现在会在事务提交后向轻量 broker 发布一次 `media-metadata-trigger`。这条触发只用于异步创建后台任务,不直接暴露为额外接口。
- broker 当前只承载“自动补一条 `MEDIA_META` 任务”这一类轻量异步触发,最终执行状态、重试与公开结果仍以 `BackgroundTask` 记录和 `/api/v2/tasks/**` 查询结果为准。
- `POST /api/v2/tasks/media-metadata`
- 用户手动创建任务的接口语义不变。
- 与此同时,媒体文件成功落库后也可能由后端自动补一条同类任务;系统会按 `correlationId` 去重,避免同一文件被 broker 重复创建多条自动任务。
## 2026-04-10 Redis Transfer Session Store
- 本批次没有新增快传 HTTP API`/api/transfer/sessions``/api/transfer/sessions/lookup``/api/transfer/sessions/{sessionId}/join` 与信令轮询接口的对外协议保持不变。
-`app.redis.enabled=true` 时,在线快传 session 会写入 Redis `transfer-sessions` 命名空间,而不再只保存在当前 JVM 进程内;这让 `lookup/join/postSignal/pollSignals` 在多实例部署下具备共享同一在线会话状态的基础。
- session 数据在 Redis 中会同时保存:
- `session:{sessionId}`:完整在线快传运行态快照。
- `pickup:{pickupCode}``pickupCode -> sessionId` 映射。
- Redis 关闭时,系统会自动回退到原有进程内存 store本地和 dev 环境不需要额外 Redis 也能继续运行。
- 离线快传不走这套 Redis store仍继续使用数据库 `OfflineTransferSession` 持久化模型。
## 2026-04-10 Redis File Event Pub/Sub
- `GET /api/v2/files/events?path=/`
- 对外 SSE 协议不变,仍要求登录并支持 `X-Yoyuzh-Client-Id`
- 首次连接仍先收到 `READY` 事件,订阅路径过滤和同 `clientId` 自抑制语义保持不变。
-`app.redis.enabled=true` 时,某个实例在事务提交后写入的文件事件会额外通过 Redis pub/sub 广播到其他实例,因此同一用户连到不同后端实例时也能收到变更通知。
- Redis pub/sub 只传播最小事件快照,不传播 `SseEmitter`、不重写 `FileEvent` 表,也不改变 `FileEvent` 作为审计持久化记录的角色。
- Redis 关闭时会自动回退为原有单实例本地广播行为。
## 2026-04-10 Spring Cache Minimal Landing
- `GET /api/admin/storage-policies`
- 瀵瑰鍗忚涓嶅彉銆?
- 褰?`app.redis.enabled=true` 鏃讹紝鍚庣浼氬皢鏁翠釜瀛樺偍绛栫暐鍒楄〃缂撳瓨鍒?`admin:storage-policies`銆?
- 褰?POST/PUT/PATCH` 瀛樺偍绛栫暐绠$悊鎺ュ彛鍐欏叆鎴愬姛鍚庯紝缂撳瓨浼氱珛鍗宠澶辨晥锛屽悗缁璇锋眰浼氶噸寤烘柊鍒楄〃銆?
- `GET /api/app/android/latest`
- 瀵瑰鍗忚涓嶅彉锛屼粛鏄叕寮€鎺ュ彛銆?
- 褰?`app.redis.enabled=true` 鏃讹紝鍚庣浼氬皢浠?`android/releases/latest.json` 鏋勫缓鍑虹殑 release metadata 鍝嶅簲缂撳瓨鍒?`android:release`銆?
- 杩欎釜缂撳瓨褰撳墠渚濊禆 TTL 鍒锋柊锛屽洜涓?latest metadata 鐨勬洿鏂版潵鑷?Android 鍙戝竷鑴氭湰鍐欏叆瀵硅薄瀛樺偍锛岃€屼笉鏄悗绔唴閮ㄦ煇涓鐞嗗啓鎺ュ彛銆?
- `GET /api/admin/summary`
- 褰撳墠鏆備笉鎺ュ叆 Spring Cache銆?
- 鍘熷洜鏄繖涓?summary 鍚屾椂鍚湁 request count銆乧aily active users銆乭ourly timeline 绛夐珮棰戠粺璁″€硷紝鐢ㄦ樉寮忓け鏁堝緢闅惧湪褰撳墠鏋舵瀯涓嬩繚鎸佸共鍑€璇箟銆?
## 2026-04-10 Spring Cache Minimal Landing Clarification
- `GET /api/admin/storage-policies`
- Response shape is unchanged.
- When `app.redis.enabled=true`, the backend caches the full storage policy list in `admin:storage-policies`.
- Successful storage policy create, update, and status-change writes evict that cache immediately.
- `GET /api/app/android/latest`
- Response shape is unchanged.
- When `app.redis.enabled=true`, the backend caches the metadata response derived from `android/releases/latest.json` in `android:release`.
- Refresh is TTL-based because the metadata is updated by the Android release publish script rather than an in-app admin write endpoint.
- `GET /api/admin/summary`
- This endpoint is intentionally not cached at the moment.
- The response mixes high-churn metrics such as request count, daily active users, and hourly request timeline data, so there is not yet a clean explicit invalidation boundary.
## 2026-04-10 DogeCloud Temporary S3 Session Clarification
- No HTTP API contract changed in this batch.
- The decision for Step 11 is architectural: DogeCloud temporary S3 sessions remain cached per backend instance inside `DogeCloudS3SessionProvider`.
- This does not change upload, download, direct-upload, or multipart endpoint shapes; it only clarifies that cross-instance Redis reuse is intentionally not introduced for these temporary runtime sessions.
## 2026-04-10 Stage 1 Validation Clarification
- No API response shape changed in Step 12.
- Validation confirmed that all new Redis-backed integrations added in Stage 1 still preserve the existing no-Redis API startup path when `app.redis.enabled=false`.
- Local boot also confirmed that the backend now has one explicit non-Redis prerequisite for runtime startup in both default and `dev` profiles: `app.jwt.secret` must be configured via `APP_JWT_SECRET` and cannot be left empty.
- Cross-instance behavior described by earlier Stage 1 notes remains architecturally valid, but it still needs real-environment verification with Redis plus multiple backend instances before being treated as deployment-proven.
## 2026-04-10 Manual Redis Validation Addendum
- No HTTP endpoint shape changed in this addendum either.
- The local two-instance Redis validation did confirm these existing API behaviors in a real runtime flow:
- `POST /api/auth/dev-login` on one instance invalidates the prior access token and refresh token even when the next authenticated read happens on the peer instance.
- `POST /api/transfer/sessions` plus `GET /api/transfer/sessions/lookup` continue to work across instances for online sessions, including after the creating instance is stopped.
- `GET /api/v2/files/events` on instance B receives a `CREATED` event after an authenticated media upload to instance A.
- `GET /api/v2/tasks` on instance B exposes the queued `MEDIA_META` task auto-created by that upload.
- Three backend fixes were internal and did not change API contracts:
- Redis cache serialization/deserialization for file list pages;
- Redis auth revocation cutoff precision;
- non-null `storage_name` persistence for directory creation and normal file upload metadata.
## 2026-04-11 Admin Backend Surface Addendum
- `GET /api/admin/file-blobs`
- Auth: admin only.
- Query params: `page`, `size`, `userQuery`, `storagePolicyId`, `objectKey`, `entityType`.
- Response items expose `FileEntity`-centric blob inspection fields including `objectKey`, `entityType`, `storagePolicyId`, `referenceCount`, `linkedStoredFileCount`, `linkedOwnerCount`, `sampleOwnerUsername`, `sampleOwnerEmail`, `createdByUserId`, `createdByUsername`, `blobMissing`, `orphanRisk`, and `referenceMismatch`.
- This endpoint is for admin diagnostics and migration visibility; it does not replace end-user file reads.
- `GET /api/admin/shares`
- Auth: admin only.
- Query params: `page`, `size`, `userQuery`, `fileName`, `token`, `passwordProtected`, `expired`.
- Response items expose share metadata from `FileShareLink`, plus owner and file summary fields.
- `DELETE /api/admin/shares/{shareId}`
- Auth: admin only.
- Deletes the target `FileShareLink` immediately.
- Intended for operational cleanup and moderation.
- `GET /api/admin/tasks`
- Auth: admin only.
- Query params: `page`, `size`, `userQuery`, `type`, `status`, `failureCategory`, `leaseState`.
- Response items expose task owner identity plus parsed task-state helpers: `failureCategory`, `retryScheduled`, `workerOwner`, and derived `leaseState`.
- `GET /api/admin/tasks/{taskId}`
- Auth: admin only.
- Returns the same admin task response shape as the list endpoint for a single task.