313 lines
10 KiB
Go
313 lines
10 KiB
Go
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
|
|
}
|