收口七月卡状态回调与系列授权兼容契约
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled

完成运营商实名回调、业务事件观测序列与受控配置装配,同时恢复 UR43 已交付的 packages[].remove 字段及旧响应兼容,统一更新 OpenSpec、OpenAPI 和交付文档。

Constraint: 七月测试环境里程碑不新增或运行自动化测试

Rejected: 以必填 operation_type 替换 packages[].remove | 会破坏已交付前端契约

Confidence: high

Scope-risk: broad

Directive: 后续修改系列套餐管理接口必须保持 packages[].remove 和 ShopSeriesGrantResponse 兼容

Tested: go run ./cmd/gendocs;go build -buildvcs=false ./...;openspec validate complete-july-iteration-test-release --strict;git diff --check

Not-tested: 按本 Change 约定未运行 go test,真实运营商与 Gateway 联调延期
This commit is contained in:
2026-07-24 19:59:24 +08:00
parent a18ed8bc8d
commit 5c4d17e9fc
79 changed files with 4341 additions and 478 deletions

View File

@@ -0,0 +1,37 @@
package bootstrap
import (
"go.uber.org/zap"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/systemconfig"
"github.com/break/junhong_cmp_fiber/pkg/constants"
)
func registerCarrierCallbackConfigDefinitions(registry *systemconfig.Registry, logger *zap.Logger) {
if registry == nil {
return
}
definitions := []systemconfig.Definition{
{Key: constants.SystemConfigCarrierCallbackCTCCRealnameEnabled, Module: constants.SystemConfigModuleCarrierCallback, ValueType: constants.SystemConfigTypeBool, DefaultValue: "false", Description: "是否处理中国电信实名回调", Control: "switch"},
{Key: constants.SystemConfigCarrierCallbackCMCCRealnameEnabled, Module: constants.SystemConfigModuleCarrierCallback, ValueType: constants.SystemConfigTypeBool, DefaultValue: "false", Description: "是否处理中国移动实名回调", Control: "switch"},
{Key: constants.SystemConfigCarrierCallbackCUCCRealnameEnabled, Module: constants.SystemConfigModuleCarrierCallback, ValueType: constants.SystemConfigTypeBool, DefaultValue: "false", Description: "是否处理中国联通实名成功回调", Control: "switch"},
{Key: constants.SystemConfigCarrierCallbackCUCCRealnameRemovalEnabled, Module: constants.SystemConfigModuleCarrierCallback, ValueType: constants.SystemConfigTypeBool, DefaultValue: "false", Description: "是否处理中国联通解除实名回调", Control: "switch"},
}
for _, definition := range definitions {
if existing, exists := registry.Get(definition.Key); exists {
if existing.ValueType != definition.ValueType || existing.Module != definition.Module {
logCarrierCallbackConfigRegistrationError(logger, definition.Key, "配置 Key 已被其他类型或模块注册")
}
continue
}
if err := registry.Register(definition); err != nil {
logCarrierCallbackConfigRegistrationError(logger, definition.Key, err.Error())
}
}
}
func logCarrierCallbackConfigRegistrationError(logger *zap.Logger, key, reason string) {
if logger != nil {
logger.Error("注册运营商回调系统配置失败,相关回调将按关闭处理", zap.String("config_key", key), zap.String("reason", reason))
}
}

View File

@@ -28,5 +28,5 @@ type Dependencies struct {
GatewayClient *gateway.Client // Gateway API 客户端(可选,配置缺失时为 nil
WechatPayment wechat.PaymentServiceInterface // 微信支付服务(可选)
SystemConfigRegistry *systemConfigInfra.Registry // 业务模块共享的受控配置注册表(可选)
SystemConfigAudit systemConfigApp.AuditWriter // 统一配置变更审计 Port可选缺失时更新失败关闭
SystemConfigAudit systemConfigApp.AuditWriter // 配置变更审计 Port可选装配后与配置同事务写入
}

View File

