diff --git a/docs/plans/2026-10-19-K19.md b/docs/plans/2026-10-19-K19.md new file mode 100644 index 0000000..d93cce0 --- /dev/null +++ b/docs/plans/2026-10-19-K19.md @@ -0,0 +1,87 @@ +# 2026-10-19 K19 客户端账号中心 + +## 基线 + +- ReelShort `develop`: `7d68699` +- FruitServer `develop`: `5c93ba9` +- 只改 ReelShort;不改 FruitServer 业务代码、ReelShortWeb、specs、main。 + +## 范围 + +对接 `docs/api-phase1.md` §1.2 的六个个人中心接口: + +| 方法 | 路径 | 客户端用途 | +| --- | --- | --- | +| GET | `/app/me` | 资料展示 | +| PUT | `/app/me` | 修改昵称、性别、生日 | +| GET | `/app/me/devices` | 只读登录设备列表 | +| GET | `/app/me/export` | 账号数据摘要与完整 JSON 复制 | +| POST | `/app/me/deactivate` | 申请注销并进入 15 天冷静期 | +| POST | `/app/me/deactivate/cancel` | 撤销待处理注销申请 | + +## 接口口径 + +1. 资料响应仅使用: + `uid/nickname/avatarUrl/gender/countryCode/regionCode/language/timezone/currency/status/maskedTarget/createdAt/lastLoginAt/vipLevel/vipExpireAt`。 + `vipLevel` 是整数;时间字段按 `LocalDateTime` 字符串解析。 +2. 资料更新只发送本次变更字段: + `nickname/avatarUrl/gender/birthday/language/timezone/regionCode`。 + 本页只修改 `nickname/gender/birthday`;昵称 trim 后非空且不超过 30 字符, + 性别取 `male/female/unknown`,生日格式 `yyyy-MM-dd`。 +3. 设备数组只解析并展示 + `deviceId/platform/deviceModel/firstSeenAt/lastSeenAt/status`; + `id/userId/deleted` 不展示、不打印。页面只读。 +4. 导出按实际结构解析 + `{profile, credentials, consents, devices}`,展示资料摘要、凭据、授权记录数和设备数; + “复制完整 JSON”只写系统剪贴板,不写本地文件。 +5. 注销请求只发送 `reason`,不采集或上送密码;成功返回 `scheduledAt`。 + 成功后清本地会话,不调用 logout。撤销接口成功 `data=null`,失败原样透传后端文案。 +6. 所有数值和 ID 使用 `jsonToId/jsonToInt`;错误由 `ApiException` 透传到逻辑层。 + +## 客户端改造 + +- `lib/core/data/remote/account_api.dart` +- `lib/core/data/models/account_profile.dart` +- `lib/core/data/models/account_device.dart` +- `lib/core/data/models/account_export.dart` +- `lib/core/data/repositories/account_repository.dart` +- `lib/pages/account/` 下四个页面及各自的 binding/logic +- `lib/pages/mine/mine_view.dart` 登录态新增“账号”卡 +- `lib/core/data/services/auth_controller.dart` 新增 `applyProfile` +- `lib/app.dart` 注册 `AccountRepository` Provider +- `lib/l10n/app_{en,zh,id}.arb` 与生成文件 +- 测试新增 `test/account/`、`test/support/fake_account_repository.dart` + +## TDD 顺序 + +1. 先写 API/模型测试:六条路径、方法、请求体、字符串/数字 ID、缺字段; + 运行定向测试确认红灯。 +2. 实现 AccountApi、三个模型和 AccountRepository,定向测试转绿。 +3. 先写 AuthController.applyProfile 测试:保留令牌、更新昵称/头像并持久化; + 再实现。 +4. 先写 profile/device/export/deactivate logic 测试: + 资料保存回调登录态、注销成功清会话、撤销失败透传文案、导出复制内容; + 再实现逻辑层。 +5. 先写 widget 测试:我的页入口登录可见、资料回填与保存、设备空态、 + 注销二次确认、导出加载/失败/重试;再实现页面和入口。 +6. 生成 l10n,跑全量门禁;最后一次全量测试与 APK 构建。 + +## 门禁 + +```text +dart analyze lib test +flutter test +flutter build apk --debug +``` + +Flutter 分支:`feature/flutter-k19-account`。 + +## 风险与待确认 + +- `GET /app/me` 不返回注销申请状态,UI 不猜测状态;撤销失败展示后端文案。 +- 无设备下线接口,设备页只读;`TC-ACCOUNT-10` 的“单点下线”保留为缺口。 +- 无头像上传接口,头像只展示;空值使用现有占位。 +- 资料响应不含 `birthday`,也没有清空入口。本批生日只能设置,无法回填或清除; + 不补后端字段,页面文案明确为本次设置。 +- 注销成功后服务端立即吊销全部会话,本地同步清理;撤销入口在重新登录后仍可使用。 +- 真机验证由用户执行,本批不提供开发机 curl 或设备侧结果作为证据。