diff --git a/docs/plans/INBOX.md b/docs/plans/INBOX.md index 1e42f43..f834871 100644 --- a/docs/plans/INBOX.md +++ b/docs/plans/INBOX.md @@ -20,6 +20,111 @@ --- +## [待处理] 2026-10-19 | K19 客户端账号中心:资料 / 设备 / 数据导出 / 注销 + +**用户指令(2026-10-19)**:「先写 K19 任务书并派单」。 +> 只改客户端 `ReelShort`。后端、`ReelShortWeb`、`specs`、`main` 一律不动。 + +### 一、背景与缺口 + +后端 `/app` 共 45 个接口,客户端已接 26 个。本轮对接账号中心 6 个(`api-phase1.md` §1.2,第 157–162 行;实现见 +`fruitshorts-app-api/.../AppMeController.java`): + +| 方法 | 路径 | 客户端现状 | +| --- | --- | --- | +| GET | `/app/me` | 未对接 | +| PUT | `/app/me` | 未对接 | +| GET | `/app/me/devices` | 未对接 | +| GET | `/app/me/export` | 未对接 | +| POST | `/app/me/deactivate` | 未对接 | +| POST | `/app/me/deactivate/cancel` | 未对接 | + +### 二、接口与字段口径(以后端代码为准确认,勿臆造) + +1. **GET `/app/me`** → `AppUserRespVO`:`uid, nickname, avatarUrl, gender, countryCode, regionCode, + language, timezone, currency, status, maskedTarget, createdAt, lastLoginAt, vipLevel(Integer), + vipExpireAt`。`vipLevel` 是数字,其余时间字段是 `LocalDateTime` 字符串。 +2. **PUT `/app/me`** → 请求 `ProfileUpdateReqVO`,全部字段可选,**只传本次改动的字段**: + `nickname, avatarUrl, gender, birthday(yyyy-MM-dd), language, timezone, regionCode`。 + `nickname` 为空串时后端忽略;`gender` 取值按 `schema-phase1.sql` 注释用小写 + `male / female / unknown`;`birthday` 后端只能设置、不能清空。响应同 `AppUserRespVO`。 +3. **GET `/app/me/devices`** → **直接返回 `AppUserDevice` 实体数组**(不是专用 VO): + `id, userId, deviceId, platform, deviceModel, firstSeenAt, lastSeenAt, status, createdAt, updatedAt, deleted`。 + 按契约 §0,`Long` 字段以字符串下发。客户端**只展示** `deviceId/platform/deviceModel/firstSeenAt/lastSeenAt/status`, + `userId`、`deleted` 一律忽略、不落日志、不上屏。 +4. **GET `/app/me/export`** → `{profile: AppUserRespVO, credentials: [{type, masked, verified}], consents: [...], devices: [...]}` + (以 `AppUserService.exportAccount` 实际结构为准)。 +5. **POST `/app/me/deactivate`** → 请求 `{reason}`(`password` 后端当前不校验,**客户端不采集、不上送**); + 成功返回 `scheduledAt`(15 天冷静期截止时间,`LocalDateTime`)。后端同时吊销全部会话。 + 已有申请时返回业务错误,文案「注销申请已在处理中」。 +6. **POST `/app/me/deactivate/cancel`** → 成功 `data = null`;无待处理申请时错误文案「没有待处理的注销申请」。 + +**契约缺口(本批如实标注,不得自行补后端)**: + +- `GET /app/me` 不返回「注销申请中」状态 → UI 不能做状态判断,只能把后端错误文案原样透传; + 「撤销注销」入口常驻,无申请时展示后端返回的文案。 +- 无「设备下线 / 踢出」接口 → 设备页**只读**;`TC-ACCOUNT-10` 中「单点下线」记为缺口,留待后续批次。 +- 无头像上传接口 → 头像**只展示**(`avatarUrl` 为空用现有占位图标),本批不做上传、不做 URL 输入框。 + +### 三、客户端改造范围 + +1. **remote**:新增 `lib/core/data/remote/account_api.dart`,六个方法,路径与请求体严格按上表; + 错误一律抛现有 `ApiException`(禁止吞异常、禁止空 catch)。 +2. **models**:新增 `account_profile.dart`、`account_device.dart`、`account_export.dart`。 + 数值/ID 解析用 `json_numbers.dart` 的 `jsonToId` / `jsonToInt`,兼容字符串与数字两种下发形式。 +3. **repository**:新增 `account_repository.dart`(接口 + `HttpAccountRepository`); + 在 `app.dart` 的 Provider 树注册,供页面与测试注入(`FakeAccountRepository` 放 `test/support/`)。 +4. **页面**(建议 `lib/pages/account/`,沿用 provider binding/logic/view 三层): + - **个人资料**:进入时 `GET /app/me`;可改 `nickname`(去空格后非空、≤30 字符,客户端校验)、 + `gender`(male/female/unknown,本地化文案)、`birthday`(日期选择,`yyyy-MM-dd`); + `uid / maskedTarget / countryCode / currency / status` 只读展示;`language / timezone / regionCode` 只读。 + 保存调 `PUT /app/me`,成功后用响应刷新页面,并**同步本地登录态**(见第 6 条)。 + - **登录设备**:`GET /app/me/devices`,列表展示平台、机型、设备号(脱敏显示,保留首尾各 4 位)、 + 首次/最近活跃时间、状态;空态用现有 `EmptyState`;本页只读。 + - **数据导出**:`GET /app/me/export`,展示资料摘要、凭据(`type/masked/verified`)、授权记录数、设备数; + 提供「复制完整 JSON」到剪贴板(`Clipboard.setData`)+ SnackBar 提示;不写本地文件。 + - **注销账号**:15 天冷静期与「申请后立即登出全部设备」的显著提示;可选注销原因(≤200 字); + 「申请注销」必须二次确认弹窗 → 成功后展示 `scheduledAt` 并清空本地会话(服务端已吊销全部会话, + 用 `AuthController.clear()`,不要再调 logout),回到未登录态; + 「撤销注销申请」常驻,成功/失败均以后端文案提示。 +5. **入口**:`lib/pages/mine/mine_view.dart` 在已登录态新增「账号」卡(置于钱包卡之后、菜单卡之前): + 个人资料 / 登录设备 / 数据导出 / 注销账号,四项带图标与分隔线;未登录不显示。样式沿用现有 `_menuCard`。 +6. **登录态同步**:`AuthController` 增加公开方法(如 `applyProfile(AuthUser)`),资料保存成功后更新 + 内存 session 的 `uid/nickname/avatarUrl` 并持久化,使「我的」页昵称即时生效;不改令牌与刷新逻辑。 +7. **l10n**:新增文案必须同时写入 `app_en.arb / app_zh.arb / app_id.arb` 并重新生成; + 印尼语为印尼市场主语言,禁止留英文占位。 +8. **测试(TDD,先红后绿)**: + - API/模型:六接口路径与请求体、`Long` 字符串/数字两种解析、缺字段容错、错误文案透传; + - logic:资料保存成功后回调登录态、注销成功后清会话、撤销失败展示后端文案、导出复制内容; + - widget:我的页四个入口仅登录态可见、资料页字段回填与保存、设备空态、注销二次确认、 + 数据导出页加载/失败/重试。 + - 既有测试不得改断言;如需适配只改注入的 Fake。 + +### 四、约束与门禁 + +- 代码规模红线:函数 ≤50 行、参数 ≤5、类 ≤300 行、文件 ≤500 行、嵌套 ≤4、圈复杂度 ≤10;禁止魔法值。 +- public 类/方法必须有文档注释,文件首行统一 `// Author: fruit.com.cn`;禁止裸 `then()`、空 catch。 +- 门禁(不达标不提交):`dart analyze lib test` 零告警;`flutter test` 全过; + `flutter build apk --debug` 构建通过。 +- 真机验证由用户执行,Codex **不得**自行做设备侧验证,也不得用本机 curl 充当设备证据。 + +### 五、Git 与交接 + +- 从干净 `develop`(基线 `7d68699`)拉 `feature/flutter-k19-account`;小步提交, + `type(scope): 描述`(≤50 字符),只 `git add` 本次文件。 +- 完成后 `git checkout develop && git merge --no-ff feature/flutter-k19-account && git push origin develop`; + **不动 `main`**;提交作者与提交者统一 `Zhou Weidong <502333560@qq.com>`。 +- 任务书与留痕属于 FruitServer 仓库:`docs/plans/PROGRESS.md` 追加命令 + 原始输出 + 解读, + 单独提交到 FruitServer `develop`(`docs(plans): ...`),不夹带客户端代码。 + +### 六、回执要求 + +完成后停住并回执:分支与提交号、六个接口的调用位置、测试条数与结果、`dart analyze` / `flutter test` / +`flutter build apk --debug` 原始结论、已知缺口(设备下线 / 头像上传 / 注销状态查询), +以及「用户可照做的真机验证步骤 + 每步预期结果」。 + +--- + ## [已处理·待用户验收] 2026-10-17 | K16 注册限流改为可配置(支持不限制) **用户指令(2026-10-17)**:「注册 429 限流做成可配置的,不限制或限制数量」。