@@ -11,6 +11,8 @@ import (
authHandler "github.com/break/junhong_cmp_fiber/internal/handler/auth"
"github.com/break/junhong_cmp_fiber/internal/handler/callback"
openapiHandler "github.com/break/junhong_cmp_fiber/internal/handler/openapi"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/carriercallback"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/integrationlog"
systemConfigInfra "github.com/break/junhong_cmp_fiber/internal/infrastructure/systemconfig"
pollingPkg "github.com/break/junhong_cmp_fiber/internal/polling"
assetQuery "github.com/break/junhong_cmp_fiber/internal/query/asset"
@@ -89,6 +91,7 @@ func initHandlers(svc *services, deps *Dependencies) *Handlers {
if systemConfigRegistry == nil {
systemConfigRegistry = systemConfigInfra.NewRegistry()
}
registerCarrierCallbackConfigDefinitions(systemConfigRegistry, deps.Logger)
systemConfigAlerts := systemConfigInfra.NewLogAlertSink(deps.Logger)
var systemConfigCache systemConfigInfra.Cache
if deps.Redis != nil {
@@ -108,15 +111,27 @@ func initHandlers(svc *services, deps *Dependencies) *Handlers {
handler.SetDefaultCreditService(roleApp.NewDefaultCreditService(deps.DB, svc.Permission))
return handler
}(),
Permission: admin.NewPermissionHandler(svc.Permission),
PersonalCustomer: app.NewPersonalCustomerHandler(svc.PersonalCustomer, deps.Logger),
ClientAuth: app.NewClientAuthHandler(svc.ClientAuth, deps.Logger),
ClientAsset: app.NewClientAssetHandler(svc.Asset, svc.CustomerBinding, assetWalletStore, packageStore, shopPackageAllocationStore, iotCardStore, deviceStore, deps.DB, deps.Logger),
ClientWallet: app.NewClientWalletHandler(svc.Asset, svc.CustomerBinding, assetWalletStore, assetWalletTransactionStore, rechargeOrderStore, paymentStore, svc.Recharge, personalCustomerOpenIDStore, svc.WechatConfig, deps.Redis, deps.Logger, deps.DB, iotCardStore, deviceStore),
ClientOrder: app.NewClientOrderHandler(clientOrderService, deps.Logger),
ClientExchange: app.NewClientExchangeHandler(svc.Exchange),
ClientRealname: app.NewClientRealnameHandler(svc.Asset, svc.CustomerBinding, iotCardStore, deviceSimBindingStore, carrierStore, deps.GatewayClient, deps.Logger, svc.PollingManualTrigger),
ClientDevice: app.NewClientDeviceHandler(svc.Asset, svc.CustomerBinding, deviceStore, deviceSimBindingStore, iotCardStore, deps.GatewayClient, deps.Logger),
Permission: admin.NewPermissionHandler(svc.Permission),
PersonalCustomer: app.NewPersonalCustomerHandler(svc.PersonalCustomer, deps.Logger),
ClientAuth: app.NewClientAuthHandler(svc.ClientAuth, deps.Logger),
ClientAsset: func() *app.ClientAssetHandler {
handler := app.NewClientAssetHandler(svc.Asset, svc.CustomerBinding, assetWalletStore, packageStore, shopPackageAllocationStore, iotCardStore, deviceStore, deps.DB, deps.Logger)
handler.SetObservationSeriesDispatcher(svc.ObservationSeries)
return handler
}(),
ClientWallet: app.NewClientWalletHandler(svc.Asset, svc.CustomerBinding, assetWalletStore, assetWalletTransactionStore, rechargeOrderStore, paymentStore, svc.Recharge, personalCustomerOpenIDStore, svc.WechatConfig, deps.Redis, deps.Logger, deps.DB, iotCardStore, deviceStore),
ClientOrder: app.NewClientOrderHandler(clientOrderService, deps.Logger),
ClientExchange: app.NewClientExchangeHandler(svc.Exchange),
ClientRealname: func() *app.ClientRealnameHandler {
handler := app.NewClientRealnameHandler(svc.Asset, svc.CustomerBinding, iotCardStore, deviceSimBindingStore, carrierStore, deps.GatewayClient, deps.Logger, svc.PollingManualTrigger)
handler.SetObservationSeriesDispatcher(svc.ObservationSeries)
return handler
}(),
ClientDevice: func() *app.ClientDeviceHandler {
handler := app.NewClientDeviceHandler(svc.Asset, svc.CustomerBinding, deviceStore, deviceSimBindingStore, iotCardStore, deps.GatewayClient, deps.Logger)
handler.SetDeviceService(svc.Device)
return handler
}(),
ClientRechargeOrder: app.NewClientRechargeOrderHandler(rechargeOrderStore, paymentStore, deps.Logger),
ClientNotification: app.NewClientNotificationHandler(notificationQuery.NewQuery(deps.DB), notificationApp.NewReadService(deps.DB), validate),
Shop: func() *admin.ShopHandler {
@@ -158,12 +173,28 @@ func initHandlers(svc *services, deps *Dependencies) *Handlers {
AdminOrder: admin.NewOrderHandler(svc.Order, validate),
AdminExchange: admin.NewExchangeHandler(svc.Exchange, exchangeQuery.NewListQuery(deps.DB), validate),
PaymentCallback: callback.NewPaymentHandler(svc.Order, svc.Recharge, rechargeOrderService, svc.AgentRecharge, deps.WechatPayment, svc.WechatConfig, paymentStore, deps.Logger),
PollingConfig: admin.NewPollingConfigHandler(svc.PollingConfig),
PollingConcurrency: admin.NewPollingConcurrencyHandler(svc.PollingConcurrency),
PollingMonitoring: admin.NewPollingMonitoringHandler(svc.PollingMonitoring),
PollingAlert: admin.NewPollingAlertHandler(svc.PollingAlert),
PollingCleanup: admin.NewPollingCleanupHandler(svc.PollingCleanup),
PollingManualTrigger: admin.NewPollingManualTriggerHandler(svc.PollingManualTrigger),
CTCCRealnameCallback: callback.NewCTCCRealnameHandler(
carriercallback.NewCTCCRealnameTranslator(), carriercallback.NewCTCCCardResolver(deps.DB),
integrationlog.NewRepository(deps.DB), svc.CardObservation, svc.CardObservationSeries, systemConfigReader, deps.Logger,
),
CMCCRealnameCallback: callback.NewCMCCRealnameHandler(
carriercallback.NewCMCCRealnameTranslator(), carriercallback.NewCMCCCardResolver(deps.DB),
integrationlog.NewRepository(deps.DB), svc.CardObservation, svc.CardObservationSeries, systemConfigReader, deps.Logger,
),
CUCCRealnameCallback: callback.NewCUCCRealnameHandler(
carriercallback.NewCUCCRealnameTranslator(), carriercallback.NewCUCCCardResolver(deps.DB),
integrationlog.NewRepository(deps.DB), svc.CardObservation, svc.CardObservationSeries, systemConfigReader, deps.Logger,
),
CUCCRealnameRemovalCallback: callback.NewCUCCRealnameRemovalHandler(
carriercallback.NewCUCCRealnameRemovalTranslator(), carriercallback.NewCUCCCardResolver(deps.DB),
integrationlog.NewRepository(deps.DB), systemConfigReader, deps.Logger,
),
PollingConfig: admin.NewPollingConfigHandler(svc.PollingConfig),
PollingConcurrency: admin.NewPollingConcurrencyHandler(svc.PollingConcurrency),
PollingMonitoring: admin.NewPollingMonitoringHandler(svc.PollingMonitoring),
PollingAlert: admin.NewPollingAlertHandler(svc.PollingAlert),
PollingCleanup: admin.NewPollingCleanupHandler(svc.PollingCleanup),
PollingManualTrigger: admin.NewPollingManualTriggerHandler(svc.PollingManualTrigger),
Asset: func() *admin.AssetHandler {
pollingQueueMgr := pollingPkg.NewPollingQueueManager(deps.Redis, constants.PollingShardCount, deps.Logger)
assetPollingSvc := pollingSvcPkg.NewAssetPollingService(
@@ -176,6 +207,7 @@ func initHandlers(svc *services, deps *Dependencies) *Handlers {
)
h := admin.NewAssetHandler(svc.Asset, svc.AssetAudit, svc.Device, svc.IotCard, svc.StopResumeService, assetPollingSvc, assetQuery.NewExchangeTraceQuery(deps.DB, deps.Logger))
h.SetLifecycleService(svc.AssetLifecycle)
h.SetObservationSeriesDispatcher(svc.ObservationSeries)
return h
}(),
AssetLifecycle: admin.NewAssetLifecycleHandler(svc.AssetLifecycle),

View File

@@ -8,6 +8,7 @@ import (
cardObservationApp "github.com/break/junhong_cmp_fiber/internal/application/cardobservation"
walletapp "github.com/break/junhong_cmp_fiber/internal/application/wallet"
cardObservationInfra "github.com/break/junhong_cmp_fiber/internal/infrastructure/cardobservation"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/integrationlog"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/messaging/outbox"
walletinfra "github.com/break/junhong_cmp_fiber/internal/infrastructure/wallet"
"github.com/break/junhong_cmp_fiber/internal/polling"
@@ -27,6 +28,7 @@ import (
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/payment"
"github.com/break/junhong_cmp_fiber/pkg/queue"
assetSvc "github.com/break/junhong_cmp_fiber/internal/service/asset"
assetWalletSvc "github.com/break/junhong_cmp_fiber/internal/service/asset_wallet"
@@ -118,6 +120,9 @@ type services struct {
AgentOpenAPI *agentOpenAPISvc.Service
CustomerBinding *customerBindingSvc.Service
OrderPackageInvalidate *orderPackageInvalidateSvc.Service
ObservationSeries cardObservationApp.BestEffortSeriesDispatcher
CardObservation *cardObservationApp.Service
CardObservationSeries *cardObservationApp.SeriesAttemptService
}
func initServices(s *stores, deps *Dependencies) *services {
@@ -143,11 +148,27 @@ func initServices(s *stores, deps *Dependencies) *services {
assetAudit,
)
cardObservationOutbox := outbox.NewRepository()
iotCard.SetCardObservationService(cardObservationApp.NewService(
observationSeriesEvents := cardObservationInfra.NewSeriesEventWriter(cardObservationOutbox)
cardObservationService := cardObservationApp.NewService(
deps.DB,
cardObservationInfra.NewEventWriter(cardObservationOutbox),
cardObservationInfra.NewCacheInvalidator(deps.Redis, deps.Logger),
))
)
iotCard.SetCardObservationService(cardObservationService)
seriesCoordinator := cardObservationInfra.NewSeriesCoordinator(deps.Redis)
seriesIntegration := integrationlog.NewRepository(deps.DB)
seriesTrigger := cardObservationApp.NewSeriesTrigger(
seriesCoordinator,
queue.NewCardObservationSeriesScheduler(deps.QueueClient),
cardObservationInfra.NewSeriesAttemptLogger(seriesIntegration),
)
cardObservationSeries := cardObservationApp.NewSeriesAttemptService(
seriesCoordinator,
cardObservationInfra.NewSeriesRunner(deps.DB, deps.GatewayClient, cardObservationService, seriesIntegration),
cardObservationInfra.NewSeriesAttemptLogger(seriesIntegration),
)
observationSeries := cardObservationInfra.NewBestEffortSeriesDispatcher(seriesTrigger, deps.Logger, s.DeviceSimBinding, s.Carrier)
iotCard.SetObservationSeriesDispatcher(observationSeries)
// 使用 PollingLifecycleService 替代 APICallback通过分片队列准确操作修复 api_callback.go 遗漏 protect 队列的 Bug3
pollingConfigStore := postgres.NewPollingConfigStore(deps.DB)
pollingConfigMgr := polling.NewPollingConfigManager(pollingConfigStore, deps.Redis, deps.Logger)
@@ -172,6 +193,7 @@ func initServices(s *stores, deps *Dependencies) *services {
s.PackageUsageDailyRecord,
deps.Logger,
)
packageActivation.SetObservationSeriesEventWriter(observationSeriesEvents)
stopResumeService := iotCardSvc.NewStopResumeService(
deps.Redis,
@@ -183,6 +205,7 @@ func initServices(s *stores, deps *Dependencies) *services {
assetAudit,
)
stopResumeService.SetPollingCallback(pollingLifecycleSvc)
stopResumeService.SetObservationSeriesEventWriter(deps.DB, observationSeriesEvents)
iotCard.SetRealnameActivator(packageActivation)
iotCard.SetStopResumeService(stopResumeService)
iotCard.SetDeviceSimBindingStore(s.DeviceSimBinding)
@@ -206,10 +229,13 @@ func initServices(s *stores, deps *Dependencies) *services {
s.EnterpriseDeviceAuthorization,
s.Enterprise,
)
device.SetObservationSeriesEventWriter(observationSeriesEvents)
device.SetObservationSeriesDispatcher(observationSeries)
operationPassword := operationPasswordSvc.New(deps.Redis)
shopCommission := shopCommissionSvc.New(s.Shop, s.Account, s.AgentWallet, s.CommissionWithdrawalRequest, s.CommissionWithdrawalSetting, s.CommissionRecord, s.AgentWalletTransaction, deps.DB, deps.Logger)
packageService := packageSvc.New(s.Package, s.PackageSeries, s.ShopPackageAllocation, s.ShopSeriesAllocation)
orderService := orderSvc.New(deps.DB, deps.Redis, s.Order, s.OrderItem, s.AgentWallet, s.AssetWallet, s.Payment, purchaseValidation, s.ShopPackageAllocation, s.ShopSeriesAllocation, s.IotCard, s.Device, s.PackageSeries, s.PackageUsage, s.Package, wechatConfig, deps.WechatPayment, paymentLoader, deps.QueueClient, deps.Logger, s.AssetIdentifier, s.PersonalCustomer, s.PersonalCustomerPhone)
orderService.SetObservationSeriesEventWriter(observationSeriesEvents)
walletOutbox := outbox.NewRepository()
walletDebitEvents := walletinfra.NewDebitEventWriter(walletOutbox)
orderService.SetAgentWalletDebitService(walletapp.NewDebitService(walletDebitEvents, nil))
@@ -244,6 +270,7 @@ func initServices(s *stores, deps *Dependencies) *services {
refundService.SetAgentWalletRefundService(walletapp.NewRefundService(walletinfra.NewRefundEventWriter(walletOutbox), nil))
assetService := assetSvc.New(deps.DB, s.Device, s.IotCard, s.PackageUsage, s.Package, s.PackageSeries, s.DeviceSimBinding, s.Shop, deps.Redis, iotCard, deps.GatewayClient, s.AssetIdentifier, s.Order, s.OrderItem, s.ExchangeOrder, assetAudit)
agentOpenAPI := agentOpenAPISvc.New(assetService, packageService, orderService, shopCommission, stopResumeService, device, s.IotCard, s.PackageUsage, s.Package, s.PackageSeries, s.AgentWallet, s.DeviceSimBinding, s.Device)
agentOpenAPI.SetObservationSeriesDispatcher(observationSeries)
return &services{
Account: account,
@@ -331,5 +358,8 @@ func initServices(s *stores, deps *Dependencies) *services {
Refund: refundService,
CustomerBinding: customerBinding,
OrderPackageInvalidate: orderPackageInvalidateSvc.New(s.OrderPackageInvalidateTask, deps.QueueClient),
ObservationSeries: observationSeries,
CardObservation: cardObservationService,
CardObservationSeries: cardObservationSeries,
}
}

View File

@@ -53,6 +53,10 @@ type Handlers struct {
AdminOrder *admin.OrderHandler
AdminExchange *admin.ExchangeHandler
PaymentCallback *callback.PaymentHandler
CTCCRealnameCallback *callback.CTCCRealnameHandler
CMCCRealnameCallback *callback.CMCCRealnameHandler
CUCCRealnameCallback *callback.CUCCRealnameHandler
CUCCRealnameRemovalCallback *callback.CUCCRealnameRemovalHandler
PollingConfig *admin.PollingConfigHandler
PollingConcurrency *admin.PollingConcurrencyHandler
PollingMonitoring *admin.PollingMonitoringHandler

View File

@@ -88,6 +88,7 @@ func initWorkerServices(stores *queue.WorkerStores, deps *WorkerDependencies) *q
deps.Logger,
)
cardObservationOutbox := outbox.NewRepository()
observationSeriesEvents := cardObservationInfra.NewSeriesEventWriter(cardObservationOutbox)
cardObservationService := cardObservationApp.NewService(
deps.DB,
cardObservationInfra.NewEventWriter(cardObservationOutbox),
@@ -141,22 +142,25 @@ func initWorkerServices(stores *queue.WorkerStores, deps *WorkerDependencies) *q
deps.Logger,
assetAudit,
)
stopResumeService.SetObservationSeriesEventWriter(deps.DB, observationSeriesEvents)
activationService.SetObservationSeriesEventWriter(observationSeriesEvents)
usageService.SetStopResumeCallback(stopResumeService)
activationService.SetResumeCallback(stopResumeService)
orderService.SetResumeCallback(stopResumeService)
resetService.SetResumeCallback(stopResumeService)
return &queue.WorkerServices{
CardObservation: cardObservationService,
CardObservationSeries: cardObservationSeriesService,
CommissionCalculation: commissionCalculationService,
CommissionStats: commissionStatsService,
UsageService: usageService,
ActivationService: activationService,
ResetService: resetService,
AlertService: alertService,
CleanupService: cleanupService,
StopResumeService: stopResumeService,
OrderExpirer: orderService,
CardObservation: cardObservationService,
CardObservationSeries: cardObservationSeriesService,
ObservationSeriesEvents: observationSeriesEvents,
CommissionCalculation: commissionCalculationService,
CommissionStats: commissionStatsService,
UsageService: usageService,
ActivationService: activationService,
ResetService: resetService,
AlertService: alertService,
CleanupService: cleanupService,
StopResumeService: stopResumeService,
OrderExpirer: orderService,
}
}