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),属既有缺陷,本仓未引入亦未修复。
117 lines
4.2 KiB
Go
117 lines
4.2 KiB
Go
package contract
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
DefaultFlowPrefix = "flow:"
|
|
DefaultSimStatusPrefix = "sim-status:"
|
|
)
|
|
|
|
// Keys is a naming contract only. It does not create indexes or storage.
|
|
// Existing run names are byte-for-byte compatible with zset.Store. Global
|
|
// run/archive indexes are outside run hash slots and cannot join run-local Lua.
|
|
type Keys struct{ prefix string }
|
|
|
|
func NewKeys(prefix string) (Keys, error) {
|
|
if strings.TrimSpace(prefix) == "" {
|
|
prefix = DefaultFlowPrefix
|
|
}
|
|
if strings.ContainsAny(prefix, "{}") {
|
|
return Keys{}, invalid("prefix must not contain Redis hash tags")
|
|
}
|
|
return Keys{prefix: prefix}, nil
|
|
}
|
|
|
|
// The zero value uses the legacy flow prefix, just like NewKeys("").
|
|
func (k Keys) Prefix() string {
|
|
if k.prefix == "" {
|
|
return DefaultFlowPrefix
|
|
}
|
|
return k.prefix
|
|
}
|
|
|
|
func (k Keys) runKey(runID, suffix string) (string, error) {
|
|
if err := ValidateID(runID); err != nil {
|
|
return "", err
|
|
}
|
|
return k.Prefix() + "{" + runID + "}:" + suffix, nil
|
|
}
|
|
|
|
func (k Keys) RunMetaKey(runID string) (string, error) { return k.runKey(runID, "meta") }
|
|
func (k Keys) RunChannelsKey(runID string) (string, error) { return k.runKey(runID, "channels") }
|
|
func (k Keys) TaskStateKey(runID string) (string, error) { return k.runKey(runID, "task-state") }
|
|
func (k Keys) FailedKey(runID string) (string, error) { return k.runKey(runID, "failed") }
|
|
func (k Keys) ArchiveStateKey(runID string) (string, error) { return k.runKey(runID, "archive") }
|
|
|
|
func (k Keys) roundKey(runID string, round int, suffix string) (string, error) {
|
|
if round < 1 {
|
|
return "", invalid("round must be positive")
|
|
}
|
|
return k.runKey(runID, fmt.Sprintf("round:%d:%s", round, suffix))
|
|
}
|
|
|
|
func (k Keys) RoundStatsKey(runID string, round int) (string, error) {
|
|
return k.roundKey(runID, round, "stats")
|
|
}
|
|
|
|
func (k Keys) roundChannelKey(runID string, round int, kind, chanKey string) (string, error) {
|
|
if strings.TrimSpace(chanKey) == "" {
|
|
return "", invalid("chanKey is empty")
|
|
}
|
|
return k.roundKey(runID, round, kind+":"+chanKey)
|
|
}
|
|
|
|
func (k Keys) RoundActiveKey(runID string, round int, chanKey string) (string, error) {
|
|
return k.roundChannelKey(runID, round, "active", chanKey)
|
|
}
|
|
|
|
func (k Keys) RoundWaitingKey(runID string, round int, chanKey string) (string, error) {
|
|
return k.roundChannelKey(runID, round, "waiting", chanKey)
|
|
}
|
|
|
|
func (k Keys) RoundLeaseKey(runID string, round int, chanKey string) (string, error) {
|
|
return k.roundChannelKey(runID, round, "lease", chanKey)
|
|
}
|
|
|
|
func (k Keys) FailedRunsKey() string { return k.Prefix() + "failed:runs" }
|
|
func (k Keys) ActiveRunsKey() string { return k.Prefix() + "runs:active" }
|
|
func (k Keys) AllRunsKey() string { return k.Prefix() + "runs:all" }
|
|
func (k Keys) PendingArchivesKey() string { return k.Prefix() + "archive:pending" }
|
|
|
|
// Replay action state, leases, idempotency reservations and pending index share
|
|
// a dedicated slot, allowing atomic acceptance/discovery. Source/target run
|
|
// writes remain in their own slots and require a recoverable replay protocol.
|
|
func (k Keys) replayKey(actionID, suffix string) (string, error) {
|
|
if err := ValidateID(actionID); err != nil {
|
|
return "", err
|
|
}
|
|
return k.Prefix() + "{replay}:action:" + actionID + ":" + suffix, nil
|
|
}
|
|
|
|
func (k Keys) ReplayActionKey(actionID string) (string, error) { return k.replayKey(actionID, "meta") }
|
|
func (k Keys) ReplayLeaseKey(actionID string) (string, error) { return k.replayKey(actionID, "lease") }
|
|
func (k Keys) PendingReplayActionsKey() string { return k.Prefix() + "{replay}:pending" }
|
|
|
|
// Hash the complete tuple so neither delimiter collisions nor arbitrary
|
|
// Idempotency-Key characters can change the Redis key or hash slot.
|
|
func (k Keys) ReplayIdempotencyKey(sourceRunID, idempotencyKey string) (string, error) {
|
|
if err := ValidateID(sourceRunID); err != nil {
|
|
return "", err
|
|
}
|
|
if err := ValidateIdempotencyKey(idempotencyKey); err != nil {
|
|
return "", err
|
|
}
|
|
data, err := json.Marshal([2]string{sourceRunID, idempotencyKey})
|
|
if err != nil {
|
|
return "", invalid("cannot encode idempotency scope: %v", err)
|
|
}
|
|
digest := sha256.Sum256(data)
|
|
return k.Prefix() + "{replay}:idempotency:" + hex.EncodeToString(digest[:]), nil
|
|
}
|