chore: 初始开源发布

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),属既有缺陷,本仓未引入亦未修复。
This commit is contained in:
MiFaZhan
2026-10-22 16:37:56 +08:00
commit 2bec257289
361 changed files with 71524 additions and 0 deletions
+157
View File
@@ -0,0 +1,157 @@
package worker
import (
"testing"
"github.com/gogf/gf/v2/os/gcfg"
)
// loadImeiTestConfig loads the real shipped config file.
//
// A plain g.Cfg() lookup is not enough here: the test binary does not resolve
// `config_default.yaml` (GoFrame searches for a file literally named
// `config`), so every MustGet would silently fall back to the default argument
// and a typo in the YAML key would go undetected. Loading the adapter
// explicitly makes the assertions below test the file, not the defaults.
func loadImeiTestConfig(t *testing.T) *gcfg.Config {
t.Helper()
adapter, err := gcfg.NewAdapterFile("config_default.yaml")
if err != nil {
t.Fatalf("load config_default.yaml: %v", err)
}
// Resolve relative to the package directory (internal/worker) up to the
// repository root, where config/ lives.
adapter.AddPath("../../config")
return gcfg.NewWithAdapter(adapter)
}
// TestImeiConfigFileResolves 验证 imeiFullSync 配置段真实存在于发布配置中,
// 且与状态链路结构对齐。键名拼错或段落缺失会让链路静默使用零值。
func TestImeiConfigFileResolves(t *testing.T) {
config := loadImeiTestConfig(t)
// enabled 必须存在且为 false:未完成预演验证前不得默认开启。
enabled, err := config.Get(nil, "imeiFullSync.enabled")
if err != nil {
t.Fatalf("读取 imeiFullSync.enabled 失败(配置段可能缺失): %v", err)
}
if enabled.Bool() {
t.Fatal("imeiFullSync.enabled 必须为 false(预演验证后再开)")
}
prefix, err := config.Get(nil, "imeiFullSync.redisKeyPrefix")
if err != nil || prefix.String() != "imei:" {
t.Fatalf("redisKeyPrefix = %q (err=%v), want imei:", prefix.String(), err)
}
// 三条链路的前缀必须互不相同,否则 Run 命名空间会串场。
for _, other := range []struct {
key string
want string
}{
{"trafficUsageFullSync.redisKeyPrefix", "flow:"},
{"simStatusQueueSync.redisKeyPrefix", "sim-status:"},
} {
value, err := config.Get(nil, other.key)
if err != nil {
t.Fatalf("读取 %s 失败: %v", other.key, err)
}
if value.String() == prefix.String() {
t.Fatalf("imei 前缀 %q 与 %s (%q) 重复", prefix.String(), other.key, value.String())
}
}
// 关键数值项逐条断言(对齐状态链路的键名)。
checks := []struct {
key string
want string
}{
{"imeiFullSync.producer.pageSize", "1000"},
{"imeiFullSync.producer.runLockKey", "imei:producer:active"},
{"imeiFullSync.producer.runLockTTL", "5m"},
{"imeiFullSync.consumer.lockKey", "imei:consumer:active"},
{"imeiFullSync.consumer.claimBatch", "100"},
{"imeiFullSync.consumer.mobileConcurrency", "100"},
{"imeiFullSync.consumer.telecomConcurrency", "8"},
{"imeiFullSync.consumer.networkRetries", "2"},
{"imeiFullSync.consumer.chanClientTimeout", "30s"},
{"imeiFullSync.retry.maxRounds", "5"},
{"imeiFullSync.retry.failureRetention", "720h"},
// 失败池告警阀值:漏配会静默退化为默认 50000,但键名拼错时
// 我们希望测试直接报错,而不是让链路跑在一个意外阀值上。
{"imeiFullSync.consumer.failurePoolAlertThreshold", "50000"},
}
for _, check := range checks {
value, err := config.Get(nil, check.key)
if err != nil {
t.Fatalf("读取 %s 失败: %v", check.key, err)
}
if value.String() != check.want {
t.Fatalf("%s = %q, want %q", check.key, value.String(), check.want)
}
}
// consumer.enabled 与 producer 同为 false:链路默认不跑。
consumerEnabled, err := config.Get(nil, "imeiFullSync.consumer.enabled")
if err != nil {
t.Fatalf("读取 consumer.enabled 失败: %v", err)
}
if consumerEnabled.Bool() {
t.Fatal("imeiFullSync.consumer.enabled 必须为 false")
}
}
// TestImeiTemplateQPSInConfigFile 模板限速必须覆盖生产实际存在的移动族模板,
// 否则会退回通用 1.5 QPS(255,976 张卡将耗时数天)。
func TestImeiTemplateQPSInConfigFile(t *testing.T) {
config := loadImeiTestConfig(t)
for _, tempKey := range []string{
"sim_yd_v5",
"sim_yd_mock_v5",
"sim_onelink_tengyu_v1",
"sim_onelink_xuzhou_mobile_v1",
"sim_onelink_lelian_v1",
"sim_onelink_beixin_v1",
} {
value, err := config.Get(nil, "imeiFullSync.consumer.templateQPS."+tempKey)
if err != nil {
t.Fatalf("templateQPS 缺少 %s(会退回通用 1.5 QPS): %v", tempKey, err)
}
if value.Float64() != 100 {
t.Fatalf("templateQPS.%s = %v, want 100", tempKey, value.Float64())
}
}
// 电信额度与状态/流量链路共享,必须压低。
telecom, err := config.Get(nil, "imeiFullSync.consumer.templateQPS.sim_dx_v1")
if err != nil {
t.Fatalf("templateQPS 缺少 sim_dx_v1: %v", err)
}
if telecom.Float64() != 5 {
t.Fatalf("templateQPS.sim_dx_v1 = %v, want 5(电信额度共享,必须压低)", telecom.Float64())
}
}
// TestFailurePoolAlertConfiguredForEveryChain 三条全量同步链路都要有失败池
// 告警阀值。漏配某一条时该链路会退化为「无告警」,而这正是内存问题最需要
// 被看见的地方,所以这里显式要求三条都在配置文件里出现。
func TestFailurePoolAlertConfiguredForEveryChain(t *testing.T) {
config := loadImeiTestConfig(t)
chains := []string{
"trafficUsageFullSync",
"simStatusQueueSync",
"imeiFullSync",
}
for _, chain := range chains {
key := chain + ".consumer.failurePoolAlertThreshold"
value, err := config.Get(nil, key)
if err != nil {
t.Fatalf("%s 未配置(该链路将无失败池告警): %v", key, err)
}
if value.Int64() <= 0 {
t.Fatalf("%s = %d,应为正数(0 表示关闭告警)", key, value.Int64())
}
}
}