- 新增资产状态、订单来源、操作人类型、实名链接类型常量 - 8个模型新增字段(asset_status/generation/source/retail_price等) - 数据库迁移000082:7张表15+字段,含存量retail_price回填 - BUG-1修复:代理零售价渠道隔离,cost_price分配锁定 - BUG-2修复:一次性佣金仅客户端订单触发 - BUG-4修复:充值回调Store操作纳入事务 - 新增资产手动停用接口(PATCH /iot-cards/:id/deactivate、/devices/:id/deactivate) - Carrier管理新增实名链接配置 - 后台订单generation写时快照 - BatchUpdatePricing支持retail_price调价目标 - 清理全部H5旧接口和个人客户旧登录方法
15 KiB
15 KiB
1. 常量定义
- 1.1 在
pkg/constants/新增asset_status.go,定义资产业务状态常量:AssetStatusInStock = 1(在库)、AssetStatusSold = 2(已销售)、AssetStatusExchanged = 3(已换货)、AssetStatusDeactivated = 4(已停用),每个常量必须有中文注释 - 1.2 在
pkg/constants/新增order_source.go,定义订单来源常量:OrderSourceAdmin = "admin"(后台)、OrderSourceClient = "client"(客户端),每个常量必须有中文注释 - 1.3 在
pkg/constants/新增operator_type.go,定义操作人类型常量:OperatorTypeAdminUser = "admin_user"(后台用户)、OperatorTypePersonalCustomer = "personal_customer"(个人客户),每个常量必须有中文注释 - 1.4 在
pkg/constants/新增realname_link.go,定义实名链接类型常量:RealnameLinkTypeNone = "none"、RealnameLinkTypeTemplate = "template"、RealnameLinkTypeGateway = "gateway",每个常量必须有中文注释
2. 模型字段新增
- 2.1 在
internal/model/iot_card.go的IotCard结构体中新增AssetStatus int字段(gorm:column:asset_status;type:int;not null;default:1;comment:业务状态 1-在库 2-已销售 3-已换货 4-已停用)和Generation int字段(gorm:column:generation;type:int;not null;default:1;comment:资产世代编号) - 2.2 在
internal/model/device.go的Device结构体中新增AssetStatus int和Generation int字段,gorm tag 同 2.1 - 2.3 在
internal/model/order.go的Order结构体中新增Source string字段(gorm:column:source;type:varchar(20);not null;default:'admin';comment:订单来源 admin-后台 client-客户端)和Generation int字段(gorm:column:generation;type:int;not null;default:1;comment:资产世代编号) - 2.4 在
internal/model/package.go的PackageUsage结构体中新增Generation int字段(gorm:column:generation;type:int;not null;default:1;comment:资产世代编号) - 2.5 在
internal/model/asset_wallet.go的AssetRechargeRecord结构体中新增以下字段:OperatorType string(column:operator_type;type:varchar(20);not null;default:'admin_user';comment:操作人类型)、Generation int(column:generation;type:int;not null;default:1;comment:资产世代编号)、LinkedPackageIDs datatypes.JSON(column:linked_package_ids;type:jsonb;default:'[]';comment:强充关联套餐ID列表)、LinkedOrderType string(column:linked_order_type;type:varchar(20);comment:关联订单类型)、LinkedCarrierType string(column:linked_carrier_type;type:varchar(20);comment:关联载体类型)、LinkedCarrierID *uint(column:linked_carrier_id;type:bigint;comment:关联载体ID) - 2.6 在
internal/model/carrier.go的Carrier结构体中新增RealnameLinkType string(column:realname_link_type;type:varchar(20);not null;default:'none';comment:实名链接类型 none-不支持 template-模板URL gateway-Gateway接口)和RealnameLinkTemplate string(column:realname_link_template;type:varchar(500);default:'';comment:实名链接模板URL) - 2.7 在
internal/model/shop_package_allocation.go的ShopPackageAllocation结构体中新增RetailPrice int64字段(gorm:column:retail_price;type:bigint;not null;default:0;comment:代理面向终端客户的零售价(分)) - 2.8 在
internal/model/personal_customer.go中将WxOpenID字段的 gorm tag 从uniqueIndex:idx_personal_customer_wx_open_id,where:deleted_at IS NULL改为index:idx_personal_customer_wx_open_id(唯一索引改为普通索引)
3. 数据库迁移
- 3.1 创建迁移文件(使用 golang-migrate 工具),包含以下 ALTER TABLE 语句:
tb_iot_cardADDasset_status int NOT NULL DEFAULT 1、ADDgeneration int NOT NULL DEFAULT 1tb_deviceADDasset_status int NOT NULL DEFAULT 1、ADDgeneration int NOT NULL DEFAULT 1tb_orderADDsource varchar(20) NOT NULL DEFAULT 'admin'、ADDgeneration int NOT NULL DEFAULT 1tb_package_usageADDgeneration int NOT NULL DEFAULT 1tb_asset_recharge_recordADDoperator_type varchar(20) NOT NULL DEFAULT 'admin_user'、ADDgeneration int NOT NULL DEFAULT 1、ADDlinked_package_ids jsonb DEFAULT '[]'、ADDlinked_order_type varchar(20)、ADDlinked_carrier_type varchar(20)、ADDlinked_carrier_id biginttb_carrierADDrealname_link_type varchar(20) NOT NULL DEFAULT 'none'、ADDrealname_link_template varchar(500) DEFAULT ''tb_shop_package_allocationADDretail_price bigint NOT NULL DEFAULT 0
- 3.2 在同一迁移文件中添加存量数据修复 SQL:
UPDATE tb_shop_package_allocation spa SET retail_price = (SELECT suggested_retail_price FROM tb_package p WHERE p.id = spa.package_id) WHERE retail_price = 0 - 3.3 在同一迁移文件中添加索引变更:DROP
idx_personal_customer_wx_open_id唯一索引,CREATE 普通索引idx_personal_customer_wx_open_idONtb_personal_customer(wx_open_id) - 3.4 编写 down 迁移文件(回滚用),包含对应的 DROP COLUMN 和索引恢复语句
- 3.5 执行迁移,使用 PostgreSQL MCP 工具验证所有字段已添加、存量
retail_price已更新、索引已变更
4. BUG-1 修复:代理零售价
- 4.1 修改
internal/service/purchase_validation/service.go的GetPurchasePrice()方法(约第 172 行):增加渠道判断,代理渠道(card.ShopID > 0)时查询ShopPackageAllocation获取RetailPrice并返回,平台渠道继续返回Package.SuggestedRetailPrice - 4.2 修改
internal/service/purchase_validation/service.go的validatePackages()方法(约第 148 行):将totalPrice += pkg.SuggestedRetailPrice改为按渠道取价逻辑(复用 4.1 的渠道判断),代理渠道额外校验allocation.RetailPrice >= allocation.CostPrice,不满足则该套餐视为不可购买 - 4.3 修改
internal/service/shop_package_batch_allocation/service.go:创建分配记录时设置RetailPrice为Package.SuggestedRetailPrice(约第 84-105 行创建 allocation 的位置) - 4.4 修改
internal/service/shop_series_grant/service.go:创建/更新ShopPackageAllocation时同步设置RetailPrice(约第 302-352 行和第 614-685 行) - 4.5 在
internal/service/shop_package_batch_pricing/service.go的BatchUpdatePricing()方法中新增 cost_price 锁定检查:更新每条 allocation 的 cost_price 前,查询是否存在下级分配记录(ShopPackageAllocation WHERE allocator_shop_id = allocation.ShopID AND package_id = allocation.PackageID),存在则跳过该条并记录到响应的skipped列表,附带原因"存在下级分配记录,请先回收后再修改成本价" - 4.6 在
internal/service/shop_series_grant/service.go中同步添加 cost_price 锁定检查:更新现有 allocation 的 CostPrice 时(约第 634 行),查询是否存在下级分配记录,存在则拒绝修改并返回错误
5. 代理零售价后台管理
- 5.1 修改
internal/model/dto/shop_package_batch_pricing_dto.go的BatchUpdateCostPriceRequest:新增PricingTarget string字段(json:"pricing_target" validate:"omitempty,oneof=cost_price retail_price" description:"调价目标 cost_price-成本价(默认) retail_price-零售价"),不传时默认为cost_price保持向后兼容 - 5.2 修改
internal/service/shop_package_batch_pricing/service.go的BatchUpdatePricing()方法:根据req.PricingTarget分流——cost_price走现有逻辑(含 4.5 的锁定检查),retail_price走新逻辑:计算新零售价、校验newRetailPrice >= allocation.CostPrice(不满足则跳过并报错"零售价不能低于成本价")、更新allocation.RetailPrice、记录价格历史(ShopPackageAllocationPriceHistory增加old_retail_price/new_retail_price字段或复用OldCostPrice/NewCostPrice字段并新增price_type标识) - 5.3 修改
internal/model/dto/package_dto.go的PackageResponse结构体:新增RetailPrice *int64字段(json:"retail_price,omitempty" description:"代理零售价(分),仅代理用户可见") - 5.4 修改
internal/service/package/service.go的toResponse()方法(约第 530-541 行):代理用户查询时,从 allocation 读取RetailPrice设入resp.RetailPrice;同时修正ProfitMargin计算:从pkg.SuggestedRetailPrice - allocation.CostPrice改为allocation.RetailPrice - allocation.CostPrice - 5.5 修改
internal/service/package/service.go的toResponseWithAllocation()方法(约第 595-603 行):同 5.4,从 allocation 读取RetailPrice、修正ProfitMargin计算
6. Carrier 管理 DTO 更新
- 6.1 修改
internal/model/dto/carrier_dto.go(或 Carrier 相关 DTO 文件):在CarrierCreateRequest和CarrierUpdateRequest中新增RealnameLinkType *string字段(json:"realname_link_type" validate:"omitempty,oneof=none template gateway" description:"实名链接类型 none-不支持 template-模板URL gateway-Gateway接口")和RealnameLinkTemplate *string字段(json:"realname_link_template" validate:"omitempty,max=500" maxLength:"500" description:"实名链接模板URL,支持 {iccid}/{msisdn}/{virtual_no} 占位符") - 6.2 修改 Carrier Service 的 Create/Update 方法:将 DTO 中的
RealnameLinkType和RealnameLinkTemplate写入 Carrier 模型;Update 时realname_link_type为template时校验realname_link_template非空 - 6.3 修改 Carrier 的响应 DTO(如
CarrierResponse):新增RealnameLinkType和RealnameLinkTemplate字段,后台列表/详情可展示
7. 后台订单 generation 快照
- 7.1 修改
internal/service/order/service.go的CreateAdminOrder()方法:创建 Order 时从资产(IotCard/Device)获取当前Generation值并设入order.Generation,不再依赖数据库默认值 1
8. 资产手动停用
- 8.1 在
internal/handler/admin/asset.go(或新建internal/handler/admin/asset_lifecycle.go)新增DeactivateAssetHandler 方法:接收资产类型和 ID,调用 Service 将asset_status设为constants.AssetStatusDeactivated(4) - 8.2 在
internal/service/asset/service.go(或相关 Service)新增Deactivate()方法:校验当前asset_status为 1(在库)或 2(已销售)才允许停用,已换货(3)或已停用(4)的拒绝操作;使用条件更新WHERE asset_status IN (1, 2)确保幂等 - 8.3 在
internal/routes/注册停用路由:PATCH /api/admin/iot-cards/:id/deactivate和PATCH /api/admin/devices/:id/deactivate(或统一为PATCH /api/admin/assets/:type/:id/deactivate) - 8.4 更新
cmd/api/docs.go和cmd/gendocs/main.go:如新增了 Handler 则同步注册到文档生成器
9. BUG-2 修复:一次性佣金触发条件
- 9.1 修改
internal/service/commission_calculation/service.go中的triggerOneTimeCommissionForCardInTx方法:将if order.IsPurchaseOnBehalf的跳过逻辑改为if order.IsPurchaseOnBehalf || order.Source != constants.OrderSourceClient,即代购订单或非客户端来源订单均跳过一次性佣金 - 9.2 修改
internal/service/commission_calculation/service.go中的triggerOneTimeCommissionForDeviceInTx方法:同 9.1 逻辑
10. BUG-4 修复:充值回调事务一致性
- 10.1 修改
internal/store/postgres/asset_recharge_store.go的UpdateStatusWithOptimisticLock方法:新增tx *gorm.DB参数(方法签名变更),内部使用传入的tx替代s.db.WithContext(ctx);同时保留原方法签名的兼容版本或修改所有调用点 - 10.2 修改
internal/store/postgres/asset_recharge_store.go的UpdatePaymentInfo方法:同 10.1,新增tx *gorm.DB参数 - 10.3 修改
internal/service/recharge/service.go的HandlePaymentCallback方法(约第 308-321 行):在事务闭包内调用 Store 方法时传入事务tx,确保UpdateStatusWithOptimisticLock、UpdatePaymentInfo和钱包入账在同一事务内 - 10.4 检查并更新
UpdateStatusWithOptimisticLock和UpdatePaymentInfo的其他调用点(如有),确保传入正确的 db 或 tx 参数
11. 旧 H5 接口清理
- 11.1 删除
internal/handler/h5/目录下全部 5 个文件:auth.go、order.go、recharge.go、package_usage.go、enterprise_device.go - 11.2 删除
internal/routes/h5.go、internal/routes/h5_enterprise_device.go、internal/routes/h5_package_usage.go - 11.3 修改
internal/routes/routes.go:移除/api/h5路由组挂载(约第 31-33 行) - 11.4 修改
internal/routes/order.go:移除registerH5OrderRoutes函数(约第 56-105 行) - 11.5 修改
internal/routes/recharge.go:移除registerH5RechargeRoutes函数(约第 11-44 行) - 11.6 修改
internal/bootstrap/handlers.go:移除 H5 Handler 构造(H5Auth、EnterpriseDeviceH5、H5PackageUsage、H5Order、H5Recharge,约第 24、31、44、49、50 行) - 11.7 修改
internal/bootstrap/types.go:移除 Handlers 结构体中的 H5 Handler 字段(约第 22、29、42、47、48 行) - 11.8 修改
internal/bootstrap/middlewares.go:移除createH5AuthMiddleware函数和 H5 跳过路径配置(约第 72-95 行) - 11.9 修改
pkg/openapi/handlers.go:移除文档生成中的 H5 Handler 构造(EnterpriseDeviceH5、H5PackageUsage、H5Order、H5Recharge) - 11.10 修改
cmd/api/main.go:移除/api/h5限流挂载(约第 250-257 行)
12. 旧个人客户登录接口清理
- 12.1 修改
internal/handler/app/personal_customer.go:删除 Login(约第 79 行)、SendCode(约第 35 行)、WechatOAuthLogin(约第 114 行)、BindWechat(约第 134 行)方法。保留 UpdateProfile 和 GetProfile - 12.2 修改
internal/routes/personal.go:移除指向已删除方法的路由注册(Login、SendCode、WechatOAuthLogin、BindWechat 的路由) - 12.3 检查
internal/bootstrap/handlers.go和internal/bootstrap/types.go:如果 PersonalCustomer Handler 有初始化引用已删除方法的依赖,同步清理
13. 验证
- 13.1 执行
go build ./...确认编译通过,无任何编译错误 - 13.2 对所有修改的文件执行
lsp_diagnostics确认无错误和警告 - 13.3 使用 PostgreSQL MCP 工具验证数据库:确认 7 张表的新字段存在、默认值正确、存量
retail_price已填充、wx_open_id索引已变更 - 13.4 验证删除的 H5 路由不再注册:检查代码中无
/api/h5相关路由残留 - 13.5 验证
BatchUpdatePricing接口扩展:确认pricing_target=retail_price参数可正常使用,不传时默认走cost_price逻辑(向后兼容) - 13.6 验证代理套餐列表:确认
PackageResponse包含retail_price字段,profit_margin计算基于retail_price - cost_price - 13.7 撰写功能总结文档
docs/client-api-data-model-fixes/功能总结.md,记录所有变更内容