2bec257289
iotlcm-worker-svr:异步执行中枢(RabbitMQ 消费者 + 全量同步编排)。 - Go module:iotlcm-worker-svr → git.5iot.com/iotlcm-public/iotlcm-worker-svr(214 文件) - 凭据与内部标识脱敏:MySQL/Redis 凭据、私网与生产 IP、内网域名、 库名 xiujie_admin → iotlcm、哨兵名占位 - 159 处真实手机号 → 合法 11 位编造号(保留号段与后 4 位,维持互异, 不破坏「校验 MSISDN 格式」等测试断言) - 移除内部资产:CI 触发器、Jenkins 触发测试文档、预演门禁与镜像 digest 记录 - Dockerfile 改用公共基础镜像(golang:1.25-alpine → alpine:3.23), 移除写死的 git 凭据 - README 重写(架构/全量同步模型/配置/已知失败),补 Apache-2.0 LICENSE,LF 行尾 + .gitattributes 已知:3 个测试包在源仓同一提交上同样失败(client/upstream、 service/allocate、service/inbound),属既有缺陷,本仓未引入亦未修复。
147 lines
6.4 KiB
Go
147 lines
6.4 KiB
Go
package contract
|
|
|
|
import (
|
|
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/fullsync/supply"
|
|
"time"
|
|
)
|
|
|
|
// RunMeta retains every existing queue metadata field and JSON tag. The
|
|
// optional fields are additive; zero extension fields are legal on old runs.
|
|
type RunMeta struct {
|
|
SupplyObservation *supply.Snapshot `json:"supplyObservation,omitempty"`
|
|
RunID string `json:"runId"`
|
|
Operation string `json:"operation"`
|
|
QueryMonth string `json:"queryMonth"`
|
|
Status RunStatus `json:"status"`
|
|
ProducerDone bool `json:"producerDone"`
|
|
CurrentRound int `json:"currentRound"`
|
|
ActiveCount int64 `json:"activeCount"`
|
|
InflightCount int64 `json:"inflightCount"`
|
|
NextRoundCount int64 `json:"nextRoundCount"`
|
|
SuccessCount int64 `json:"successCount"`
|
|
FailedCount int64 `json:"failedCount"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
ProducerCompletedAt time.Time `json:"producerCompletedAt"`
|
|
CompletedAt time.Time `json:"completedAt"`
|
|
|
|
SyncType SyncType `json:"syncType,omitempty"`
|
|
SourceRunID string `json:"sourceRunId,omitempty"`
|
|
ReplayCycle int `json:"replayCycle,omitempty"`
|
|
TriggerSource string `json:"triggerSource,omitempty"`
|
|
ScannedCount int64 `json:"scannedCount,omitempty"`
|
|
EnqueuedCount int64 `json:"enqueuedCount,omitempty"`
|
|
UpstreamAttemptCount int64 `json:"upstreamAttemptCount,omitempty"`
|
|
BatchCount int64 `json:"batchCount,omitempty"`
|
|
MaxRound int `json:"maxRound,omitempty"`
|
|
PendingFailureCount int64 `json:"pendingFailureCount,omitempty"`
|
|
StartedAt *time.Time `json:"startedAt,omitempty"`
|
|
ScanCompletedAt *time.Time `json:"scanCompletedAt,omitempty"`
|
|
ArchiveStatus ArchiveStatus `json:"archiveStatus,omitempty"`
|
|
ArchiveVersion int `json:"archiveVersion,omitempty"`
|
|
ArchivePath string `json:"archivePath,omitempty"`
|
|
ArchiveChecksum string `json:"archiveChecksum,omitempty"`
|
|
ArchivedAt *time.Time `json:"archivedAt,omitempty"`
|
|
ReplayDisposition ReplayDisposition `json:"replayDisposition,omitempty"`
|
|
// Scope fields mirror zset.RunMeta. ScopeChannels == 0 means a full sync.
|
|
ScopeChannels int `json:"scopeChannels,omitempty"`
|
|
ScopeDigest string `json:"scopeDigest,omitempty"`
|
|
ScopeChannelsSample string `json:"scopeChannelsSample,omitempty"`
|
|
RoundStats []RoundStats `json:"rounds,omitempty"`
|
|
}
|
|
|
|
func (m RunMeta) Validate() error {
|
|
if err := ValidateID(m.RunID); err != nil {
|
|
return err
|
|
}
|
|
if m.Status != "" && !m.Status.Valid() {
|
|
return invalid("unknown run status")
|
|
}
|
|
if m.SyncType != "" && !m.SyncType.Valid() {
|
|
return invalid("unknown sync type")
|
|
}
|
|
if m.ArchiveStatus != "" && !m.ArchiveStatus.Valid() {
|
|
return invalid("unknown archive status")
|
|
}
|
|
if m.ReplayDisposition != "" && !m.ReplayDisposition.Valid() {
|
|
return invalid("unknown replay disposition")
|
|
}
|
|
if m.SourceRunID != "" {
|
|
if err := ValidateID(m.SourceRunID); err != nil {
|
|
return err
|
|
}
|
|
if m.SourceRunID == m.RunID {
|
|
return invalid("a run cannot replay itself")
|
|
}
|
|
}
|
|
if m.SyncType == SyncReplay && (m.SourceRunID == "" || m.ReplayCycle < 1) {
|
|
return invalid("replay requires sourceRunId and a positive replayCycle")
|
|
}
|
|
if m.SyncType == SyncNormal && (m.SourceRunID != "" || m.ReplayCycle != 0) {
|
|
return invalid("normal runs cannot have replay provenance")
|
|
}
|
|
if m.MaxRound > 0 && m.CurrentRound > m.MaxRound {
|
|
return invalid("currentRound exceeds maxRound")
|
|
}
|
|
return nonnegative(int64(m.CurrentRound), int64(m.MaxRound), int64(m.ReplayCycle), int64(m.ArchiveVersion), m.ActiveCount, m.InflightCount, m.NextRoundCount, m.SuccessCount, m.FailedCount, m.ScannedCount, m.EnqueuedCount, m.UpstreamAttemptCount, m.BatchCount, m.PendingFailureCount, int64(m.ScopeChannels))
|
|
}
|
|
|
|
// ValidateAccounting is opt-in for runs whose extended counters are known.
|
|
// Do not apply it to legacy metadata with a missing enqueuedCount. Inflight is
|
|
// a subset of active and is never added to the completion equation.
|
|
func (m RunMeta) ValidateAccounting() error {
|
|
if err := m.Validate(); err != nil {
|
|
return err
|
|
}
|
|
remaining := m.EnqueuedCount
|
|
for _, count := range []int64{m.SuccessCount, m.FailedCount, m.ActiveCount, m.NextRoundCount} {
|
|
if count > remaining {
|
|
return invalid("enqueuedCount does not balance run outcomes")
|
|
}
|
|
remaining -= count
|
|
}
|
|
if remaining != 0 || m.InflightCount > m.ActiveCount || m.PendingFailureCount > m.FailedCount {
|
|
return invalid("inconsistent run counters")
|
|
}
|
|
if m.Status.Terminal() && (!m.ProducerDone || m.ActiveCount != 0 || m.NextRoundCount != 0) {
|
|
return invalid("terminal run still has producer or queue work")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RoundStats contains only bounded aggregate fields. It must never grow with
|
|
// the number of cards, channels, task IDs, upstream codes, or error messages.
|
|
type RoundStats struct {
|
|
RunID string `json:"runId"`
|
|
Round int `json:"round"`
|
|
EnqueuedCount int64 `json:"enqueuedCount"`
|
|
ActiveCount int64 `json:"activeCount"`
|
|
NextRoundCount int64 `json:"nextRoundCount"`
|
|
SuccessCount int64 `json:"successCount"`
|
|
FailedCount int64 `json:"failedCount"`
|
|
UpstreamAttemptCount int64 `json:"upstreamAttemptCount"`
|
|
BatchCount int64 `json:"batchCount"`
|
|
StartedAt *time.Time `json:"startedAt,omitempty"`
|
|
CompletedAt *time.Time `json:"completedAt,omitempty"`
|
|
ErrorCounts ErrorCounts `json:"errorCounts"`
|
|
}
|
|
|
|
func (s RoundStats) Validate() error {
|
|
if err := ValidateID(s.RunID); err != nil {
|
|
return err
|
|
}
|
|
if s.Round < 1 {
|
|
return invalid("round must be positive")
|
|
}
|
|
if err := nonnegative(s.EnqueuedCount, s.ActiveCount, s.NextRoundCount, s.SuccessCount, s.FailedCount, s.UpstreamAttemptCount, s.BatchCount); err != nil {
|
|
return err
|
|
}
|
|
_, err := s.ErrorCounts.Normalized()
|
|
return err
|
|
}
|
|
|
|
type RunView struct {
|
|
Source RunSource `json:"source"`
|
|
Run RunMeta `json:"run"`
|
|
Rounds []RoundStats `json:"rounds,omitempty"`
|
|
}
|