Enable dual-device login and mobile APK update checks

This commit is contained in:
yoyuzh
2026-04-03 16:28:09 +08:00
parent 56f2a9fe0d
commit 52b5bbfe8e
50 changed files with 1659 additions and 164 deletions

View File

@@ -33,8 +33,10 @@
- 采用 `Authorization: Bearer <accessToken>`
- `refreshToken` 通过 `/api/auth/refresh` 换取新的登录态
- 当前实现为“单账号单设备在线
- 新设备登录后,旧设备的 access token 会失效
- 当前实现为“按客户端类型拆分会话
- 桌面端与移动端可以同时在线
- 同一端类型再次登录后,该端旧 access token 会失效
- `/api/auth/register``/api/auth/login``/api/auth/refresh` 与开发环境 `/api/auth/dev-login` 支持可选请求头 `X-Yoyuzh-Client: desktop|mobile`
### 权限分层
@@ -64,6 +66,7 @@
- 使用邀请码注册
- 注册成功后直接返回登录态
- 邀请码成功使用后会自动刷新
- 若请求未显式带 `X-Yoyuzh-Client`,后端默认按 `desktop` 处理
请求重点字段:
@@ -90,6 +93,11 @@
- `refreshToken`
- `user`
补充说明:
- 可选请求头 `X-Yoyuzh-Client` 用于声明当前登录来自桌面端还是移动端
- 同账号桌面端与移动端可同时保持登录,但同类型端再次登录会顶掉旧会话
### 2.3 刷新登录态
`POST /api/auth/refresh`
@@ -102,6 +110,7 @@
- 刷新后会返回新的 access token 与 refresh token
- 当前系统会让旧 refresh token 失效
- 刷新会沿用该 refresh token 原本所属的客户端类型;请求头缺省时仍按 `desktop` 兜底
### 2.4 开发环境登录
@@ -111,6 +120,7 @@
- 仅用于开发联调
- 是否可用取决于当前环境配置
- 同样支持可选请求头 `X-Yoyuzh-Client: desktop|mobile`
### 2.5 获取用户资料
@@ -188,6 +198,8 @@
- `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`
说明:
@@ -195,6 +207,9 @@
- `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 分享链接

View File

@@ -107,15 +107,16 @@
- 用户资料查询和修改
- 用户自行修改密码
- 头像上传
- 单设备登录控制
- 按客户端类型拆分的登录会话控制
- 邀请码消费与轮换
关键实现说明:
- access token 使用 JWT
- refresh token 持久化到数据库
- 当前会话通过 `activeSessionId + JWT sid claim` 绑定
- 新登录会挤掉旧设备
- 当前会话通过“客户端类型 + 会话 ID”绑定JWT 同时携带 `sid``client` claim
- 用户表分别记录桌面端与移动端活跃会话;桌面端仍同步回写旧的 `activeSessionId` 以兼容存量逻辑
- 同账号现在允许桌面端与移动端同时在线,但同一端类型再次登录仍会挤掉旧会话
- 当前密码策略统一为“至少 8 位且包含大写字母”
### 3.2 网盘模块
@@ -132,6 +133,7 @@
- 文件/文件夹上传、下载、删除、重命名
- 目录创建与分页列表
- 移动、复制
- 回收站列表、恢复与过期清理
- 分享链接与导入
- 前端树状目录导航
@@ -143,10 +145,13 @@
- 支持本地磁盘和 S3 兼容对象存储
- 分享导入与网盘复制会直接复用源文件的 `FileBlob`,不会再次写入字节内容
- 文件重命名、移动只更新 `StoredFile` 元数据,不会移动底层对象
- 删除文件时会先删除 `StoredFile` 引用;只有最后一个引用消失时,才真正删除 `FileBlob` 对应的底层对象
- 删除文件时不会立刻物理删除,而是把 `StoredFile` 及其目录树标记为回收站条目;根条目会记录 `deletedAt`、原始父路径和回收分组 ID回收站保留期固定为 10 天
- 回收站恢复会把整组条目恢复到原路径,并在恢复前检查同名冲突和用户剩余配额
- 定时清理任务会删除超过 10 天的回收站条目;只有当某个 `FileBlob` 的最后一个逻辑引用随之消失时,才真正删除底层对象
- 应用启动时会把旧 `portal_file.storage_name` 行自动回填到新的 `blob_id` 引用,保证存量数据能继续读取
- 当前线上网盘文件存储已切到多吉云对象存储,后端先通过多吉云临时密钥 API 换取短期 S3 会话,再访问底层 COS 兼容桶
- 前端会缓存目录列表和最后访问路径
- 桌面网盘页在左侧树状目录栏底部固定展示回收站入口;移动端在网盘页顶部提供回收站入口;两端共用独立 `RecycleBin` 页面调用 `/api/files/recycle-bin` 与恢复接口
Android 壳补充说明:
@@ -156,6 +161,9 @@ Android 壳补充说明:
- 后端 CORS 默认放行 `http://localhost``https://localhost``http://127.0.0.1``https://127.0.0.1``capacitor://localhost`,以兼容 Web 开发环境和 Android WebView 壳
- Web 端构建完成后,通过 `npx cap sync android` 把静态资源复制到 `front/android/app/src/main/assets/public`
- Android 调试包当前通过 `cd front/android && ./gradlew assembleDebug` 生成,输出路径是 `front/android/app/build/outputs/apk/debug/app-debug.apk`
- 前端总览页会在 Web 环境展示稳定 APK 下载入口 `/downloads/yoyuzh-portal.apk`
- Capacitor 原生壳内的移动端总览页会改为“检查更新”入口;前端会先对 OSS 上的 APK 做 `HEAD` 探测并读取最新修改时间,再直接打开下载链接完成更新
- 前端 OSS 发布脚本会在上传 `front/dist` 后,额外把 `front/android/app/build/outputs/apk/debug/app-debug.apk` 上传到同一个静态站桶里的 `downloads/yoyuzh-portal.apk`;这里刻意不把 APK 放进 `front/dist`,以避免后续 `npx cap sync android` 时把旧 APK 再次打进新的 Android 包
- 由于当前开发机直连 `dl.google.com` 与 Google Android Maven 仓库存在 TLS 握手失败,本地 Android 构建仓库源已切到可访问镜像;如果后续重新生成 Capacitor 工程,需要重新确认镜像配置仍存在
### 3.3 快传模块
@@ -228,10 +236,11 @@ Android 壳补充说明:
1. 前端登录页调用 `/api/auth/login`
2. 后端鉴权成功后签发 access token + refresh token
3. 后端刷新 `activeSessionId`
4. 前端本地存储 `portal-session`
5. 后续请求通过 `Authorization: Bearer <token>` 访问
6. JWT 过滤器校验 token、用户状态和会话 ID 是否仍匹配
3. 前端同时上送 `X-Yoyuzh-Client` 标记当前是 `desktop` 还是 `mobile`
4. 后端按客户端类型刷新对应的活跃会话 ID 与 refresh token 集合
5. 前端本地存储 `portal-session`
6. 后续请求通过 `Authorization: Bearer <token>` 访问,并继续带上 `X-Yoyuzh-Client`
7. JWT 过滤器校验 token、用户状态以及当前客户端类型对应的会话 ID 是否仍匹配
补充说明:
@@ -295,7 +304,7 @@ Android 壳补充说明:
1. 管理台调用 `PUT /api/admin/users/{userId}/password`
2. 后端按统一密码规则校验新密码
3. 后端重算密码哈希并写回用户表
4. 后端刷新 `activeSessionId` 并撤销该用户全部 refresh token
4. 后端刷新桌面端与移动端全部活跃会话,并撤销该用户全部 refresh token
5. 旧密码后续登录应失败,新密码登录成功
## 5. 前端路由架构
@@ -330,14 +339,15 @@ Android 壳补充说明:
- `GET /api/files/share-links/{token}` 公开
- `/api/files/**``/api/user/**``/api/admin/**` 需登录
### 6.2 单设备登录
### 6.2 分端单会话登录
当前实现不是只撤销 refresh token而是同时控制 access token
当前实现不是只撤销 refresh token而是同时控制 access token,并按客户端类型拆分
- 用户表记录 `activeSessionId`
- JWT 里包含 `sid`
- 过滤器每次请求都会比对当前用户的 `activeSessionId`
- 新登录成功后,旧设备 token 会失效
- 前端会在鉴权与上传请求里附带 `X-Yoyuzh-Client: desktop|mobile`
- 用户表记录 `desktopActiveSessionId``mobileActiveSessionId`
- JWT 里同时包含 `sid``client`
- 过滤器每次请求都会按 token 里的 `client` 去比对对应端的活跃会话 ID
- 桌面端与移动端可以同时在线,但同一端再次登录成功后,该端旧 token 会失效
## 7. 存储架构

