package app import ( "context" "encoding/json" "fmt" "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" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" ) type sAppDiagnosis struct{} const ( appDiagnosisRegionCacheTTL = time.Hour appOpenDiagnosisRateWindow = time.Minute appOpenDiagnosisRateLimit = 20 ) 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"` } 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{} } if err = appEnsureFeatureAccess(ctx, "card:diagnosis"); err != nil { return nil, err } _, cardLogic, row, err := s.resolveDiagnosisCard(ctx, param.QueryCode) if err != nil { return nil, err } return s.buildDiagnosisInfo(ctx, row, cardLogic.publicCardRowToModel(ctx, row, param.QueryCode)) } 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("卡片信息为空,不能诊断") } 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) } } totalFlow := row.PackageTotalFlowMb usedFlow := row.UsedFlowMb remainFlow := totalFlow - usedFlow if remainFlow < 0 { remainFlow = 0 } packageType := fmt.Sprintf("%d", appCardPackageType(row)) hasLocalCard := row.Id > 0 var status *string var dataStatus *string var packageTotalFlowMb *float64 var infoUsedFlowMb *float64 var infoRemainFlowMb *float64 if hasLocalCard { statusValue := fmt.Sprintf("%d", row.BossCardStatus) dataStatusValue := fmt.Sprintf("%d", row.DataServiceStatus) status = &statusValue dataStatus = &dataStatusValue packageTotalFlowMb = &totalFlow infoUsedFlowMb = &usedFlow infoRemainFlowMb = &remainFlow } var packageInfo *model.AppDiagnosisPackageItem if hasLocalCard { packageInfo = &model.AppDiagnosisPackageItem{ PackageName: appDiagnosisStringPtr(appCardPackageName(row)), PackageType: appDiagnosisStringPtr(packageType), EffectiveStatus: appDiagnosisPackageEffectiveStatus(row.PackageStatus), PackageTotalFlowMb: &totalFlow, UsedFlowMb: &usedFlow, RemainFlowMb: &remainFlow, DurationMonths: appDiagnosisDurationMonths(row.FlowPkgBeginAt, row.FlowPkgEndAt), ExpectedExpireTime: appDiagnosisTimeString(row.FlowPkgEndAt), } } res = &model.AppDiagnosisInfoModel{ Info: &model.AppDiagnosisInfoItem{ Iccid: appDiagnosisStringPtr(row.Iccid), Imsi: appDiagnosisStringPtr(row.Imsi), Msisdn: appDiagnosisStringPtr(row.Msisdn), Status: status, DataStatus: dataStatus, PackageTotalFlowMb: packageTotalFlowMb, UsedFlowMb: infoUsedFlowMb, RemainFlowMb: infoRemainFlowMb, }, Session: &model.AppDiagnosisSessionItem{}, Package: packageInfo, Modules: make(map[string]*model.AppDiagnosisModuleItem), Card: cardModel, } appSetDiagnosisModule(res, "card", "success", "", false, "") if hasLocalCard { appSetDiagnosisModule(res, "package", "success", "", false, "") } else { appSetDiagnosisModule(res, "package", "skipped", "本地卡表暂无套餐信息", false, "") } if appDiagnosisIsMobileChannel(row.TempKey, row.ChanKey) { res.Info.HumanLimitStatus = appDiagnosisStringPtr("3") } 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) }) } runDiagnose := func(wg *sync.WaitGroup, fn func()) { wg.Add(1) go func() { defer wg.Done() fn() }() } var wg sync.WaitGroup 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.attachUpstreamDeviceStatus(ctx, simKey, row, res, updateDiagnosis, setModule) }) runDiagnose(&wg, func() { s.attachUpstreamBindStatus(ctx, simKey, res, updateDiagnosis, setModule) }) runDiagnose(&wg, func() { s.attachUpstreamRegionLimitStatus(ctx, simKey, 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, res, updateDiagnosis, setModule) }) wg.Wait() 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{} } return s.OpenFlow(ctx, &model.AppOpenDiagnosisFlowParam{ QueryCode: param.QueryCode, QueryType: 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.Imei = 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.Imei = "" 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("卡片不存在") } 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("卡片不存在") } 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("卡片不存在") } 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) { if cardLogic == nil { return nil, nil } queryCode = strings.TrimSpace(queryCode) if queryCode == "" { return nil, nil } info, err := cardLogic.findAssocCardChannelInfo(ctx, tenantId, queryCode) if err != nil { return nil, gerror.Wrap(err, operation+"失败") } if info == nil || strings.TrimSpace(info.ChanKey) == "" { return nil, nil } return &appCardQueryRow{ TSimCardInfo: entity.TSimCardInfo{ TenantId: tenantId, Iccid: queryCode, ChanKey: info.ChanKey, }, ChanName: info.ChanName, ChanAlias: info.ChanAlias, TempKey: info.TempKey, }, nil } func (s *sAppDiagnosis) resolveDiagnosisCardChannel(ctx context.Context, cardLogic *sAppCard, row *appCardQueryRow, queryCode string, operation string) error { if cardLogic == nil || row == nil { return nil } applyInfo := func(info *appCardChannelInfo) bool { if info == nil || strings.TrimSpace(info.ChanKey) == "" { return false } row.ChanKey = info.ChanKey row.ChanName = info.ChanName row.ChanAlias = info.ChanAlias row.TempKey = info.TempKey return true } var assocErr error for _, value := range []string{queryCode, row.Iccid} { value = strings.TrimSpace(value) if value == "" { continue } info, err := cardLogic.findAssocCardChannelInfo(ctx, row.TenantId, value) if err != nil { g.Log().Warningf(ctx, "app diagnosis resolve assoc channel failed, value:%s, err:%+v", appDiagnosisMaskIdentifier(value), err) if assocErr == nil { assocErr = err } continue } if applyInfo(info) { return nil } } if strings.TrimSpace(row.ChanKey) != "" { return nil } if assocErr != nil { return gerror.Wrap(assocErr, operation+"失败") } return cardLogic.attachDiagnosisCardChannelInfo(ctx, row) } func (s *sAppDiagnosis) prepareDiagnosisSimKey(ctx context.Context, row *appCardQueryRow) (*appUpstreamSimKey, error) { simKey, err := appUpstreamSimKeyFromRow(row) if err != nil { return nil, err } if 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) 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 status := appDiagnosisCardStatus(appDiagnosisRawString(item.ResData, "simStatus", "sim_status", "cardStatus", "card_status", "status")); status != nil { res.Info.Status = status } 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) 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 } 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 } } }) setModule("cardStatus", "success", "", false, "") } func (s *sAppDiagnosis) attachUpstreamSession(ctx context.Context, simKey *appUpstreamSimKey, row *appCardQueryRow, res *model.AppDiagnosisInfoModel, update appDiagnosisUpdateFunc, setModule appDiagnosisSetModuleFunc) { session, source, err := queryAppCardRealtimeSessionWithKey(ctx, row, simKey) if err != nil { g.Log().Warningf(ctx, "app diagnosis query sim session failed, iccid:%s, err:%+v", row.Iccid, err) setModule("session", "error", err.Error(), false, "") return } if source == "onelink_mock" { g.Log().Debugf(ctx, "app diagnosis sim session loaded from onelink mock, iccid:%s", row.Iccid) } if session == nil || 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.Imei = appDiagnosisStringPtr(session.Imei) 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) }) message := "" if source == "onelink_mock" { message = "已使用OneLink挡板会话结果" } setModule("session", "success", message, false, "") } 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 } 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 { status, message := appDiagnosisUpstreamFailure("上游机卡绑定状态查询失败", item.Code, item.Message) setModule("cardBind", status, message, 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, res *model.AppDiagnosisInfoModel, update appDiagnosisUpdateFunc, setModule appDiagnosisSetModuleFunc) { if cachedRegion, cachedAt, ok := appGetCachedDiagnosisRegionLimit(ctx, simKey); ok { update(func() { appDiagnosisApplyRegionLimit(res, cachedRegion) }) status, message := appCachedDiagnosisRegionModule(cachedRegion) setModule("region", status, message, true, cachedAt) return } items, err := queryAppUpstreamSimWithKey(ctx, simKey, "/sim/query/sim-region-limit-status", map[string]any{}) 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 } item := appDiagnosisFirstAvailableItem(items) if item == nil { setModule("region", "skipped", "上游区域限制未返回有效结果", false, "") return } update(func() { appDiagnosisApplyRegionLimit(res, item) }) appSetCachedDiagnosisRegionLimit(ctx, simKey, *item) if appDiagnosisRegionLimitHasData(item) { message := "" if item.Code != 0 { message = appDiagnosisModuleMessage(item, "区域限制查询已返回有效结果") } setModule("region", "success", message, false, "") 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 } 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 } 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, 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", err.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 { usedFlow = 0 } 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) } message = appDiagnosisNormalizeCustomerMessage(message) res.Modules[key] = &model.AppDiagnosisModuleItem{ Status: strings.TrimSpace(status), Message: strings.TrimSpace(message), Cached: cached, UpdatedAt: updatedAt, } } 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 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 } return &payload.Item, payload.CachedAt, true } func appSetCachedDiagnosisRegionLimit(ctx context.Context, simKey *appUpstreamSimKey, item appUpstreamSimQueryItem) { payload := appDiagnosisRegionCachePayload{ Item: item, CachedAt: gtime.Now().Format("Y-m-d H:i:s"), } 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 } 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 != "" { 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 == "" { 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 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", "" } return "error", appDiagnosisUpstreamMessage(prefix, code, message) } 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 = "查询暂时未完成,请稍后重试" func appDiagnosisIsUnsupportedMessage(message string) bool { message = strings.TrimSpace(message) if message == "" { return false } return strings.Contains(message, "暂不提供") || strings.Contains(message, "暂未提供") || strings.Contains(message, "未提供独立") || strings.Contains(message, "不支持查询") || strings.Contains(message, "运营商暂不支持") || strings.Contains(message, "暂未提供当前达量限速查询能力") || strings.Contains(message, "不提供查询能力") } func appDiagnosisNormalizeCustomerMessage(message string) string { message = strings.TrimSpace(message) if message == "" { return "" } lower := strings.ToLower(message) switch { case appDiagnosisIsUnsupportedMessage(message): return "" case 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, "token"), strings.Contains(lower, "http://"), strings.Contains(lower, "https://"): 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 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 }