4e2cd43a55
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
161 lines
5.7 KiB
Go
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()
|
|
}
|