docs(plans): J1 回执(全局 ID 字符串化 + 契约统一),标为已处理·待用户验收
This commit is contained in:
1 parent
f35db2cd3b
commit
0cab094194
2 files changed
+205
-1
No files matched your search
+89
-1
@@ -19,7 +19,7 @@
|
||||
|
||||
---
|
||||
|
||||
## [待处理] 2026-10-16 18:30 | J1 全局 ID 字符串化(Long → String)+ 契约统一
|
||||
## [已处理·待用户验收] 2026-10-16 18:30 | J1 全局 ID 字符串化(Long → String)+ 契约统一
|
||||
|
||||
**用户裁定(2026-10-16 18:10)**:雪花 id 精度问题 **「按方案 (a) 全局修改,统一契约」**。
|
||||
|
||||
@@ -201,3 +201,91 @@ curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8080/v3/api-docs
|
||||
任务只有 S0/S1/S0-16、A1–A6、B1/B2、C1–C3 —— **没有 I1(后台管理端 商品配置 + 系统公告)这一条**,
|
||||
所以本轮没有可同步的任务号,我没有动任何既有任务的状态(避免误改 B1/C1/A* 这些别的批次)。
|
||||
下一步请给 I1 建任务并告知任务号,我补一次状态同步(`codex01` 的 token 已可用)。
|
||||
|
||||
---
|
||||
|
||||
### 回执(Codex,2026-10-16):J1 完成,停在干净交接点
|
||||
|
||||
**提交**(三个仓库各自 `develop`,`main` 未动)
|
||||
- FruitServer `f35db2c`(develop,已推):全局 Long→String + 契约 §0 + 测试
|
||||
- ReelShortWeb `3ec9ea6`(develop,已推):4 处 id 数字语义适配
|
||||
- ReelShort `94ff9f1`(**新建 develop**,已推):客户端 Long 计数兼容解析
|
||||
**留痕**:`docs/plans/PROGRESS.md`(本轮追加 J1 段,含原始输出);计划 `docs/plans/2026-10-16-J1.md`。
|
||||
|
||||
**门禁数字**
|
||||
| 门禁 | 结果 |
|
||||
| --- | --- |
|
||||
| `mvn test` | **BUILD SUCCESS** — commerce 33 / admin-api 21 / app-api 49 / worker 12 = **115 条全过**(存量 110 未跑红,新增 5) |
|
||||
| `mvn … package -DskipTests` | BUILD SUCCESS |
|
||||
| `pnpm ts:check` / eslint / `pnpm build:pro` | 0 错误 / 0 error / Build successful |
|
||||
| `dart analyze lib test` / `flutter test` | No issues found! / All tests passed!(+167) |
|
||||
|
||||
**每个仓库改了什么**
|
||||
|
||||
FruitServer(后端)
|
||||
- 新增 `fruitshorts-framework/.../web/JacksonLongToStringConfig.java`(`Long`/`long` → 字符串;
|
||||
`PageResult.total` 用 mixin 保持数字;`PlainLongSerializer` 供 mixin 使用)
|
||||
- `docs/api-phase1.md`:§0 新增「ID 字段一律以字符串下发」总口径(含示例、边界、`total` 例外),
|
||||
§2.11/§2.12 改为引用 §0
|
||||
- I1 的特例回退:`ProductRespVO` / `NoticeRespVO` 去掉 `@JsonSerialize`,
|
||||
`ProductAdminController` / `NoticeAdminController` 的 `create` 回到 `Result<Long>`
|
||||
- 测试:新增 `admin-api/.../web/LongToStringSerializationTest.java`(5 条);
|
||||
`ControllerContractSupport`(admin + app 两处)挂上模块,避免契约测试假绿;
|
||||
C 端 4 条 id 断言改成 `value("…") + isString()`
|
||||
|
||||
ReelShortWeb(4 个文件)
|
||||
- `src/utils/creatorImpersonation.ts` — 代看创作者 id 全程字符串(原来 `Number(raw)`,19 位会被四舍五入)
|
||||
- `src/views/contentManagement/EpisodeManagementView.vue` — `Number(route.query.drama)` → 字符串比较
|
||||
- `src/views/creator-center/composables/useDramaEdit.js` — `Number(route.params.id)` → 字符串比较
|
||||
- `src/views/permission/composables/useMenuManagement.js` — 顶级菜单哨兵 `0` → `'0'`(与接口的 `"0"` 对齐)
|
||||
|
||||
ReelShort(Flutter)逐文件清单(**只有这些,没夹带别的**)
|
||||
- `lib/core/data/models/json_numbers.dart`(新增)— `jsonToInt`:接受数字或数字字符串
|
||||
- `lib/core/data/models/auth_session.dart` — `expiresIn`(后端 `long`)改用 `jsonToInt`
|
||||
- `lib/core/data/models/drama.dart` — `viewCount`(后端 `Long`)两处改用 `jsonToInt`
|
||||
- `test/core/json_id_parsing_test.dart`(新增)— 8 条回归用例
|
||||
|
||||
**三个需要你确认的口径**
|
||||
|
||||
1. **`PageResult.total` 保持数字(唯一例外)** —— 它是条数不是 id,前端分页组件(el-pagination / Flutter 分页)
|
||||
都要数字。实测 `total = 2 int`。若你要求"Long 一律字符串",删掉 `JacksonLongToStringConfig` 里那一行 mixin 即可。
|
||||
2. **反向(请求方向)未做限制**:请求体 `"id":"…"`、路径变量、查询参数都接受字符串并转 `Long`(有测试)。
|
||||
`Integer`/`int`(`pageIndex`/`pageSize`/`Result.code`/`totalEpisodes`/`durationSec`…)保持数字 —— 实测已确认。
|
||||
3. **客户端适配范围比"只改 id 解析"宽了一点点,原因是物理上必须**:客户端 id 本来就是
|
||||
`(json['id'] ?? '').toString()`,不会崩;真正会崩的是被全局规则扫到的 **Long 计数**,
|
||||
所以改了 `expiresIn` 与 `viewCount` 共 3 处(`json_numbers.dart` 是新增工具)。
|
||||
其余 8 处 `as num?` 对应的 Java 字段都是 `Integer`/`int`,**按最小改动原则没动**。
|
||||
⚠️ **ReelShort 仓库原来只有 `main`**(无 develop),我按条目要求新建了 `develop` 并在上面提交。
|
||||
客户端由另一位同事推进,若他继续在 `main` 上走,需要把这笔 `94ff9f1` 带过去。
|
||||
|
||||
**用户可照做的验证步骤(每步都有预期结果)**
|
||||
|
||||
准备:`cd FruitServer && mvn -pl fruitshorts-app-api,fruitshorts-admin-api -am package -DskipTests`,起两个 jar;
|
||||
`curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8080/v3/api-docs` → 200。
|
||||
|
||||
1. **复现 PM 那条(最直观)**:注册一个 C 端账号
|
||||
```
|
||||
curl -s -X POST http://127.0.0.1:8081/app/auth/register -H 'Content-Type: application/json' \
|
||||
-d '{"targetType":"EMAIL","target":"你的邮箱@example.com","password":"Passw0rd!23","nickname":"手测","deviceId":"manual-1"}'
|
||||
```
|
||||
预期:`"expiresIn": "7200"`(字符串)。
|
||||
2. **用响应里的 user id 调后台**:`curl -s "http://127.0.0.1:8080/admin/app-users/page?pageSize=50" -H "Authorization: Bearer <admin token>"`,
|
||||
拿新用户的 `id`(预期是 `"2111…"` 这种**带引号的字符串**、`total` 是**不带引号的数字**),再
|
||||
`curl -s "http://127.0.0.1:8080/admin/app-users/<id>" -H "Authorization: Bearer <token>"` → 预期 `code=0`、`data.id` 与列表一致
|
||||
(**修复前这里返回 `200002 数据不存在`**)。
|
||||
3. **JS 逐位一致**:把第 2 步的响应存成 `r.json`,然后
|
||||
```
|
||||
node -e 'const d=JSON.parse(require("fs").readFileSync("r.json","utf8"));console.log(String(d.data.id))'
|
||||
```
|
||||
预期与 `psql -c "select id::text from app_user where uid='<uid>'"` **逐位相同**。
|
||||
对照:`node -e 'console.log(String(JSON.parse("2111015748376858626")))'` → `2111015748376858620` 附近(会被改写),说明精度问题真实存在。
|
||||
4. **后台界面(:3005)**:`admin/admin123` 登录 → 商品配置新建一个商品 → **点它那一行的状态/编辑**,
|
||||
预期正常(toast 成功,不再"商品不存在");菜单管理编辑一个顶级菜单,预期父级下拉显示「顶级菜单」而不是 `0`。
|
||||
5. **短剧管理 → 剧集管理带参跳转**:从短剧管理点某剧的「剧集」,URL 带 `?drama=<id>`,
|
||||
预期剧集页自动选中该剧(改成字符串比较前这里会选不中)。
|
||||
6. **客户端**:`cd ReelShort && flutter test`(预期 All tests passed);有设备时 `flutter run` 打开首页看播放量/详情
|
||||
不报解析异常(后端 `viewCount` 现在下发字符串)。
|
||||
7. **异常路径**:请求体里 id 传**数字**(老客户端行为)也应被接受:`PUT /admin/products/<id>/status?status=1` 用数字 id 路径同样 200。
|
||||
|
||||
**禅道**:J1 在禅道里同样没有对应任务(项目 2 下仍是 S0/S1/S0-16、A1–A6、B1/B2、C1–C3),未动任何既有任务状态。
|
||||
**服务现状**:8080/8081 是我刚重建起回的新 jar(自测时在跑);:3005 的 dev server 也在。若被环境回收,按上面「准备」起回来即可。
|
||||
@@ -242,3 +242,119 @@ DELETE 1 # 公告 I1 复测公告:印尼开服
|
||||
1. 雪花 id 的全局方案:admin-api 是否统一 `Long → String`(涉及所有既有页面的 `id` 类型),
|
||||
app-api 是否要动(影响 Flutter 客户端解析)——属于平台级改动,等裁定。
|
||||
2. `/app/orders/verify` 的真发币链路需要 Google Play 凭据(B1-1 已列清单)。
|
||||
|
||||
---
|
||||
|
||||
## 2026-10-16 J1:全局 ID 字符串化(Long → String)+ 契约统一
|
||||
|
||||
计划文件:`docs/plans/2026-10-16-J1.md`(先写计划再动手)。用户裁定「方案 (a) 全局修改,统一契约」。
|
||||
|
||||
### 动作 1:后端全局序列化(fruitshorts-framework)
|
||||
|
||||
新增 `JacksonLongToStringConfig`:`SimpleModule` 把 `Long.class` / `Long.TYPE` → `ToStringSerializer`,
|
||||
经 `Jackson2ObjectMapperBuilderCustomizer` 挂进 Spring Boot 的 `ObjectMapper`。
|
||||
两个入口都是 `scanBasePackages = "com.fruitshorts"`,一处生效两端一致,**没改 yml**。
|
||||
|
||||
一个刻意的例外:`PageResult.total` 是条数不是 id,用 mixin 显式保持数字(前端分页组件要数字)。
|
||||
为此给 mixin 加了个 `PlainLongSerializer`(`NumberSerializer` 没有无参构造,`using=` 装不上)。
|
||||
|
||||
I1 里给商品/公告 VO 逐个加的 `@JsonSerialize` 与 `Result<String>` 新建返回**已回退成自然写法** ——
|
||||
全局生效后它们只是冗余特例(现在是"所有模块一个口径")。
|
||||
|
||||
### 动作 2:契约 §0 总口径
|
||||
|
||||
`docs/api-phase1.md` §0 新增「**ID 字段一律以字符串下发**」+ 示例 + 边界(`total` 与 `Integer` 保持数字、
|
||||
id 回传按字符串传),§2.11/§2.12 的逐模块表述改成引用 §0。
|
||||
|
||||
### 动作 3:契约测试的 standalone mapper 也挂上模块(否则假绿)
|
||||
|
||||
`ControllerContractSupport`(app-api / admin-api 两处)自带手搓 `JsonMapper`,不含全局模块 ——
|
||||
不改它的话,测试断言数字 id 也会过,而生产早就变成字符串了。挂上模块后:
|
||||
|
||||
```
|
||||
$ mvn test
|
||||
[INFO] Tests run: 33, Failures: 0, Errors: 0, Skipped: 0 (fruitshorts-module-commerce)
|
||||
[INFO] Tests run: 21, Failures: 0, Errors: 0, Skipped: 0 (fruitshorts-admin-api)
|
||||
[INFO] Tests run: 49, Failures: 0, Errors: 0, Skipped: 0 (fruitshorts-app-api)
|
||||
[INFO] Tests run: 12, Failures: 0, Errors: 0, Skipped: 0 (fruitshorts-transcode-worker)
|
||||
[INFO] BUILD SUCCESS # 共 115 条(存量 110 + 新增 5)
|
||||
```
|
||||
|
||||
新增 `LongToStringSerializationTest` 5 条:普通字段 / 嵌套对象 / List / Map 值 / 分页(total 数字 + list id 字符串)
|
||||
/ 请求体 `"id":"…"` 反序列化成 Long / 19 位路径变量绑定成 Long。
|
||||
另外把 C 端 4 条 id 断言从数字改成 `value("…") + isString()`(原来"数字也能过"的宽松比较不算证据)。
|
||||
|
||||
### 动作 4:Web 与客户端适配
|
||||
|
||||
见回执的逐文件清单。要点:Web 审计出 4 处把 id 当数字用的地方(代看 id 过 `Number`、
|
||||
两处 `Number(route.…)` 与 id 比较、菜单顶级哨兵 0 与 "0" 不一致);Flutter 侧 id 本来就是
|
||||
`(json['id'] ?? '').toString()`,会崩的是被全局规则扫到的 **Long 计数**,只改了 `expiresIn` 与 `viewCount`。
|
||||
|
||||
```
|
||||
$ pnpm ts:check → 0 错误
|
||||
$ pnpm exec eslint … ./src → 0 error
|
||||
$ pnpm build:pro → Build successful
|
||||
$ dart analyze lib test → No issues found!
|
||||
$ flutter test → All tests passed! (+167)
|
||||
```
|
||||
|
||||
### 动作 5:端到端复现"修复前后"(真实服务)
|
||||
|
||||
```
|
||||
① 复现 PM 那个场景:C 端注册 → admin 列表/详情
|
||||
$ curl -X POST :8081/app/auth/register -d '{"targetType":"EMAIL","target":"j1.selftest@example.com",…}'
|
||||
→ "expiresIn": "7200" # long 字段,已是字符串
|
||||
$ curl -H "Authorization: …" :8080/admin/app-users/page?pageSize=50
|
||||
total = 2 int # 分页 total 仍是数字(例外生效)
|
||||
命中用户: uid=U79DG5IOM id='2111015748376858626' id类型=str
|
||||
$ curl -H "Authorization: …" :8080/admin/app-users/2111015748376858626
|
||||
code= 0 message= ok # 修复前这里就是 200002
|
||||
data.id= '2111015748376858626' uid= U79DG5IOM
|
||||
```
|
||||
|
||||
```
|
||||
② JS 侧逐位一致(真实响应喂 node)
|
||||
$ node -e '…'
|
||||
响应原文里的 id(JSON 字符串): "2111015748376858626"
|
||||
typeof 解析后 : string
|
||||
String(parsed.id) : 2111015748376858626
|
||||
库里 app_user.id : 2111015748376858626
|
||||
逐位相等? : true
|
||||
---- 反面对照:同一串数字当裸数字解析 ----
|
||||
裸数字 -> String(parsed.id) : 2111015748376858600
|
||||
逐位相等? : false
|
||||
```
|
||||
|
||||
```
|
||||
③ 全局范围:换两个模块(商品 / 创作者)走"新建 → 用响应 id 查详情 → 改状态"
|
||||
新建商品响应 : {"code":0,"data":"2111015841427492865"}
|
||||
商品详情 : code=0 id='2111015841427492865'
|
||||
商品上架 : {"code":0}
|
||||
新建创作者响应 : {"code":0,"data":"2111015841771425794"}
|
||||
创作者详情 : code=0 id='2111015841771425794' name=J1 探针工作室
|
||||
```
|
||||
|
||||
```
|
||||
④ C 端字段类型(匿名可读的链路)
|
||||
/app/home/feed : drama card id='5'(str) viewCount='3100000'(str)
|
||||
/app/dramas/5 : id='5'(str) viewCount='3100000'(str) totalEpisodes=72(int) episode id='7'(str)
|
||||
/app/episodes/7/play : episodeId='7'(str) dramaId='5'(str) durationSec=228(int) unlockCoin=0(int)
|
||||
```
|
||||
|
||||
解读:**Long → 字符串**(id、viewCount、expiresIn、coinAmount),**Integer/int 与分页 total 仍是数字**,
|
||||
与 §0 的规则和例外完全对上。
|
||||
|
||||
### 动作 6:自测数据清理(含一次口径纠正)
|
||||
|
||||
```
|
||||
DELETE 1 # 自测用户 app_user(含 credential 2 / session 2 / device 2 / consent 3 / wallet 1)
|
||||
DELETE 1 # 商品 j1_probe_coin
|
||||
DELETE 1 # 创作者 J1PROBE
|
||||
全库扫描 uid / 邮箱 / 雪花 id → 无任何残留(NOTICE: 扫描完成)
|
||||
现存:creator 只剩 PM 那条 id=25(CR20261015001)、app_user 只剩 id=28(UHR-DLTH8)、
|
||||
product 3 行种子、ops_notice 0 行 —— 都不是本轮数据。
|
||||
```
|
||||
|
||||
⚠️ 口径纠正:我第一版清理脚本用 `psql -t -A -c "delete … returning 1" | wc -l` 数"行数",
|
||||
**数到的其实是 psql 的命令标签**(实测删 0 行时输出 `DELETE 0`,也是 1 行),所以那轮打印的
|
||||
"删除 1 行"是假数字。真正的证据是后面的**全库残留扫描**(零命中)与现存数据核对,已按此结论写回执。
|
||||
Reference in new issue
Block a user