Files
iotlcm/server/internal/logic/app/app_diagnosis.go
T

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
}