Files
iotlcm/internal/service/generator.go
T
MiFaZhan 4e2cd43a55 chore: 初始开源发布
iotlcm-onelink-mock:OneLink 挡板服务(本地测试替身)。

- Go module 迁移至 git.5iot.com/iotlcm-public/iotlcm-onelink-mock
- 修复同毫秒内多次生成卡导致 ICCID/IMSI 唯一约束冲突
- 补 Apache-2.0 LICENSE,README 重写并新增 docs/deploy.md
- 移除内部部署资产(CI 触发器 / 镜像推送脚本 / 指定环境部署文档)
- 测试凭据改为 demo_appid / demo_password,真实手机号与内网 IP 脱敏
- 统一 LF 行尾并加 .gitattributes
2026-10-22 15:08:27 +08:00

161 lines
5.7 KiB
Go

package service
import (
"fmt"
"strconv"
"sync"
"time"
)
const (
generatedChanKey = "channel_yd_mock"
generatedCardType = "1"
generatedPackageCode = "CMIOT-SD-500M-24M-D01"
generatedSupplierCode = "SUP_CMCC"
generatedMonthlyUsage = 18432.0
generatedDailyUsage = 614.4
generatedPoolUsageByte = 18874368.0
)
type inboundExportColumn struct {
Header string
Key string
}
var inboundExportColumns = []inboundExportColumn{
{Header: "ICCID*", Key: "ICCID"},
{Header: "MSISDN*", Key: "MSISDN"},
{Header: "IMSI*", Key: "IMSI"},
{Header: "IMEI", Key: "IMEI"},
{Header: "通道编号*", Key: "chanKey"},
{Header: "卡类型", Key: "cardType"},
{Header: "上游套餐编码", Key: "upPkgCode"},
{Header: "供应商编码", Key: "suppliersCode"},
{Header: "上游激活时间", Key: "upstreamActivationTime"},
{Header: "是否贴片转插拔", Key: "insertToPlugEnabled"},
{Header: "是否运营商达量断网", Key: "carrierTrafficCutoffEnabled"},
{Header: "协议到期时间", Key: "agreementExpiryDate"},
{Header: "是否有测试期", Key: "hasTestPeriod"},
{Header: "测试期天数", Key: "testPeriodDays"},
{Header: "测试期最晚过期日期", Key: "testPeriodLatestExpiryDate"},
{Header: "是否有沉默期", Key: "hasSilentPeriod"},
{Header: "沉默期天数", Key: "silentPeriodDays"},
{Header: "沉默期最晚过期日期", Key: "silentPeriodLatestExpiryDate"},
}
var inboundExportHeaders = func() []string {
headers := make([]string, 0, len(inboundExportColumns))
for _, column := range inboundExportColumns {
headers = append(headers, column.Header)
}
return headers
}()
// 生成序号分配器。
//
// 序号直接来自毫秒时间戳时,同一毫秒内的两次生成会得到完全相同的 seq,
// 进而生成重复的 ICCID/IMSI,触发 mock_t_sim_card 的唯一约束冲突
// (表现为「一键生成」连点两次或两个分组连续生成时报
// UNIQUE constraint failed: mock_t_sim_card.imsi)。
//
// 这里用进程内单调递增的上界保证 seq 严格递增:时间戳只决定起点,
// 真正的唯一性由 nextGeneratedSeq 保证。
var (
generatedSeqMu sync.Mutex
generatedSeqLast int64
)
// nextGeneratedSeq 返回严格大于上一次分配值的序号。
func nextGeneratedSeq(candidate int64) int64 {
generatedSeqMu.Lock()
defer generatedSeqMu.Unlock()
if candidate <= generatedSeqLast {
candidate = generatedSeqLast + 1
}
generatedSeqLast = candidate
return candidate
}
func GenerateSIMCards(batchTime time.Time, count int) []SIMCard {
if count <= 0 {
return []SIMCard{}
}
cards := make([]SIMCard, 0, count)
base := batchTime.UnixMilli()
dbTime := batchTime.Format("2006-01-02 15:04:05")
for i := 0; i < count; i++ {
seq := nextGeneratedSeq(base + int64(i+1))
card := SIMCard{
MSISDN: fmt.Sprintf("141%08d", seq%100000000),
ICCID: fmt.Sprintf("898604%014d", seq%100000000000000),
IMSI: fmt.Sprintf("46004%010d", seq%10000000000),
IMEI: fmt.Sprintf("86%013d", seq%10000000000000),
Remark: sourceRemark("CMCC", generatedPackageCode, generatedPackageCode),
CardStatus: "2",
LastChangeDate: dbTime,
StopReason: "000000000000",
FrozenStatus: "0",
FrozenOperTime: batchTime.Format("20060102150405"),
OpenDate: dbTime,
ActiveDate: dbTime,
OnOffStatus: "1",
SessionStatus: "01",
IP: fmt.Sprintf("10.%d.%d.%d", (seq/10000)%255, (seq/100)%255, seq%255),
IPv6Prefix: "",
IPv6: "",
RAT: "6",
CardBindStatus: "1",
RegionLimitStatus: "0",
AreaRestrictType: "1",
RestrictedArea: "110000,310000,440000",
MonthUsageKB: generatedMonthlyUsage,
PoolUsageBytes: generatedPoolUsageByte,
DailyBaseKB: generatedDailyUsage,
MonthlyBaseKB: generatedMonthlyUsage,
GrowthPerMinuteKB: 0,
LastUsageUpdateTime: batchTime.Format(time.RFC3339),
APNs: []APN{
{Name: "CMIOT", Status: "1", UsageBytes: generatedPoolUsageByte, ModifyTime: batchTime.Format("20060102150405"), ServiceUsageState: "1", PCCs: []PCC{{ServiceCode: "default", Name: "APN 缺省流量", PolicyStatus: "2", ServiceUsageState: "1", ModifyTime: batchTime.Format("20060102150405")}}},
},
}
cards = append(cards, card)
}
return cards
}
func inboundExportRow(card SIMCard) map[string]string {
activation := parseCardTime(card.ActiveDate)
return map[string]string{
"ICCID": card.ICCID,
"MSISDN": card.MSISDN,
"IMSI": card.IMSI,
"IMEI": card.IMEI,
"chanKey": generatedChanKey,
"cardType": generatedCardType,
"upPkgCode": generatedPackageCode,
"suppliersCode": generatedSupplierCode,
"upstreamActivationTime": activation.Format("2006-01-02 15:04:05"),
"insertToPlugEnabled": "true",
"carrierTrafficCutoffEnabled": "false",
"agreementExpiryDate": activation.AddDate(1, 0, 0).Format("2006-01-02"),
"hasTestPeriod": "true",
"testPeriodDays": "30",
"testPeriodLatestExpiryDate": activation.AddDate(0, 0, 30).Format("2006-01-02"),
"hasSilentPeriod": "false",
"silentPeriodDays": "",
"silentPeriodLatestExpiryDate": "",
}
}
func parseCardTime(value string) time.Time {
for _, layout := range []string{"2006-01-02 15:04:05", time.RFC3339, time.RFC3339Nano} {
if t, err := time.ParseInLocation(layout, value, time.Local); err == nil {
return t
}
}
if unixMilli, err := strconv.ParseInt(value, 10, 64); err == nil {
return time.UnixMilli(unixMilli)
}
return time.Now()
}