feat(资产钱包自动续费): 新增全局配置、每日扫描续购与可靠复机
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 10m15s
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:
@@ -33,6 +33,8 @@ func generateOpenAPIDocs(outputPath string, logger *zap.Logger) {
|
||||
handlers.PhoneAssetAssociation = admin.NewPhoneAssetAssociationHandler(nil, nil)
|
||||
// 套餐真流量预警 Handler 必须同时进入文档工厂,避免新增管理接口遗漏文档注册。
|
||||
handlers.PackageTrafficAlert = admin.NewPackageTrafficAlertHandler(nil, nil, nil, nil)
|
||||
// 资产钱包自动续费配置 Handler 必须同时进入文档工厂,避免新增管理接口遗漏文档注册。
|
||||
handlers.AssetAutoRenewal = admin.NewAssetAutoRenewalConfigHandler(nil, nil)
|
||||
handlers.ClientPopup = apphandler.NewClientPopupHandler(nil, nil, nil)
|
||||
handlers.H5PopupConfiguration = admin.NewH5PopupConfigurationHandler(nil, nil, nil)
|
||||
// 企业微信 Handler 在此显式装配,避免新增管理接口遗漏文档注册。
|
||||
|
||||
@@ -42,6 +42,8 @@ func generateAdminDocs(outputPath string) error {
|
||||
handlers.PhoneAssetAssociation = admin.NewPhoneAssetAssociationHandler(nil, nil)
|
||||
// 套餐真流量预警 Handler 必须同时进入文档工厂,避免新增管理接口遗漏文档注册。
|
||||
handlers.PackageTrafficAlert = admin.NewPackageTrafficAlertHandler(nil, nil, nil, nil)
|
||||
// 资产钱包自动续费配置 Handler 必须同时进入文档工厂,避免新增管理接口遗漏文档注册。
|
||||
handlers.AssetAutoRenewal = admin.NewAssetAutoRenewalConfigHandler(nil, nil)
|
||||
handlers.ClientPopup = apphandler.NewClientPopupHandler(nil, nil, nil)
|
||||
handlers.H5PopupConfiguration = admin.NewH5PopupConfigurationHandler(nil, nil, nil)
|
||||
// 企业微信 Handler 在此显式装配,避免新增管理接口遗漏文档注册。
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
agentrechargeApp "github.com/break/junhong_cmp_fiber/internal/application/agentrecharge"
|
||||
approvalApp "github.com/break/junhong_cmp_fiber/internal/application/approval"
|
||||
assetAutoRenewalApp "github.com/break/junhong_cmp_fiber/internal/application/assetautorenewal"
|
||||
auditArchiveApp "github.com/break/junhong_cmp_fiber/internal/application/auditarchive"
|
||||
cardObservationApp "github.com/break/junhong_cmp_fiber/internal/application/cardobservation"
|
||||
carrierThresholdApp "github.com/break/junhong_cmp_fiber/internal/application/carrierthreshold"
|
||||
@@ -28,6 +29,7 @@ import (
|
||||
"github.com/break/junhong_cmp_fiber/internal/bootstrap"
|
||||
"github.com/break/junhong_cmp_fiber/internal/gateway"
|
||||
approvalInfra "github.com/break/junhong_cmp_fiber/internal/infrastructure/approval"
|
||||
assetAutoRenewalInfra "github.com/break/junhong_cmp_fiber/internal/infrastructure/assetautorenewal"
|
||||
auditInfra "github.com/break/junhong_cmp_fiber/internal/infrastructure/audit"
|
||||
cardObservationInfra "github.com/break/junhong_cmp_fiber/internal/infrastructure/cardobservation"
|
||||
carrierThresholdInfra "github.com/break/junhong_cmp_fiber/internal/infrastructure/carrierthreshold"
|
||||
@@ -94,6 +96,8 @@ type workerRuntime struct {
|
||||
refundChannelService *refundchannelApp.Service
|
||||
// carrierThresholdService 是通道流量阈值停复机与两个计划任务的唯一用例实例。
|
||||
carrierThresholdService *carrierThresholdApp.Service
|
||||
// assetAutoRenewalService 是资产钱包自动续费的每日扫描、复机消费者与恢复扫描的唯一用例实例。
|
||||
assetAutoRenewalService *assetAutoRenewalApp.Service
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -167,6 +171,7 @@ func runWorker(cfg *config.Config) {
|
||||
registerAgentRechargeRecoveryTask(taskHandler.GetMux(), runtime, appLogger)
|
||||
registerRefundChannelRecoveryTask(taskHandler.GetMux(), runtime, appLogger)
|
||||
registerCarrierThresholdTasks(taskHandler.GetMux(), runtime, appLogger)
|
||||
registerAssetAutoRenewalTasks(taskHandler.GetMux(), runtime, appLogger)
|
||||
registerRefundCommissionRecoveryTask(taskHandler.GetMux(), runtime, appLogger)
|
||||
registerAuditArchiveTask(taskHandler.GetMux(), runtime, cfg.Worker.AuditRetentionCleanupEnabled, cfg.Worker.AuditArchiveTasksEnabled, appLogger, retentionLogger)
|
||||
outboxHandler := outbox.NewHandler(runtime.outboxConsumers)
|
||||
@@ -342,12 +347,15 @@ func initWorkerRuntime(ctx context.Context, cfg *config.Config, appLogger *zap.L
|
||||
lifecycleSvc: lifecycleSvc,
|
||||
// 通道阈值停复机复用既有停复机服务作为唯一执行事实源(重试、Integration Log、统一审计与既有判定)。
|
||||
carrierThresholdService: workerResult.Services.CarrierThreshold,
|
||||
// 自动续费的复机执行同样复用既有停复机单一事实源。
|
||||
assetAutoRenewalService: workerResult.Services.AssetAutoRenewal,
|
||||
}
|
||||
registerNotificationOutboxConsumer(runtime, appLogger)
|
||||
registerWalletOutboxConsumer(runtime, appLogger)
|
||||
registerCardObservationOutboxConsumer(runtime, appLogger)
|
||||
registerPriorityPollingOutboxConsumer(runtime, appLogger)
|
||||
registerCarrierThresholdOutboxConsumer(runtime, appLogger)
|
||||
registerAssetAutoRenewalOutboxConsumer(runtime, appLogger)
|
||||
registerWeComApprovalOutboxConsumer(runtime, cfg, appLogger)
|
||||
return runtime
|
||||
}
|
||||
@@ -387,6 +395,19 @@ func registerCarrierThresholdOutboxConsumer(runtime *workerRuntime, appLogger *z
|
||||
}
|
||||
}
|
||||
|
||||
// registerAssetAutoRenewalOutboxConsumer 注册资产钱包自动续费的复机请求事件消费者。
|
||||
// 消费者按尝试记录认领执行权后执行复机,重复投递不会产生第二次外部调用。
|
||||
func registerAssetAutoRenewalOutboxConsumer(runtime *workerRuntime, appLogger *zap.Logger) {
|
||||
if runtime == nil || runtime.assetAutoRenewalService == nil {
|
||||
appLogger.Fatal("资产钱包自动续费用例未配置")
|
||||
}
|
||||
consumer := assetAutoRenewalApp.NewResumeConsumer(runtime.assetAutoRenewalService)
|
||||
if err := runtime.outboxConsumers.Register(constants.OutboxEventTypeAssetAutoRenewalResumeRequested, consumer); err != nil {
|
||||
appLogger.Fatal("注册资产钱包自动续费 Outbox 消费者失败",
|
||||
zap.String("event_type", constants.OutboxEventTypeAssetAutoRenewalResumeRequested), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// registerWeComApprovalOutboxConsumer 注册企业微信审批提交和标准终态业务消费者。
|
||||
func registerWeComApprovalOutboxConsumer(runtime *workerRuntime, cfg *config.Config, appLogger *zap.Logger) {
|
||||
auditWriter, ok := runtime.workerResult.Services.PaymentAudit.(*auditInfra.Writer)
|
||||
@@ -588,6 +609,20 @@ func registerCarrierThresholdTasks(mux *asynq.ServeMux, runtime *workerRuntime,
|
||||
appLogger.Info("注册运营商通道流量阈值结果恢复任务处理器", zap.String("task_type", constants.TaskTypeCarrierThresholdRecovery))
|
||||
}
|
||||
|
||||
// registerAssetAutoRenewalTasks 注册资产钱包自动续费的每日扫描与复机结果恢复任务。
|
||||
// 两个任务共用同一用例实例:每日扫描负责终态收敛与续购执行,恢复扫描只查询状态回填,绝不重复发起复机。
|
||||
func registerAssetAutoRenewalTasks(mux *asynq.ServeMux, runtime *workerRuntime, appLogger *zap.Logger) {
|
||||
if runtime == nil || runtime.assetAutoRenewalService == nil {
|
||||
appLogger.Fatal("资产钱包自动续费用例未配置")
|
||||
}
|
||||
scanHandler := assetAutoRenewalInfra.NewDailyScanTaskHandler(runtime.assetAutoRenewalService)
|
||||
mux.HandleFunc(constants.TaskTypeAssetAutoRenewalScan, scanHandler.Handle)
|
||||
appLogger.Info("注册资产钱包自动续费每日扫描任务处理器", zap.String("task_type", constants.TaskTypeAssetAutoRenewalScan))
|
||||
recoveryHandler := assetAutoRenewalInfra.NewRecoveryTaskHandler(runtime.assetAutoRenewalService)
|
||||
mux.HandleFunc(constants.TaskTypeAssetAutoRenewalRecovery, recoveryHandler.Handle)
|
||||
appLogger.Info("注册资产钱包自动续费复机结果恢复任务处理器", zap.String("task_type", constants.TaskTypeAssetAutoRenewalRecovery))
|
||||
}
|
||||
|
||||
// registerCardObservationOutboxConsumer 注册卡观测领域事件消费者。
|
||||
func registerCardObservationOutboxConsumer(runtime *workerRuntime, appLogger *zap.Logger) {
|
||||
stopResumeService, _ := runtime.workerResult.Services.StopResumeService.(iot_card_svc.StopResumeServiceInterface)
|
||||
@@ -948,6 +983,25 @@ func registerAsynqScheduleTasks(asynqScheduler *asynq.Scheduler, auditArchiveEna
|
||||
)); err != nil {
|
||||
return fmt.Errorf("注册每日套餐真流量达量预警扫描定时任务失败: %w", err)
|
||||
}
|
||||
if _, err := asynqScheduler.Register("CRON_TZ=Asia/Shanghai 0 7 * * *", asynq.NewTask(
|
||||
constants.TaskTypeAssetAutoRenewalScan,
|
||||
nil,
|
||||
asynq.MaxRetry(3),
|
||||
asynq.Timeout(30*time.Minute),
|
||||
asynq.Queue(constants.QueueForTaskType(constants.TaskTypeAssetAutoRenewalScan)),
|
||||
)); err != nil {
|
||||
return fmt.Errorf("注册每日资产钱包自动续费扫描定时任务失败: %w", err)
|
||||
}
|
||||
if _, err := asynqScheduler.Register("@every 5m", asynq.NewTask(
|
||||
constants.TaskTypeAssetAutoRenewalRecovery,
|
||||
nil,
|
||||
asynq.MaxRetry(3),
|
||||
asynq.Timeout(10*time.Minute),
|
||||
asynq.Unique(5*time.Minute),
|
||||
asynq.Queue(constants.QueueForTaskType(constants.TaskTypeAssetAutoRenewalRecovery)),
|
||||
)); err != nil {
|
||||
return fmt.Errorf("注册资产钱包自动续费复机结果恢复定时任务失败: %w", err)
|
||||
}
|
||||
if _, err := asynqScheduler.Register(
|
||||
"0 2 * * *",
|
||||
asynq.NewTask(
|
||||
|
||||
Reference in New Issue
Block a user