Files
junhong_cmp_fiber/internal/task/ingest_message.go
邹进 4967f4df43
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
feat: 卡实名时间以上游 archiveTime 为准并记录实名时间历史,新增二次实名批次
2026-11-08 19:12:46 +09:00

158 lines
5.6 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 task
import (
"context"
"encoding/json"
"time"
carddomain "github.com/break/junhong_cmp_fiber/internal/domain/cardobservation"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
)
// 入库消息的观测类型。
const (
ingestKindTraffic = "traffic"
ingestKindNetwork = "network"
ingestKindRealname = "realname"
)
// ingestRawResponseLimit 是入库消息携带原始响应的上限:比采集记录保存上限多 1 字节,使"超过 4KB 截断"判断不变,同时控制消息大小。
const ingestRawResponseLimit = 4096 + 1
// IngestMessage 是一次轮询观测的入库消息(topic card.ingest)。采集时间取网关返回时刻,入库全程按它归属日期与判断新旧。
type IngestMessage struct {
Kind string `json:"kind"`
CardID uint `json:"card_id"`
CarrierID uint `json:"carrier_id"`
ICCID string `json:"iccid"`
ObservationID string `json:"observation_id"`
ObservedAt time.Time `json:"observed_at"`
Source string `json:"source"`
Scene string `json:"scene"`
CorrelationID string `json:"correlation_id"`
// TaskType 是发起观测的轮询任务类型,入库时用于审计上下文与流量评估标记。
TaskType string `json:"task_type"`
Traffic *IngestTrafficPayload `json:"traffic,omitempty"`
Network *IngestNetworkPayload `json:"network,omitempty"`
Realname *IngestRealnamePayload `json:"realname,omitempty"`
Raw *carddomain.GatewayRaw `json:"raw,omitempty"`
PublishedAt time.Time `json:"published_at"`
}
// IngestTrafficPayload 是流量观测内容。
type IngestTrafficPayload struct {
ReadingMB float64 `json:"reading_mb"`
ReadingResult string `json:"reading_result"`
ResetDay int `json:"reset_day"`
DurationMS int `json:"duration_ms"`
}
// IngestNetworkPayload 是网络状态观测内容。
type IngestNetworkPayload struct {
Status string `json:"status"`
Extend string `json:"extend"`
IMEI string `json:"imei"`
}
// IngestRealnamePayload 是实名观测内容。
type IngestRealnamePayload struct {
Verified bool `json:"verified"`
UpstreamRealnameAt *time.Time `json:"upstream_realname_at,omitempty"`
}
// metadata 还原观测元数据。
func (m IngestMessage) metadata() carddomain.ObservationMetadata {
return carddomain.ObservationMetadata{
ObservationID: m.ObservationID, Source: m.Source, Scene: m.Scene,
ObservedAt: m.ObservedAt, CorrelationID: m.CorrelationID,
}
}
// valid 校验消息必需字段。
func (m IngestMessage) valid() bool {
if m.CardID == 0 || m.ObservationID == "" || m.ObservedAt.IsZero() {
return false
}
switch m.Kind {
case ingestKindTraffic:
return m.Traffic != nil
case ingestKindNetwork:
return m.Network != nil
case ingestKindRealname:
return m.Realname != nil
}
return false
}
// CollectionMessage 是一条采集记录消息(topic card.collection),成功记录与失败记录二选一。
type CollectionMessage struct {
Success *model.CardTrafficCollection `json:"success,omitempty"`
Failure *model.CardTrafficCollectionFailure `json:"failure,omitempty"`
}
// MessagePublisher 发布 NSQ 消息。
type MessagePublisher interface {
Publish(topic string, body []byte) error
}
// IngestPublisher 发布入库消息与采集记录消息。
type IngestPublisher struct {
producer MessagePublisher
}
// NewIngestPublisher 创建入库消息发布器;producer 为 nil 时所有发布都返回错误,调用方降级同步入库。
func NewIngestPublisher(producer MessagePublisher) *IngestPublisher {
return &IngestPublisher{producer: producer}
}
// PublishIngest 发布一条入库消息。
func (p *IngestPublisher) PublishIngest(_ context.Context, msg IngestMessage) error {
if p == nil || p.producer == nil {
return errors.New(errors.CodeInternalError, "入库消息发布器未配置")
}
if msg.Raw != nil && len(msg.Raw.Response) > ingestRawResponseLimit {
trimmed := *msg.Raw
trimmed.Response = trimmed.Response[:ingestRawResponseLimit]
msg.Raw = &trimmed
}
msg.PublishedAt = time.Now()
body, err := json.Marshal(msg)
if err != nil {
return errors.Wrap(errors.CodeInternalError, err, "编码入库消息失败")
}
if err := p.producer.Publish(constants.NSQCardIngestTopic, body); err != nil {
return errors.Wrap(errors.CodeInternalError, err, "发布入库消息失败")
}
return nil
}
// PublishCollection 发布一条采集记录消息,实现卡观测服务的 CollectionSink。
func (p *IngestPublisher) PublishCollection(_ context.Context, success *model.CardTrafficCollection, failure *model.CardTrafficCollectionFailure) error {
if p == nil || p.producer == nil {
return errors.New(errors.CodeInternalError, "采集记录消息发布器未配置")
}
body, err := json.Marshal(CollectionMessage{Success: success, Failure: failure})
if err != nil {
return errors.Wrap(errors.CodeInternalError, err, "编码采集记录消息失败")
}
if err := p.producer.Publish(constants.NSQCardCollectionTopic, body); err != nil {
return errors.Wrap(errors.CodeInternalError, err, "发布采集记录消息失败")
}
return nil
}
// ingestRetryDelay 返回第 attempts 次投递失败后的重投延迟;超过序列长度取最后一项,不设次数上限。
func ingestRetryDelay(attempts uint16) time.Duration {
delays := constants.PollingIngestRetryDelays
index := int(attempts) - 1
if index < 0 {
index = 0
}
if index >= len(delays) {
index = len(delays) - 1
}
return delays[index]
}