- /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 条全过
5.5 KiB
5.5 KiB
I1 后台管理端:商品(金币包)配置 + 系统公告管理 —— 计划(2026-10-16)
依据:
FruitServer/docs/plans/INBOX.md17:30 条目。用户口径「现在做后台管理端,规范还是按照 specs」。 本轮=后端接口 + ReelShortWeb 页面一起做。指令通道已迁到本仓库(FruitServer/docs/plans/), 留痕写FruitServer/docs/plans/PROGRESS.md(本轮新建);specs 只读、不再写入。
一、两个缺口(PM 已勘察,直接用)
- 商品/金币包配置后台无入口:
product表 16 字段已建、3 行种子、C 端 verify 依赖store_product_id对齐商店商品,但 admin-api 没有任何商品 CRUD。 - 公告有读没写:
ops_notice表 0 行,E1 交付了 C 端读,后台没有写入口。
二、勘察补充(影响做法,已实测)
- 前端挂载机制(与条目描述略有出入,已核实):
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),本轮命名会避开。 - id 保留段:权限点已用到 1155、菜单已用到 30 → 权限点用 1160-1163、菜单用 31/32/33。
- 前端既有约定: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 订阅本轮不做」,所以启用才是会产生误导的动作,拦在这里。
四、后端做法
- 契约先行:
docs/api-phase1.md新增一节(放在 §2.6 订单与权益之后的新编号),写清两个模块的 接口、字段、校验、错误码、权限点。 - 商品(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(前端不写死中文)。 - 公告(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 端可见 / 过期或下架 → 不可见"用的是同一段条件,不会漂移。 - 菜单与权限点:
sql/data-phase1.sql幂等追加(权限点 1160-1163、菜单 31/32/33 与超管授权)。 商品挂「订单与权益」(id 22) 下;公告新建「运营管理」(id 32) 分组 + 子菜单 (33)。 - 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可见 → 下架/过期 → 不可见
七、风险 / 待确认
- 不新增静态路由是否与条目"两边都要加"冲突:我按实测的加载机制做,若你要求同时静态登记,我再补(不影响正确性)。
/app/orders/verify走真实 Google Play 才有完整链路;本机无商店凭据,端到端验证到"200002 消失、进入校验环节"为止。- 公告"发布时间"语义:
publish_at为空视为立即生效(C 端条件里已按"为空或 <= now"处理)。