Files
iotlcm/internal/service/simstatus/handler.go
T
MiFaZhan 2bec257289 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),属既有缺陷,本仓未引入亦未修复。
2026-10-22 16:37:56 +08:00

132 lines
4.1 KiB
Go

package simstatus
import (
"context"
"fmt"
"strings"
zsetconsumer "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consumer/zset"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model"
queue "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/queue/zset"
simstatusrepo "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/repository/simstatus"
)
const (
telecomRetryableCode = "S00004"
telecomStatusTemplate = "sim_dx_v1"
chanGatewayInternalError = 9999
// tokenExpiredCode is the carrier code for an expired/invalid upstream
// token. The gateway already invalidates and refreshes the token once on
// this code; when it still reaches the worker, retrying in a later round
// lets a refreshed token succeed instead of parking the card as a terminal
// business failure.
tokenExpiredCode = 12021
)
type StatusClient interface {
QuerySimCardStatus(context.Context, *model.SimCardStatusInput) ([]model.SimBaseRes[model.SimCardStatusItem], error)
}
type StatusRepository interface {
ApplyBatch(context.Context, []simstatusrepo.Result) error
}
type Handler struct {
client StatusClient
repo StatusRepository
networkRetries int
}
func NewHandler(client StatusClient, repo StatusRepository, networkRetries ...int) (*Handler, error) {
if client == nil || repo == nil {
return nil, fmt.Errorf("sim status handler dependencies are incomplete")
}
retries := 0
if len(networkRetries) > 0 && networkRetries[0] > 0 {
retries = networkRetries[0]
}
return &Handler{client: client, repo: repo, networkRetries: retries}, nil
}
func (h *Handler) Query(ctx context.Context, task queue.Task) zsetconsumer.Result {
result := zsetconsumer.Result{Task: task}
input := &model.SimCardStatusInput{SimKeys: []*model.SimKey{{
ChanKey: task.ChanKey,
ICCID: task.ICCID,
IMSI: task.IMSI,
MSISDN: task.MSISDN,
}}}
var (
output []model.SimBaseRes[model.SimCardStatusItem]
err error
)
for attempt := 0; attempt <= h.networkRetries; attempt++ {
output, err = h.client.QuerySimCardStatus(ctx, input)
if err == nil {
break
}
if ctx.Err() != nil {
break
}
}
if err != nil {
result.ErrorClass = "network"
result.Retryable = true
result.Message = err.Error()
return result
}
if len(output) == 0 {
result.ErrorClass = "protocol"
result.Retryable = true
result.Message = "chan returned empty status result"
return result
}
item := output[0]
if item.SimKey == nil {
item.SimKey = &model.SimKey{ChanKey: task.ChanKey, ICCID: task.ICCID, IMSI: task.IMSI, MSISDN: task.MSISDN}
}
if task.ICCID != "" && item.SimKey.ICCID != task.ICCID {
result.ErrorClass = "protocol"
result.Retryable = true
result.Message = "chan returned mismatched card identifier"
return result
}
result.Code = item.Code
result.Message = item.Message
result.ErrCode = item.ErrCode
if item.Code != 0 {
result.ErrorClass = "business"
if model.IsTelecomTemplate(task.TempKey) && strings.EqualFold(strings.TrimSpace(item.ErrCode), telecomRetryableCode) {
result.ErrorClass = "carrier_retry"
result.Retryable = true
} else if item.Code == chanGatewayInternalError && strings.TrimSpace(item.ErrCode) == "" {
result.ErrorClass = "gateway"
result.Retryable = true
} else if item.Code == tokenExpiredCode {
result.ErrorClass = "token_expired"
result.Retryable = true
}
return result
}
result.Success = true
status := item.ResData.CardStatus
result.CardStatus = &status
return result
}
func (h *Handler) Apply(ctx context.Context, results []zsetconsumer.Result) error {
updates := make([]simstatusrepo.Result, 0, len(results))
for _, result := range results {
if result.Success && result.CardStatus != nil {
updates = append(updates, simstatusrepo.Result{CardID: result.Task.CardID, CardStatus: *result.CardStatus})
}
}
return h.repo.ApplyBatch(ctx, updates)
}
// ShouldRetryNextRound is the full-status-sync retry policy. It is passed to
// the Run-aware ZSet consumer only; single-card tasks retain lease recovery.
func ShouldRetryNextRound(result zsetconsumer.Result) bool {
return !result.Success && result.Retryable
}