Files
FruitServer/docs/plans/2026-10-16-I1.md
T
jianghaifeng 8d19a0cc0a feat(admin): 商品配置与系统公告管理接口,id 以字符串下发
- /admin/products 六个接口 + 校验(code 唯一、store_product_id 同平台唯一、
  COIN_PACK 需 coin_amount>0、VIP 需 vip_days>0、VIP 只建不启)
- /admin/notices 六个接口,与 C 端 /app/notices 共用 OpsNoticeService.pageForApp 一套筛选口径
- 权限点 product:list/product:update/notice:list/notice:update + 菜单 31/32/33(sql/data-phase1.sql 幂等)
- 雪花 id 超过 JS 安全整数范围:id 以字符串下发,避免浏览器四舍五入导致回查 200002
- C 端商品查询收敛到 ProductService.findForStore,OrderService 不再自带一份
- 测试:module-commerce 33 / admin-api 16 / app-api 49 / worker 12,共 110 条全过
2026-10-16 16:33:15 +08:00

5.5 KiB
Raw Blame History

I1 后台管理端:商品(金币包)配置 + 系统公告管理 —— 计划(2026-10-16)

依据:FruitServer/docs/plans/INBOX.md 17:30 条目。用户口径「现在做后台管理端,规范还是按照 specs」。 本轮=后端接口 + ReelShortWeb 页面一起做。指令通道已迁到本仓库(FruitServer/docs/plans/), 留痕写 FruitServer/docs/plans/PROGRESS.md(本轮新建);specs 只读、不再写入。

一、两个缺口(PM 已勘察,直接用)

  1. 商品/金币包配置后台无入口:product 表 16 字段已建、3 行种子、C 端 verify 依赖 store_product_id 对齐商店商品,但 admin-api 没有任何商品 CRUD。
  2. 公告有读没写:ops_notice 表 0 行,E1 交付了 C 端读,后台没有写入口。

二、勘察补充(影响做法,已实测)

  1. 前端挂载机制(与条目描述略有出入,已核实):src/utils/routerHelper.ts 用 import.meta.glob('../views/**/*.{vue,tsx}') + 后端菜单的 component 字符串动态解析; src/router/index.ts 的 asyncRouterMap 只静态声明了 dashboard/content/user/system 等早期分组, commerce/governance/board 这些都没有静态登记。→ 所以新增页面只需:① 新建 .vue; ② sql/data-phase1.sql 里加菜单且 component 与视图路径一致。静态路由不必改(改了也无害)。 真正的坑是 route_name 唯一(曾因 CreatorCenter 重名导致 404),本轮命名会避开。
  2. id 保留段:权限点已用到 1155、菜单已用到 30 → 权限点用 1160-1163、菜单用 31/32/33。
  3. 前端既有约定:api 封装在 src/api/admin/*(import request from '@/axios' + PageResult), 页面用 useRbac().can(code) 控权、dateCell 格式化日期、PageHeading 标题;金额/金币这类大数值本项目 没有 formatCount,实际用的是 src/utils/format.js,我会用其中的数值格式化工具或 toLocaleString, 并在回执说明(不新造 util)。

三、口径决定(需在回执与契约里写清)

VIP 商品:允许创建、不允许启用(PUT /{id}/status 把 type=VIP 置为启用 → 400)。 理由:运营可以先把 VIP 商品与商店 product id 录好,等 B2 订阅上线后一键启用;直接拒绝创建会让上线日重新补数据。 B1-1 已定「VIP 订阅本轮不做」,所以启用才是会产生误导的动作,拦在这里。

四、后端做法

  1. 契约先行:docs/api-phase1.md 新增一节(放在 §2.6 订单与权益之后的新编号),写清两个模块的 接口、字段、校验、错误码、权限点。
  2. 商品(module-commerce 加 service/VO,admin-api 加 controller): GET /admin/products/page(type/status/platform/keyword + 分页)、GET /{id}、POST、PUT /{id}、 PUT /{id}/status、DELETE /{id};字段按表结构;校验:code 全局唯一、store_product_id 同 platform 唯一、 type=COIN → coin_amount>0、type=VIP → vip_days>0、VIP 不可启用。响应带 statusText/typeText(前端不写死中文)。
  3. 公告(module-system 加 OpsNoticeService + VO,admin-api 加 controller): GET /admin/notices/page、GET /{id}、POST、PUT /{id}、PUT /{id}/status、DELETE /{id}。 筛选逻辑收敛到 OpsNoticeService.pageForApp() 一处,C 端 AppNoticeService 改为委托它 (改我自己 E1 写的文件),这样"后台发布 → C 端可见 / 过期或下架 → 不可见"用的是同一段条件,不会漂移。
  4. 菜单与权限点:sql/data-phase1.sql 幂等追加(权限点 1160-1163、菜单 31/32/33 与超管授权)。 商品挂「订单与权益」(id 22) 下;公告新建「运营管理」(id 32) 分组 + 子菜单 (33)。
  5. Swagger:两个新控制器按 S1 规范加 @Tag/@Operation,并把分组补进 admin-api 的 @OpenAPIDefinition。

五、前端做法(ReelShortWeb,develop 分支)

  • src/api/admin/product.ts、src/api/admin/notice.ts(按 commerce.ts 的写法)
  • src/views/commerce/ProductManagementView.vue(列表/筛选/新建编辑弹窗/上下架/删除)
  • src/views/operation/NoticeManagementView.vue(列表/新建编辑/发布下架/删除)
  • sql/data-phase1.sql 的菜单 component 写 views/commerce/ProductManagementView、views/operation/NoticeManagementView
  • 不改既有页面;route_name 用 commerce-products / operation-notices(先 grep 确认不重名)

六、门禁

  • 后端:mvn test 存量 91 条不得跑红 + 新增测试(商品三类校验、公告可见性委托、 两个控制器的接入层契约、权限点注解检查)
  • 前端:pnpm build:pro 通过 + eslint 零错误
  • 端到端:后台登录 → 菜单出现两个新页面 → 建商品 → 用该商品在 C 端 verify 能对上 (store_product_id 命中,不再是 200002 该商品未在后台配置);公告发布 → C 端 /app/notices 可见 → 下架/过期 → 不可见

七、风险 / 待确认

  1. 不新增静态路由是否与条目"两边都要加"冲突:我按实测的加载机制做,若你要求同时静态登记,我再补(不影响正确性)。
  2. /app/orders/verify 走真实 Google Play 才有完整链路;本机无商店凭据,端到端验证到"200002 消失、进入校验环节"为止。
  3. 公告"发布时间"语义:publish_at 为空视为立即生效(C 端条件里已按"为空或 <= now"处理)。