## 目标 - 将现有单件发货接口扩展为批量接口,支持一次提交多个资产 ID(如:{"inventory_ids":[52,53,...]})。 - 复用现有发货能力(默认地址、资产状态迁移、备注标记),保证幂等与错误可追踪。 ## 现状 - 单件接口:`POST /api/app/users/{user_id}/inventory/request-shipping`,请求体 `{ inventory_id }` - 处理器位置:internal/api/user/request_shipping_app.go:30-47 - 服务层:`RequestShipping(ctx, userID, inventoryID)` 设置资产状态为已申请发货并记录备注 - 代码位置:internal/service/user/address_share.go:120-126(`UPDATE user_inventory SET status=3, ... remark+='|shipping_requested' WHERE status=1`) ## 接口设计 - 新增:`POST /api/app/users/{user_id}/inventory/request-shipping-batch` - 请求体: - `inventory_ids` 数组,必填,长度 1–100,去重后处理 - `address_id` 可选;若不提供则使用用户默认地址 - 返回体: - `address_id` 实际使用的地址 ID - `success_ids` 已成功提交的资产 ID 列表 - `skipped` 数组:[{ id, reason }](如 not_found、not_owned、invalid_status、already_requested) - `failed` 数组:[{ id, reason }](如 DB 错误等) ## 行为与规则 - 地址选择 - 若提供 `address_id`:校验属于该用户且有效;否则返回 400 - 若未提供:读取默认地址;不存在则返回 400(沿用现有单件逻辑) - 资产校验(逐个) - 必须属于该用户 - `status=1`(可发货)才处理;`status=3`(已申请)则标记为 `already_requested` 并 skip(幂等) - 不存在或不属于该用户 → `skipped.not_owned/not_found` - 幂等性 - 若 remark 已包含 `shipping_requested` 或状态为 3,则视为已处理;加入 `skipped` 并不报错 - 原子性 - 批量以“逐条子事务”处理(每条调用服务层更新),确保单条失败不影响其他条目;最终返回成功/跳过/失败三类列表 - 审计 - 在 `user_points_ledger` 无需记录(该流程与积分无关);如需审计可在后续增加 `user_operations` 表留痕 ## 服务层扩展 - 新增:`RequestShippings(ctx, userID int64, inventoryIDs []int64, addressID *int64) (addrID int64, success []int64, skipped []struct{id int64; reason string}, failed []struct{id int64; reason string}, err error)` - 内部: - 若 `addressID==nil`,读取默认地址(沿用单件方法) - 循环:校验→调用现有 `RequestShipping(ctx, userID, inventoryID)`;捕获错误进行分流 - 可选优化:对同一用户的多件,合并一次地址校验;对 DB 更新使用独立事务(已在现有方法内处理) ## 处理器实现 - 新增处理器:`RequestShippingBatch()`: - 位置:internal/api/user/request_shipping_app.go(或新文件 `request_shipping_batch_app.go`) - 解析 `inventory_ids`(去重、长度限制);解析可选 `address_id` - 调用服务层批量方法,组装响应 - 统一错误码:参数错误 `code.ParamBindError`,无地址 `10021`(沿用或新增),其他子项错误填入 `failed` 字段 ## 错误码与返回示例 - 400 参数错误:`{"code":10023,"message":"invalid inventory_ids"}` - 200 成功+部分跳过: ``` { "address_id": 888, "success_ids": [52, 53], "skipped": [{"id": 54, "reason": "already_requested"}], "failed": [] } ``` ## 测试用例 - 有默认地址,提交 1、N 个有效资产 → 全成功 - 混合:包含非本用户、已申请、不存在 → 分别进 `skipped` - 指定 address_id 非本用户 → 400 - 无默认地址且未指定 address → 400 - 幂等:重复提交相同资产 → 均进入 `already_requested` ## 兼容性 - 不改动现有单件接口;前端可增设批量勾选后调用新接口 - DB 无结构变化;仍依赖 `user_inventory.status` 与 `remark` 标记 ## 交付内容 - 新增批量处理器与服务方法 - Swagger 注释与接口文档 - 单元测试:服务层批量逻辑、处理器参数校验 请确认按此方案实施,我将立即落地代码、接口与测试。