View File

@@ -0,0 +1,45 @@
# Overview APK Download Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 在总览界面增加 Android APK 下载入口,并让前端 OSS 发布流程稳定上传最新调试 APK。
**Architecture:** 总览页使用固定的前端静态下载路径展示 APK 下载按钮避免引入额外后端接口。OSS 发布仍沿用现有 `scripts/deploy-front-oss.mjs`,只额外把 `front/android/app/build/outputs/apk/debug/app-debug.apk` 上传到稳定对象 key避免把 APK 混入 `front/dist` 进而被 Capacitor 再次打包进 Android 壳。
**Tech Stack:** React 19, TypeScript, Vite 6, Node.js ESM scripts
---
### Task 1: 总览页下载入口状态与链接
**Files:**
- Modify: `front/src/lib/app-shell.ts`
- Modify: `front/src/lib/app-shell.test.ts`
- Modify: `front/src/pages/overview-state.ts`
- Modify: `front/src/pages/overview-state.test.ts`
- [ ] **Step 1: Write the failing test**
- [ ] **Step 2: Run `cd front && npm run test` to verify the new state tests fail for the missing helpers**
- [ ] **Step 3: Add native-shell detection and APK download path helpers**
- [ ] **Step 4: Run `cd front && npm run test` to verify the helper tests pass**
### Task 2: 桌面端与移动端总览入口
**Files:**
- Modify: `front/src/pages/Overview.tsx`
- Modify: `front/src/mobile-pages/MobileOverview.tsx`
- [ ] **Step 1: Render the APK download entry only in web contexts where it is useful**
- [ ] **Step 2: Keep the current overview information architecture intact and reuse the shared helper**
- [ ] **Step 3: Run `cd front && npm run lint` to verify the React/TypeScript changes type-check**
### Task 3: OSS 发布时上传 APK
**Files:**
- Modify: `scripts/deploy-front-oss.mjs`
- Modify: `memory.md`
- Modify: `docs/architecture.md`
- [ ] **Step 1: Extend the existing OSS deploy script to upload the built APK to a stable key**
- [ ] **Step 2: Verify with `node scripts/deploy-front-oss.mjs --dry-run` if credentials are available, otherwise document the gap**
- [ ] **Step 3: Update project memory and architecture notes for the new APK distribution path**

