feat: 卡流量读数先过采集队列再入账,扣减按采集时刻确定套餐
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m27s

- 每卡 Redis 采集队列(默认 N=3、跨天清空、回退容差 1 MB),攒满且不下降才写最后一个点
- 测试环境全量切换日快照计量,下线灰度名单、切换水位与卡表基线扣费;全部卡置待建起点
- 读数区分没给值与 0;有套餐的卡读数 0 也写快照与日用量 0 行;重置日比较基准为 0 照常防旧值
- 新增采集记录(按月分区保留 90 天)、人工冲正流水与后台接口、采集告警
- 扣减按采集时刻有效套餐查找,只对生效中套餐做状态迁移,查不到套餐按成功跳过
- 换货新卡(新设备下的卡)打待建起点;采集间隔上限只校验启用中的轮询配置

Change: add-card-traffic-queue-and-observed-deduction

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
邹进
2026-10-29 12:18:47 +09:00
parent c33cc6f6c3
commit 9c3c258f16
55 changed files with 3142 additions and 881 deletions

View File

@@ -1,8 +1,10 @@
package bootstrap
import (
"context"
agentrechargeApp "github.com/break/junhong_cmp_fiber/internal/application/agentrecharge"
businessUserGroupApp "github.com/break/junhong_cmp_fiber/internal/application/businessusergroup"
cardObservationApp "github.com/break/junhong_cmp_fiber/internal/application/cardobservation"
employeecollectionApp "github.com/break/junhong_cmp_fiber/internal/application/employeecollection"
h5PopupApp "github.com/break/junhong_cmp_fiber/internal/application/h5popup"
merchantPaymentApp "github.com/break/junhong_cmp_fiber/internal/application/merchantpayment"
@@ -129,6 +131,7 @@ func initHandlers(svc *services, deps *Dependencies) *Handlers {
gatewaylimit.RegisterConfigDefinitions(systemConfigRegistry, deps.Logger)
carrierqueue.RegisterConfigDefinitions(systemConfigRegistry, deps.Logger)
packagesilenceApp.RegisterConfigDefinitions(systemConfigRegistry, deps.Logger)
registerTrafficQueueConfigDefinitions(systemConfigRegistry, deps)
systemConfigAlerts := systemConfigInfra.NewLogAlertSink(deps.Logger)
var systemConfigCache systemConfigInfra.Cache
if deps.Redis != nil {
@@ -437,5 +440,19 @@ func newAssetPollingService(svc *services, deps *Dependencies) *pollingSvcPkg.As
)
svc.Order.SetPollingRestorer(assetPollingSvc)
svc.Asset.SetPollingRestorer(assetPollingSvc, svc.StopResumeService, deps.Logger)
svc.Asset.SetTrafficAdjustmentEvaluator(svc.StopResumeService)
return assetPollingSvc
}
// registerTrafficQueueConfigDefinitions 注册卡流量采集队列配置:修改队列长度、跨天清空、最长点龄前
// 校验全部启用中的轮询配置的流量采集间隔仍在上限内,否则拒绝修改。
func registerTrafficQueueConfigDefinitions(registry *systemConfigInfra.Registry, deps *Dependencies) {
settings := cardObservationApp.NewDefaultTrafficQueueSettingsProvider(deps.DB, deps.Redis, deps.Logger)
current := func() cardObservationApp.TrafficQueueSettings { return settings.Current(context.Background()) }
checker := func(next cardObservationApp.TrafficQueueSettings) error {
return pollingSvcPkg.CheckEnabledTrafficIntervals(context.Background(), deps.DB, pollingSvcPkg.TrafficQueueLimits{
Size: next.Size, ClearOnNewDay: next.ClearOnNewDay, MaxPointAgeHours: next.MaxPointAgeHours,
})
}
cardObservationApp.RegisterTrafficQueueConfigDefinitions(registry, deps.Logger, current, checker)
}

View File

@@ -207,12 +207,9 @@ func initServices(s *stores, deps *Dependencies) *services {
cardObservationService.SetStateAuditWriter(iotCard)
// 运营商通道流量阈值达量判定嵌入流量观测事务:与卡流量事实同事务写周期锁与停机事件。
cardObservationService.SetChannelThresholdEvaluator(carrierThresholdService)
// 卡流量计量切换策略:启动时展开灰度名单为内存卡集合,API 与全部 Worker 必须使用一致配置。
var trafficCalcPolicy *cardObservationApp.TrafficCalcPolicy
if cfg := config.Get(); cfg != nil {
trafficCalcPolicy = cardObservationApp.BuildTrafficCalcPolicy(context.Background(), deps.DB, cfg.TrafficCalc, deps.Logger)
}
cardObservationService.SetTrafficCalcPolicy(trafficCalcPolicy, deps.Logger)
// 卡流量采集队列:读数先进每卡 Redis 队列,满足写入条件才写日快照;队列配置为受控系统配置。
cardObservationService.SetTrafficQueue(cardObservationInfra.NewTrafficQueue(deps.Redis),
cardObservationApp.NewDefaultTrafficQueueSettingsProvider(deps.DB, deps.Redis, deps.Logger), deps.Logger)
cardObservationService.SetCommandGrace(commandGraceProvider(deps.DB, deps.Redis, deps.Logger))
iotCard.SetCardObservationService(cardObservationService)
iotCard.SetSpeedTierIntegrationLog(integrationlog.NewRepository(deps.DB))
@@ -509,6 +506,11 @@ func initServices(s *stores, deps *Dependencies) *services {
pollingConfigService := pollingSvc.NewConfigService(s.PollingConfig, deps.Redis, deps.Logger)
pollingConfigService.SetAudit(deps.DB, auditWriter)
pollingConfigService.SetTierDeps(pollingConfigMgr, deps.QueueClient)
trafficQueueSettings := cardObservationApp.NewDefaultTrafficQueueSettingsProvider(deps.DB, deps.Redis, deps.Logger)
pollingConfigService.SetTrafficQueueLimits(func(ctx context.Context) pollingSvc.TrafficQueueLimits {
current := trafficQueueSettings.Current(ctx)
return pollingSvc.TrafficQueueLimits{Size: current.Size, ClearOnNewDay: current.ClearOnNewDay, MaxPointAgeHours: current.MaxPointAgeHours}
})
pollingConcurrencyService := pollingSvc.NewConcurrencyService(s.PollingConcurrencyConfig, deps.Redis)
pollingConcurrencyService.SetAudit(deps.DB, auditWriter)
pollingAlertService := pollingSvc.NewAlertService(s.PollingAlertRule, s.PollingAlertHistory, deps.Redis, deps.Logger)
@@ -604,7 +606,7 @@ func initServices(s *stores, deps *Dependencies) *services {
AgentRechargePaymentConfirm: agentRechargePaymentConfirm,
AgentRechargeVoucherOCR: agentRechargeVoucherOCR,
PackageActivation: packageActivation,
TrafficQuery: trafficSvc.NewQueryService(deps.Redis, s.CardDailyUsage).WithFlowSnapshots(trafficCalcPolicy.Switched, postgres.NewCardFlowDayStore(deps.DB)),
TrafficQuery: trafficSvc.NewQueryService(deps.Redis, s.CardDailyUsage).WithFlowSnapshots(func(uint) bool { return true }, postgres.NewCardFlowDayStore(deps.DB)),
OperationPassword: operationPassword,
AgentOpenAPI: agentOpenAPI,
Refund: refundService,

View File

@@ -1,13 +1,10 @@
package bootstrap
import (
"context"
pollingPkg "github.com/break/junhong_cmp_fiber/internal/polling"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/config"
assetAutoRenewalApp "github.com/break/junhong_cmp_fiber/internal/application/assetautorenewal"
cardObservationApp "github.com/break/junhong_cmp_fiber/internal/application/cardobservation"
carrierThresholdApp "github.com/break/junhong_cmp_fiber/internal/application/carrierthreshold"
@@ -140,11 +137,9 @@ func initWorkerServices(stores *queue.WorkerStores, deps *WorkerDependencies) *q
// 运营商通道流量阈值达量判定嵌入流量观测事务:与卡流量事实同事务写周期锁与停机事件。
cardObservationService.SetChannelThresholdEvaluator(carrierThresholdService)
cardObservationService.SetCommandGrace(commandGraceProvider(deps.DB, deps.Redis, deps.Logger))
// 卡流量计量切换策略:启动时展开灰度名单为内存卡集合,API 与全部 Worker 必须使用一致配置。
if cfg := config.Get(); cfg != nil {
cardObservationService.SetTrafficCalcPolicy(
cardObservationApp.BuildTrafficCalcPolicy(context.Background(), deps.DB, cfg.TrafficCalc, deps.Logger), deps.Logger)
}
// 卡流量采集队列:读数先进每卡 Redis 队列,满足写入条件才写日快照;队列配置为受控系统配置。
cardObservationService.SetTrafficQueue(cardObservationInfra.NewTrafficQueue(deps.Redis),
cardObservationApp.NewDefaultTrafficQueueSettingsProvider(deps.DB, deps.Redis, deps.Logger), deps.Logger)
// 激活前流量结算:与 API 进程一致,注入激活服务与订单服务。
preActivationSettler := cardObservationInfra.NewPreActivationSettler(deps.DB, deps.GatewayClient, cardObservationService, postgres.NewCarrierStore(deps.DB), deps.Logger)
activationService.SetPreActivationSettler(preActivationSettler)