chore: 并入 iotlcm-openapi 历史到 openapi/(原提交 3b05d5a)

This commit is contained in:
MiFaZhan
2026-11-02 13:28:02 +08:00
484 changed files with 34732 additions and 0 deletions
+312
View File
@@ -0,0 +1,312 @@
package query
import (
"context"
"strings"
"github.com/gogf/gf/v2/errors/gerror"
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/dao"
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/model/entity"
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/service"
)
// querySimBatchMaxCards 批量查询单次的卡数量上限(与 QueryTrafficCutoffConfig 同量级)。
// 纯本地库读、不调上游,故偏保守;如需更大块,改这一个常量即可(注意 IN 列表与报文大小)。
const querySimBatchMaxCards = 1000
// simBatchIdentifierMaxLen 单标识允许的最大字符数,对齐 t_sim_card_info 列宽(varchar(32))并留余量,
// 用于把请求体内存占用钉死在可控范围(1000 张 * 3 字段 * 64 ≈ 192KB),防止超长字符串滥用。
const simBatchIdentifierMaxLen = 64
type simBatchIdentifier struct {
Msisdn string
Iccid string
Imsi string
}
func validateSimBatchIdentifier(item simBatchIdentifier) error {
count := 0
if item.Msisdn != "" {
if len(item.Msisdn) > simBatchIdentifierMaxLen {
return gerror.New("msisdn长度超限")
}
count++
}
if item.Iccid != "" {
if len(item.Iccid) > simBatchIdentifierMaxLen {
return gerror.New("iccid长度超限")
}
count++
}
if item.Imsi != "" {
if len(item.Imsi) > simBatchIdentifierMaxLen {
return gerror.New("imsi长度超限")
}
count++
}
if count == 0 {
return gerror.New("msisdn、iccid、imsi至少传一个")
}
return nil
}
// simBatchCardMatchesIdentifier 校验返回的卡是否同时匹配输入里提供的所有标识。
// 防止单条输入同时填了多个互相矛盾的标识(如 iccid 属于 A、msisdn 属于 B)时,
// 仍返回优先级最高那个标识对应卡的数据。沿用 traffic_cutoff_config.go 的同名校验逻辑。
func simBatchCardMatchesIdentifier(card *entity.TSimCardInfo, identifier simBatchIdentifier) bool {
if card == nil {
return false
}
if identifier.Iccid != "" && strings.TrimSpace(card.Iccid) != identifier.Iccid {
return false
}
if identifier.Msisdn != "" && strings.TrimSpace(card.Msisdn) != identifier.Msisdn {
return false
}
if identifier.Imsi != "" && strings.TrimSpace(card.Imsi) != identifier.Imsi {
return false
}
return true
}
func simBatchIdentifierKey(item simBatchIdentifier) string {
if item.Iccid != "" {
return "iccid:" + item.Iccid
}
if item.Msisdn != "" {
return "msisdn:" + item.Msisdn
}
return "imsi:" + item.Imsi
}
// parseSimBatchIdentifiers 校验并去重,返回按 iccid/msisdn/imsi 分桶的标识列表。
func parseSimBatchIdentifiers(cards []model.QuerySimBatchCard) ([]simBatchIdentifier, error) {
identifiers := make([]simBatchIdentifier, 0, len(cards))
seen := make(map[string]struct{}, len(cards))
for _, item := range cards {
identifier := simBatchIdentifier{
Msisdn: strings.TrimSpace(item.Msisdn),
Iccid: strings.TrimSpace(item.Iccid),
Imsi: strings.TrimSpace(item.Imsi),
}
if err := validateSimBatchIdentifier(identifier); err != nil {
return nil, err
}
key := simBatchIdentifierKey(identifier)
if _, ok := seen[key]; ok {
return nil, gerror.Newf("卡片标识重复:%s", key)
}
seen[key] = struct{}{}
identifiers = append(identifiers, identifier)
}
return identifiers, nil
}
// loadSimBatchCards 按标识分桶做 WhereIn 查询(自动带部门数据权限),返回 type:key -> 卡 的映射。
func loadSimBatchCards(ctx context.Context, identifiers []simBatchIdentifier) (map[string]*entity.TSimCardInfo, error) {
columns := dao.TSimCardInfo.Columns()
result := make(map[string]*entity.TSimCardInfo, len(identifiers))
lookup := func(field string, values []string) error {
if len(values) == 0 {
return nil
}
var cards []*entity.TSimCardInfo
err := service.IotTSimCardInfo().Model(ctx).
Fields(columns.Iccid, columns.Msisdn, columns.Imsi, columns.BossCardStatus, columns.UsedFlowMb,
columns.Imei).
WhereIn(field, values).Scan(&cards)
if err != nil {
return gerror.Wrap(err, "批量查询卡信息失败")
}
for _, card := range cards {
if card == nil {
continue
}
if card.Iccid != "" {
result["iccid:"+strings.TrimSpace(card.Iccid)] = card
}
if card.Msisdn != "" {
result["msisdn:"+strings.TrimSpace(card.Msisdn)] = card
}
if card.Imsi != "" {
result["imsi:"+strings.TrimSpace(card.Imsi)] = card
}
}
return nil
}
iccidValues, msisdnValues, imsiValues := make([]string, 0), make([]string, 0), make([]string, 0)
for _, item := range identifiers {
switch {
case item.Iccid != "":
iccidValues = append(iccidValues, item.Iccid)
case item.Msisdn != "":
msisdnValues = append(msisdnValues, item.Msisdn)
default:
imsiValues = append(imsiValues, item.Imsi)
}
}
if err := lookup(columns.Iccid, iccidValues); err != nil {
return nil, err
}
if err := lookup(columns.Msisdn, msisdnValues); err != nil {
return nil, err
}
if err := lookup(columns.Imsi, imsiValues); err != nil {
return nil, err
}
return result, nil
}
// QuerySimStatusBatch 批量查询卡状态(纯读本地库,不调上游通道)。
// 返回结构与单卡 QuerySimStatus 一致(iccid / lifeCycle 内部码 / lifeCycleName),
// 但按输入顺序逐卡返回,未匹配项用 Matched=false + errorCode 标记。
func (s *sQuerySim) QuerySimStatusBatch(ctx context.Context, in *model.QuerySimStatusBatchParam) (*model.QuerySimStatusBatchOutput, error) {
if in == nil {
return nil, gerror.New("查询参数不能为空")
}
if strings.TrimSpace(in.AppId) == "" {
return nil, gerror.New("appId不能为空")
}
if len(in.Cards) == 0 {
return nil, gerror.New("查询卡不能为空")
}
if len(in.Cards) > querySimBatchMaxCards {
return nil, gerror.Newf("单次最多查询%d张卡", querySimBatchMaxCards)
}
identifiers, err := parseSimBatchIdentifiers(in.Cards)
if err != nil {
return nil, err
}
cardMap, err := loadSimBatchCards(ctx, identifiers)
if err != nil {
return nil, err
}
output := &model.QuerySimStatusBatchOutput{
Total: len(identifiers),
Items: make([]model.QuerySimStatusBatchItem, 0, len(identifiers)),
}
for _, identifier := range identifiers {
result := model.QuerySimStatusBatchItem{}
card := cardMap[simBatchIdentifierKey(identifier)]
if card == nil || !simBatchCardMatchesIdentifier(card, identifier) {
result.ErrorCode = "CARD_NOT_FOUND"
result.ErrorMessage = "卡片不存在或无权限"
output.NotFoundCount++
output.Items = append(output.Items, result)
continue
}
result.Matched = true
result.Iccid = strings.TrimSpace(card.Iccid)
statusName, ok := consts.V3ApiSimBossCardStatusNameMap[card.BossCardStatus]
if !ok {
statusName = "未知"
}
result.LifeCycle = card.BossCardStatus
result.LifeCycleName = statusName
output.MatchedCount++
output.Items = append(output.Items, result)
}
return output, nil
}
// QuerySimUsageBatch 批量查询卡流量(used_flow_mb,纯读本地库)。
func (s *sQuerySim) QuerySimUsageBatch(ctx context.Context, in *model.QuerySimUsageBatchParam) (*model.QuerySimUsageBatchOutput, error) {
if in == nil {
return nil, gerror.New("查询参数不能为空")
}
if strings.TrimSpace(in.AppId) == "" {
return nil, gerror.New("appId不能为空")
}
if len(in.Cards) == 0 {
return nil, gerror.New("查询卡不能为空")
}
if len(in.Cards) > querySimBatchMaxCards {
return nil, gerror.Newf("单次最多查询%d张卡", querySimBatchMaxCards)
}
identifiers, err := parseSimBatchIdentifiers(in.Cards)
if err != nil {
return nil, err
}
cardMap, err := loadSimBatchCards(ctx, identifiers)
if err != nil {
return nil, err
}
output := &model.QuerySimUsageBatchOutput{
Total: len(identifiers),
Items: make([]model.QuerySimUsageBatchItem, 0, len(identifiers)),
}
for _, identifier := range identifiers {
result := model.QuerySimUsageBatchItem{}
card := cardMap[simBatchIdentifierKey(identifier)]
if card == nil || !simBatchCardMatchesIdentifier(card, identifier) {
result.ErrorCode = "CARD_NOT_FOUND"
result.ErrorMessage = "卡片不存在或无权限"
output.NotFoundCount++
output.Items = append(output.Items, result)
continue
}
result.Matched = true
result.Iccid = strings.TrimSpace(card.Iccid)
result.UseUnit = "MB"
result.UseAmount = card.UsedFlowMb
output.MatchedCount++
output.Items = append(output.Items, result)
}
return output, nil
}
// QuerySimImeiBatch 批量查询卡 IMEI(纯读本地库,不调上游通道)。
//
// 数据来源是 t_sim_card_info.imei 快照,由 worker 的 IMEI 全量同步链路维护;
// 另有一个补充来源:单卡 /openapi/v3/query/sim-imei 在库值为空时会实时调运营商
// 并回写(不写 imei_sync_time,只写 imei_update_time)。
//
// 语义要点:
// - imei 为空**不算 notFound**:卡存在但上游从未查到值(如不支持的模板通道)。
// - 本接口**不触发任何上游请求**,是纯本地库读(与 sim-status-batch 一致)。
func (s *sQuerySim) QuerySimImeiBatch(ctx context.Context, in *model.QuerySimImeiBatchParam) (*model.QuerySimImeiBatchOutput, error) {
if in == nil {
return nil, gerror.New("查询参数不能为空")
}
if strings.TrimSpace(in.AppId) == "" {
return nil, gerror.New("appId不能为空")
}
if len(in.Cards) == 0 {
return nil, gerror.New("查询卡不能为空")
}
if len(in.Cards) > querySimBatchMaxCards {
return nil, gerror.Newf("单次最多查询%d张卡", querySimBatchMaxCards)
}
identifiers, err := parseSimBatchIdentifiers(in.Cards)
if err != nil {
return nil, err
}
cardMap, err := loadSimBatchCards(ctx, identifiers)
if err != nil {
return nil, err
}
output := &model.QuerySimImeiBatchOutput{
Total: len(identifiers),
Items: make([]model.QuerySimImeiBatchItem, 0, len(identifiers)),
}
for _, identifier := range identifiers {
result := model.QuerySimImeiBatchItem{}
card := cardMap[simBatchIdentifierKey(identifier)]
if card == nil || !simBatchCardMatchesIdentifier(card, identifier) {
result.ErrorCode = "CARD_NOT_FOUND"
result.ErrorMessage = "卡片不存在或无权限"
output.NotFoundCount++
output.Items = append(output.Items, result)
continue
}
result.Matched = true
result.Iccid = strings.TrimSpace(card.Iccid)
result.Imei = strings.TrimSpace(card.Imei)
output.MatchedCount++
output.Items = append(output.Items, result)
}
return output, nil
}