View File

@@ -0,0 +1,59 @@
# Recycle Bin Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 为网盘增加回收站,删除后的文件或目录自动进入回收站,保留 10 天,可在保留期内恢复,并在网盘左侧目录侧栏底部提供入口。
**Architecture:** 后端把删除操作从“物理删除”改成“回收站软删除”,为 `StoredFile` 增加删除时间、原始路径/名称、回收站分组信息,并提供“列出回收站”“恢复”“清空过期项”能力。前端在 `Files` 页目录树侧栏底部增加回收站入口,并新增回收站页面/状态,复用现有文件卡片风格展示删除时间、原始位置和恢复操作。
**Tech Stack:** Spring Boot 3.3, JPA/Hibernate update schema, React 19, TypeScript, Vite
---
### Task 1: 后端回收站模型与服务
**Files:**
- Modify: `backend/src/main/java/com/yoyuzh/files/StoredFile.java`
- Modify: `backend/src/main/java/com/yoyuzh/files/StoredFileRepository.java`
- Modify: `backend/src/main/java/com/yoyuzh/files/FileService.java`
- Modify: `backend/src/main/java/com/yoyuzh/files/FileController.java`
- Create: `backend/src/main/java/com/yoyuzh/files/RecycleBinItemResponse.java`
- Create: `backend/src/main/java/com/yoyuzh/files/RestoreRecycleBinItemRequest.java`
- [ ] **Step 1: 写后端失败测试覆盖删除进入回收站、回收站列表、恢复、10 天过期清理**
- [ ] **Step 2: 运行 `cd backend && mvn test` 确认新增测试先失败**
- [ ] **Step 3: 给 `StoredFile` 增加回收站字段,并让普通文件列表/最近文件默认排除已删除项**
- [ ] **Step 4: 把删除改成软删除,目录删除时按组放入回收站,恢复时按组恢复**
- [ ] **Step 5: 增加回收站列表、恢复接口和定时清理过期项**
- [ ] **Step 6: 运行 `cd backend && mvn test` 确认后端通过**
### Task 2: 前端回收站入口与页面
**Files:**
- Modify: `front/src/components/layout/Layout.tsx`
- Modify: `front/src/components/layout/Layout.test.ts`
- Modify: `front/src/App.tsx`
- Modify: `front/src/pages/Files.tsx`
- Create: `front/src/pages/RecycleBin.tsx`
- Create: `front/src/pages/recycle-bin-state.ts`
- Create: `front/src/pages/recycle-bin-state.test.ts`
- Modify: `front/src/lib/types.ts`
- Modify: `front/src/MobileApp.tsx`
- Modify: `front/src/mobile-pages/MobileFiles.tsx`
- [ ] **Step 1: 写前端失败测试,锁住侧栏底部入口和回收站状态变换**
- [ ] **Step 2: 运行 `cd front && npm run test` 确认前端新增测试先失败**
- [ ] **Step 3: 新增回收站页面和状态,接后端列表/恢复接口**
- [ ] **Step 4: 在桌面网盘左侧侧栏最下方加回收站入口,并把删除确认文案改为“移入回收站”**
- [ ] **Step 5: 给移动端补可访问的回收站入口和路由**
- [ ] **Step 6: 运行 `cd front && npm run test`、`cd front && npm run lint`、`cd front && npm run build`**
### Task 3: 文档与项目记忆
**Files:**
- Modify: `memory.md`
- Modify: `docs/architecture.md`
- Modify: `docs/api-reference.md`
- [ ] **Step 1: 记录回收站行为、保留周期、入口位置和恢复约束**
- [ ] **Step 2: 在交付前确认验证命令和已知限制写入文档**