## Context 在 `add-force-recharge-system` 功能归档后,发现两个关键遗漏: 1. **套餐系列分配强充配置不可管理**: - 数据库字段已添加:`enable_force_recharge`、`force_recharge_amount`、`force_recharge_trigger_type` - Service 层已使用这些字段进行强充验证(`RechargeService.GetRechargeCheck`、`OrderService.GetPurchaseCheck`) - 但 DTO 层完全没有暴露这些字段,管理员无法通过 API 配置 2. **后台订单创建逻辑重复**: - 存在两套 DTO:`CreateOrderRequest` 和 `CreatePurchaseOnBehalfRequest`(字段完全相同) - 存在两个 Service 方法:`Create` 和 `CreatePurchaseOnBehalf`(核心逻辑相似,仅支付方式和成本价计算不同) - 实际业务中,后台订单只有两种支付方式: - `wallet`:扣代理钱包,需要强充验证,触发佣金 - `offline`:线下已收款,不扣钱包,不触发佣金(即代购) **现有架构**: - 强充验证逻辑完整:`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,不影响现有逻辑 **实现**: ```go // 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 (代购订单) ``` **实现**: ```go 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` 标识控制 - 统一方法减少接口暴露,降低复杂度 **实现结构**: ```go 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 代理和平台都可用 **实现**: ```go 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` 替代 **验证步骤**: ```bash grep -r "CreatePurchaseOnBehalfRequest" internal/ ``` ### 风险 3:Service 方法签名变更可能影响现有调用 **风险**:`Service.Create` 方法签名变更(增加 `userType`、`userID` 参数),现有调用方可能失败 **缓解措施**: - 通过 LSP 查找所有调用方 - 仅有 `OrderHandler.Create` 和测试用例调用,影响范围可控 **影响范围**: - `internal/handler/admin/order.go` - `internal/handler/h5/order.go`(H5 订单不使用 offline,不受影响) - `internal/service/order/service_test.go` ### 权衡 4:合并方法增加了单个方法的复杂度 **权衡**:`Service.Create` 方法内部有 if/else 分支,复杂度略微增加 **接受理由**: - 两个独立方法的维护成本更高(重复代码、接口复杂) - 内部分支清晰,使用辅助方法拆分逻辑(`buildPurchaseOnBehalfOrder`、`buildNormalOrder`) - 测试覆盖两种分支场景,确保正确性 ## Migration Plan ### 部署步骤 1. **代码变更**: - 修改 DTO(3 个文件) - 修改 Service(2 个文件) - 修改 Handler(1 个文件) - 修改测试用例(2 个文件) 2. **测试验证**: - 运行单元测试确保所有测试通过 - 运行 `lsp_diagnostics` 检查类型错误 - 本地验证接口功能 3. **部署**: - 无数据库迁移,直接部署即可 - 通知前端团队同步修改 `POST /api/admin/orders` 调用 4. **验证**: - 测试套餐系列分配的创建/更新/查询,确认强充配置正常显示 - 测试后台订单创建(wallet 和 offline),确认业务逻辑正确 ### 回滚策略 如果发现问题,可以: 1. 回滚代码到上一版本(Git revert) 2. 无数据库变更,回滚无风险 3. 强充配置字段可选,即使旧代码未传递也不会出错 ### 兼容性保障 - **强充配置字段**:可选字段,默认值 false/0,现有数据不受影响 - **订单创建接口**:`payment_method` 必填,需前端同步修改(可控) - **删除的 DTO 和方法**:仅内部使用,无外部依赖 ## Open Questions 无待解决问题。