feat(资产钱包自动续费): 新增全局配置、每日扫描续购与可靠复机
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 10m15s

- 新增单行配置表 tb_asset_auto_renewal_config 与尝试记录表 tb_asset_auto_renewal_attempt(迁移 000229/000230)
- 每日按上海自然日扫描,窗口内以同一资产钱包可用余额续购当前主套餐,资金/订单/套餐/审计同一事务闭合
- 唯一键保证每资产每日至多一次尝试,占位中断由后续扫描收敛,当日不重试
- 四类失败原因向客户与店铺各投递每日至多一条站内通知,并注册通知类型与个人客户白名单
- 续费成功后按条件经 Outbox 可靠投递复机,新增恢复扫描只查询回填,不使用即发即弃调用
- 配置读写仅超级管理员与平台账号,保存记录操作者、前后值快照并登记统一审计
- tasks 7.1–7.15 全部验证通过(本机隔离 PostgreSQL/Redis,零外部渠道调用)
This commit is contained in:
2026-09-17 16:39:26 +08:00
parent 70e6b186df
commit d52be16802
54 changed files with 5164 additions and 97 deletions

View File

@@ -106,6 +106,9 @@ func RegisterAdminRoutes(router fiber.Router, handlers *bootstrap.Handlers, midd
if handlers.PackageTrafficAlert != nil {
registerPackageTrafficAlertRoutes(authGroup, handlers.PackageTrafficAlert, doc, basePath)
}
if handlers.AssetAutoRenewal != nil {
registerAssetAutoRenewalRoutes(authGroup, handlers.AssetAutoRenewal, doc, basePath)
}
if handlers.ShopPackageBatchAllocation != nil {
registerShopPackageBatchAllocationRoutes(authGroup, handlers.ShopPackageBatchAllocation, doc, basePath)
}

View File

@@ -0,0 +1,43 @@
package routes
import (
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/handler/admin"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"github.com/break/junhong_cmp_fiber/pkg/openapi"
)
// registerAssetAutoRenewalRoutes 注册资产钱包自动续费配置的读写路由。
// 沿用超管/平台路由组级 gate 先例:代理、企业与个人客户账号一律 403无任何读取或修改入口。
func registerAssetAutoRenewalRoutes(router fiber.Router, handler *admin.AssetAutoRenewalConfigHandler, doc *openapi.Generator, basePath string) {
group := router.Group("", func(c *fiber.Ctx) error {
userType := middleware.GetUserTypeFromContext(c.UserContext())
if userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform {
return errors.New(errors.CodeForbidden, constants.PlatformManagementForbiddenMessage)
}
return c.Next()
})
path := basePath + "/asset-auto-renewal-config"
Register(group, doc, path, "GET", "", handler.GetConfig, RouteSpec{
Summary: "查询资产钱包自动续费配置",
Description: "返回唯一一份全局配置的总开关、适用范围、指定主套餐集合、到期前天数与配置版本;仅超级管理员与平台账号可访问",
Tags: []string{"资产钱包"},
Output: new(dto.AssetAutoRenewalConfigResponse),
Auth: true,
})
Register(group, doc, path, "PUT", "", handler.UpdateConfig, RouteSpec{
Summary: "保存资产钱包自动续费配置",
Description: "指定范围时集合必须非空且只能选择当前可售主套餐;保存记录操作者与前后值快照并递增配置版本,只影响后续扫描",
Tags: []string{"资产钱包"},
Body: new(dto.UpdateAssetAutoRenewalConfigRequest),
Output: new(dto.AssetAutoRenewalConfigResponse),
Auth: true,
})
}