Files
junhong_cmp_fiber/internal/infrastructure/cardobservation/best_effort.go
break 5c4d17e9fc
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 联调延期
2026-07-24 19:59:24 +08:00

222 lines
8.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package cardobservation
import (
"context"
"strconv"
"strings"
"time"
"github.com/google/uuid"
"go.uber.org/zap"
cardapp "github.com/break/junhong_cmp_fiber/internal/application/cardobservation"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
)
// BestEffortSeriesDispatcher 将读入口触发失败降级为中文安全日志。
type BestEffortSeriesDispatcher struct {
trigger *cardapp.SeriesTrigger
logger *zap.Logger
jobs chan cardapp.SeriesRequest
deviceJobs chan cardapp.DeviceCardsSeriesRequest
deviceControlJobs chan cardapp.DeviceControlSeriesRequest
realnameJobs chan cardapp.RealnameCapabilitySeriesRequest
deviceSimBindingStore *postgres.DeviceSimBindingStore
carrierStore *postgres.CarrierStore
}
const bestEffortSeriesQueueSize = 1024
// NewBestEffortSeriesDispatcher 创建读入口观测序列分发器。
func NewBestEffortSeriesDispatcher(trigger *cardapp.SeriesTrigger, logger *zap.Logger, deviceSimBindingStore *postgres.DeviceSimBindingStore, carrierStore *postgres.CarrierStore) *BestEffortSeriesDispatcher {
dispatcher := &BestEffortSeriesDispatcher{
trigger: trigger, logger: logger,
jobs: make(chan cardapp.SeriesRequest, bestEffortSeriesQueueSize),
deviceJobs: make(chan cardapp.DeviceCardsSeriesRequest, bestEffortSeriesQueueSize),
deviceControlJobs: make(chan cardapp.DeviceControlSeriesRequest, bestEffortSeriesQueueSize),
realnameJobs: make(chan cardapp.RealnameCapabilitySeriesRequest, bestEffortSeriesQueueSize),
deviceSimBindingStore: deviceSimBindingStore, carrierStore: carrierStore,
}
go dispatcher.run()
return dispatcher
}
// DispatchDeviceControl 在后台解析设备控制前后的相关卡,避免增加原操作响应耗时。
func (d *BestEffortSeriesDispatcher) DispatchDeviceControl(_ context.Context, request cardapp.DeviceControlSeriesRequest) {
if d == nil || d.trigger == nil || request.DeviceID == 0 {
return
}
request.Request = normalizeRequest(request.Request)
select {
case d.deviceControlJobs <- request:
default:
d.logFailure(request.Request, "", false, nil, "后台设备控制观测队列已满")
}
}
// Dispatch 尝试创建或合并序列,失败不向原查询调用方返回。
func (d *BestEffortSeriesDispatcher) Dispatch(_ context.Context, request cardapp.SeriesRequest) {
if d == nil || d.trigger == nil {
return
}
request = normalizeRequest(request)
select {
case d.jobs <- request:
default:
d.logFailure(request, "", false, nil, "后台观测队列已满")
}
}
// DispatchDeviceCards 在后台展开设备当前有效绑定卡,避免请求协程逐卡查询数据库。
func (d *BestEffortSeriesDispatcher) DispatchDeviceCards(_ context.Context, request cardapp.DeviceCardsSeriesRequest) {
if d == nil || d.trigger == nil || request.DeviceID == 0 || d.deviceSimBindingStore == nil {
return
}
request.Request = normalizeRequest(request.Request)
select {
case d.deviceJobs <- request:
default:
d.logFailure(request.Request, "", false, nil, "后台设备观测队列已满")
}
}
// DispatchRealnameWithCapability 在后台读取运营商实名能力none 能力不触发观测。
func (d *BestEffortSeriesDispatcher) DispatchRealnameWithCapability(_ context.Context, request cardapp.RealnameCapabilitySeriesRequest) {
if d == nil || d.trigger == nil || request.CarrierID == 0 || d.carrierStore == nil {
return
}
request.Request = normalizeRequest(request.Request)
select {
case d.realnameJobs <- request:
default:
d.logFailure(request.Request, "", false, nil, "后台实名观测队列已满")
}
}
func (d *BestEffortSeriesDispatcher) run() {
for {
select {
case request := <-d.jobs:
d.triggerRequest(request)
case request := <-d.deviceJobs:
d.expandDeviceRequest(request)
case request := <-d.deviceControlJobs:
d.expandDeviceControlRequest(request)
case request := <-d.realnameJobs:
d.expandRealnameRequest(request)
}
}
}
func (d *BestEffortSeriesDispatcher) expandDeviceControlRequest(request cardapp.DeviceControlSeriesRequest) {
deviceRequest := request.Request
deviceRequest.ResourceType = constants.CardObservationResourceTypeDevice
deviceRequest.ResourceID = strconv.FormatUint(uint64(request.DeviceID), 10)
deviceRequest.SyncType = constants.CardObservationSyncTypeDeviceInfo
deviceRequest.ExpectedValue = strings.TrimSpace(request.TargetICCID)
d.triggerRequest(deviceRequest)
if strings.TrimSpace(request.TargetICCID) == "" {
for _, cardID := range request.BoundCardIDs {
d.triggerCardControlRequest(request.Request, cardID, constants.CardObservationSyncTypeNetwork)
}
return
}
d.triggerCardControlRequest(request.Request, request.SourceCardID, constants.CardObservationSyncTypeNetwork)
if request.TargetCardID != request.SourceCardID {
d.triggerCardControlRequest(request.Request, request.TargetCardID, constants.CardObservationSyncTypeNetwork)
}
if request.IncludeTargetTraffic {
d.triggerCardControlRequest(request.Request, request.TargetCardID, constants.CardObservationSyncTypeTraffic)
}
}
func (d *BestEffortSeriesDispatcher) triggerCardControlRequest(base cardapp.SeriesRequest, cardID uint, syncType string) {
if cardID == 0 {
return
}
base.ResourceType = constants.CardObservationResourceTypeCard
base.ResourceID = strconv.FormatUint(uint64(cardID), 10)
base.SyncType = syncType
base.ExpectedValue = ""
d.triggerRequest(base)
}
func (d *BestEffortSeriesDispatcher) triggerRequest(request cardapp.SeriesRequest) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
seriesID, merged, err := d.trigger.Trigger(ctx, request)
cancel()
if err != nil {
d.logFailure(request, seriesID, merged, err, "后台观测序列触发失败")
}
}
func (d *BestEffortSeriesDispatcher) expandDeviceRequest(request cardapp.DeviceCardsSeriesRequest) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
bindings, err := d.deviceSimBindingStore.ListByDeviceID(ctx, request.DeviceID)
cancel()
if err != nil {
d.logFailure(request.Request, "", false, err, "查询设备有效绑定卡失败")
return
}
for _, binding := range bindings {
if binding == nil || binding.IotCardID == 0 {
continue
}
cardRequest := request.Request
cardRequest.ResourceType = constants.CardObservationResourceTypeCard
cardRequest.ResourceID = strconv.FormatUint(uint64(binding.IotCardID), 10)
d.triggerRequest(cardRequest)
}
}
func (d *BestEffortSeriesDispatcher) expandRealnameRequest(request cardapp.RealnameCapabilitySeriesRequest) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
carrier, err := d.carrierStore.GetByID(ctx, request.CarrierID)
cancel()
if err != nil {
d.logFailure(request.Request, "", false, err, "查询运营商实名能力失败")
return
}
if carrier.RealnameLinkType == constants.RealnameLinkTypeNone {
return
}
d.triggerRequest(request.Request)
}
func normalizeRequest(request cardapp.SeriesRequest) cardapp.SeriesRequest {
request.RequestID = strings.TrimSpace(request.RequestID)
request.CorrelationID = strings.TrimSpace(request.CorrelationID)
if request.RequestID == "" && request.CorrelationID == "" {
request.RequestID = uuid.NewString()
request.CorrelationID = request.RequestID
} else if request.RequestID == "" {
request.RequestID = request.CorrelationID
} else if request.CorrelationID == "" {
request.CorrelationID = request.RequestID
}
return request
}
func (d *BestEffortSeriesDispatcher) logFailure(request cardapp.SeriesRequest, seriesID string, merged bool, err error, message string) {
if d.logger == nil {
return
}
fields := []zap.Field{
zap.String("scene", request.Scene),
zap.String("resource_type", request.ResourceType),
zap.String("resource_id", request.ResourceID),
zap.String("sync_type", request.SyncType),
zap.String("series_id", seriesID),
zap.Bool("merged", merged),
zap.String("request_id", request.RequestID),
zap.String("correlation_id", request.CorrelationID),
}
if err != nil {
fields = append(fields, zap.Error(err))
}
d.logger.Warn(message+",已保持原查询结果", fields...)
}
var _ cardapp.BestEffortSeriesDispatcher = (*BestEffortSeriesDispatcher)(nil)