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),属既有缺陷,本仓未引入亦未修复。
210 lines
6.2 KiB
Go
210 lines
6.2 KiB
Go
package renewchange
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
"github.com/gogf/gf/v2/os/gtime"
|
|
|
|
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consts"
|
|
rc "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/repository/renewchange"
|
|
)
|
|
|
|
type subscriptionValidation struct {
|
|
ICCID string
|
|
MSISDN string
|
|
IMSI string
|
|
TenantID string
|
|
CustomerDeptID int64
|
|
PackageCode string
|
|
SourceBatchNo string
|
|
}
|
|
|
|
// resolveCurrentSubscriptions selects the current package fact per ICCID.
|
|
// It keeps future pending subscriptions for change-package cleanup, while
|
|
// allowing renew to append after the farthest pending subscription.
|
|
func resolveCurrentSubscriptions(rows []*rc.SimCardBindPkg, iccids []string) (map[string]*rc.SimCardBindPkg, map[string][]*rc.SimCardBindPkg, error) {
|
|
grouped := make(map[string][]*rc.SimCardBindPkg, len(iccids))
|
|
for _, row := range rows {
|
|
if row == nil {
|
|
continue
|
|
}
|
|
iccid := strings.TrimSpace(row.Iccid)
|
|
if iccid == "" {
|
|
continue
|
|
}
|
|
grouped[iccid] = append(grouped[iccid], row)
|
|
}
|
|
|
|
current := make(map[string]*rc.SimCardBindPkg, len(iccids))
|
|
future := make(map[string][]*rc.SimCardBindPkg, len(iccids))
|
|
for _, rawICCID := range iccids {
|
|
iccid := strings.TrimSpace(rawICCID)
|
|
selected, pending, err := selectCurrentSubscription(grouped[iccid], iccid)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
current[iccid] = selected
|
|
future[iccid] = pending
|
|
}
|
|
return current, future, nil
|
|
}
|
|
|
|
func selectCurrentSubscription(rows []*rc.SimCardBindPkg, iccid string) (*rc.SimCardBindPkg, []*rc.SimCardBindPkg, error) {
|
|
var (
|
|
effective *rc.SimCardBindPkg
|
|
pending []*rc.SimCardBindPkg
|
|
expired []*rc.SimCardBindPkg
|
|
)
|
|
for _, row := range rows {
|
|
if row == nil || strings.TrimSpace(row.Iccid) != strings.TrimSpace(iccid) {
|
|
continue
|
|
}
|
|
if consts.IsAddPackageType(row.PackageType) {
|
|
continue
|
|
}
|
|
switch row.EffectiveStatus {
|
|
case consts.EffectiveStatusEffective:
|
|
if effective != nil {
|
|
return nil, nil, gerror.Newf("同一张卡存在多个生效中的套餐订购记录:%s", iccid)
|
|
}
|
|
effective = row
|
|
case consts.EffectiveStatusPending:
|
|
pending = append(pending, row)
|
|
case consts.EffectiveStatusExpired:
|
|
expired = append(expired, row)
|
|
}
|
|
}
|
|
sortPendingSubscriptions(pending)
|
|
if effective != nil {
|
|
return effective, pending, nil
|
|
}
|
|
if len(pending) > 0 {
|
|
return pending[0], pending[1:], nil
|
|
}
|
|
if latest := latestExpiredSubscription(expired); latest != nil {
|
|
return latest, nil, nil
|
|
}
|
|
return nil, nil, gerror.Newf("ICCID没有当前套餐订购记录:%s", iccid)
|
|
}
|
|
|
|
func latestExpiredSubscription(rows []*rc.SimCardBindPkg) *rc.SimCardBindPkg {
|
|
var latest *rc.SimCardBindPkg
|
|
for _, row := range rows {
|
|
if row == nil {
|
|
continue
|
|
}
|
|
if latest == nil || subscriptionAfter(row, latest) {
|
|
latest = row
|
|
}
|
|
}
|
|
return latest
|
|
}
|
|
|
|
func sortPendingSubscriptions(rows []*rc.SimCardBindPkg) {
|
|
if len(rows) <= 1 {
|
|
return
|
|
}
|
|
for i := 1; i < len(rows); i++ {
|
|
current := rows[i]
|
|
j := i - 1
|
|
for j >= 0 && subscriptionAfter(rows[j], current) {
|
|
rows[j+1] = rows[j]
|
|
j--
|
|
}
|
|
rows[j+1] = current
|
|
}
|
|
}
|
|
|
|
func subscriptionAfter(left, right *rc.SimCardBindPkg) bool {
|
|
if compareTime(expectedExpireTime(left), expectedExpireTime(right)) != 0 {
|
|
return compareTime(expectedExpireTime(left), expectedExpireTime(right)) > 0
|
|
}
|
|
if compareTime(effectiveTime(left), effectiveTime(right)) != 0 {
|
|
return compareTime(effectiveTime(left), effectiveTime(right)) > 0
|
|
}
|
|
return strings.TrimSpace(subscriptionNo(left)) > strings.TrimSpace(subscriptionNo(right))
|
|
}
|
|
|
|
func expectedExpireTime(row *rc.SimCardBindPkg) *gtime.Time {
|
|
if row == nil {
|
|
return nil
|
|
}
|
|
return row.ExpectedExpireTime
|
|
}
|
|
|
|
func effectiveTime(row *rc.SimCardBindPkg) *gtime.Time {
|
|
if row == nil {
|
|
return nil
|
|
}
|
|
return row.EffectiveTime
|
|
}
|
|
|
|
func subscriptionNo(row *rc.SimCardBindPkg) string {
|
|
if row == nil {
|
|
return ""
|
|
}
|
|
return row.SubscriptionNo
|
|
}
|
|
|
|
func compareTime(left, right *gtime.Time) int {
|
|
switch {
|
|
case left == nil && right == nil:
|
|
return 0
|
|
case left == nil:
|
|
return -1
|
|
case right == nil:
|
|
return 1
|
|
case left.Before(right):
|
|
return -1
|
|
case left.After(right):
|
|
return 1
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func validateCurrentSubscription(card *rc.SimCardInfo, bind *rc.SimCardBindPkg, expected subscriptionValidation) error {
|
|
if card == nil || bind == nil {
|
|
return gerror.New("卡信息或当前订购记录缺失")
|
|
}
|
|
iccid := strings.TrimSpace(expected.ICCID)
|
|
if iccid == "" || strings.TrimSpace(card.Iccid) != iccid || strings.TrimSpace(bind.Iccid) != iccid {
|
|
return gerror.New("ICCID不一致")
|
|
}
|
|
if strings.TrimSpace(expected.TenantID) != "" &&
|
|
(strings.TrimSpace(card.TenantId) != expected.TenantID || strings.TrimSpace(bind.TenantId) != expected.TenantID) {
|
|
return gerror.New("租户不一致")
|
|
}
|
|
// t_sim_card_info.dept_id is the current card owner. A card allocation
|
|
// changes that field, while the historical bind row keeps the department
|
|
// that originally purchased the subscription.
|
|
if expected.CustomerDeptID <= 0 || card.DeptId != expected.CustomerDeptID {
|
|
return gerror.New("客户部门不一致")
|
|
}
|
|
packageCode := strings.TrimSpace(expected.PackageCode)
|
|
if packageCode == "" || strings.TrimSpace(card.CurrentPackageCode) != packageCode || strings.TrimSpace(bind.PackageCode) != packageCode {
|
|
return gerror.New("当前套餐不一致")
|
|
}
|
|
if card.PackageStatus == consts.PackageStatusNone {
|
|
return gerror.New("卡没有当前套餐")
|
|
}
|
|
if msisdn := strings.TrimSpace(expected.MSISDN); msisdn != "" && strings.TrimSpace(card.Msisdn) != msisdn {
|
|
return gerror.New("MSISDN不一致")
|
|
}
|
|
if bindMSISDN := strings.TrimSpace(bind.Msisdn); bindMSISDN != "" && strings.TrimSpace(card.Msisdn) != bindMSISDN {
|
|
return gerror.New("订购记录MSISDN不一致")
|
|
}
|
|
if imsi := strings.TrimSpace(expected.IMSI); imsi != "" && strings.TrimSpace(card.Imsi) != imsi {
|
|
return gerror.New("IMSI不一致")
|
|
}
|
|
if sourceBatchNo := strings.TrimSpace(expected.SourceBatchNo); sourceBatchNo != "" {
|
|
bindBatchNo := strings.TrimSpace(bind.BatchNo)
|
|
outboundBatchNo := strings.TrimSpace(card.OutboundBatchNo)
|
|
if bindBatchNo != sourceBatchNo && outboundBatchNo != sourceBatchNo {
|
|
return gerror.New("当前订购来源批次不一致")
|
|
}
|
|
}
|
|
return nil
|
|
}
|