Files
huang 61155952a7
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m56s
feat: 新增代理分配套餐上架状态(shelf_status)功能
- 新增数据库迁移:为 shop_package_allocation 表添加 shelf_status 字段
- 更新模型/DTO:ShopPackageAllocation 增加 ShelfStatus 字段及相关枚举
- 更新套餐分配 Service:支持上架/下架状态管理逻辑
- 更新套餐 Store/Service:根据 shelf_status 过滤可售套餐
- 更新购买验证 Service:引入上架状态校验逻辑
- 归档 OpenSpec 变更:2026-03-02-agent-allocation-shelf-status
- 同步更新主规范文档:allocation-shelf-status、package-management、purchase-validation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 15:38:54 +08:00

4.7 KiB
Raw Permalink Blame History

Context

当前状态

tb_packageshelf_status 字段1-上架, 2-下架),由平台控制套餐的全局可见性。tb_shop_package_allocation 只有 status 字段1-启用, 2-禁用),没有代理独立的上下架字段。

当代理调用 PATCH /api/admin/packages/:id/shelfservice 层直接修改 tb_package.shelf_status,导致该操作影响全平台——所有代理和平台的该套餐都被下架。

核心矛盾

"上下架"在业务上有两个层次:

  • 平台层:控制平台自营店面的客户侧可见性,与代理无关
  • 代理层:每个代理独立控制自己客户侧的可见性,互不影响

目前两个层次合并成一个字段,是 Bug 的根源。

约束

  • tb_shop_package_allocation.status(启用/禁用)语义为"分配者临时暂停该分配",不等同于上下架
  • 平台若要停止某套餐全网销售,应回收分配而非修改 shelf_status
  • Package.status(全局禁用)是唯一影响全平台购买的开关

Goals / Non-Goals

Goals:

  • tb_shop_package_allocation 新增 shelf_status 字段,默认上架
  • PATCH /packages/:id/shelf 接口按调用者角色路由到不同数据层平台→package代理→allocation
  • 代理查看套餐列表/详情时,shelf_status 返回自己分配记录的值
  • 购买校验按购买场景分流:代理场景检查 allocation.shelf_status,平台场景检查 package.shelf_status
  • 修复 UpdateAllocationStatus 接口缺少所有者校验的安全 Bug

Non-Goals:

  • 不引入级联上下架代理A下架不影响代理B
  • 不修改 Package.status(全局禁用)的语义和操作权限
  • 不在代理层增加"启用/禁用套餐"能力status 仍只有分配者可改)
  • 不变更 URL 路径(同一接口服务不同角色)

Decisions

决策 1角色上下文路由在 Service 层实现

选择:在 PackageService.UpdateShelfStatus() 中通过 middleware.GetUserTypeFromContext(ctx) 判断角色,路由到不同的 store 操作。

理由Handler 层保持薄,不含业务逻辑。角色判断属于业务规则,放 Service 层符合分层原则。URL 不变对前端友好,无需区分调用地址。

备选方案:为代理新增单独的 API 路由(如 PATCH /shop-package-allocations/:id/shelf)。问题是代理需要知道 allocation ID 而非 package ID增加前端复杂度且未来其他类似接口都要新增一套路由。

决策 2默认上架

选择:分配给代理时,allocation.shelf_status 默认为 1上架

理由:分配行为本身代表分配者希望下级销售此套餐,默认上架减少操作步骤。代理可主动下架。

决策 3购买校验按场景分流

选择

  • 客户直接从平台购买 → 检查 Package.status == enabled AND Package.shelf_status == on
  • 客户通过代理购买 → 检查 Package.status == enabled AND allocation(seller).shelf_status == on

理由:平台 shelf_status 仅代表平台自营店面状态,与代理销售解耦。代理链路只检查最终销售代理的 allocation不向上追溯上级若不想让下级卖应回收分配

决策 4分配 status 修改加所有者校验

选择UpdateAllocationStatus 检查调用者是否为该 allocation 的 allocator_shop_id(平台用户则通过,代理用户需 allocator_shop_id == 调用者 shop_id

理由:当前无校验,任意代理可修改任意分配记录的 status是安全漏洞。

Risks / Trade-offs

[风险] 存量数据的 shelf_status 默认值 → 迁移时 ALTER TABLEDEFAULT 1 NOT NULL,存量记录全部视为上架,符合业务现状(原来没有这个字段时代理就是在卖)。

[风险] 购买校验需要额外查询 allocation → 校验路径需增加一次 GetByShopAndPackage 查询。订单创建场景已有多个查询,增加一次影响可接受;可加索引 (shop_id, package_id) 优化(已存在)。

[风险] 前端展示的 shelf_status 语义变化 → 代理端看到的 shelf_status 从 package 级变为 allocation 级,前端无需改动(字段名相同,值含义不变),但平台管理员在查看"代理管理的套餐"时无法直接看到各代理的 shelf_status这属于 Non-Goal。

Migration Plan

  1. 生成数据库迁移文件:tb_shop_package_allocation 增加 shelf_status INT NOT NULL DEFAULT 1
  2. 执行迁移(无停机,仅 ADD COLUMN
  3. 部署新代码(接口行为自动分流)
  4. 无回滚风险:新增字段,原有字段语义不变

Open Questions

无。所有设计决策已在探索阶段与业务确认。