All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m19s
- 订单管理:增加 payment_method 字段支持,合并代购订单逻辑 - 套餐系列分配:增加强充配置字段(enable_force_recharge、force_recharge_amount、force_recharge_trigger_type) - 数据库迁移:添加 force_recharge_trigger_type 字段 - 测试:更新订单服务测试用例 - OpenSpec:归档 fix-force-recharge-missing-interfaces 变更
10 KiB
10 KiB
Context
在 add-force-recharge-system 功能归档后,发现两个关键遗漏:
-
套餐系列分配强充配置不可管理:
- 数据库字段已添加:
enable_force_recharge、force_recharge_amount、force_recharge_trigger_type - Service 层已使用这些字段进行强充验证(
RechargeService.GetRechargeCheck、OrderService.GetPurchaseCheck) - 但 DTO 层完全没有暴露这些字段,管理员无法通过 API 配置
- 数据库字段已添加:
-
后台订单创建逻辑重复:
- 存在两套 DTO:
CreateOrderRequest和CreatePurchaseOnBehalfRequest(字段完全相同) - 存在两个 Service 方法:
Create和CreatePurchaseOnBehalf(核心逻辑相似,仅支付方式和成本价计算不同) - 实际业务中,后台订单只有两种支付方式:
wallet:扣代理钱包,需要强充验证,触发佣金offline:线下已收款,不扣钱包,不触发佣金(即代购)
- 存在两套 DTO:
现有架构:
- 强充验证逻辑完整:
RechargeService、OrderService已实现 - 数据模型完整:
ShopSeriesAllocation.enable_force_recharge等字段已存在 - 仅缺少管理接口暴露
Goals / Non-Goals
Goals:
- 暴露强充配置字段到套餐系列分配的 CRUD 接口
- 统一后台订单创建接口,使用
payment_method字段区分普通订单和代购订单 - 删除重复代码和冗余 DTO
- 保持现有业务逻辑不变(强充验证、佣金计算、成本价计算)
Non-Goals:
- 不修改强充验证逻辑(已在
add-force-recharge-system中实现) - 不修改数据库结构(字段已存在)
- 不修改 H5 订单接口(H5 仅支持微信/支付宝支付,不涉及 offline)
- 不修改佣金计算逻辑(
CommissionCalculationService已正确处理is_purchase_on_behalf)
Decisions
决策 1:强充配置字段作为可选字段暴露
决策:在套餐系列分配的 DTO 中增加强充配置字段,作为可选字段(omitempty)
理由:
- 强充是累计充值强充的可选配置(
enable_force_recharge默认 false) - 与一次性佣金配置保持一致(也是可选配置)
- 向后兼容:现有数据默认值为 false/0,不影响现有逻辑
实现:
// CreateShopSeriesAllocationRequest
type CreateShopSeriesAllocationRequest struct {
// ... 现有字段
EnableForceRecharge *bool `json:"enable_force_recharge" description:"是否启用强充(累计充值强充)"`
ForceRechargeAmount *int64 `json:"force_recharge_amount" description:"强充金额(分,0表示使用阈值金额)"`
ForceRechargeTriggerType *int `json:"force_recharge_trigger_type" description:"强充触发类型(1:单次充值, 2:累计充值)"`
}
// UpdateShopSeriesAllocationRequest (同上)
// ShopSeriesAllocationResponse
type ShopSeriesAllocationResponse struct {
// ... 现有字段
EnableForceRecharge bool `json:"enable_force_recharge" description:"是否启用强充"`
ForceRechargeAmount int64 `json:"force_recharge_amount" description:"强充金额(分)"`
ForceRechargeTriggerType int `json:"force_recharge_trigger_type" description:"强充触发类型"`
}
替代方案:
- ❌ 独立接口配置强充:增加接口复杂度,与现有设计不一致
- ❌ 强制字段:破坏向后兼容性,现有创建请求会失败
决策 2:使用 payment_method 字段统一订单创建
决策:在 CreateOrderRequest 增加 payment_method 必填字段,删除 CreatePurchaseOnBehalfRequest
理由:
- 后台订单本质上只有两种支付方式:
wallet和offline is_purchase_on_behalf是业务标识,应由系统自动设置,不应由前端传递- 减少 DTO 冗余,统一接口设计
映射关系:
payment_method = "wallet" → is_purchase_on_behalf = false (普通订单)
payment_method = "offline" → is_purchase_on_behalf = true (代购订单)
实现:
type CreateOrderRequest struct {
OrderType string `json:"order_type" validate:"required,oneof=single_card device"`
IotCardID *uint `json:"iot_card_id" validate:"required_if=OrderType single_card"`
DeviceID *uint `json:"device_id" validate:"required_if=OrderType device"`
PackageIDs []uint `json:"package_ids" validate:"required,min=1,max=10"`
PaymentMethod string `json:"payment_method" validate:"required,oneof=wallet offline"` // 新增
}
替代方案:
- ❌ 保留两个 DTO:代码重复,维护成本高
- ❌ 使用
is_purchase_on_behalf字段:业务标识不应由前端控制,存在安全风险
决策 3:Service 层合并逻辑但保留代码分支
决策:合并 Service.Create 和 Service.CreatePurchaseOnBehalf 为统一方法,内部使用 if/else 分支处理
理由:
- 两个方法核心流程相似(验证 → 计算价格 → 创建订单 → 激活套餐 → 触发佣金)
- 关键差异仅在:
- 成本价计算:普通订单用卖家成本价,代购用买家成本价
- 支付状态:普通订单待支付,代购直接已支付
- 佣金触发:通过
is_purchase_on_behalf标识控制
- 统一方法减少接口暴露,降低复杂度
实现结构:
func (s *Service) Create(ctx context.Context, req *dto.CreateOrderRequest, userType string, shopID, userID uint) (*dto.OrderResponse, error) {
// 1. 通用验证(资源存在、套餐有效)
validationResult, err := s.validatePurchase(ctx, req)
// 2. 根据 payment_method 分支处理
var order *model.Order
if req.PaymentMethod == model.PaymentMethodOffline {
// 代购逻辑
order = s.buildPurchaseOnBehalfOrder(ctx, req, validationResult, userID)
} else {
// 普通逻辑
order = s.buildNormalOrder(ctx, req, validationResult, shopID, userID)
}
// 3. 通用创建流程(保存订单 → 激活套餐 → 触发佣金)
return s.createOrderCommon(ctx, order, validationResult)
}
替代方案:
- ❌ 保留两个独立方法:Handler 需要路由逻辑,接口复杂度高
- ❌ 完全合并为一个线性方法:可读性差,分支逻辑混乱
决策 4:Handler 层权限验证前置
决策:在 OrderHandler.Create 中根据 payment_method 进行权限验证,再调用 Service
理由:
- 权限验证是 Handler 层职责
- 提前拦截非法请求,避免无效的 Service 调用
- 明确业务规则:offline 仅平台可用,wallet 代理和平台都可用
实现:
func (h *OrderHandler) Create(c *fiber.Ctx) error {
var req dto.CreateOrderRequest
// ... 解析请求
ctx := c.UserContext()
userType := middleware.GetUserTypeFromContext(ctx)
shopID := middleware.GetShopIDFromContext(ctx)
userID := middleware.GetUserIDFromContext(ctx)
// 权限验证
if req.PaymentMethod == model.PaymentMethodOffline {
if userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform {
return errors.New(errors.CodeForbidden, "只有平台可以使用线下支付")
}
} else if req.PaymentMethod == model.PaymentMethodWallet {
if userType != constants.UserTypeAgent &&
userType != constants.UserTypePlatform &&
userType != constants.UserTypeSuperAdmin {
return errors.New(errors.CodeForbidden, "无权创建订单")
}
}
// 调用统一方法
order, err := h.service.Create(ctx, &req, userType, shopID, userID)
return response.Success(c, order)
}
替代方案:
- ❌ Service 层验证:违反分层原则,Handler 应负责权限
- ❌ 中间件验证:无法访问请求体字段
Risks / Trade-offs
风险 1:CreateOrderRequest 字段变更可能影响前端
风险:新增 payment_method 必填字段后,现有前端调用会失败
缓解措施:
- 这是后台管理接口,前端由团队控制,可同步修改
- 如需兼容,可设置默认值(wallet),但不推荐(隐式行为会引起混淆)
推荐:要求前端同步修改,明确传递 payment_method
风险 2:删除 CreatePurchaseOnBehalfRequest 可能影响现有调用
风险:如果其他代码引用了 CreatePurchaseOnBehalfRequest DTO,会编译失败
缓解措施:
- 通过
grep搜索确认无引用(仅在 Service 测试中使用) - 修改测试用例,使用
CreateOrderRequest替代
验证步骤:
grep -r "CreatePurchaseOnBehalfRequest" internal/
风险 3:Service 方法签名变更可能影响现有调用
风险:Service.Create 方法签名变更(增加 userType、userID 参数),现有调用方可能失败
缓解措施:
- 通过 LSP 查找所有调用方
- 仅有
OrderHandler.Create和测试用例调用,影响范围可控
影响范围:
internal/handler/admin/order.gointernal/handler/h5/order.go(H5 订单不使用 offline,不受影响)internal/service/order/service_test.go
权衡 4:合并方法增加了单个方法的复杂度
权衡:Service.Create 方法内部有 if/else 分支,复杂度略微增加
接受理由:
- 两个独立方法的维护成本更高(重复代码、接口复杂)
- 内部分支清晰,使用辅助方法拆分逻辑(
buildPurchaseOnBehalfOrder、buildNormalOrder) - 测试覆盖两种分支场景,确保正确性
Migration Plan
部署步骤
-
代码变更:
- 修改 DTO(3 个文件)
- 修改 Service(2 个文件)
- 修改 Handler(1 个文件)
- 修改测试用例(2 个文件)
-
测试验证:
- 运行单元测试确保所有测试通过
- 运行
lsp_diagnostics检查类型错误 - 本地验证接口功能
-
部署:
- 无数据库迁移,直接部署即可
- 通知前端团队同步修改
POST /api/admin/orders调用
-
验证:
- 测试套餐系列分配的创建/更新/查询,确认强充配置正常显示
- 测试后台订单创建(wallet 和 offline),确认业务逻辑正确
回滚策略
如果发现问题,可以:
- 回滚代码到上一版本(Git revert)
- 无数据库变更,回滚无风险
- 强充配置字段可选,即使旧代码未传递也不会出错
兼容性保障
- 强充配置字段:可选字段,默认值 false/0,现有数据不受影响
- 订单创建接口:
payment_method必填,需前端同步修改(可控) - 删除的 DTO 和方法:仅内部使用,无外部依赖
Open Questions
无待解决问题。