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 联调延期
222 lines
8.2 KiB
Go
222 lines
8.2 KiB
Go
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)
|