2ed5dfa6a1
iotlcm-app:小程序端 + 用户端服务。 - app/:uni-app 小程序前端(基于 unibest,MIT) - 微信 AppID → YOUR_WX_APPID,uni-app AppID → __UNI__YOURAPPID - 内网后端地址 → admin.iotlcm.example.com - 应用标题与 unibest 模板 README 重写(保留上游致谢与 MIT 声明) - server/:GoFrame 用户端服务(基于 XiuAdmin,Apache-2.0) - module xiuadmin → git.5iot.com/iotlcm-public/iotlcm-app(360 文件) - 支付回调地址 → app.iotlcm.example.com,商户名与应用名泛化 - 真实手机号 → 合法 11 位编造号(保留号段前 3 位 + 后 4 位) - 依赖号码尾部的断言同步更新 - 补 server/LICENSE(Apache-2.0) - 移除内部资产:CI 触发器 - 补根 README,LF 行尾 + .gitattributes 已知:TestSysDept_RefreshDeptAncestors 在源仓同一提交上同样失败,属既有缺陷。
1539 lines
50 KiB
Go
1539 lines
50 KiB
Go
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 != "<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 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
|
|
}
|