1970 lines
66 KiB
Go
1970 lines
66 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
|
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
|
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
|
|
"git.5iot.com/iotlcm-public/iotlcm-app/internal/service"
|
|
"git.5iot.com/iotlcm-public/iotlcm-app/utility"
|
|
"git.5iot.com/iotlcm-public/iotlcm-app/utility/uuid32"
|
|
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"github.com/gogf/gf/v2/os/gtime"
|
|
"golang.org/x/sync/singleflight"
|
|
)
|
|
|
|
type sAppDiagnosis struct{}
|
|
|
|
const (
|
|
appDiagnosisRegionCacheTTL = time.Hour
|
|
appDiagnosisRegionLockTTL = 15 * time.Second
|
|
appDiagnosisRegionLockWait = 12 * time.Second
|
|
appDiagnosisRegionLockPoll = 100 * time.Millisecond
|
|
appOpenDiagnosisRateWindow = time.Minute
|
|
appOpenDiagnosisRateLimit = 20
|
|
appDiagnosisNotIncludedMessage = "该卡暂未纳入服务"
|
|
)
|
|
|
|
var appDiagnosisRegionFlight singleflight.Group
|
|
|
|
type appDiagnosisUpdateFunc func(func())
|
|
type appDiagnosisSetModuleFunc func(key string, status string, message string, cached bool, updatedAt string)
|
|
|
|
type appDiagnosisRegionCachePayload struct {
|
|
Item appUpstreamSimQueryItem `json:"item"`
|
|
CachedAt string `json:"cachedAt"`
|
|
}
|
|
|
|
type appDiagnosisRegionQueryResult struct {
|
|
Item *appUpstreamSimQueryItem
|
|
CachedAt string
|
|
Cached bool
|
|
}
|
|
|
|
func NewAppDiagnosis() *sAppDiagnosis {
|
|
return &sAppDiagnosis{}
|
|
}
|
|
|
|
func init() {
|
|
service.RegisterAppDiagnosis(NewAppDiagnosis())
|
|
}
|
|
|
|
// Info 查询登录用户可见的SIM卡诊断信息,并保留完整会话字段。
|
|
func (s *sAppDiagnosis) Info(ctx context.Context, param *model.AppDiagnosisInfoParam) (res *model.AppDiagnosisInfoModel, err error) {
|
|
if param == nil {
|
|
param = &model.AppDiagnosisInfoParam{}
|
|
}
|
|
user, cardLogic, row, err := s.resolveDiagnosisCard(ctx, param.QueryCode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cardModel := cardLogic.cardRowToModel(ctx, row, user, cardLogic.ownerType(ctx, user), true)
|
|
res, err = s.buildDiagnosisInfo(ctx, row, cardModel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if row.Id == 0 {
|
|
return sanitizeOpenDiagnosisInfo(res), nil
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func (s *sAppDiagnosis) OpenInfo(ctx context.Context, param *model.AppOpenDiagnosisInfoParam) (res *model.AppDiagnosisInfoModel, err error) {
|
|
if param == nil {
|
|
param = &model.AppOpenDiagnosisInfoParam{}
|
|
}
|
|
if err = appEnsureOpenDiagnosisRateLimit(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
appAuditOpenDiagnosis(ctx, "info", param.QueryCode, "")
|
|
cardLogic, row, err := s.resolveOpenDiagnosisCard(ctx, param.TenantId, param.QueryCode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res, err = s.buildDiagnosisInfo(ctx, row, cardLogic.publicCardRowToModel(ctx, row, param.QueryCode))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return sanitizeOpenDiagnosisInfo(res), nil
|
|
}
|
|
|
|
func (s *sAppDiagnosis) buildDiagnosisInfo(ctx context.Context, row *appCardQueryRow, cardModel *model.AppCardModel) (res *model.AppDiagnosisInfoModel, err error) {
|
|
if row == nil {
|
|
return nil, gerror.New("卡片信息为空,不能诊断")
|
|
}
|
|
ctx = context.WithValue(ctx, appDiagnosisProxyContextKey{}, true)
|
|
simKey, err := s.prepareDiagnosisSimKey(ctx, row)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cardModel != nil {
|
|
if strings.TrimSpace(row.Iccid) != "" {
|
|
cardModel.Iccid = strings.TrimSpace(row.Iccid)
|
|
}
|
|
if strings.TrimSpace(row.Imsi) != "" {
|
|
cardModel.Imsi = strings.TrimSpace(row.Imsi)
|
|
}
|
|
if strings.TrimSpace(row.Msisdn) != "" {
|
|
cardModel.Msisdn = strings.TrimSpace(row.Msisdn)
|
|
}
|
|
cardModel.UsedFlowMb = 0
|
|
cardModel.RemainFlowMb = 0
|
|
cardModel.UsedFlowPercent = 0
|
|
}
|
|
res = &model.AppDiagnosisInfoModel{
|
|
Info: &model.AppDiagnosisInfoItem{
|
|
Iccid: appDiagnosisStringPtr(row.Iccid),
|
|
Imsi: appDiagnosisStringPtr(row.Imsi),
|
|
Msisdn: appDiagnosisStringPtr(row.Msisdn),
|
|
},
|
|
Session: &model.AppDiagnosisSessionItem{},
|
|
Modules: make(map[string]*model.AppDiagnosisModuleItem),
|
|
Card: cardModel,
|
|
}
|
|
res.Info.DataServiceSource = "apn"
|
|
if appDiagnosisIsTelecomChannel(row.TempKey, row.ChanKey) {
|
|
res.Info.DataServiceSource = "network_block"
|
|
}
|
|
appSetDiagnosisModule(res, "card", "success", "", false, "")
|
|
|
|
var mu sync.Mutex
|
|
updateDiagnosis := func(fn func()) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
fn()
|
|
}
|
|
setModule := func(key string, status string, message string, cached bool, updatedAt string) {
|
|
updateDiagnosis(func() {
|
|
appSetDiagnosisModule(res, key, status, message, cached, updatedAt)
|
|
if key == "cardStatus" && res.Info.DataServiceSource == "network_block" && res.Info.NetBlockStatus == nil && status != "success" && status != "cached" {
|
|
appSetDiagnosisModule(res, "dataService", status, message, cached, updatedAt)
|
|
}
|
|
})
|
|
}
|
|
runDiagnose := func(wg *sync.WaitGroup, fn func()) {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
fn()
|
|
}()
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
runDiagnose(&wg, func() { s.attachUpstreamDataService(ctx, simKey, row, res, updateDiagnosis, setModule) })
|
|
runDiagnose(&wg, func() { s.attachUpstreamBasicInfo(ctx, simKey, row, res, updateDiagnosis, setModule) })
|
|
runDiagnose(&wg, func() { s.attachUpstreamCardStatus(ctx, simKey, res, updateDiagnosis, setModule) })
|
|
runDiagnose(&wg, func() { s.attachUpstreamSession(ctx, simKey, row, res, updateDiagnosis, setModule) })
|
|
runDiagnose(&wg, func() { s.attachUpstreamLatestImei(ctx, simKey, res, updateDiagnosis, setModule) })
|
|
runDiagnose(&wg, func() { s.attachUpstreamDeviceStatus(ctx, simKey, row, res, updateDiagnosis, setModule) })
|
|
runDiagnose(&wg, func() { s.attachUpstreamBindStatus(ctx, simKey, res, updateDiagnosis, setModule) })
|
|
runDiagnose(&wg, func() {
|
|
s.attachUpstreamRegionLimitStatus(ctx, simKey, row.TempKey, row.ChanKey, res, updateDiagnosis, setModule)
|
|
})
|
|
runDiagnose(&wg, func() { s.attachUpstreamRateLimitStatus(ctx, simKey, res, updateDiagnosis, setModule) })
|
|
runDiagnose(&wg, func() { s.attachUpstreamStopReason(ctx, simKey, res, updateDiagnosis, setModule) })
|
|
runDiagnose(&wg, func() { s.attachUpstreamFrozenState(ctx, simKey, res, updateDiagnosis, setModule) })
|
|
runDiagnose(&wg, func() { s.attachUpstreamCurrentFlowUsage(ctx, simKey, row, res, updateDiagnosis, setModule) })
|
|
wg.Wait()
|
|
// Online status is determined solely by the realtime session. The data service
|
|
// status is independent and must not make an online session appear offline.
|
|
sessionStatus := appDiagnosisSessionStatus(appDiagnosisPtrValue(res.Session.Status))
|
|
if res.Session.Status == nil {
|
|
res.Info.OnlineStatus = nil
|
|
if res.Card != nil {
|
|
res.Card.OnlineStatus = ""
|
|
}
|
|
} else {
|
|
res.Info.OnlineStatus = appDiagnosisOnlineStatus(sessionStatus)
|
|
if res.Card != nil {
|
|
// AppCardModel uses the upstream session status convention:
|
|
// 0 means online and -1 means offline.
|
|
res.Card.OnlineStatus = sessionStatus
|
|
}
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
// Flow 查询登录用户可见SIM卡的流量趋势。
|
|
func (s *sAppDiagnosis) Flow(ctx context.Context, param *model.AppDiagnosisFlowParam) (res *model.AppDiagnosisFlowModel, err error) {
|
|
if param == nil {
|
|
param = &model.AppDiagnosisFlowParam{}
|
|
}
|
|
_, _, row, err := s.resolveDiagnosisCard(ctx, param.QueryCode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.queryDiagnosisFlowByCard(ctx, row, param.QueryType)
|
|
}
|
|
|
|
func (s *sAppDiagnosis) OpenFlow(ctx context.Context, param *model.AppOpenDiagnosisFlowParam) (res *model.AppDiagnosisFlowModel, err error) {
|
|
if param == nil {
|
|
param = &model.AppOpenDiagnosisFlowParam{}
|
|
}
|
|
if err = appEnsureOpenDiagnosisRateLimit(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
appAuditOpenDiagnosis(ctx, "flow", param.QueryCode, param.QueryType)
|
|
_, row, err := s.resolveOpenDiagnosisCard(ctx, param.TenantId, param.QueryCode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.queryDiagnosisFlowByCard(ctx, row, param.QueryType)
|
|
}
|
|
|
|
func appEnsureOpenDiagnosisRateLimit(ctx context.Context) error {
|
|
clientIp := strings.TrimSpace(utility.GetClientIp(ctx))
|
|
if clientIp == "" {
|
|
clientIp = "unknown"
|
|
}
|
|
key := fmt.Sprintf("iotlcm:app:open-diagnosis:rate:%s", appDiagnosisSafeCachePart(clientIp))
|
|
value, err := g.Redis().Do(ctx, "INCR", key)
|
|
if err != nil {
|
|
g.Log().Warningf(ctx, "open diagnosis rate limit unavailable, key:%s, err:%+v", key, err)
|
|
return nil
|
|
}
|
|
count := value.Int()
|
|
if count == 1 {
|
|
if _, err = g.Redis().Do(ctx, "EXPIRE", key, int(appOpenDiagnosisRateWindow.Seconds())); err != nil {
|
|
g.Log().Warningf(ctx, "open diagnosis rate limit expiry failed, key:%s, err:%+v", key, err)
|
|
}
|
|
}
|
|
if count > appOpenDiagnosisRateLimit {
|
|
return gerror.New("公开诊断请求过于频繁,请稍后再试")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func appAuditOpenDiagnosis(ctx context.Context, kind string, queryCode string, queryType string) {
|
|
g.Log().Infof(ctx, "open diagnosis query, kind:%s, queryType:%s, query:%s, clientIp:%s", kind, queryType, appDiagnosisMaskIdentifier(queryCode), utility.GetClientIp(ctx))
|
|
}
|
|
|
|
func appDiagnosisMaskIdentifier(value string) string {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return ""
|
|
}
|
|
if len(value) <= 4 {
|
|
return "****"
|
|
}
|
|
if len(value) <= 8 {
|
|
return value[:2] + "****" + value[len(value)-2:]
|
|
}
|
|
return value[:4] + "****" + value[len(value)-4:]
|
|
}
|
|
|
|
func sanitizeOpenDiagnosisInfo(res *model.AppDiagnosisInfoModel) *model.AppDiagnosisInfoModel {
|
|
if res == nil {
|
|
return nil
|
|
}
|
|
if res.Info != nil {
|
|
res.Info.Iccid = appDiagnosisMaskedStringPtr(res.Info.Iccid)
|
|
res.Info.Imsi = nil
|
|
res.Info.Msisdn = appDiagnosisMaskedStringPtr(res.Info.Msisdn)
|
|
res.Info.RateLimitApnDetails = nil
|
|
}
|
|
if res.Session != nil {
|
|
res.Session.ApnId = nil
|
|
res.Session.Ipv4 = nil
|
|
res.Session.Ipv6 = nil
|
|
res.Session.Ipv6Prefix = nil
|
|
}
|
|
if res.Card != nil {
|
|
res.Card.Iccid = appDiagnosisMaskIdentifier(res.Card.Iccid)
|
|
res.Card.Msisdn = appDiagnosisMaskIdentifier(res.Card.Msisdn)
|
|
res.Card.Imsi = ""
|
|
res.Card.ApnName = ""
|
|
res.Card.ApnId = ""
|
|
res.Card.Ipv4 = ""
|
|
res.Card.Ipv6 = ""
|
|
res.Card.Ipv6Prefix = ""
|
|
res.Card.Remark = ""
|
|
}
|
|
return res
|
|
}
|
|
|
|
func appDiagnosisMaskedStringPtr(value *string) *string {
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
return appDiagnosisStringPtr(appDiagnosisMaskIdentifier(*value))
|
|
}
|
|
|
|
func (s *sAppDiagnosis) queryDiagnosisFlowByCard(ctx context.Context, row *appCardQueryRow, queryType string) (*model.AppDiagnosisFlowModel, error) {
|
|
switch strings.TrimSpace(queryType) {
|
|
case "7d":
|
|
return s.queryDiagnosisDailyFlow(ctx, row.Iccid, 7)
|
|
case "30d":
|
|
return s.queryDiagnosisDailyFlow(ctx, row.Iccid, 30)
|
|
case "3M":
|
|
return s.queryDiagnosisMonthlyFlow(ctx, row.Iccid, 3)
|
|
case "6M":
|
|
return s.queryDiagnosisMonthlyFlow(ctx, row.Iccid, 6)
|
|
default:
|
|
return nil, gerror.New("查询类型只能为7d、30d、3M或6M")
|
|
}
|
|
}
|
|
|
|
func (s *sAppDiagnosis) resolveDiagnosisCard(ctx context.Context, queryCode string) (*entity.SysUser, *sAppCard, *appCardQueryRow, error) {
|
|
queryCode = strings.TrimSpace(queryCode)
|
|
if queryCode == "" {
|
|
return nil, nil, nil, gerror.New("请输入ICCID、MSISDN、IMSI或卡号")
|
|
}
|
|
user, err := appCurrentUser(ctx)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
cardLogic := NewAppCard()
|
|
row, err := cardLogic.findDiagnosisCard(ctx, user, queryCode)
|
|
if err != nil {
|
|
return nil, nil, nil, gerror.Wrap(err, "查询诊断卡片失败")
|
|
}
|
|
if row == nil || row.Id == 0 {
|
|
openRow, openErr := cardLogic.findOpenCard(ctx, "", queryCode)
|
|
if openErr != nil {
|
|
return nil, nil, nil, gerror.Wrap(openErr, "查询诊断卡片失败")
|
|
}
|
|
if openRow != nil && openRow.Id > 0 {
|
|
return nil, nil, nil, gerror.New(appDiagnosisNotIncludedMessage)
|
|
}
|
|
row, err = s.resolveAssocOnlyDiagnosisCard(ctx, cardLogic, user.TenantId, queryCode, "查询诊断卡片")
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
}
|
|
if row == nil {
|
|
return nil, nil, nil, gerror.New(appDiagnosisNotIncludedMessage)
|
|
}
|
|
if err = s.resolveDiagnosisCardChannel(ctx, cardLogic, row, queryCode, "查询诊断卡片"); err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
return user, cardLogic, row, nil
|
|
}
|
|
|
|
func (s *sAppDiagnosis) resolveOpenDiagnosisCard(ctx context.Context, tenantId string, queryCode string) (*sAppCard, *appCardQueryRow, error) {
|
|
queryCode = strings.TrimSpace(queryCode)
|
|
if queryCode == "" {
|
|
return nil, nil, gerror.New("请输入ICCID、MSISDN、IMSI或卡号")
|
|
}
|
|
cardLogic := NewAppCard()
|
|
tenantId = strings.TrimSpace(tenantId)
|
|
row, err := cardLogic.findOpenCard(ctx, tenantId, queryCode)
|
|
if err != nil {
|
|
return nil, nil, gerror.Wrap(err, "查询公开诊断卡片失败")
|
|
}
|
|
if row == nil || row.Id == 0 {
|
|
row, err = s.resolveAssocOnlyDiagnosisCard(ctx, cardLogic, tenantId, queryCode, "查询公开诊断卡片")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
if row == nil {
|
|
return nil, nil, gerror.New(appDiagnosisNotIncludedMessage)
|
|
}
|
|
if err = s.resolveDiagnosisCardChannel(ctx, cardLogic, row, queryCode, "查询公开诊断卡片"); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return cardLogic, row, nil
|
|
}
|
|
|
|
func (s *sAppDiagnosis) resolveAssocOnlyDiagnosisCard(ctx context.Context, cardLogic *sAppCard, tenantId string, queryCode string, operation string) (*appCardQueryRow, error) {
|
|
info, err := cardLogic.findDiagnosisAssocChannel(ctx, tenantId, queryCode)
|
|
if err != nil {
|
|
return nil, gerror.Wrap(err, operation+"失败")
|
|
}
|
|
if info != nil {
|
|
if err = validateDiagnosisCardChannelInfo(info); err != nil {
|
|
return nil, err
|
|
}
|
|
return &appCardQueryRow{
|
|
TSimCardInfo: entity.TSimCardInfo{TenantId: tenantId, Iccid: queryCode, ChanKey: info.ChanKey},
|
|
ChanName: info.ChanName, ChanAlias: info.ChanAlias, TempKey: info.TempKey,
|
|
}, nil
|
|
}
|
|
card, err := queryLegacyDiagnosisCard(ctx, queryCode)
|
|
if err != nil {
|
|
return nil, gerror.Wrap(err, operation+"失败")
|
|
}
|
|
if card == nil {
|
|
return nil, nil
|
|
}
|
|
return &appCardQueryRow{
|
|
TSimCardInfo: entity.TSimCardInfo{
|
|
TenantId: tenantId,
|
|
Iccid: card.ICCID,
|
|
Imsi: card.IMSI,
|
|
Msisdn: card.MSISDN,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (s *sAppDiagnosis) resolveDiagnosisCardChannel(ctx context.Context, cardLogic *sAppCard, row *appCardQueryRow, queryCode string, operation string) error {
|
|
if cardLogic == nil || row == nil {
|
|
return gerror.New(appDiagnosisNotIncludedMessage)
|
|
}
|
|
assocInfo, assocErr := cardLogic.findDiagnosisAssocChannel(ctx, row.TenantId, row.Iccid)
|
|
if assocErr != nil {
|
|
return gerror.Wrap(assocErr, operation+"失败")
|
|
}
|
|
if assocInfo != nil {
|
|
if err := validateDiagnosisCardChannelInfo(assocInfo); err != nil {
|
|
return err
|
|
}
|
|
row.ChanKey = assocInfo.ChanKey
|
|
row.ChanName = assocInfo.ChanName
|
|
row.ChanAlias = assocInfo.ChanAlias
|
|
row.TempKey = assocInfo.TempKey
|
|
return nil
|
|
}
|
|
if row.Id > 0 && strings.TrimSpace(row.ChanKey) != "" {
|
|
info, err := cardLogic.findLegacyCardChannelInfo(ctx, row.TenantId, row.ChanKey)
|
|
if err != nil {
|
|
return gerror.Wrap(err, operation+"失败")
|
|
}
|
|
if validateDiagnosisCardChannelInfo(info) == nil {
|
|
row.ChanKey = info.ChanKey
|
|
row.ChanName = info.ChanName
|
|
row.ChanAlias = info.ChanAlias
|
|
row.TempKey = info.TempKey
|
|
return nil
|
|
}
|
|
}
|
|
lookupCode := strings.TrimSpace(row.Iccid)
|
|
if lookupCode == "" {
|
|
lookupCode = strings.TrimSpace(queryCode)
|
|
}
|
|
card, err := queryLegacyDiagnosisCard(ctx, lookupCode)
|
|
if err != nil {
|
|
return gerror.Wrap(err, operation+"失败")
|
|
}
|
|
if card == nil || (row.Iccid != "" && card.ICCID != row.Iccid) {
|
|
return gerror.New(appDiagnosisNotIncludedMessage)
|
|
}
|
|
info, err := cardLogic.findLegacyCardChannelInfo(ctx, row.TenantId, card.ChannelKey)
|
|
if err != nil {
|
|
return gerror.Wrap(err, operation+"失败")
|
|
}
|
|
if err = validateDiagnosisCardChannelInfo(info); err != nil {
|
|
return err
|
|
}
|
|
row.Iccid = card.ICCID
|
|
row.Imsi = card.IMSI
|
|
row.Msisdn = card.MSISDN
|
|
row.ChanKey = info.ChanKey
|
|
row.ChanName = info.ChanName
|
|
row.ChanAlias = info.ChanAlias
|
|
row.TempKey = info.TempKey
|
|
return nil
|
|
}
|
|
|
|
func validateDiagnosisCardChannelInfo(info *appCardChannelInfo) error {
|
|
if info == nil || strings.TrimSpace(info.ChanKey) == "" || info.ChanStatus != 0 {
|
|
return gerror.New(appDiagnosisNotIncludedMessage)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *sAppDiagnosis) prepareDiagnosisSimKey(ctx context.Context, row *appCardQueryRow) (*appUpstreamSimKey, error) {
|
|
simKey, err := appUpstreamSimKeyFromRow(row)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if row.Id > 0 && strings.TrimSpace(simKey.MSISDN) != "" && strings.TrimSpace(simKey.IMSI) != "" {
|
|
return simKey, nil
|
|
}
|
|
threeCodes, err := s.resolveDiagnosisThreeCodes(ctx, simKey)
|
|
if err != nil {
|
|
g.Log().Warningf(ctx, "app diagnosis resolve three codes failed, iccid:%s, chanKey:%s, err:%+v", simKey.ICCID, simKey.ChanKey, err)
|
|
if row.Id == 0 {
|
|
return nil, err
|
|
}
|
|
return simKey, nil
|
|
}
|
|
if threeCodes == nil {
|
|
return simKey, nil
|
|
}
|
|
if value := strings.TrimSpace(threeCodes.ICCID); value != "" {
|
|
simKey.ICCID = value
|
|
row.Iccid = value
|
|
}
|
|
if value := strings.TrimSpace(threeCodes.IMSI); value != "" {
|
|
simKey.IMSI = value
|
|
row.Imsi = value
|
|
}
|
|
if value := strings.TrimSpace(threeCodes.MSISDN); value != "" {
|
|
simKey.MSISDN = value
|
|
row.Msisdn = value
|
|
}
|
|
return simKey, nil
|
|
}
|
|
|
|
func (s *sAppDiagnosis) resolveDiagnosisThreeCodes(ctx context.Context, simKey *appUpstreamSimKey) (*appUpstreamSimKey, error) {
|
|
items, err := queryAppUpstreamSimWithKey(ctx, simKey, "/sim/query/sim-basic-info", map[string]any{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
item := appDiagnosisFirstAvailableItem(items)
|
|
if item == nil || item.Code == 5000 || appDiagnosisIsUnsupportedMessage(item.Message) {
|
|
return nil, nil
|
|
}
|
|
if item.Code != 0 {
|
|
return nil, gerror.New(appDiagnosisModuleMessage(item, "上游基础信息查询失败"))
|
|
}
|
|
threeCodes := &appUpstreamSimKey{ChanKey: strings.TrimSpace(simKey.ChanKey)}
|
|
if item.SimKey != nil {
|
|
threeCodes.ICCID = strings.TrimSpace(item.SimKey.ICCID)
|
|
threeCodes.IMSI = strings.TrimSpace(item.SimKey.IMSI)
|
|
threeCodes.MSISDN = strings.TrimSpace(item.SimKey.MSISDN)
|
|
}
|
|
if value := appDiagnosisRawString(item.ResData, "iccid", "ICCID"); value != "" {
|
|
threeCodes.ICCID = value
|
|
}
|
|
if value := appDiagnosisRawString(item.ResData, "imsi", "IMSI"); value != "" {
|
|
threeCodes.IMSI = value
|
|
}
|
|
if value := appDiagnosisRawString(item.ResData, "msisdn", "MSISDN"); value != "" {
|
|
threeCodes.MSISDN = value
|
|
}
|
|
if threeCodes.ICCID == "" && threeCodes.IMSI == "" && threeCodes.MSISDN == "" {
|
|
return nil, nil
|
|
}
|
|
return threeCodes, nil
|
|
}
|
|
|
|
func (s *sAppDiagnosis) attachUpstreamBasicInfo(ctx context.Context, simKey *appUpstreamSimKey, row *appCardQueryRow, res *model.AppDiagnosisInfoModel, update appDiagnosisUpdateFunc, setModule appDiagnosisSetModuleFunc) {
|
|
items, err := queryAppUpstreamSimWithKey(ctx, simKey, "/sim/query/sim-basic-info", map[string]any{})
|
|
if err != nil {
|
|
g.Log().Warningf(ctx, "app diagnosis query sim basic info failed, iccid:%s, err:%+v", row.Iccid, err)
|
|
setModule("basicInfo", "error", err.Error(), false, "")
|
|
return
|
|
}
|
|
item := appDiagnosisFirstAvailableItem(items)
|
|
if item == nil {
|
|
setModule("basicInfo", "skipped", "上游基础信息未返回有效结果", false, "")
|
|
return
|
|
}
|
|
if item.Code != 0 {
|
|
if item.Code == 5000 {
|
|
setModule("basicInfo", "skipped", appDiagnosisModuleMessage(item, "上游基础信息暂不支持"), false, "")
|
|
} else {
|
|
status, message := appDiagnosisUpstreamFailure("上游基础信息查询失败", item.Code, item.Message)
|
|
setModule("basicInfo", status, message, false, "")
|
|
}
|
|
return
|
|
}
|
|
update(func() {
|
|
if value := appDiagnosisStringPtr(appDiagnosisRawString(item.ResData, "iccid", "ICCID")); value != nil {
|
|
res.Info.Iccid = value
|
|
}
|
|
if value := appDiagnosisStringPtr(appDiagnosisRawString(item.ResData, "imsi", "IMSI")); value != nil {
|
|
res.Info.Imsi = value
|
|
}
|
|
if value := appDiagnosisStringPtr(appDiagnosisRawString(item.ResData, "msisdn", "MSISDN")); value != nil {
|
|
res.Info.Msisdn = value
|
|
}
|
|
if value := appDiagnosisRawString(item.ResData, "imei", "IMEI"); value != "" && res.Card != nil {
|
|
res.Card.Imei = value
|
|
}
|
|
if res.Info.RegionLimitType == nil {
|
|
res.Info.RegionLimitType = appDiagnosisStringPtr(appDiagnosisRawString(item.ResData, "areaLimitType", "area_limit_type"))
|
|
}
|
|
if res.Info.RegionLimitArea == nil {
|
|
res.Info.RegionLimitArea = appDiagnosisStringPtr(appDiagnosisRawString(item.ResData, "areaLimitInfo", "area_limit_info"))
|
|
}
|
|
rawHumanLimitStatus := appDiagnosisRawString(item.ResData, "isHumanLimit", "is_human_limit")
|
|
if rawHumanLimitStatus != "" {
|
|
humanLimitStatus := appDiagnosisHumanLimitStatus(rawHumanLimitStatus)
|
|
if humanLimitStatus != nil && *humanLimitStatus != "3" {
|
|
res.Info.HumanLimitStatus = humanLimitStatus
|
|
}
|
|
}
|
|
})
|
|
setModule("basicInfo", "success", "", false, "")
|
|
}
|
|
|
|
func (s *sAppDiagnosis) attachUpstreamDataService(ctx context.Context, simKey *appUpstreamSimKey, row *appCardQueryRow, res *model.AppDiagnosisInfoModel, update appDiagnosisUpdateFunc, setModule appDiagnosisSetModuleFunc) {
|
|
if appDiagnosisIsTelecomChannel(row.TempKey, row.ChanKey) {
|
|
return
|
|
}
|
|
items, err := queryAppUpstreamSimWithKey(ctx, simKey, "/sim/query/sim-apn-info", map[string]any{})
|
|
if err != nil {
|
|
g.Log().Warningf(ctx, "app diagnosis query sim data service failed, iccid:%s, err:%v", simKey.ICCID, err)
|
|
setModule("dataService", "error", err.Error(), false, "")
|
|
return
|
|
}
|
|
dataStatus := appDiagnosisDataServiceStatusFromItemsForChannel(items, row.TempKey, row.ChanKey)
|
|
if dataStatus != nil {
|
|
update(func() {
|
|
res.Info.DataStatus = dataStatus
|
|
if res.Card != nil {
|
|
if value, err := strconv.Atoi(*dataStatus); err == nil {
|
|
res.Card.DataServiceStatus = value
|
|
}
|
|
}
|
|
})
|
|
setModule("dataService", "success", "", false, "")
|
|
return
|
|
}
|
|
item := appDiagnosisFirstAvailableItem(items)
|
|
if item == nil {
|
|
setModule("dataService", "skipped", "上游数据服务未返回有效结果", false, "")
|
|
return
|
|
}
|
|
if item.Code == 5000 {
|
|
setModule("dataService", "skipped", appDiagnosisModuleMessage(item, "上游数据服务暂不支持"), false, "")
|
|
return
|
|
}
|
|
if item.Code != 0 {
|
|
status, message := appDiagnosisUpstreamFailure("上游数据服务查询失败", item.Code, item.Message)
|
|
setModule("dataService", status, message, false, "")
|
|
return
|
|
}
|
|
setModule("dataService", "skipped", "上游数据服务未返回有效状态", false, "")
|
|
}
|
|
|
|
func (s *sAppDiagnosis) attachUpstreamCardStatus(ctx context.Context, simKey *appUpstreamSimKey, res *model.AppDiagnosisInfoModel, update appDiagnosisUpdateFunc, setModule appDiagnosisSetModuleFunc) {
|
|
items, err := queryAppUpstreamSimWithKey(ctx, simKey, "/sim/query/sim-status", map[string]any{})
|
|
if err != nil {
|
|
g.Log().Warningf(ctx, "app diagnosis query sim card status failed, iccid:%s, err:%+v", simKey.ICCID, err)
|
|
setModule("cardStatus", "error", err.Error(), false, "")
|
|
return
|
|
}
|
|
item := appDiagnosisFirstAvailableItem(items)
|
|
if item == nil {
|
|
setModule("cardStatus", "skipped", "上游卡状态未返回有效结果", false, "")
|
|
return
|
|
}
|
|
if item.Code == 5000 {
|
|
setModule("cardStatus", "skipped", appDiagnosisModuleMessage(item, "上游卡状态暂不支持"), false, "")
|
|
return
|
|
}
|
|
if item.Code != 0 {
|
|
status, message := appDiagnosisUpstreamFailure("上游卡状态查询失败", item.Code, item.Message)
|
|
setModule("cardStatus", status, message, false, "")
|
|
return
|
|
}
|
|
if res.Info.DataServiceSource == "network_block" {
|
|
update(func() {
|
|
if appDiagnosisApplyNetworkBlock(res.Info, item.ResData) {
|
|
appSetDiagnosisModule(res, "dataService", "success", "", false, "")
|
|
} else {
|
|
appSetDiagnosisModule(res, "dataService", "skipped", "电信未返回有效断网状态", false, "")
|
|
}
|
|
})
|
|
}
|
|
status := appDiagnosisCardStatus(appDiagnosisRawString(item.ResData, "cardStatus", "card_status", "simStatus", "sim_status", "status"))
|
|
if status == nil {
|
|
setModule("cardStatus", "skipped", "上游卡状态未返回有效结果", false, "")
|
|
return
|
|
}
|
|
update(func() {
|
|
res.Info.Status = status
|
|
if res.Card != nil {
|
|
if value, err := strconv.Atoi(*status); err == nil {
|
|
res.Card.BossCardStatus = value
|
|
}
|
|
}
|
|
appSetDiagnosisModule(res, "cardStatus", "success", "", false, "")
|
|
})
|
|
}
|
|
|
|
func (s *sAppDiagnosis) attachUpstreamSession(ctx context.Context, simKey *appUpstreamSimKey, row *appCardQueryRow, res *model.AppDiagnosisInfoModel, update appDiagnosisUpdateFunc, setModule appDiagnosisSetModuleFunc) {
|
|
items, err := queryAppUpstreamSimWithKey(ctx, simKey, "/sim/query/sim-session", nil)
|
|
if err != nil {
|
|
setModule("session", "error", err.Error(), false, "")
|
|
return
|
|
}
|
|
item := appDiagnosisFirstAvailableItem(items)
|
|
if item == nil {
|
|
setModule("session", "skipped", "上游会话信息未返回有效结果", false, "")
|
|
return
|
|
}
|
|
if item.Code != 0 {
|
|
status, message := appDiagnosisUpstreamFailure("上游会话查询失败", item.Code, item.Message)
|
|
setModule("session", status, message, false, "")
|
|
return
|
|
}
|
|
session := &appOneLinkMockSimSessionItem{}
|
|
if err := json.Unmarshal(appDiagnosisUnwrapResData(item.ResData), session); err != nil {
|
|
setModule("session", "error", "会话响应格式无效", false, "")
|
|
return
|
|
}
|
|
appNormalizeUpstreamSession(session, item.ResData)
|
|
if session.isZero() {
|
|
setModule("session", "skipped", "上游会话信息未返回有效结果", false, "")
|
|
return
|
|
}
|
|
update(func() {
|
|
sessionStatus := appDiagnosisSessionStatus(session.Status)
|
|
res.Info.OnlineStatus = appDiagnosisOnlineStatus(sessionStatus)
|
|
res.Session.ApnId = appDiagnosisStringPtr(session.ApnId)
|
|
res.Session.Rat = appDiagnosisStringPtr(session.Rat)
|
|
res.Session.CreateTime = appDiagnosisStringPtr(session.CreateTime)
|
|
res.Session.Ipv4 = appDiagnosisStringPtr(session.Ipv4)
|
|
res.Session.Ipv6 = appDiagnosisStringPtr(session.Ipv6)
|
|
res.Session.Ipv6Prefix = appDiagnosisStringPtr(session.Ipv6Prefix)
|
|
res.Session.Status = appDiagnosisStringPtr(sessionStatus)
|
|
})
|
|
setModule("session", "success", "", false, "")
|
|
}
|
|
|
|
func (s *sAppDiagnosis) attachUpstreamLatestImei(ctx context.Context, simKey *appUpstreamSimKey, res *model.AppDiagnosisInfoModel, update appDiagnosisUpdateFunc, setModule appDiagnosisSetModuleFunc) {
|
|
items, err := queryAppUpstreamSimWithKey(ctx, simKey, "/sim/query/sim-imei", nil)
|
|
if err != nil {
|
|
setModule("imei", "error", err.Error(), false, "")
|
|
return
|
|
}
|
|
item := appDiagnosisFirstAvailableItem(items)
|
|
if item == nil {
|
|
setModule("imei", "skipped", "上游IMEI未返回有效结果", false, "")
|
|
return
|
|
}
|
|
if item.Code != 0 {
|
|
status, message := appDiagnosisUpstreamFailure("上游IMEI查询失败", item.Code, item.Message)
|
|
setModule("imei", status, message, false, "")
|
|
return
|
|
}
|
|
imei := appDiagnosisLatestImei(items)
|
|
if imei == "" {
|
|
setModule("imei", "skipped", "上游IMEI未返回有效结果", false, "")
|
|
return
|
|
}
|
|
update(func() { res.Session.Imei = appDiagnosisStringPtr(imei) })
|
|
setModule("imei", "success", "", false, "")
|
|
}
|
|
|
|
func appDiagnosisLatestImei(items []appUpstreamSimQueryItem) string {
|
|
for _, item := range items {
|
|
if item.Code != 0 {
|
|
continue
|
|
}
|
|
if imei := appDiagnosisRawString(item.ResData, "imei", "IMEI"); imei != "" {
|
|
return imei
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (s *sAppDiagnosis) attachUpstreamDeviceStatus(ctx context.Context, simKey *appUpstreamSimKey, row *appCardQueryRow, res *model.AppDiagnosisInfoModel, update appDiagnosisUpdateFunc, setModule appDiagnosisSetModuleFunc) {
|
|
items, err := queryAppUpstreamSimWithKey(ctx, simKey, "/sim/query/sim-device-status", map[string]any{})
|
|
if err != nil {
|
|
g.Log().Warningf(ctx, "app diagnosis query sim device status failed, iccid:%s, err:%+v", row.Iccid, err)
|
|
setModule("device", "error", err.Error(), false, "")
|
|
return
|
|
}
|
|
item := appDiagnosisFirstAvailableItem(items)
|
|
if item == nil {
|
|
setModule("device", "skipped", "上游设备状态未返回有效结果", false, "")
|
|
return
|
|
}
|
|
if item.Code == 5000 {
|
|
update(func() {
|
|
res.Info.DeviceStatus = nil
|
|
})
|
|
setModule("device", "skipped", appDiagnosisModuleMessage(item, "上游设备状态暂不支持"), false, "")
|
|
return
|
|
}
|
|
if item.Code != 0 {
|
|
status, message := appDiagnosisUpstreamFailure("上游设备状态查询失败", item.Code, item.Message)
|
|
setModule("device", status, message, false, "")
|
|
return
|
|
}
|
|
if appDiagnosisDeviceStatus(appDiagnosisRawString(item.ResData, "deviceStatus", "device_status")) == nil && appDiagnosisOperatingStatus(appDiagnosisRawString(item.ResData, "operatingStatus", "operating_status")) == nil {
|
|
setModule("device", "skipped", "上游设备状态未返回有效结果", false, "")
|
|
return
|
|
}
|
|
update(func() {
|
|
res.Info.DeviceStatus = appDiagnosisDeviceStatus(appDiagnosisRawString(item.ResData, "deviceStatus", "device_status"))
|
|
res.Info.OperatingStatus = appDiagnosisOperatingStatus(appDiagnosisRawString(item.ResData, "operatingStatus", "operating_status"))
|
|
})
|
|
setModule("device", "success", "", false, "")
|
|
}
|
|
|
|
func (s *sAppDiagnosis) attachUpstreamBindStatus(ctx context.Context, simKey *appUpstreamSimKey, res *model.AppDiagnosisInfoModel, update appDiagnosisUpdateFunc, setModule appDiagnosisSetModuleFunc) {
|
|
items, err := queryAppUpstreamSimWithKey(ctx, simKey, "/sim/query/sim-card-bind-status", map[string]any{"testType": 0})
|
|
if err != nil {
|
|
g.Log().Warningf(ctx, "app diagnosis query sim bind status failed, iccid:%s, err:%+v", simKey.ICCID, err)
|
|
setModule("cardBind", "error", err.Error(), false, "")
|
|
return
|
|
}
|
|
item := appDiagnosisFirstAvailableItem(items)
|
|
if item == nil {
|
|
setModule("cardBind", "skipped", "上游机卡绑定状态未返回有效结果", false, "")
|
|
return
|
|
}
|
|
if item.Code == 5000 {
|
|
update(func() {
|
|
res.Info.ImeiStatus = nil
|
|
})
|
|
setModule("cardBind", "skipped", appDiagnosisModuleMessage(item, "上游机卡绑定状态暂不支持"), false, "")
|
|
return
|
|
}
|
|
if item.Code != 0 {
|
|
g.Log().Warningf(ctx, "app diagnosis sim bind status returned error, iccid:%s, code:%d, message:%s", simKey.ICCID, item.Code, item.Message)
|
|
status, message := appDiagnosisUpstreamFailure("上游机卡绑定状态查询失败", item.Code, item.Message)
|
|
setModule("cardBind", status, message, false, "")
|
|
return
|
|
}
|
|
if appDiagnosisRawString(item.ResData, "orderStatus", "order_status") == "1" {
|
|
setModule("cardBind", "skipped", "该卡未订购机卡绑定服务", false, "")
|
|
return
|
|
}
|
|
if appDiagnosisRawString(item.ResData, "bindStatus", "bind_status", "cardBindStatus") == "2" {
|
|
message := appDiagnosisRawString(item.ResData, "errorDes", "error_des", "errorCode", "error_code")
|
|
if message == "" {
|
|
message = "上游机卡绑定状态查询失败"
|
|
}
|
|
setModule("cardBind", "error", message, false, "")
|
|
return
|
|
}
|
|
if appDiagnosisRawString(item.ResData, "bindStatus", "bind_status", "cardBindStatus") == "-" {
|
|
setModule("cardBind", "error", "上游机卡绑定网络查询失败", false, "")
|
|
return
|
|
}
|
|
if appDiagnosisRawString(item.ResData, "bindStatus", "bind_status", "cardBindStatus", "cardBindingType", "card_binding_type") == "" {
|
|
setModule("cardBind", "skipped", "上游机卡绑定状态未返回有效结果", false, "")
|
|
return
|
|
}
|
|
bindStatus := appDiagnosisBindStatus(
|
|
appDiagnosisRawString(item.ResData, "bindStatus", "bind_status", "cardBindStatus"),
|
|
appDiagnosisRawString(item.ResData, "cardBindingType", "card_binding_type"),
|
|
)
|
|
update(func() {
|
|
res.Info.ImeiStatus = appDiagnosisImeiStatus(bindStatus)
|
|
})
|
|
setModule("cardBind", "success", "", false, "")
|
|
}
|
|
|
|
func (s *sAppDiagnosis) attachUpstreamRegionLimitStatus(ctx context.Context, simKey *appUpstreamSimKey, tempKey string, chanKey string, res *model.AppDiagnosisInfoModel, update appDiagnosisUpdateFunc, setModule appDiagnosisSetModuleFunc) {
|
|
result, err := appQueryDiagnosisRegionLimit(ctx, simKey, appDiagnosisShouldCacheRegionLimit(tempKey, chanKey))
|
|
if err != nil {
|
|
g.Log().Warningf(ctx, "app diagnosis query sim region limit status failed, iccid:%s, err:%+v", simKey.ICCID, err)
|
|
setModule("region", "error", err.Error(), false, "")
|
|
return
|
|
}
|
|
if result == nil {
|
|
setModule("region", "skipped", "上游区域限制未返回有效结果", false, "")
|
|
return
|
|
}
|
|
item := result.Item
|
|
if item == nil {
|
|
setModule("region", "skipped", "上游区域限制未返回有效结果", result.Cached, result.CachedAt)
|
|
return
|
|
}
|
|
update(func() {
|
|
appDiagnosisApplyRegionLimit(res, item)
|
|
})
|
|
if result.Cached {
|
|
status, message := appCachedDiagnosisRegionModule(item)
|
|
setModule("region", status, message, true, result.CachedAt)
|
|
return
|
|
}
|
|
if appDiagnosisRegionLimitHasData(item) {
|
|
message := ""
|
|
if item.Code != 0 {
|
|
message = appDiagnosisModuleMessage(item, "区域限制查询已返回有效结果")
|
|
}
|
|
setModule("region", "success", message, false, result.CachedAt)
|
|
return
|
|
}
|
|
if item.Code == 5000 {
|
|
setModule("region", "skipped", appDiagnosisModuleMessage(item, "上游区域限制暂不支持"), false, "")
|
|
return
|
|
}
|
|
if item.Code != 0 {
|
|
status, message := appDiagnosisUpstreamFailure("上游区域限制查询失败", item.Code, item.Message)
|
|
setModule("region", status, message, false, "")
|
|
return
|
|
}
|
|
setModule("region", "error", "上游区域限制未返回有效数据", false, "")
|
|
}
|
|
|
|
func (s *sAppDiagnosis) attachUpstreamRateLimitStatus(ctx context.Context, simKey *appUpstreamSimKey, res *model.AppDiagnosisInfoModel, update appDiagnosisUpdateFunc, setModule appDiagnosisSetModuleFunc) {
|
|
items, err := queryAppUpstreamSimWithKey(ctx, simKey, "/sim/query/sim-data-diagnosis", map[string]any{})
|
|
if err != nil {
|
|
g.Log().Warningf(ctx, "app diagnosis query sim data diagnosis failed, iccid:%s, err:%+v", simKey.ICCID, err)
|
|
setModule("rateLimit", "error", err.Error(), false, "")
|
|
return
|
|
}
|
|
item := appDiagnosisFirstAvailableItem(items)
|
|
if item == nil {
|
|
setModule("rateLimit", "skipped", "上游达量限速未返回有效结果", false, "")
|
|
return
|
|
}
|
|
if item.Code == 0 {
|
|
item.ResData = appDiagnosisNormalizeRateLimitBody(item.ResData)
|
|
}
|
|
if item.Code == 0 && appDiagnosisRawString(item.ResData, "rateLimitStatus", "rate_limit_status", "rateLimitReason", "rateLimitDetail") == "" && len(appDiagnosisRateLimitApnDetails(item.ResData)) == 0 {
|
|
setModule("rateLimit", "skipped", "上游达量限速未返回有效结果", false, "")
|
|
return
|
|
}
|
|
update(func() {
|
|
appDiagnosisApplyRateLimit(res, item)
|
|
})
|
|
if item.Code == 5000 {
|
|
setModule("rateLimit", "skipped", appDiagnosisModuleMessage(item, "上游达量限速暂不支持"), false, "")
|
|
return
|
|
}
|
|
if item.Code != 0 {
|
|
status, message := appDiagnosisUpstreamFailure("上游达量限速查询失败", item.Code, item.Message)
|
|
setModule("rateLimit", status, message, false, "")
|
|
return
|
|
}
|
|
setModule("rateLimit", "success", "", false, "")
|
|
}
|
|
|
|
func (s *sAppDiagnosis) attachUpstreamStopReason(ctx context.Context, simKey *appUpstreamSimKey, res *model.AppDiagnosisInfoModel, update appDiagnosisUpdateFunc, setModule appDiagnosisSetModuleFunc) {
|
|
items, err := queryAppUpstreamSimWithKey(ctx, simKey, "/sim/query/sim-stop-reason", map[string]any{})
|
|
if err != nil {
|
|
g.Log().Warningf(ctx, "app diagnosis query sim stop reason failed, iccid:%s, err:%+v", simKey.ICCID, err)
|
|
setModule("stopReason", "error", err.Error(), false, "")
|
|
return
|
|
}
|
|
item := appDiagnosisFirstAvailableItem(items)
|
|
if item == nil {
|
|
setModule("stopReason", "skipped", "上游停机原因未返回有效结果", false, "")
|
|
return
|
|
}
|
|
if item.Code == 5000 {
|
|
setModule("stopReason", "skipped", appDiagnosisModuleMessage(item, "上游停机原因暂不支持"), false, "")
|
|
return
|
|
}
|
|
if item.Code != 0 {
|
|
status, message := appDiagnosisUpstreamFailure("上游停机原因查询失败", item.Code, item.Message)
|
|
setModule("stopReason", status, message, false, "")
|
|
return
|
|
}
|
|
update(func() {
|
|
res.Info.StopReason = appDiagnosisStringPtr(appDiagnosisNormalizeStopReason(appDiagnosisRawString(item.ResData, "stopReason", "stop_reason")))
|
|
})
|
|
setModule("stopReason", "success", "", false, "")
|
|
}
|
|
|
|
func (s *sAppDiagnosis) attachUpstreamFrozenState(ctx context.Context, simKey *appUpstreamSimKey, res *model.AppDiagnosisInfoModel, update appDiagnosisUpdateFunc, setModule appDiagnosisSetModuleFunc) {
|
|
items, err := queryAppUpstreamSimWithKey(ctx, simKey, "/sim/query/sim-frozen-state", map[string]any{})
|
|
if err != nil {
|
|
g.Log().Warningf(ctx, "app diagnosis query sim frozen state failed, iccid:%s, err:%+v", simKey.ICCID, err)
|
|
setModule("frozen", "error", err.Error(), false, "")
|
|
return
|
|
}
|
|
item := appDiagnosisFirstAvailableItem(items)
|
|
if item == nil {
|
|
setModule("frozen", "skipped", "上游冻结状态未返回有效结果", false, "")
|
|
return
|
|
}
|
|
if item.Code == 5000 {
|
|
setModule("frozen", "skipped", appDiagnosisModuleMessage(item, "上游冻结状态暂不支持"), false, "")
|
|
return
|
|
}
|
|
if item.Code != 0 {
|
|
status, message := appDiagnosisUpstreamFailure("上游冻结状态查询失败", item.Code, item.Message)
|
|
setModule("frozen", status, message, false, "")
|
|
return
|
|
}
|
|
if appDiagnosisRawString(item.ResData, "frozenStatus", "frozen_status") == "" {
|
|
setModule("frozen", "skipped", "上游冻结状态未返回有效结果", false, "")
|
|
return
|
|
}
|
|
update(func() {
|
|
res.Info.FrozenStatus = appDiagnosisStringPtr(appDiagnosisRawString(item.ResData, "frozenStatus", "frozen_status"))
|
|
res.Info.FrozenReason = appDiagnosisStringPtr(appDiagnosisRawString(item.ResData, "frozenReason", "frozen_reason"))
|
|
res.Info.FrozenOperTime = appDiagnosisStringPtr(appDiagnosisRawString(item.ResData, "operTime", "oper_time"))
|
|
})
|
|
setModule("frozen", "success", "", false, "")
|
|
}
|
|
|
|
func (s *sAppDiagnosis) attachUpstreamCurrentFlowUsage(ctx context.Context, simKey *appUpstreamSimKey, row *appCardQueryRow, res *model.AppDiagnosisInfoModel, update appDiagnosisUpdateFunc, setModule appDiagnosisSetModuleFunc) {
|
|
items, err := queryAppUpstreamSimWithKey(ctx, simKey, "/sim/query/sim-datausage", map[string]any{})
|
|
if err != nil {
|
|
g.Log().Warningf(ctx, "app diagnosis query current flow usage failed, iccid:%s, err:%+v", simKey.ICCID, err)
|
|
setModule("flowUsage", "error", "", false, "")
|
|
return
|
|
}
|
|
item := appDiagnosisFirstAvailableItem(items)
|
|
if item == nil {
|
|
setModule("flowUsage", "skipped", "上游实时流量未返回有效结果", false, "")
|
|
return
|
|
}
|
|
if item.Code == 5000 {
|
|
setModule("flowUsage", "skipped", appDiagnosisModuleMessage(item, "上游实时流量暂不支持"), false, "")
|
|
return
|
|
}
|
|
if item.Code != 0 {
|
|
status, message := appDiagnosisUpstreamFailure("上游实时流量查询失败", item.Code, item.Message)
|
|
setModule("flowUsage", status, message, false, "")
|
|
return
|
|
}
|
|
usedFlow, ok := appDiagnosisRawFloat(item.ResData, "dataUsage", "data_usage")
|
|
if !ok {
|
|
setModule("flowUsage", "skipped", "上游实时流量未返回有效结果", false, "")
|
|
return
|
|
}
|
|
if usedFlow < 0 || math.IsNaN(usedFlow) || math.IsInf(usedFlow, 0) {
|
|
setModule("flowUsage", "error", "实时流量未返回有效数值", false, "")
|
|
return
|
|
}
|
|
update(func() {
|
|
now := gtime.Now()
|
|
nowText := now.Format("Y-m-d H:i:s")
|
|
totalFlow := float64(0)
|
|
if res.Package != nil && res.Package.PackageTotalFlowMb != nil {
|
|
totalFlow = *res.Package.PackageTotalFlowMb
|
|
}
|
|
remainFlow := totalFlow - usedFlow
|
|
if remainFlow < 0 {
|
|
remainFlow = 0
|
|
}
|
|
if res.Package != nil {
|
|
res.Package.UsedFlowMb = &usedFlow
|
|
if totalFlow > 0 {
|
|
res.Package.RemainFlowMb = &remainFlow
|
|
} else {
|
|
res.Package.RemainFlowMb = nil
|
|
}
|
|
}
|
|
if res.Info != nil {
|
|
res.Info.UsedFlowMb = &usedFlow
|
|
if totalFlow > 0 {
|
|
res.Info.RemainFlowMb = &remainFlow
|
|
} else {
|
|
res.Info.RemainFlowMb = nil
|
|
}
|
|
res.Info.FlowSyncTime = &nowText
|
|
}
|
|
if res.Card != nil {
|
|
res.Card.UsedFlowMb = usedFlow
|
|
res.Card.RemainFlowMb = remainFlow
|
|
if totalFlow > 0 {
|
|
res.Card.UsedFlowPercent = usedFlow / totalFlow * 100
|
|
} else {
|
|
res.Card.UsedFlowPercent = 0
|
|
}
|
|
res.Card.FlowSyncTime = now
|
|
}
|
|
})
|
|
setModule("flowUsage", "success", "", false, "")
|
|
}
|
|
|
|
func appSetDiagnosisModule(res *model.AppDiagnosisInfoModel, key string, status string, message string, cached bool, updatedAt string) {
|
|
if res == nil {
|
|
return
|
|
}
|
|
if res.Modules == nil {
|
|
res.Modules = make(map[string]*model.AppDiagnosisModuleItem)
|
|
}
|
|
reason := appDiagnosisModuleReason(status, message)
|
|
message = appDiagnosisNormalizeCustomerMessage(message)
|
|
res.Modules[key] = &model.AppDiagnosisModuleItem{
|
|
Status: strings.TrimSpace(status),
|
|
Reason: reason,
|
|
Message: strings.TrimSpace(message),
|
|
Cached: cached,
|
|
UpdatedAt: updatedAt,
|
|
}
|
|
}
|
|
|
|
func appDiagnosisModuleReason(status string, message string) string {
|
|
status = strings.TrimSpace(status)
|
|
message = strings.TrimSpace(message)
|
|
switch {
|
|
case status == "error" && (strings.Contains(message, "60分钟") || strings.Contains(message, "频率限制")):
|
|
return "rate_limited"
|
|
case status == "error":
|
|
return "query_error"
|
|
case appDiagnosisIsUnsubscribedMessage(message):
|
|
return "not_subscribed"
|
|
case appDiagnosisIsUnsupportedCapabilityMessage(message):
|
|
return "unsupported"
|
|
case status == "skipped" && appDiagnosisIsNoDataMessage(message):
|
|
return "no_data"
|
|
case status == "skipped":
|
|
return "unsupported"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func appDiagnosisIsUnsubscribedMessage(message string) bool {
|
|
message = strings.TrimSpace(message)
|
|
return strings.Contains(message, "未订购") ||
|
|
strings.Contains(message, "接口未开通") ||
|
|
strings.Contains(message, "能力未开通") ||
|
|
strings.Contains(message, "服务未开通") ||
|
|
strings.Contains(message, "未开通该接口") ||
|
|
strings.Contains(message, "通道未申请")
|
|
}
|
|
|
|
func appDiagnosisIsUnsupportedCapabilityMessage(message string) bool {
|
|
message = strings.TrimSpace(message)
|
|
return strings.Contains(message, "暂不提供") ||
|
|
strings.Contains(message, "暂未提供") ||
|
|
strings.Contains(message, "未提供独立") ||
|
|
strings.Contains(message, "不支持查询") ||
|
|
strings.Contains(message, "运营商暂不支持") ||
|
|
strings.Contains(message, "暂未提供当前达量限速查询能力") ||
|
|
strings.Contains(message, "不提供查询能力")
|
|
}
|
|
|
|
func appDiagnosisIsNoDataMessage(message string) bool {
|
|
message = strings.TrimSpace(message)
|
|
return strings.Contains(message, "未返回") ||
|
|
strings.Contains(message, "未查询到") ||
|
|
strings.Contains(message, "无数据") ||
|
|
strings.Contains(message, "暂无数据")
|
|
}
|
|
|
|
func appDiagnosisModuleMessage(item *appUpstreamSimQueryItem, fallback string) string {
|
|
if item == nil || strings.TrimSpace(item.Message) == "" {
|
|
return fallback
|
|
}
|
|
return strings.TrimSpace(item.Message)
|
|
}
|
|
|
|
func appDiagnosisRegionCacheKey(simKey *appUpstreamSimKey) string {
|
|
if simKey == nil {
|
|
return "iotlcm:sim-diagnose:region:unknown"
|
|
}
|
|
parts := []string{
|
|
appDiagnosisSafeCachePart(simKey.ChanKey),
|
|
appDiagnosisSafeCachePart(simKey.ICCID),
|
|
appDiagnosisSafeCachePart(simKey.IMSI),
|
|
appDiagnosisSafeCachePart(simKey.MSISDN),
|
|
}
|
|
return "iotlcm:sim-diagnose:region:" + strings.Join(parts, ":")
|
|
}
|
|
|
|
func appDiagnosisSafeCachePart(value string) string {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return "-"
|
|
}
|
|
replacer := strings.NewReplacer(":", "_", "\n", "_", "\r", "_", "\t", "_", " ", "_")
|
|
return replacer.Replace(value)
|
|
}
|
|
|
|
func appDiagnosisShouldCacheRegionLimit(tempKey string, chanKey string) bool {
|
|
value := strings.ToLower(strings.TrimSpace(tempKey + " " + chanKey))
|
|
return strings.Contains(value, "sim_yd") ||
|
|
strings.Contains(value, "yd_mock") ||
|
|
strings.Contains(value, "onelink") ||
|
|
strings.Contains(value, "shared_token")
|
|
}
|
|
|
|
func appQueryDiagnosisRegionLimit(ctx context.Context, simKey *appUpstreamSimKey, useCache bool) (*appDiagnosisRegionQueryResult, error) {
|
|
if !useCache {
|
|
return appFetchDiagnosisRegionLimit(ctx, simKey)
|
|
}
|
|
if cached, cachedAt, ok := appGetCachedDiagnosisRegionLimit(ctx, simKey); ok {
|
|
return &appDiagnosisRegionQueryResult{Item: cached, CachedAt: cachedAt, Cached: true}, nil
|
|
}
|
|
|
|
value, err, _ := appDiagnosisRegionFlight.Do(appDiagnosisRegionCacheKey(simKey), func() (any, error) {
|
|
if cached, cachedAt, ok := appGetCachedDiagnosisRegionLimit(ctx, simKey); ok {
|
|
return &appDiagnosisRegionQueryResult{Item: cached, CachedAt: cachedAt, Cached: true}, nil
|
|
}
|
|
return appQueryDiagnosisRegionLimitWithLock(ctx, simKey)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result, ok := value.(*appDiagnosisRegionQueryResult)
|
|
if !ok || result == nil {
|
|
return nil, gerror.New("区域限制查询未返回有效结果")
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func appQueryDiagnosisRegionLimitWithLock(ctx context.Context, simKey *appUpstreamSimKey) (*appDiagnosisRegionQueryResult, error) {
|
|
lockKey := appDiagnosisRegionCacheKey(simKey) + ":lock"
|
|
lockValue := uuid32.S()
|
|
acquired, err := appAcquireDiagnosisRegionLock(ctx, lockKey, lockValue)
|
|
if err != nil {
|
|
g.Log().Warningf(ctx, "app diagnosis acquire region cache lock failed, key:%s, err:%+v", lockKey, err)
|
|
return appFetchAndCacheDiagnosisRegionLimit(ctx, simKey)
|
|
}
|
|
if acquired {
|
|
defer appReleaseDiagnosisRegionLock(lockKey, lockValue)
|
|
if cached, cachedAt, ok := appGetCachedDiagnosisRegionLimit(ctx, simKey); ok {
|
|
return &appDiagnosisRegionQueryResult{Item: cached, CachedAt: cachedAt, Cached: true}, nil
|
|
}
|
|
return appFetchAndCacheDiagnosisRegionLimit(ctx, simKey)
|
|
}
|
|
|
|
deadline := time.NewTimer(appDiagnosisRegionLockWait)
|
|
ticker := time.NewTicker(appDiagnosisRegionLockPoll)
|
|
defer deadline.Stop()
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
case <-deadline.C:
|
|
return nil, gerror.New("区域限制查询正在处理中,请稍后重试")
|
|
case <-ticker.C:
|
|
if cached, cachedAt, ok := appGetCachedDiagnosisRegionLimit(ctx, simKey); ok {
|
|
return &appDiagnosisRegionQueryResult{Item: cached, CachedAt: cachedAt, Cached: true}, nil
|
|
}
|
|
acquired, lockErr := appAcquireDiagnosisRegionLock(ctx, lockKey, lockValue)
|
|
if lockErr != nil {
|
|
g.Log().Warningf(ctx, "app diagnosis retry region cache lock failed, key:%s, err:%+v", lockKey, lockErr)
|
|
continue
|
|
}
|
|
if acquired {
|
|
defer appReleaseDiagnosisRegionLock(lockKey, lockValue)
|
|
return appFetchAndCacheDiagnosisRegionLimit(ctx, simKey)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func appFetchDiagnosisRegionLimit(ctx context.Context, simKey *appUpstreamSimKey) (*appDiagnosisRegionQueryResult, error) {
|
|
items, err := queryAppUpstreamSimWithKey(ctx, simKey, "/sim/query/sim-region-limit-status", map[string]any{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
item := appDiagnosisFirstAvailableItem(items)
|
|
if item == nil {
|
|
return nil, nil
|
|
}
|
|
return &appDiagnosisRegionQueryResult{
|
|
Item: item,
|
|
CachedAt: gtime.Now().Format("Y-m-d H:i:s"),
|
|
}, nil
|
|
}
|
|
|
|
func appFetchAndCacheDiagnosisRegionLimit(ctx context.Context, simKey *appUpstreamSimKey) (*appDiagnosisRegionQueryResult, error) {
|
|
result, err := appFetchDiagnosisRegionLimit(ctx, simKey)
|
|
if err != nil || result == nil || result.Item == nil {
|
|
return result, err
|
|
}
|
|
if appDiagnosisRegionLimitHasData(result.Item) {
|
|
appSetCachedDiagnosisRegionLimit(ctx, simKey, *result.Item, result.CachedAt)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func appAcquireDiagnosisRegionLock(ctx context.Context, key string, value string) (bool, error) {
|
|
res, err := g.Redis().Do(ctx, "SET", key, value, "NX", "PX", appDiagnosisRegionLockTTL.Milliseconds())
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return strings.EqualFold(strings.TrimSpace(res.String()), "OK"), nil
|
|
}
|
|
|
|
func appReleaseDiagnosisRegionLock(key string, value string) {
|
|
const script = `if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end`
|
|
releaseCtx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
defer cancel()
|
|
if _, err := g.Redis().Do(releaseCtx, "EVAL", script, 1, key, value); err != nil {
|
|
g.Log().Warningf(releaseCtx, "app diagnosis release region cache lock failed, key:%s, err:%+v", key, err)
|
|
}
|
|
}
|
|
|
|
func appGetCachedDiagnosisRegionLimit(ctx context.Context, simKey *appUpstreamSimKey) (*appUpstreamSimQueryItem, string, bool) {
|
|
cacheKey := appDiagnosisRegionCacheKey(simKey)
|
|
value, err := g.Redis().Get(ctx, cacheKey)
|
|
if err != nil || value == nil || strings.TrimSpace(value.String()) == "" {
|
|
return nil, "", false
|
|
}
|
|
var payload appDiagnosisRegionCachePayload
|
|
if err = json.Unmarshal([]byte(value.String()), &payload); err != nil {
|
|
g.Log().Warningf(ctx, "app diagnosis unmarshal region cache failed, key:%s, err:%+v", cacheKey, err)
|
|
return nil, "", false
|
|
}
|
|
if !appDiagnosisRegionLimitHasData(&payload.Item) {
|
|
if _, err = g.Redis().Do(ctx, "DEL", cacheKey); err != nil {
|
|
g.Log().Warningf(ctx, "app diagnosis delete invalid region cache failed, key:%s, err:%+v", cacheKey, err)
|
|
}
|
|
return nil, "", false
|
|
}
|
|
return &payload.Item, payload.CachedAt, true
|
|
}
|
|
|
|
func appSetCachedDiagnosisRegionLimit(ctx context.Context, simKey *appUpstreamSimKey, item appUpstreamSimQueryItem, cachedAt string) {
|
|
if strings.TrimSpace(cachedAt) == "" {
|
|
cachedAt = gtime.Now().Format("Y-m-d H:i:s")
|
|
}
|
|
payload := appDiagnosisRegionCachePayload{
|
|
Item: item,
|
|
CachedAt: cachedAt,
|
|
}
|
|
raw, err := json.Marshal(payload)
|
|
if err != nil {
|
|
g.Log().Warningf(ctx, "app diagnosis marshal region cache failed, iccid:%s, err:%+v", appDiagnosisSimKeyLogIccid(simKey), err)
|
|
return
|
|
}
|
|
if _, err = g.Redis().Do(ctx, "SETEX", appDiagnosisRegionCacheKey(simKey), int(appDiagnosisRegionCacheTTL.Seconds()), string(raw)); err != nil {
|
|
g.Log().Warningf(ctx, "app diagnosis set region cache failed, iccid:%s, err:%+v", appDiagnosisSimKeyLogIccid(simKey), err)
|
|
}
|
|
}
|
|
|
|
func appCachedDiagnosisRegionModule(item *appUpstreamSimQueryItem) (status string, message string) {
|
|
if item == nil {
|
|
return "error", "区域限制结果来自缓存,但缓存内容为空"
|
|
}
|
|
message = strings.TrimSpace(item.Message)
|
|
if message == "" {
|
|
message = "区域限制结果来自缓存"
|
|
} else {
|
|
message = "区域限制结果来自缓存:" + message
|
|
}
|
|
if appDiagnosisRegionLimitHasData(item) {
|
|
return "cached", message
|
|
}
|
|
switch item.Code {
|
|
case 0:
|
|
return "error", "区域限制结果来自缓存,但缓存内容为空"
|
|
case 5000:
|
|
return "skipped", message
|
|
default:
|
|
return "error", message
|
|
}
|
|
}
|
|
|
|
func appDiagnosisApplyRegionLimit(res *model.AppDiagnosisInfoModel, item *appUpstreamSimQueryItem) {
|
|
if res == nil || res.Info == nil || item == nil {
|
|
return
|
|
}
|
|
hasData := appDiagnosisRegionLimitHasData(item)
|
|
if item.Code == 5000 && !hasData {
|
|
res.Info.RegionLimitStatus = nil
|
|
return
|
|
}
|
|
if item.Code != 0 && !hasData {
|
|
return
|
|
}
|
|
res.Info.RegionLimitStatus = appDiagnosisRegionLimitStatus(appDiagnosisRawString(item.ResData, "regionLimitStatus", "region_limit_status"))
|
|
res.Info.RegionLimitOrderStatus = appDiagnosisStringPtr(appDiagnosisRawString(item.ResData, "orderStatus", "order_status"))
|
|
res.Info.RegionLimitTriggerStatus = appDiagnosisStringPtr(appDiagnosisRawString(item.ResData, "limitStatus", "limit_status"))
|
|
res.Info.RegionLimitType = appDiagnosisFirstStringPtr(
|
|
appDiagnosisRawString(item.ResData, "limitType", "limit_type"),
|
|
appDiagnosisPtrValue(res.Info.RegionLimitType),
|
|
)
|
|
res.Info.RegionLimitArea = appDiagnosisFirstStringPtr(
|
|
appDiagnosisRawString(item.ResData, "limitArea", "limit_area"),
|
|
appDiagnosisPtrValue(res.Info.RegionLimitArea),
|
|
)
|
|
}
|
|
|
|
func appDiagnosisRegionLimitHasData(item *appUpstreamSimQueryItem) bool {
|
|
if item == nil {
|
|
return false
|
|
}
|
|
for _, value := range []string{
|
|
appDiagnosisRawString(item.ResData, "regionLimitStatus", "region_limit_status"),
|
|
appDiagnosisRawString(item.ResData, "orderStatus", "order_status"),
|
|
appDiagnosisRawString(item.ResData, "limitStatus", "limit_status"),
|
|
appDiagnosisRawString(item.ResData, "limitType", "limit_type"),
|
|
appDiagnosisRawString(item.ResData, "limitArea", "limit_area"),
|
|
} {
|
|
if strings.TrimSpace(value) != "" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func appDiagnosisApplyRateLimit(res *model.AppDiagnosisInfoModel, item *appUpstreamSimQueryItem) {
|
|
if res == nil || res.Info == nil || item == nil {
|
|
return
|
|
}
|
|
if item.Code == 5000 {
|
|
res.Info.RateLimitStatus = nil
|
|
res.Info.RateLimitReason = nil
|
|
res.Info.RateLimitDetail = nil
|
|
res.Info.RateLimitApnDetails = nil
|
|
return
|
|
}
|
|
if item.Code != 0 {
|
|
return
|
|
}
|
|
item.ResData = appDiagnosisNormalizeRateLimitBody(item.ResData)
|
|
res.Info.RateLimitStatus = appDiagnosisRateLimitStatus(appDiagnosisRawString(item.ResData, "rateLimitStatus", "rate_limit_status"))
|
|
res.Info.RateLimitReason = appDiagnosisStringPtr(appDiagnosisRawString(item.ResData, "rateLimitReason", "rate_limit_reason"))
|
|
res.Info.RateLimitDetail = appDiagnosisStringPtr(appDiagnosisRawString(item.ResData, "rateLimitDetail", "rate_limit_detail"))
|
|
res.Info.RateLimitApnDetails = appDiagnosisRateLimitApnDetails(item.ResData)
|
|
}
|
|
|
|
func appDiagnosisIsMobileChannel(tempKey string, chanKey string) bool {
|
|
value := strings.ToLower(strings.TrimSpace(tempKey + " " + chanKey))
|
|
return strings.Contains(value, "sim_yd") || strings.Contains(value, "yd_mock")
|
|
}
|
|
|
|
func appDiagnosisIsTelecomChannel(tempKey string, chanKey string) bool {
|
|
value := strings.ToLower(strings.TrimSpace(tempKey + " " + chanKey))
|
|
return strings.Contains(value, "sim_dx") || strings.Contains(value, "dx_")
|
|
}
|
|
|
|
func appDiagnosisNormalizeStopReason(stopReason string) string {
|
|
switch strings.TrimSpace(stopReason) {
|
|
case "", "000000000000":
|
|
return "未停机或系统暂无停机原因"
|
|
default:
|
|
return strings.TrimSpace(stopReason)
|
|
}
|
|
}
|
|
|
|
func appDiagnosisFirstAvailableItem(items []appUpstreamSimQueryItem) *appUpstreamSimQueryItem {
|
|
if len(items) == 0 {
|
|
return nil
|
|
}
|
|
return &items[0]
|
|
}
|
|
|
|
func appDiagnosisRateLimitApnDetails(raw json.RawMessage) []model.AppDiagnosisRateLimitApnItem {
|
|
raw = appDiagnosisUnwrapResData(raw)
|
|
if len(raw) == 0 || string(raw) == "null" {
|
|
return nil
|
|
}
|
|
payload := struct {
|
|
ApnDetails []model.AppDiagnosisRateLimitApnItem `json:"apnDetails"`
|
|
}{}
|
|
if err := json.Unmarshal(raw, &payload); err != nil || len(payload.ApnDetails) == 0 {
|
|
return nil
|
|
}
|
|
details := make([]model.AppDiagnosisRateLimitApnItem, 0, len(payload.ApnDetails))
|
|
for _, apn := range payload.ApnDetails {
|
|
item := model.AppDiagnosisRateLimitApnItem{
|
|
ApnName: strings.TrimSpace(apn.ApnName),
|
|
ApnStatus: strings.TrimSpace(apn.ApnStatus),
|
|
ModifyTime: strings.TrimSpace(apn.ModifyTime),
|
|
OverUseDetails: strings.TrimSpace(apn.OverUseDetails),
|
|
ApnServiceUsageState: strings.TrimSpace(apn.ApnServiceUsageState),
|
|
}
|
|
for _, pcc := range apn.PccDetails {
|
|
pccItem := model.AppDiagnosisRateLimitPccItem{
|
|
PccServiceCode: strings.TrimSpace(pcc.PccServiceCode),
|
|
PccName: strings.TrimSpace(pcc.PccName),
|
|
PccPolicyStatus: strings.TrimSpace(pcc.PccPolicyStatus),
|
|
PccServiceUsageState: strings.TrimSpace(pcc.PccServiceUsageState),
|
|
ModifyTime: strings.TrimSpace(pcc.ModifyTime),
|
|
}
|
|
if appDiagnosisHasRateLimitPccDetail(pccItem) {
|
|
item.PccDetails = append(item.PccDetails, pccItem)
|
|
}
|
|
}
|
|
if appDiagnosisHasRateLimitApnDetail(item) {
|
|
details = append(details, item)
|
|
}
|
|
}
|
|
return details
|
|
}
|
|
|
|
func appDiagnosisUnwrapResData(raw json.RawMessage) json.RawMessage {
|
|
if len(raw) == 0 || string(raw) == "null" {
|
|
return nil
|
|
}
|
|
wrapper := struct {
|
|
ResData json.RawMessage `json:"resData"`
|
|
}{}
|
|
if err := json.Unmarshal(raw, &wrapper); err == nil && len(wrapper.ResData) > 0 && string(wrapper.ResData) != "null" {
|
|
return wrapper.ResData
|
|
}
|
|
return raw
|
|
}
|
|
|
|
func appDiagnosisHasRateLimitApnDetail(item model.AppDiagnosisRateLimitApnItem) bool {
|
|
return appDiagnosisFirstNonEmptyString(
|
|
item.ApnName,
|
|
item.ApnStatus,
|
|
item.ModifyTime,
|
|
item.OverUseDetails,
|
|
item.ApnServiceUsageState,
|
|
) != "" || len(item.PccDetails) > 0
|
|
}
|
|
|
|
func appDiagnosisHasRateLimitPccDetail(item model.AppDiagnosisRateLimitPccItem) bool {
|
|
return appDiagnosisFirstNonEmptyString(
|
|
item.PccServiceCode,
|
|
item.PccName,
|
|
item.PccPolicyStatus,
|
|
item.PccServiceUsageState,
|
|
item.ModifyTime,
|
|
) != ""
|
|
}
|
|
|
|
func appDiagnosisRawString(raw json.RawMessage, keys ...string) string {
|
|
if len(raw) == 0 || string(raw) == "null" {
|
|
return ""
|
|
}
|
|
var payload map[string]any
|
|
if err := json.Unmarshal(raw, &payload); err != nil || len(payload) == 0 {
|
|
return ""
|
|
}
|
|
if nested, ok := payload["resData"]; ok && nested != nil {
|
|
if nestedRaw, err := json.Marshal(nested); err == nil {
|
|
if value := appDiagnosisRawString(nestedRaw, keys...); value != "" {
|
|
return value
|
|
}
|
|
}
|
|
}
|
|
for _, key := range keys {
|
|
if value, ok := payload[key]; ok && value != nil {
|
|
text := strings.TrimSpace(fmt.Sprint(value))
|
|
if text != "" && text != "<nil>" {
|
|
return text
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func appDiagnosisRawFloat(raw json.RawMessage, keys ...string) (float64, bool) {
|
|
if len(raw) == 0 || string(raw) == "null" {
|
|
return 0, false
|
|
}
|
|
var payload map[string]any
|
|
if err := json.Unmarshal(raw, &payload); err != nil || len(payload) == 0 {
|
|
return 0, false
|
|
}
|
|
if nested, ok := payload["resData"]; ok && nested != nil {
|
|
if nestedRaw, err := json.Marshal(nested); err == nil {
|
|
if value, ok := appDiagnosisRawFloat(nestedRaw, keys...); ok {
|
|
return value, true
|
|
}
|
|
}
|
|
}
|
|
for _, key := range keys {
|
|
value, ok := payload[key]
|
|
if !ok || value == nil {
|
|
continue
|
|
}
|
|
switch typed := value.(type) {
|
|
case float64:
|
|
return typed, true
|
|
case float32:
|
|
return float64(typed), true
|
|
case int:
|
|
return float64(typed), true
|
|
case int64:
|
|
return float64(typed), true
|
|
case json.Number:
|
|
parsed, err := typed.Float64()
|
|
return parsed, err == nil
|
|
default:
|
|
text := strings.TrimSpace(fmt.Sprint(value))
|
|
if text == "" || text == "<nil>" {
|
|
continue
|
|
}
|
|
parsed, err := strconv.ParseFloat(text, 64)
|
|
return parsed, err == nil
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func appDiagnosisFirstNonEmptyString(values ...string) string {
|
|
for _, value := range values {
|
|
if strings.TrimSpace(value) != "" {
|
|
return strings.TrimSpace(value)
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func appDiagnosisSimKeyLogIccid(simKey *appUpstreamSimKey) string {
|
|
if simKey == nil {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(simKey.ICCID)
|
|
}
|
|
|
|
func appDiagnosisStringPtr(value string) *string {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return nil
|
|
}
|
|
return &value
|
|
}
|
|
|
|
func appDiagnosisFirstStringPtr(values ...string) *string {
|
|
for _, value := range values {
|
|
if ptr := appDiagnosisStringPtr(value); ptr != nil {
|
|
return ptr
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func appDiagnosisPtrValue(value *string) string {
|
|
if value == nil {
|
|
return ""
|
|
}
|
|
return *value
|
|
}
|
|
|
|
func appDiagnosisTimeString(value *gtime.Time) *string {
|
|
if value == nil || value.IsZero() {
|
|
return nil
|
|
}
|
|
text := value.Format("Y-m-d H:i:s")
|
|
return &text
|
|
}
|
|
|
|
func appDiagnosisDurationMonths(begin, end *gtime.Time) *int {
|
|
if begin == nil || begin.IsZero() || end == nil || end.IsZero() {
|
|
return nil
|
|
}
|
|
beginTime := begin.Time
|
|
endTime := end.Time
|
|
months := (endTime.Year()-beginTime.Year())*12 + int(endTime.Month()-beginTime.Month())
|
|
if endTime.Day() >= beginTime.Day()-1 {
|
|
months++
|
|
}
|
|
if months < 0 {
|
|
return nil
|
|
}
|
|
return &months
|
|
}
|
|
|
|
func appDiagnosisDataServiceStatus(status string) *string {
|
|
var value string
|
|
switch strings.ToUpper(strings.TrimSpace(status)) {
|
|
case "0", "OFF", "CLOSED", "DISABLED":
|
|
value = "0"
|
|
case "1", "ON", "OPEN", "ENABLED":
|
|
value = "1"
|
|
case "-1", "2", "UNKNOWN":
|
|
value = "-1"
|
|
default:
|
|
return nil
|
|
}
|
|
return &value
|
|
}
|
|
|
|
func appDiagnosisDataServiceStatusFromItems(items []appUpstreamSimQueryItem) *string {
|
|
for _, item := range items {
|
|
if item.Code == 0 {
|
|
if status := appDiagnosisDataServiceStatusFromApn(item.ResData); status != nil {
|
|
return status
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func appDiagnosisDataServiceStatusFromItemsForChannel(items []appUpstreamSimQueryItem, tempKey string, chanKey string) *string {
|
|
mobile := appDiagnosisIsMobileChannel(tempKey, chanKey)
|
|
for _, item := range items {
|
|
if item.Code == 0 {
|
|
if status := appDiagnosisDataServiceStatusFromApnForChannel(item.ResData, mobile); status != nil {
|
|
return status
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
func appDiagnosisDataServiceStatusFromApn(raw json.RawMessage) *string {
|
|
return appDiagnosisDataServiceStatusFromApnForChannel(raw, true)
|
|
}
|
|
|
|
func appDiagnosisDataServiceStatusFromApnForChannel(raw json.RawMessage, mobile bool) *string {
|
|
if mobile {
|
|
return appDiagnosisDataServiceStatusFromApnTarget(raw)
|
|
}
|
|
return appDiagnosisDataServiceStatusFromApnAny(raw)
|
|
}
|
|
|
|
func appDiagnosisDataServiceStatusFromApnTarget(raw json.RawMessage) *string {
|
|
return appDiagnosisDataServiceStatusFromApnFiltered(raw, true)
|
|
}
|
|
|
|
func appDiagnosisDataServiceStatusFromApnAny(raw json.RawMessage) *string {
|
|
return appDiagnosisDataServiceStatusFromApnFiltered(raw, false)
|
|
}
|
|
|
|
func appDiagnosisDataServiceStatusFromApnFiltered(raw json.RawMessage, targetOnly bool) *string {
|
|
if len(raw) == 0 || string(raw) == "null" {
|
|
return nil
|
|
}
|
|
var payload struct {
|
|
ApnList []struct {
|
|
ApnName string `json:"apnName"`
|
|
Status string `json:"status"`
|
|
} `json:"apnList"`
|
|
}
|
|
if err := json.Unmarshal(appDiagnosisUnwrapResData(raw), &payload); err != nil {
|
|
return nil
|
|
}
|
|
var selected *string
|
|
for _, apn := range payload.ApnList {
|
|
isTarget := strings.EqualFold(strings.TrimSpace(apn.ApnName), consts.SimCardDataApnName)
|
|
if targetOnly && !isTarget {
|
|
continue
|
|
}
|
|
status := strings.TrimSpace(apn.Status)
|
|
if strings.TrimSpace(apn.ApnName) == "" || (status != "0" && status != "1") {
|
|
return nil
|
|
}
|
|
if selected != nil && *selected != status {
|
|
return nil
|
|
}
|
|
selected = appDiagnosisStringPtr(status)
|
|
}
|
|
return selected
|
|
}
|
|
func appDiagnosisOnlineStatus(status string) *string {
|
|
var value string
|
|
switch strings.ToUpper(strings.TrimSpace(status)) {
|
|
case "0", "01", "ONLINE":
|
|
value = "1"
|
|
case "-1", "00", "OFFLINE":
|
|
value = "0"
|
|
case "-2", "02", "2", "UNKNOWN":
|
|
value = "2"
|
|
default:
|
|
value = "2"
|
|
}
|
|
return &value
|
|
}
|
|
|
|
func appDiagnosisSessionStatus(status string) string {
|
|
switch strings.ToUpper(strings.TrimSpace(status)) {
|
|
case "0", "01", "ONLINE":
|
|
return "0"
|
|
case "-1", "00", "OFFLINE":
|
|
return "-1"
|
|
case "-2", "02", "2", "UNKNOWN":
|
|
return "-2"
|
|
default:
|
|
return "-2"
|
|
}
|
|
}
|
|
|
|
func appDiagnosisDeviceStatus(status string) *string {
|
|
var value string
|
|
switch strings.TrimSpace(status) {
|
|
case "0":
|
|
value = "0"
|
|
case "1":
|
|
value = "1"
|
|
default:
|
|
return nil
|
|
}
|
|
return &value
|
|
}
|
|
|
|
func appDiagnosisOperatingStatus(status string) *string {
|
|
switch strings.TrimSpace(status) {
|
|
case "0":
|
|
value := "0"
|
|
return &value
|
|
case "1":
|
|
value := "1"
|
|
return &value
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func appDiagnosisImeiStatus(status string) *string {
|
|
var value string
|
|
switch strings.TrimSpace(status) {
|
|
case "0":
|
|
value = "0"
|
|
case "1":
|
|
value = "1"
|
|
case "2":
|
|
value = "2"
|
|
default:
|
|
value = "2"
|
|
}
|
|
return &value
|
|
}
|
|
|
|
func appDiagnosisBindStatus(bindStatus string, cardBindingType string) string {
|
|
switch strings.TrimSpace(bindStatus) {
|
|
case "0", "1", "2":
|
|
return strings.TrimSpace(bindStatus)
|
|
}
|
|
switch strings.TrimSpace(cardBindingType) {
|
|
case "1", "2", "3", "4", "5":
|
|
return "1"
|
|
case "", "-1":
|
|
return "2"
|
|
default:
|
|
return "2"
|
|
}
|
|
}
|
|
|
|
func appDiagnosisCardStatus(status string) *string {
|
|
switch strings.TrimSpace(status) {
|
|
case "":
|
|
return nil
|
|
case "0", "未知":
|
|
return appDiagnosisCardStatusPtr(0)
|
|
case "1", "待激活":
|
|
return appDiagnosisCardStatusPtr(1)
|
|
case "2", "已激活", "正常", "在用":
|
|
return appDiagnosisCardStatusPtr(2)
|
|
case "4", "停机", "已停机":
|
|
return appDiagnosisCardStatusPtr(4)
|
|
case "6", "可测试", "测试期":
|
|
return appDiagnosisCardStatusPtr(6)
|
|
case "7", "库存":
|
|
return appDiagnosisCardStatusPtr(7)
|
|
case "8", "预销户":
|
|
return appDiagnosisCardStatusPtr(8)
|
|
case "9", "已销户", "销户":
|
|
return appDiagnosisCardStatusPtr(9)
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func appDiagnosisCardStatusPtr(status int) *string {
|
|
if status < 0 {
|
|
return nil
|
|
}
|
|
value := strconv.Itoa(status)
|
|
return &value
|
|
}
|
|
|
|
func appDiagnosisRegionLimitStatus(status string) *string {
|
|
var value string
|
|
switch strings.TrimSpace(status) {
|
|
case "0":
|
|
value = "0"
|
|
case "1":
|
|
value = "1"
|
|
default:
|
|
value = "2"
|
|
}
|
|
return &value
|
|
}
|
|
|
|
func appDiagnosisUpstreamFailure(prefix string, code int, message string) (status string, displayMessage string) {
|
|
if code == 5000 || appDiagnosisIsUnsupportedMessage(message) {
|
|
return "skipped", message
|
|
}
|
|
if appDiagnosisIsConnectivityMessage(message) {
|
|
return "error", ""
|
|
}
|
|
return "error", appDiagnosisRetryMessage
|
|
}
|
|
|
|
func appDiagnosisUpstreamMessage(prefix string, code int, message string) string {
|
|
message = strings.TrimSpace(message)
|
|
if message != "" {
|
|
if appDiagnosisIsUnsupportedMessage(message) {
|
|
return ""
|
|
}
|
|
if normalized := appDiagnosisNormalizeCustomerMessage(message); normalized != "" {
|
|
return normalized
|
|
}
|
|
}
|
|
prefix = strings.TrimSpace(prefix)
|
|
if prefix == "" {
|
|
prefix = "上游查询失败"
|
|
}
|
|
if appDiagnosisIsUnsupportedMessage(prefix) {
|
|
return ""
|
|
}
|
|
return appDiagnosisNormalizeCustomerMessage(prefix)
|
|
}
|
|
|
|
const appDiagnosisRetryMessage = "服务繁忙,请重试"
|
|
const appDiagnosisMissingMSISDNMessage = "机卡绑定信息暂不可查"
|
|
|
|
func appDiagnosisIsUnsupportedMessage(message string) bool {
|
|
return appDiagnosisIsUnsubscribedMessage(message) || appDiagnosisIsUnsupportedCapabilityMessage(message)
|
|
}
|
|
|
|
func appDiagnosisNormalizeCustomerMessage(message string) string {
|
|
if strings.Contains(message, "同一卡号60分钟") {
|
|
return "区域限制查询受运营商频率限制,暂未取得有效结果,请在上次查询60分钟后再试"
|
|
}
|
|
message = strings.TrimSpace(message)
|
|
if message == "" {
|
|
return ""
|
|
}
|
|
lower := strings.ToLower(message)
|
|
switch {
|
|
case appDiagnosisIsUnsupportedMessage(message):
|
|
return ""
|
|
case strings.Contains(lower, "missing msisdn"):
|
|
return appDiagnosisMissingMSISDNMessage
|
|
case appDiagnosisIsConnectivityMessage(message):
|
|
return appDiagnosisRetryMessage
|
|
case strings.Contains(message, "上游"),
|
|
strings.Contains(message, "缓存"),
|
|
strings.Contains(message, "60分钟"),
|
|
strings.Contains(message, "未返回有效结果"),
|
|
strings.Contains(message, "通道未实例化"):
|
|
return appDiagnosisRetryMessage
|
|
default:
|
|
return message
|
|
}
|
|
}
|
|
|
|
func appDiagnosisIsConnectivityMessage(message string) bool {
|
|
lower := strings.ToLower(strings.TrimSpace(message))
|
|
return strings.Contains(lower, "context deadline exceeded") ||
|
|
strings.Contains(lower, "client.timeout") ||
|
|
strings.Contains(lower, "dial tcp") ||
|
|
strings.Contains(lower, "connection refused") ||
|
|
strings.Contains(lower, "request failed") ||
|
|
strings.Contains(lower, "timeout") ||
|
|
strings.Contains(lower, "no such host") ||
|
|
strings.Contains(lower, "network is unreachable") ||
|
|
strings.Contains(lower, "connection reset") ||
|
|
strings.Contains(lower, "http://") ||
|
|
strings.Contains(lower, "https://")
|
|
}
|
|
|
|
func appDiagnosisHumanLimitStatus(status string) *string {
|
|
var value string
|
|
switch strings.TrimSpace(status) {
|
|
case "1":
|
|
value = "0"
|
|
case "0":
|
|
value = "1"
|
|
case "2":
|
|
value = "2"
|
|
case "3":
|
|
value = "3"
|
|
default:
|
|
value = "2"
|
|
}
|
|
return &value
|
|
}
|
|
|
|
func appDiagnosisRateLimitStatus(status string) *string {
|
|
var value string
|
|
switch strings.TrimSpace(status) {
|
|
case "0":
|
|
value = "0"
|
|
case "1":
|
|
value = "1"
|
|
case "3":
|
|
value = "3"
|
|
default:
|
|
value = "2"
|
|
}
|
|
return &value
|
|
}
|
|
|
|
func appDiagnosisPackageEffectiveStatus(status int) *string {
|
|
var value string
|
|
switch status {
|
|
case consts.PackageStatusPending:
|
|
value = "0"
|
|
case consts.PackageStatusEffective:
|
|
value = "1"
|
|
case consts.PackageStatusExpired:
|
|
value = "2"
|
|
default:
|
|
return nil
|
|
}
|
|
return &value
|
|
}
|