- 新增数据库迁移:为 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>
4.7 KiB
Context
当前状态
tb_package 有 shelf_status 字段(1-上架, 2-下架),由平台控制套餐的全局可见性。tb_shop_package_allocation 只有 status 字段(1-启用, 2-禁用),没有代理独立的上下架字段。
当代理调用 PATCH /api/admin/packages/:id/shelf 时,service 层直接修改 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 TABLE 设 DEFAULT 1 NOT NULL,存量记录全部视为上架,符合业务现状(原来没有这个字段时代理就是在卖)。
[风险] 购买校验需要额外查询 allocation → 校验路径需增加一次 GetByShopAndPackage 查询。订单创建场景已有多个查询,增加一次影响可接受;可加索引 (shop_id, package_id) 优化(已存在)。
[风险] 前端展示的 shelf_status 语义变化 → 代理端看到的 shelf_status 从 package 级变为 allocation 级,前端无需改动(字段名相同,值含义不变),但平台管理员在查看"代理管理的套餐"时无法直接看到各代理的 shelf_status,这属于 Non-Goal。
Migration Plan
- 生成数据库迁移文件:
tb_shop_package_allocation增加shelf_status INT NOT NULL DEFAULT 1 - 执行迁移(无停机,仅 ADD COLUMN)
- 部署新代码(接口行为自动分流)
- 无回滚风险:新增字段,原有字段语义不变
Open Questions
无。所有设计决策已在探索阶段与业务确认。