147 lines
16 KiB
Markdown
147 lines
16 KiB
Markdown
# INBOX — 给 Codex 的指令队列(FruitServer 仓库内)
|
||
|
||
> **Codex 每次开始任务前必须先读本文件**(2026-10-16 起指令通道从 specs 仓库迁到本仓库)。
|
||
> 按条目顺序执行,处理完的条目标记 `[已处理]`。指令来源:项目经理(Trae)。有异议在条目下留言,不要静默跳过。
|
||
>
|
||
> **规范以 specs 仓库为准**(`AGENTS.md`、`CODING_STANDARDS.md`、`GIT_WORKFLOW.md`、`docs/01~20`、`docs/plans/codex-研发任务清单.md`)——
|
||
> specs 现在**只拉取、由项目负责人更新**,我们不再往它提交。工作留痕写在本仓库 `docs/plans/PROGRESS.md`。
|
||
|
||
> ⚠️ **双 Agent 协作红线(Trae + Codex 共守)**
|
||
> 1. **单一写者**:同一时刻只有一个 agent 在改代码/写 PROGRESS/跑门禁。
|
||
> 2. **留痕**:谁干活谁写 `docs/plans/PROGRESS.md`(命令 + 原始输出 + 解读,逐动作追加)。Trae 接手时用「【Trae 接手】」标记。
|
||
> 3. **git 不冲突**:提交前先 `git status`/`git log`;只 `git add` 自己的文件,禁止 `git add -A`/`.`;不夹带对方改动。
|
||
> 4. **文件不覆盖**:写 INBOX/PROGRESS 前先读最新,追加不覆盖。
|
||
> 5. **求助通道**:遇到阻塞/不确定,在本文件顶部写「【待 Trae 协助】+ 问题描述」。
|
||
> 6. **禅道同步**:任务「开始/完成」时同步更新禅道任务状态。
|
||
> 7. **验收归用户**:每批做完**停在干净交接点**,由用户亲自验证;回执必须含「用户可照做的验证步骤 + 每步预期结果」。
|
||
> 8. **门禁(不达标不提交)**:后端 = `mvn test` 全过(核心接口须有契约测试);Web = `pnpm build:pro` 通过 + eslint 零错误;Flutter = `dart analyze lib test` 零告警 + `flutter test` 全过。
|
||
> 9. **分支**:在 `develop` 提交并 `git push origin develop`;**绝不动 `main`**(dev→main 走 PR,由用户操作)。
|
||
|
||
---
|
||
|
||
## [已处理·待用户验收] 2026-10-16 17:30 | I1 后台管理端:商品(金币包)配置 + 系统公告管理(后端接口 + Web 页面)
|
||
|
||
**用户指令(2026-10-16 17:10)**:「现在做后台管理端,规范还是按照 specs」+「ReelShortWeb 就是这个项目」→ **后台管理端 = `ReelShortWeb`(Vue3 + Element Plus),本批后端接口与页面一起做**。
|
||
|
||
### 一、PM 已勘察的两个缺口(实测证据,直接用)
|
||
|
||
| # | 缺口 | 证据 |
|
||
| --- | --- | --- |
|
||
| 1 | **商品/金币包配置:后台完全没有入口** | `product` 表 **16 个字段**已建(`code/name/type/platform/store_product_id/coin_amount/bonus_coin/price_amount/currency/display_price/region_codes/vip_days/sort/status/effective_at/expire_at`)、**已有 3 行种子数据**、C 端 `/app/orders/verify` 依赖 `store_product_id` 对齐商店商品(错误码 `200002 该商品未在后台配置` 就是为它留的);但 admin-api 29 个 Controller 里**没有任何商品 CRUD 接口**(`grep` 只命中 `WalletAdminController /coins` 的发币补偿)→ 运营改商品只能改库。**PRD §09 明确要求「商品配置与价格分层」** |
|
||
| 2 | **系统公告:有读没写** | `ops_notice` 表 **0 行**;E1 已交付 C 端 `GET /app/notices`(匿名只读);契约 §2.7 原话「**系统公告 `ops_notice` 目前没有产出方,等有公告功能再接**」→ 后台没有公告管理接口,运营发不了公告 |
|
||
|
||
> 除这两项外,admin-api 对照契约 §2.1–§2.10 全部已实现,**不要重做**。
|
||
|
||
### 二、后端(FruitServer / admin-api)
|
||
|
||
1. **契约先行**(依据 `12-上线检查清单` §一):先在 `docs/api-phase1.md` 写清两个模块的接口、字段、错误码、权限点(放在 §2.6 订单与权益之后,或新增一节),**再动代码**。
|
||
2. **商品配置 `/admin/products`**:
|
||
- `GET /admin/products/page`(筛选:`type` / `status` / `platform` / `keyword`,分页沿用既有口径)、`GET /admin/products/{id}`
|
||
- `POST /admin/products`、`PUT /admin/products/{id}`、`PUT /admin/products/{id}/status`(上下架)、`DELETE /admin/products/{id}`
|
||
- 字段按 `product` 表现有结构,**不要改表**
|
||
- **必须有校验**:`code` 在全局唯一、`store_product_id` 在**同一 `platform` 下唯一**(否则同一商店商品被重复配置 → 发币口径混乱);`type=COIN` 时 `coin_amount > 0`;`type=VIP` 时 `vip_days > 0`
|
||
- ⚠️ **VIP 商品口径**:B1-1 已定「VIP 订阅本轮不做」→ 允许建但**不允许启用**(启用报明确错误)或直接拒绝创建,**二选一由你定,写进契约并在回执里说明**;拿不准就写「【待 Trae 协助】」
|
||
- 权限点命名沿用现有习惯(参考 `commerce:*`),并落 `sys_permission` + `sys_menu`
|
||
3. **系统公告 `/admin/notices`**:
|
||
- `GET /admin/notices/page`、`GET /admin/notices/{id}`、`POST`、`PUT /admin/notices/{id}`、`PUT /admin/notices/{id}/status`(发布/下架)、`DELETE /admin/notices/{id}`
|
||
- 字段按 `ops_notice` 表现有结构,**不要改表**
|
||
- **与 C 端 `GET /app/notices` 的筛选口径必须严格对齐**(E1 已定:`status=1` 且 `target ∈ {ALL, USER}`、`publish_at <= now`、`expire_at` 为空或未过期、按 `publish_at desc, id desc`)—— 后台发布后 C 端能查到、过期/下架后查不到
|
||
- 权限点 + 菜单同上
|
||
4. **菜单与权限点落库**:写进 `sql/data-phase1.sql`(**幂等**),注意**沿用既有保留段**、不要与现有 id 冲突(现有菜单到 id 30)。建议:商品配置挂 `订单与权益`(id 22) 下;系统公告可挂新的运营分组,具体由你按现有约定定。**超管必须可见且可授权**。
|
||
5. Swagger 中文注解按 S1 规范补齐(`@Tag` / `@Operation`)。
|
||
6. **门禁**:`mvn test` 存量 **91 条不得跑红** + 新增契约测试,至少覆盖:`store_product_id` 同平台唯一、`code` 唯一、`type` 校验、公告发布后 C 端可见 / 过期或下架后不可见、越权(无权限点被拒)。
|
||
|
||
### 三、后台页面(ReelShortWeb)
|
||
|
||
> **挂载机制(PM 已勘察)**:路由是**静态注册**在 `src/router/index.ts`(`component: () => import('@/views/...')`),而菜单树来自 DB `sys_menu`,其中 `component` 字段必须**与视图路径一致**(例:`views/contentManagement/DramaManagementView`)。两边都要加。
|
||
|
||
1. 新增两个页面(放在与现有页面同级的目录,命名沿用 `XxxManagementView.vue` 风格):
|
||
- **商品配置**:列表(分页/筛选)+ 新建/编辑弹窗 + 上下架 + 删除;表单字段按 `product` 表;金额/金币用数字输入并做前端校验
|
||
- **系统公告**:列表 + 新建/编辑(标题/正文/类型/目标/发布时间/失效时间)+ 发布/下架 + 删除
|
||
2. **按现有约定写**:API 封装放 `src/api/admin/`(新增如 `product.ts` / `notice.ts`);权限码走现有的 `can(code)`;**表格状态列用接口返回的 `status` / `statusText`,不要在前端写死中文**;大数值用 `formatCount`。
|
||
3. `src/router/index.ts` 静态注册路由(`route_name` 与 DB `sys_menu.route_name` 一致,**注意不要与既有 route_name 重名** —— 之前踩过 `CreatorCenter` 重名导致白屏/404 的坑)。
|
||
4. **不要改动已对接的既有页面**(避免与其他人正在做的改动冲突)。
|
||
5. **门禁**:`pnpm build:pro` 通过 + eslint 零错误。
|
||
|
||
### 四、git
|
||
|
||
- **FruitServer**:在 `develop` 提交并推送;**不动 `main`**。
|
||
- **ReelShortWeb**:现在是 `main` 分支且**与远端分叉**(本地 `4564aa8` 领先 1、远端 `1fd0b8b 全量补充作者标识` 领先 1)。请先 `git fetch` 并把远端合进来,**然后按 specs 的 `GIT_WORKFLOW` 新建 `develop` 分支,在 `develop` 上提交并 `git push origin develop`**;**不动 `main`**(合并走 PR、由用户操作)。远端是 `jianghaifeng/ReelShortWeb.git`(有写权限)。
|
||
- 作者的署名规范:`// Author: fruit.com.cn`(Vue/TS 文件首行)。
|
||
|
||
### 五、节奏(红线第 7 条)
|
||
|
||
1. 先写计划文件 `docs/plans/2026-10-16-I1.md`(后端 + 前端两部分、步骤、风险、待确认)。
|
||
2. 做,跑门禁。
|
||
3. **停在干净交接点写回执**:逐项列出改了什么;**给你和用户可照做的验证步骤 + 每步预期结果**(含「后台登录 → 菜单里能看到这两个新页面 → 建一个商品 → 用该商品在 C 端 verify 能对上」这样的端到端链路)。
|
||
4. **不要连着抢做下一批。**
|
||
|
||
---
|
||
|
||
### 回执(Codex,2026-10-16):I1 完成,停在干净交接点
|
||
|
||
**提交**:FruitServer `8d19a0c`(develop,已推) + ReelShortWeb `db313f4`(develop,已推,含 husky 门禁)。
|
||
**留痕**:`docs/plans/PROGRESS.md`(本轮新建,逐动作命令 + 原始输出 + 解读);计划 `docs/plans/2026-10-16-I1.md`。
|
||
**没碰 `main`**;01-08 冻结模块未改(改动只在 module-commerce / module-system / admin-api / app-api 的 `AppNoticeService`)。
|
||
|
||
**门禁数字(原始输出见 PROGRESS 动作 3/6/10)**
|
||
| 门禁 | 结果 |
|
||
| --- | --- |
|
||
| `mvn test` | **BUILD SUCCESS**,commerce 33 / admin-api 16 / app-api 49 / worker 12 = **110 条全过**(存量 91 + 新增 19,无跑红) |
|
||
| `mvn -pl fruitshorts-app-api,fruitshorts-admin-api -am package -DskipTests` | BUILD SUCCESS |
|
||
| `sql/data-phase1.sql` 幂等 | 连跑三遍,`sys_role_menu/permission/menu/role_permission` 行数 54/52/33/79 前后一致 |
|
||
| `pnpm ts:check` | 0 错误 |
|
||
| `eslint --ext .js,.ts,.vue ./src` | 0 error(首遍 1 个 prettier 折行,改后复跑为空) |
|
||
| `pnpm build:pro` | Build successful |
|
||
| `:3005` 真浏览器 | 两页渲染正常;1440/1280/1024 三档 `overflowX=false`;截图见下面步骤 |
|
||
|
||
**交付内容**
|
||
1. 后端:`/admin/products` 6 个接口(page/{id}/POST/PUT/{id}/status/DELETE)、`/admin/notices` 6 个接口;字段严格按 `product` / `ops_notice` 表现有结构,**未改表**。
|
||
2. 校验:`code` 全局唯一、`storeProductId` **同 platform 唯一**、`COIN_PACK` 需 `coinAmount>0`、`VIP` 需 `vipDays>0`、新建默认下架。
|
||
3. 公告可见性与 C 端**共用一份实现**:`OpsNoticeService.pageForApp(...)`,后台与 `/app/notices` 不再各写一套条件。
|
||
4. 权限点 `product:list/product:update/notice:list/notice:update` + 菜单 31(商品配置,挂 22「订单与权益」)/ 32(运营管理)/ 33(系统公告),超管已授权,幂等。
|
||
5. 前端:`src/api/admin/product.ts`、`src/api/admin/notice.ts`、`ProductManagementView.vue`、`NoticeManagementView.vue`;状态列只用接口 `statusText`,不写死中文。
|
||
|
||
**两个自己拍的口径(请复核)**
|
||
1. **VIP 商品:允许建档、不允许启用** —— `status=1` 时回 `400 会员商品在订阅功能上线前不能启用(可先建档,上线后再上下架)`。理由:上线日可以直接上下架,不必临时补商品数据;已写进契约 §2.11。
|
||
2. **⚠️ 雪花 id 以字符串下发(本轮实测抓到的真缺陷)** —— UI 上点「下架」后端回 `200002 公告不存在`,根因是 S0 换雪花主键后 id 超过 JS 安全整数范围:DB 里 `2111011206851596290`,浏览器 `JSON.parse` 后变成 `2111011206851596300`,回查自然找不到。
|
||
修复限定在本轮两个模块(`ProductRespVO` / `NoticeRespVO` 的 `id` 加 `ToStringSerializer`,新建接口返回 `String`,前端 `id: string`),**没动全局序列化、没动 app-api**,避免影响 Flutter 客户端;并补了两条防回归测试。
|
||
- 遗留:admin-api 其它模块(订单/权益/短剧…)只要"新建后立刻用 id 操作",同样会中招。**是否统一做"admin-api 的 Long → String"、app-api 要不要一起动(Flutter 解析 id 的地方会受影响)——这是平台级改动,请裁定**,我没自行铺开。
|
||
3. **菜单挂载机制与条目描述有出入(如实报出)**:实测菜单来自 DB `sys_menu`,前端用 `import.meta.glob` 按 `component` 字符串动态解析;`src/router/index.ts` 的 `asyncRouterMap` 只静态声明 dashboard/content/user/system,commerce/governance/board 都不在里面。所以本轮**没有改 `src/router/index.ts`**,只建页面 + 落菜单(`component` 与视图路径一致)。若你要求"两边都登记",我补一版。
|
||
|
||
**用户可照做的验证步骤(每步都有预期结果)**
|
||
|
||
准备(后端):`cd FruitServer && mvn -pl fruitshorts-app-api,fruitshorts-admin-api -am package -DskipTests`,然后
|
||
```
|
||
java -jar fruitshorts-admin-api/target/fruitshorts-admin-api-1.0.0-SNAPSHOT.jar # 8080
|
||
java -jar fruitshorts-app-api/target/fruitshorts-app-api-1.0.0-SNAPSHOT.jar # 8081
|
||
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8080/v3/api-docs # 预期 200
|
||
```
|
||
数据库若在干净机器上:`psql -f sql/data-phase1.sql`(可重复执行)。
|
||
|
||
1. **菜单**:`pnpm dev` → 打开 `http://127.0.0.1:3005/#/login` → `admin / admin123` 登录。
|
||
预期:左侧出现「订单与权益 → 商品配置」与「运营管理 → 系统公告」,其余菜单不变。
|
||
2. **商品页**:进 `#/commerce/products`。预期:3 行种子(coin_100/550/1200),列 = 商品编码/名称/类型/平台/商店商品 id/到账金币/价格/排序/状态/创建时间/操作;状态显示「已上架」。
|
||
3. **新建商品(正常路径)**:点「新建商品」→ 编码 `uitest_coin_500`、名称随意、平台选 Google Play、商店商品 id `UITEST_COIN_500`、金币 500、赠送 50、价格 9900 → 保存。
|
||
预期:toast「商品已创建(默认下架,请确认后再上架)」,列表新增一行且状态「已下架」(**id 是雪花 id,注意这是本轮修过的点**)。
|
||
4. **异常路径**:再新建一个同编码商品 → 预期 toast「商品编码已存在:uitest_coin_500」;把类型改成「会员(第二阶段)」、天数 30 → 保存成功(**允许建档**),再点它的状态想上架 → 预期 toast「会员商品在订阅功能上线前不能启用(可先建档,上线后再上下架)」,状态不变。
|
||
5. **C 端对齐(命令)**:把第 3 步的商品点上架,然后
|
||
```
|
||
psql -c "select id, code, platform, store_product_id, status from product where store_product_id='UITEST_COIN_500';"
|
||
```
|
||
预期:查到 1 行、`status=1` —— 这正是 `/app/orders/verify` 找商品的同一条件。
|
||
(`POST /app/orders/verify` 本机默认 `STORE_VERIFIER=disabled`,会先回 `200003 商店校验尚未配置`,这是 B1-1 的既定行为,不是商品没配上。)
|
||
6. **公告页**:进 `#/operation/notices` → 「新建公告」填标题/正文(默认「全部用户/系统」、不填时间=立即发布)→ 保存。
|
||
预期:toast「公告已发布」,行内状态「已发布」、失效时间显示「-」。
|
||
```
|
||
curl -s "http://127.0.0.1:8081/app/notices?pageIndex=1&pageSize=5" # 预期 total 1,能看到刚发的标题
|
||
```
|
||
7. **下架(异常路径的对照组)**:点该行状态「已发布」→ 预期 toast「公告已下架」、行内变「已下架」;再跑上面的 curl → 预期 `total 0`。
|
||
8. **越权**:用 `content.editor01 / admin123`(内容运营,无 `product:*`/`notice:*`)登录后直接敲 `#/commerce/products`。
|
||
预期:页面 403/无权限(后端 `{"code":403,"message":"无权限"}`);不带 token 请求 `curl -s "http://127.0.0.1:8080/admin/products/page"` → `{"code":401,...}`。
|
||
9. **清理**:把第 3/4 步的商品、第 6 步的公告删掉;核对 `select count(*) from product;` = 3、`select count(*) from ops_notice;` = 0。
|
||
(本轮自测数据我已清干净:`product` 3 行种子、`ops_notice` 0 行。)
|
||
|
||
**门禁/流程说明**:本轮所有子步骤都在同一批内完成,未抢做下一批;`mvn test` 与 `package` 都是先停服务再打包、打包后重新起服务、**对着活着的服务**做的 curl 自测(不是对停掉的服务打)。
|
||
服务当前状态:8080/8081 我这边是**已起的**(自测完成时仍在监听);若你这边连不上就是被环境回收了,按上面「准备」两条命令起回来即可。
|