1625 lines
56 KiB
Go
1625 lines
56 KiB
Go
package iot
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||
upstreamlogic "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/upstream"
|
||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/uuid32"
|
||
"sort"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/gogf/gf/v2/errors/gerror"
|
||
"github.com/gogf/gf/v2/frame/g"
|
||
"github.com/gogf/gf/v2/os/gtime"
|
||
"golang.org/x/sync/singleflight"
|
||
)
|
||
|
||
type sIotTSimDiagnose struct{}
|
||
|
||
type tSimDiagnoseCardInfo struct {
|
||
Iccid string `json:"iccid"`
|
||
Imsi string `json:"imsi"`
|
||
Msisdn string `json:"msisdn"`
|
||
ChanKey string `json:"chanKey"`
|
||
TempKey string `json:"tempKey"`
|
||
BossCardStatus int `json:"bossCardStatus"`
|
||
DataServiceStatus int `json:"dataServiceStatus"`
|
||
PackageStatus int `json:"packageStatus"`
|
||
PackageTotalFlowMb float64 `json:"packageTotalFlowMb"`
|
||
UsedFlowMb float64 `json:"usedFlowMb"`
|
||
FlowPkgBeginAt *gtime.Time `json:"flowPkgBeginAt"`
|
||
FlowPkgEndAt *gtime.Time `json:"flowPkgEndAt"`
|
||
FlowSyncTime *gtime.Time `json:"flowSyncTime"`
|
||
PackageName string `json:"packageName"`
|
||
PackageType string `json:"packageType"`
|
||
HasLocalCard bool `json:"hasLocalCard"`
|
||
}
|
||
|
||
const (
|
||
simDiagnoseRegionCacheTTL = time.Hour
|
||
simDiagnoseRegionLockTTL = 15 * time.Second
|
||
simDiagnoseRegionLockWait = 12 * time.Second
|
||
simDiagnoseRegionLockPoll = 100 * time.Millisecond
|
||
)
|
||
|
||
var simDiagnoseRegionFlight singleflight.Group
|
||
|
||
type simDiagnoseRegionCachePayload struct {
|
||
Item model.SimBaseRes[model.SimRegionLimitStatusItem] `json:"item"`
|
||
CachedAt string `json:"cachedAt"`
|
||
}
|
||
|
||
type simDiagnoseRegionQueryResult struct {
|
||
Item *model.SimBaseRes[model.SimRegionLimitStatusItem]
|
||
CachedAt string
|
||
Cached bool
|
||
}
|
||
|
||
func NewIotTSimDiagnose() *sIotTSimDiagnose {
|
||
return &sIotTSimDiagnose{}
|
||
}
|
||
|
||
func init() {
|
||
service.RegisterIotTSimDiagnose(NewIotTSimDiagnose())
|
||
}
|
||
|
||
// resolveDiagnoseCardInfo 查询诊断用卡信息,并优先使用卡通道关联表修正上游通道。
|
||
func (s *sIotTSimDiagnose) resolveDiagnoseCardInfo(ctx context.Context, queryCode string, operation string) (*tSimDiagnoseCardInfo, error) {
|
||
queryCode = strings.TrimSpace(queryCode)
|
||
if queryCode == "" {
|
||
return nil, gerror.New(operation + "失败,请确认查询码是否正确")
|
||
}
|
||
|
||
assoc, err := resolveCardChannelAssocByIccid(ctx, queryCode)
|
||
if err != nil {
|
||
return nil, gerror.Wrap(err, operation+"失败,请稍后重试")
|
||
}
|
||
var assocDisplay cardChannelDisplayRecord
|
||
var assocDisplayErr error
|
||
hasAssoc := false
|
||
if assoc != nil {
|
||
assocDisplay, hasAssoc, assocDisplayErr = resolveDiagnoseAssocChannelDisplay(ctx, assoc, operation)
|
||
if assocDisplayErr != nil {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.resolveDiagnoseCardInfo, resolve assoc channel failed, queryCode %s, channelKey %s, err %+v", queryCode, assoc.ChannelKey, assocDisplayErr)
|
||
}
|
||
}
|
||
|
||
cardInfoTable := dao.TSimCardInfo.Table()
|
||
channelTable := dao.TSimChannels.Table()
|
||
flowPackageTable := dao.TSimFlowPackageInfo.Table()
|
||
cardValueCondition := "(" + cardInfoTable + "." + dao.TSimCardInfo.Columns().Msisdn + " = ? OR " +
|
||
cardInfoTable + "." + dao.TSimCardInfo.Columns().Iccid + " = ? OR " +
|
||
cardInfoTable + "." + dao.TSimCardInfo.Columns().Imsi + " = ?)"
|
||
|
||
var cardInfo *tSimDiagnoseCardInfo
|
||
err = service.IotTSimCardInfo().
|
||
Model(ctx).
|
||
LeftJoin(channelTable, channelTable+"."+dao.TSimChannels.Columns().ChanKey+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().ChanKey+" AND "+channelTable+"."+dao.TSimChannels.Columns().TenantId+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().TenantId).
|
||
LeftJoin(flowPackageTable, flowPackageTable+"."+dao.TSimFlowPackageInfo.Columns().PackageCode+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().CurrentPackageCode+" AND "+flowPackageTable+"."+dao.TSimFlowPackageInfo.Columns().TenantId+"="+cardInfoTable+"."+dao.TSimCardInfo.Columns().TenantId).
|
||
Where(cardValueCondition, queryCode, queryCode, queryCode).
|
||
Fields(
|
||
cardInfoTable+"."+dao.TSimCardInfo.Columns().Iccid+" as iccid",
|
||
cardInfoTable+"."+dao.TSimCardInfo.Columns().Imsi+" as imsi",
|
||
cardInfoTable+"."+dao.TSimCardInfo.Columns().Msisdn+" as msisdn",
|
||
cardInfoTable+"."+dao.TSimCardInfo.Columns().ChanKey+" as chanKey",
|
||
channelTable+"."+dao.TSimChannels.Columns().TempKey+" as tempKey",
|
||
cardInfoTable+"."+dao.TSimCardInfo.Columns().BossCardStatus+" as bossCardStatus",
|
||
cardInfoTable+"."+dao.TSimCardInfo.Columns().DataServiceStatus+" as dataServiceStatus",
|
||
cardInfoTable+"."+dao.TSimCardInfo.Columns().PackageStatus+" as packageStatus",
|
||
cardInfoTable+"."+dao.TSimCardInfo.Columns().PackageTotalFlowMb+" as packageTotalFlowMb",
|
||
cardInfoTable+"."+dao.TSimCardInfo.Columns().UsedFlowMb+" as usedFlowMb",
|
||
cardInfoTable+"."+dao.TSimCardInfo.Columns().FlowPkgBeginAt+" as flowPkgBeginAt",
|
||
cardInfoTable+"."+dao.TSimCardInfo.Columns().FlowPkgEndAt+" as flowPkgEndAt",
|
||
cardInfoTable+"."+dao.TSimCardInfo.Columns().FlowSyncTime+" as flowSyncTime",
|
||
"COALESCE(NULLIF("+flowPackageTable+"."+dao.TSimFlowPackageInfo.Columns().PackageName+", ''), "+cardInfoTable+"."+dao.TSimCardInfo.Columns().CurrentPackageCode+") as packageName",
|
||
flowPackageTable+"."+dao.TSimFlowPackageInfo.Columns().PackageType+" as packageType",
|
||
).
|
||
Limit(1).
|
||
Scan(&cardInfo)
|
||
if err != nil {
|
||
return nil, gerror.Wrap(err, operation+"失败,请稍后重试")
|
||
}
|
||
if cardInfo == nil {
|
||
if hasAssoc {
|
||
cardInfo = &tSimDiagnoseCardInfo{
|
||
Iccid: queryCode,
|
||
ChanKey: assocDisplay.ChanKey,
|
||
TempKey: assocDisplay.TempKey,
|
||
}
|
||
} else {
|
||
if assocDisplayErr != nil {
|
||
return nil, assocDisplayErr
|
||
}
|
||
cardInfo, err = resolveLegacyDiagnoseCardInfo(ctx, queryCode)
|
||
if err != nil {
|
||
return nil, gerror.Wrap(err, operation+"失败")
|
||
}
|
||
if cardInfo == nil {
|
||
return nil, gerror.New(operation + "失败,请确认查询码是否正确")
|
||
}
|
||
}
|
||
} else {
|
||
cardInfo.HasLocalCard = true
|
||
if !hasAssoc && strings.TrimSpace(cardInfo.Iccid) != "" {
|
||
localAssoc, resolveErr := resolveCardChannelAssocByIccid(ctx, cardInfo.Iccid)
|
||
if resolveErr != nil {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.resolveDiagnoseCardInfo, resolve card channel failed, iccid %s, err %+v", cardInfo.Iccid, resolveErr)
|
||
} else if display, ok, displayErr := resolveDiagnoseAssocChannelDisplay(ctx, localAssoc, operation); displayErr != nil {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.resolveDiagnoseCardInfo, resolve local assoc channel failed, iccid %s, err %+v", cardInfo.Iccid, displayErr)
|
||
if assocDisplayErr == nil {
|
||
assocDisplayErr = displayErr
|
||
}
|
||
} else if ok {
|
||
assocDisplay = display
|
||
hasAssoc = true
|
||
}
|
||
}
|
||
if hasAssoc {
|
||
cardInfo.ChanKey = assocDisplay.ChanKey
|
||
cardInfo.TempKey = assocDisplay.TempKey
|
||
}
|
||
}
|
||
if strings.TrimSpace(cardInfo.ChanKey) == "" {
|
||
if assocDisplayErr != nil {
|
||
return nil, assocDisplayErr
|
||
}
|
||
return nil, gerror.New(operation + "失败,未找到卡对应的上游通道")
|
||
}
|
||
return cardInfo, nil
|
||
}
|
||
|
||
func resolveDiagnoseAssocChannelDisplay(ctx context.Context, assoc *cardChannelAssocRecord, operation string) (cardChannelDisplayRecord, bool, error) {
|
||
if assoc == nil {
|
||
return cardChannelDisplayRecord{}, false, nil
|
||
}
|
||
display, ok, err := resolveCardChannelDisplayByAssoc(ctx, assoc, nil)
|
||
if err != nil {
|
||
return cardChannelDisplayRecord{}, false, gerror.Wrap(err, operation+"失败,请稍后重试")
|
||
}
|
||
if !ok || strings.TrimSpace(display.ChanKey) == "" {
|
||
return cardChannelDisplayRecord{}, false, gerror.New(operation + "失败,卡通道关联已匹配,但未找到对应通道配置")
|
||
}
|
||
return display, true, nil
|
||
}
|
||
|
||
// Info 查询SIM卡诊断信息
|
||
func (s *sIotTSimDiagnose) Info(ctx context.Context, in *model.TSimDiagnoseInfoReq) (res *model.TSimDiagnoseInfoOutput, err error) {
|
||
cardInfo, err := s.resolveDiagnoseCardInfo(ctx, in.QueryCode, "查询SIM卡诊断信息")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if in.OnlyFlow {
|
||
return s.buildDiagnosisCurrentFlow(ctx, cardInfo), nil
|
||
}
|
||
return s.buildDiagnosisInfo(ctx, cardInfo)
|
||
}
|
||
|
||
func (s *sIotTSimDiagnose) buildDiagnosisCurrentFlow(ctx context.Context, cardInfo *tSimDiagnoseCardInfo) *model.TSimDiagnoseInfoOutput {
|
||
res := &model.TSimDiagnoseInfoOutput{
|
||
Info: &model.TSimDiagnoseInfoItem{
|
||
Iccid: emptyStringPtr(cardInfo.Iccid), Imsi: emptyStringPtr(cardInfo.Imsi), Msisdn: emptyStringPtr(cardInfo.Msisdn),
|
||
},
|
||
Modules: make(map[string]*model.TSimDiagnoseModuleItem),
|
||
}
|
||
diagCtx := upstreamlogic.WithPreservedUpstreamResults(upstreamlogic.WithDiagnoseUpstreamConfig(ctx))
|
||
if strings.TrimSpace(cardInfo.Imsi) == "" || strings.TrimSpace(cardInfo.Msisdn) == "" {
|
||
threeCodes, threeErr := resolveSimKeyThreeCodesFromUpstream(ctx, diagCtx, cardInfo.ChanKey, "iccid", cardInfo.Iccid)
|
||
if threeErr != nil {
|
||
setDiagnoseModule(res, "flowUsage", "error", threeErr.Error(), false, "")
|
||
return res
|
||
}
|
||
if threeCodes != nil {
|
||
cardInfo.Imsi = strings.TrimSpace(threeCodes.IMSI)
|
||
cardInfo.Msisdn = strings.TrimSpace(threeCodes.MSISDN)
|
||
res.Info.Imsi = emptyStringPtr(cardInfo.Imsi)
|
||
res.Info.Msisdn = emptyStringPtr(cardInfo.Msisdn)
|
||
}
|
||
}
|
||
s.applyDiagnoseCurrentFlowUsage(ctx, diagCtx, res, cardInfo.ChanKey)
|
||
return res
|
||
}
|
||
|
||
func (s *sIotTSimDiagnose) buildDiagnosisInfo(ctx context.Context, cardInfo *tSimDiagnoseCardInfo) (res *model.TSimDiagnoseInfoOutput, err error) {
|
||
res = &model.TSimDiagnoseInfoOutput{
|
||
Info: &model.TSimDiagnoseInfoItem{
|
||
Iccid: emptyStringPtr(cardInfo.Iccid),
|
||
Imsi: emptyStringPtr(cardInfo.Imsi),
|
||
Msisdn: emptyStringPtr(cardInfo.Msisdn),
|
||
Status: nil,
|
||
DataStatus: nil,
|
||
UsedFlowMb: nil,
|
||
FlowSyncTime: nil,
|
||
},
|
||
Session: &model.TSimDiagnoseSessionItem{},
|
||
Modules: make(map[string]*model.TSimDiagnoseModuleItem),
|
||
}
|
||
res.Info.DataServiceSource = "apn"
|
||
if isTelecomDiagnoseChannel(cardInfo.TempKey, cardInfo.ChanKey) {
|
||
res.Info.DataServiceSource = "network_block"
|
||
}
|
||
applyDiagnoseRemainFlow(res.Info)
|
||
setDiagnoseModule(res, "card", "success", "", false, "")
|
||
|
||
if isMobileDiagnoseChannel(cardInfo.TempKey, cardInfo.ChanKey) {
|
||
res.Info.HumanLimitStatus = nil
|
||
}
|
||
if isTelecomDiagnoseChannel(cardInfo.TempKey, cardInfo.ChanKey) {
|
||
res.Info.RateLimitStatus = nil
|
||
res.Info.RateLimitReason = nil
|
||
}
|
||
|
||
simKey := &model.SimKey{
|
||
ChanKey: cardInfo.ChanKey,
|
||
ICCID: cardInfo.Iccid,
|
||
IMSI: cardInfo.Imsi,
|
||
MSISDN: cardInfo.Msisdn,
|
||
}
|
||
diagCtx := upstreamlogic.WithPreservedUpstreamResults(upstreamlogic.WithDiagnoseUpstreamConfig(ctx))
|
||
|
||
var mu sync.Mutex
|
||
updateDiagnose := func(fn func()) {
|
||
mu.Lock()
|
||
defer mu.Unlock()
|
||
fn()
|
||
}
|
||
setModule := func(key string, status string, message string, cached bool, updatedAt string) {
|
||
updateDiagnose(func() {
|
||
setDiagnoseModule(res, key, status, message, cached, updatedAt)
|
||
if key == "cardStatus" && res.Info.DataServiceSource == "network_block" && status != "success" && status != "cached" {
|
||
setDiagnoseModule(res, "dataService", status, message, cached, updatedAt)
|
||
}
|
||
})
|
||
}
|
||
setUpstreamSuccess := func(key string, cached bool, updatedAt string) {
|
||
status, message := diagnoseCachedModule(cached)
|
||
setModule(key, status, message, cached, updatedAt)
|
||
}
|
||
|
||
// 本地卡信息缺失 MSISDN/IMSI(如仅通过号段关联命中通道、未录入本地卡表)时,
|
||
// 先用 ICCID 调运营商接口补全三码,避免后续诊断因缺码而全部返回“码号信息错误”。
|
||
if strings.TrimSpace(simKey.MSISDN) == "" || strings.TrimSpace(simKey.IMSI) == "" {
|
||
cardValueType, cardValue := diagnoseLookupCardValue(simKey)
|
||
if cardValueType == "" {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.Info, no valid card identifier for three-code lookup, chanKey %s", cardInfo.ChanKey)
|
||
} else if threeCodes, threeErr := resolveSimKeyThreeCodesFromUpstream(ctx, diagCtx, cardInfo.ChanKey, cardValueType, cardValue); threeErr != nil {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.Info, resolve three codes failed, iccid %s, chanKey %s, err %+v", cardInfo.Iccid, cardInfo.ChanKey, threeErr)
|
||
} else if threeCodes != nil {
|
||
simKey.ICCID = strings.TrimSpace(threeCodes.ICCID)
|
||
simKey.IMSI = strings.TrimSpace(threeCodes.IMSI)
|
||
simKey.MSISDN = strings.TrimSpace(threeCodes.MSISDN)
|
||
updateDiagnose(func() {
|
||
if value := emptyStringPtr(simKey.ICCID); value != nil {
|
||
res.Info.Iccid = value
|
||
}
|
||
if value := emptyStringPtr(simKey.IMSI); value != nil {
|
||
res.Info.Imsi = value
|
||
}
|
||
if value := emptyStringPtr(simKey.MSISDN); value != nil {
|
||
res.Info.Msisdn = value
|
||
}
|
||
})
|
||
if cardInfo != nil {
|
||
cardInfo.Iccid = simKey.ICCID
|
||
cardInfo.Imsi = simKey.IMSI
|
||
cardInfo.Msisdn = simKey.MSISDN
|
||
}
|
||
}
|
||
}
|
||
|
||
runDiagnose := func(wg *sync.WaitGroup, fn func()) {
|
||
wg.Add(1)
|
||
go func() {
|
||
defer wg.Done()
|
||
fn()
|
||
}()
|
||
}
|
||
|
||
var wg sync.WaitGroup
|
||
runDiagnose(&wg, func() {
|
||
if res.Info.DataServiceSource == "network_block" {
|
||
return
|
||
}
|
||
apnOut, queryErr := service.UpstreamChan().QuerySimApnInfo(diagCtx, &model.SimApnInfoInput{
|
||
SimKeys: []*model.SimKey{simKey},
|
||
ReqData: model.SimInputReqNullDate{},
|
||
})
|
||
if queryErr != nil {
|
||
setModule("dataService", "error", queryErr.Error(), false, "")
|
||
return
|
||
}
|
||
if len(apnOut) == 0 {
|
||
setModule("dataService", "error", "上游数据服务未返回有效结果", false, "")
|
||
return
|
||
}
|
||
for _, item := range apnOut {
|
||
if item.Code != 0 {
|
||
continue
|
||
}
|
||
if dataStatus := diagnoseDataServiceStatusForChannel(item.ResData.ApnList, cardInfo.TempKey, cardInfo.ChanKey); dataStatus != nil {
|
||
updateDiagnose(func() { res.Info.DataStatus = dataStatus })
|
||
setUpstreamSuccess("dataService", item.Cached, item.UpdatedAt)
|
||
return
|
||
}
|
||
}
|
||
if apnOut[0].Code == 5000 {
|
||
setModule("dataService", "skipped", apnOut[0].Message, false, "")
|
||
return
|
||
}
|
||
if apnOut[0].Code != 0 {
|
||
status, message := diagnoseUpstreamFailure("上游数据服务查询失败", apnOut[0].Code, apnOut[0].Message)
|
||
setModule("dataService", status, message, false, "")
|
||
return
|
||
}
|
||
setModule("dataService", "skipped", "上游数据服务未返回有效状态", false, "")
|
||
})
|
||
runDiagnose(&wg, func() {
|
||
if basicOut, err := service.UpstreamChan().QuerySimBasicInfo(diagCtx, &model.SimBasicInfoInput{
|
||
SimKeys: []*model.SimKey{simKey},
|
||
ReqData: model.SimInputReqNullDate{},
|
||
}); err != nil {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.Info, query sim basic info failed, simKey %+v, err %+v", simKey, err)
|
||
setModule("basicInfo", "error", err.Error(), false, "")
|
||
} else if len(basicOut) > 0 {
|
||
item := basicOut[0]
|
||
if item.Code == 0 {
|
||
data := item.ResData
|
||
updateDiagnose(func() {
|
||
if value := emptyStringPtr(data.ICCID); value != nil {
|
||
res.Info.Iccid = value
|
||
}
|
||
if value := emptyStringPtr(data.IMSI); value != nil {
|
||
res.Info.Imsi = value
|
||
}
|
||
if value := emptyStringPtr(data.MSISDN); value != nil {
|
||
res.Info.Msisdn = value
|
||
}
|
||
if res.Info.RegionLimitType == nil {
|
||
res.Info.RegionLimitType = emptyStringPtr(data.AreaLimitType)
|
||
}
|
||
if res.Info.RegionLimitArea == nil {
|
||
res.Info.RegionLimitArea = emptyStringPtr(data.AreaLimitInfo)
|
||
}
|
||
humanLimitStatus := mapHumanLimitStatus(data.IsHumanLimit)
|
||
if humanLimitStatus != nil && *humanLimitStatus != "2" {
|
||
res.Info.HumanLimitStatus = humanLimitStatus
|
||
} else if !isMobileDiagnoseChannel(cardInfo.TempKey, cardInfo.ChanKey) {
|
||
res.Info.HumanLimitStatus = humanLimitStatus
|
||
}
|
||
})
|
||
setUpstreamSuccess("basicInfo", item.Cached, item.UpdatedAt)
|
||
} else if item.Code == 5000 {
|
||
setModule("basicInfo", "skipped", item.Message, false, "")
|
||
} else {
|
||
setModule("basicInfo", "error", diagnoseUpstreamMessage("上游基础信息查询失败", item.Code, item.Message), false, "")
|
||
}
|
||
} else {
|
||
setModule("basicInfo", "error", "上游基础信息未返回有效结果", false, "")
|
||
}
|
||
})
|
||
|
||
runDiagnose(&wg, func() {
|
||
if statusOut, err := service.UpstreamChan().QuerySimCardStatus(diagCtx, &model.SimCardStatusInput{
|
||
SimKeys: []*model.SimKey{simKey},
|
||
}); err != nil {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.Info, query sim card status failed, simKey %+v, err %+v", simKey, err)
|
||
setModule("cardStatus", "error", err.Error(), false, "")
|
||
} else if len(statusOut) > 0 {
|
||
item := statusOut[0]
|
||
if item.Code == 0 {
|
||
updateDiagnose(func() {
|
||
res.Info.Status = diagnoseCardStatusPtr(item.ResData.CardStatus)
|
||
if res.Info.DataServiceSource == "network_block" {
|
||
if applyDiagnoseNetworkBlock(res.Info, item.ResData) {
|
||
dataStatus, dataMessage := diagnoseCachedModule(item.Cached)
|
||
setDiagnoseModule(res, "dataService", dataStatus, dataMessage, item.Cached, item.UpdatedAt)
|
||
} else {
|
||
setDiagnoseModule(res, "dataService", "skipped", "电信未返回有效断网状态", false, "")
|
||
}
|
||
}
|
||
status, message := diagnoseCachedModule(item.Cached)
|
||
setDiagnoseModule(res, "cardStatus", status, message, item.Cached, item.UpdatedAt)
|
||
})
|
||
} else if item.Code == 5000 {
|
||
setModule("cardStatus", "skipped", item.Message, false, "")
|
||
} else {
|
||
status, message := diagnoseUpstreamFailure("上游卡状态查询失败", item.Code, item.Message)
|
||
setModule("cardStatus", status, message, false, "")
|
||
}
|
||
} else {
|
||
setModule("cardStatus", "error", "上游卡状态未返回有效结果", false, "")
|
||
}
|
||
})
|
||
|
||
runDiagnose(&wg, func() {
|
||
if sessionOut, err := service.UpstreamChan().QuerySimSession(diagCtx, &model.SimSessionInput{
|
||
SimKeys: []*model.SimKey{simKey},
|
||
}); err != nil {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.Info, query sim session failed, simKey %+v, err %+v", simKey, err)
|
||
setModule("session", "error", err.Error(), false, "")
|
||
} else if len(sessionOut) > 0 {
|
||
item := sessionOut[0]
|
||
if item.Code == 0 {
|
||
data := item.ResData
|
||
if data.Status == "" && data.ApnId == "" && data.Ipv4 == "" && data.Ipv6 == "" && data.Rat == "" && data.CreateTime == "" {
|
||
setModule("session", "skipped", "上游会话信息未返回有效结果", false, "")
|
||
return
|
||
}
|
||
sessionStatus := diagnoseSessionStatus(data.Status)
|
||
updateDiagnose(func() {
|
||
res.Info.OnlineStatus = mapOnlineStatus(sessionStatus)
|
||
res.Session.ApnId = emptyStringPtr(data.ApnId)
|
||
res.Session.Rat = emptyStringPtr(data.Rat)
|
||
res.Session.Ipv4 = emptyStringPtr(data.Ipv4)
|
||
res.Session.Ipv6 = emptyStringPtr(data.Ipv6)
|
||
res.Session.Ipv6Prefix = emptyStringPtr(data.Ipv6Prefix)
|
||
res.Session.Status = emptyStringPtr(sessionStatus)
|
||
res.Session.CreateTime = emptyStringPtr(data.CreateTime)
|
||
})
|
||
setUpstreamSuccess("session", item.Cached, item.UpdatedAt)
|
||
} else if item.Code == 5000 {
|
||
setModule("session", "skipped", item.Message, false, "")
|
||
} else {
|
||
setModule("session", "error", diagnoseUpstreamMessage("上游会话信息查询失败", item.Code, item.Message), false, "")
|
||
}
|
||
} else {
|
||
setModule("session", "error", "上游会话信息未返回有效结果", false, "")
|
||
}
|
||
})
|
||
|
||
runDiagnose(&wg, func() {
|
||
items, queryErr := service.UpstreamChan().QuerySimImei(diagCtx, &model.SimImeiInput{
|
||
SimKeys: []*model.SimKey{simKey},
|
||
})
|
||
if queryErr != nil {
|
||
setModule("imei", "error", queryErr.Error(), false, "")
|
||
return
|
||
}
|
||
if len(items) == 0 {
|
||
setModule("imei", "skipped", "上游IMEI未返回有效结果", false, "")
|
||
return
|
||
}
|
||
item := items[0]
|
||
if item.Code != 0 {
|
||
status, message := diagnoseUpstreamFailure("上游IMEI查询失败", item.Code, item.Message)
|
||
setModule("imei", status, message, false, "")
|
||
return
|
||
}
|
||
imei := emptyStringPtr(item.ResData.Imei)
|
||
if imei == nil {
|
||
setModule("imei", "skipped", "上游IMEI未返回有效结果", false, "")
|
||
return
|
||
}
|
||
updateDiagnose(func() { res.Session.Imei = imei })
|
||
setUpstreamSuccess("imei", item.Cached, item.UpdatedAt)
|
||
})
|
||
|
||
runDiagnose(&wg, func() {
|
||
if deviceOut, err := service.UpstreamChan().QuerySimDeviceStatus(diagCtx, &model.SimDeviceStatusInput{
|
||
SimKeys: []*model.SimKey{simKey},
|
||
}); err != nil {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.Info, query sim device status failed, simKey %+v, err %+v", simKey, err)
|
||
setModule("device", "error", err.Error(), false, "")
|
||
} else if len(deviceOut) > 0 {
|
||
if deviceOut[0].Code == 5000 {
|
||
updateDiagnose(func() {
|
||
res.Info.DeviceStatus = nil
|
||
})
|
||
setModule("device", "skipped", deviceOut[0].Message, false, "")
|
||
} else if deviceOut[0].Code == 0 {
|
||
if mapDeviceStatus(deviceOut[0].ResData.DeviceStatus) == nil && mapOperatingStatus(deviceOut[0].ResData.OperatingStatus) == nil {
|
||
setModule("device", "skipped", "上游设备状态未返回有效结果", false, "")
|
||
return
|
||
}
|
||
updateDiagnose(func() {
|
||
res.Info.DeviceStatus = mapDeviceStatus(deviceOut[0].ResData.DeviceStatus)
|
||
res.Info.OperatingStatus = mapOperatingStatus(deviceOut[0].ResData.OperatingStatus)
|
||
})
|
||
setUpstreamSuccess("device", deviceOut[0].Cached, deviceOut[0].UpdatedAt)
|
||
} else {
|
||
status, message := diagnoseUpstreamFailure("上游设备状态查询失败", deviceOut[0].Code, deviceOut[0].Message)
|
||
setModule("device", status, message, false, "")
|
||
}
|
||
} else {
|
||
setModule("device", "error", "上游设备状态未返回有效结果", false, "")
|
||
}
|
||
})
|
||
|
||
runDiagnose(&wg, func() {
|
||
if bindOut, err := service.UpstreamChan().QuerySimCardBindStatus(diagCtx, &model.SimCardBindStatusInput{
|
||
SimKeys: []*model.SimKey{simKey},
|
||
ReqData: model.SimCardBindStatusInputItem{TestType: 0},
|
||
}); err != nil {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.Info, query sim card bind status failed, simKey %+v, err %+v", simKey, err)
|
||
setModule("cardBind", "error", err.Error(), false, "")
|
||
} else if len(bindOut) > 0 {
|
||
if bindOut[0].Code == 5000 {
|
||
updateDiagnose(func() {
|
||
res.Info.ImeiStatus = nil
|
||
})
|
||
setModule("cardBind", "skipped", bindOut[0].Message, false, "")
|
||
} else if bindOut[0].Code == 0 {
|
||
bindStatus := strings.TrimSpace(bindOut[0].ResData.BindStatus)
|
||
if bindOut[0].ResData.OrderStatus == "1" {
|
||
updateDiagnose(func() { res.Info.ImeiStatus = nil })
|
||
setModule("cardBind", "skipped", "该卡未订购机卡绑定服务", false, "")
|
||
} else if bindStatus == "2" || bindStatus == "-" {
|
||
message := strings.TrimSpace(bindOut[0].ResData.ErrorDes)
|
||
if message == "" {
|
||
message = strings.TrimSpace(bindOut[0].ResData.ErrorCode)
|
||
}
|
||
if message == "" {
|
||
message = "上游机卡绑定网络查询失败"
|
||
}
|
||
updateDiagnose(func() {
|
||
res.Info.ImeiStatus = nil
|
||
})
|
||
setModule("cardBind", "error", message, false, "")
|
||
} else {
|
||
updateDiagnose(func() {
|
||
res.Info.ImeiStatus = mapImeiStatusByBindResult(bindStatus)
|
||
})
|
||
setUpstreamSuccess("cardBind", bindOut[0].Cached, bindOut[0].UpdatedAt)
|
||
}
|
||
} else {
|
||
status, message := diagnoseUpstreamFailure("上游机卡绑定状态查询失败", bindOut[0].Code, bindOut[0].Message)
|
||
setModule("cardBind", status, message, false, "")
|
||
}
|
||
} else {
|
||
setModule("cardBind", "error", "上游机卡绑定状态未返回有效结果", false, "")
|
||
}
|
||
})
|
||
|
||
runDiagnose(&wg, func() {
|
||
regionResult, err := queryDiagnoseRegionLimit(ctx, diagCtx, simKey, shouldCacheDiagnoseRegionLimit(cardInfo.TempKey, cardInfo.ChanKey))
|
||
if err != nil {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.Info, query sim region limit status failed, simKey %+v, err %+v", simKey, err)
|
||
setModule("region", "error", err.Error(), false, "")
|
||
} else if regionResult != nil && regionResult.Item != nil {
|
||
updateDiagnose(func() {
|
||
applyDiagnoseRegionLimit(res, regionResult.Item)
|
||
})
|
||
status, message, updatedAt := diagnoseRegionModule(regionResult)
|
||
setModule("region", status, message, regionResult.Cached, updatedAt)
|
||
} else {
|
||
setModule("region", "error", "区域限制未返回有效数据,请稍后重试", false, "")
|
||
}
|
||
})
|
||
|
||
runDiagnose(&wg, func() {
|
||
if rateLimitOut, err := service.UpstreamChan().QuerySimDataDiagnosis(diagCtx, &model.SimDataDiagnosisInput{
|
||
SimKeys: []*model.SimKey{simKey},
|
||
}); err != nil {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.Info, query sim data diagnosis failed, simKey %+v, err %+v", simKey, err)
|
||
setModule("rateLimit", "error", err.Error(), false, "")
|
||
} else if len(rateLimitOut) > 0 {
|
||
updateDiagnose(func() {
|
||
applyDiagnoseRateLimit(res, &rateLimitOut[0])
|
||
})
|
||
if rateLimitOut[0].Code == 5000 {
|
||
setModule("rateLimit", "skipped", rateLimitOut[0].Message, false, "")
|
||
} else if rateLimitOut[0].Code == 0 {
|
||
setUpstreamSuccess("rateLimit", rateLimitOut[0].Cached, rateLimitOut[0].UpdatedAt)
|
||
} else {
|
||
status, message := diagnoseUpstreamFailure("上游达量限速查询失败", rateLimitOut[0].Code, rateLimitOut[0].Message)
|
||
setModule("rateLimit", status, message, false, "")
|
||
}
|
||
} else {
|
||
setModule("rateLimit", "error", "上游达量限速未返回有效结果", false, "")
|
||
}
|
||
})
|
||
|
||
runDiagnose(&wg, func() {
|
||
if stopOut, err := service.UpstreamChan().QuerySimStopReason(diagCtx, &model.SimStopReasonInput{
|
||
SimKeys: []*model.SimKey{simKey},
|
||
}); err != nil {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.Info, query sim stop reason failed, simKey %+v, err %+v", simKey, err)
|
||
setModule("stopReason", "error", err.Error(), false, "")
|
||
} else if len(stopOut) > 0 {
|
||
if stopOut[0].Code == 0 {
|
||
updateDiagnose(func() {
|
||
res.Info.StopReason = emptyStringPtr(normalizeDiagnoseStopReason(stopOut[0].ResData.StopReason))
|
||
})
|
||
setUpstreamSuccess("stopReason", stopOut[0].Cached, stopOut[0].UpdatedAt)
|
||
} else if stopOut[0].Code == 5000 {
|
||
setModule("stopReason", "skipped", stopOut[0].Message, false, "")
|
||
} else {
|
||
status, message := diagnoseUpstreamFailure("上游停机原因查询失败", stopOut[0].Code, stopOut[0].Message)
|
||
setModule("stopReason", status, message, false, "")
|
||
}
|
||
} else {
|
||
setModule("stopReason", "error", "上游停机原因未返回有效结果", false, "")
|
||
}
|
||
})
|
||
|
||
runDiagnose(&wg, func() {
|
||
if frozenOut, err := service.UpstreamChan().QuerySimFrozenState(diagCtx, &model.SimFrozenStateInput{
|
||
SimKeys: []*model.SimKey{simKey},
|
||
}); err != nil {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.Info, query sim frozen state failed, simKey %+v, err %+v", simKey, err)
|
||
setModule("frozen", "error", err.Error(), false, "")
|
||
} else if len(frozenOut) > 0 {
|
||
if frozenOut[0].Code == 0 {
|
||
if strings.TrimSpace(frozenOut[0].ResData.FrozenStatus) == "" {
|
||
setModule("frozen", "skipped", "上游冻结状态未返回有效结果", false, "")
|
||
return
|
||
}
|
||
updateDiagnose(func() {
|
||
res.Info.FrozenStatus = emptyStringPtr(frozenOut[0].ResData.FrozenStatus)
|
||
res.Info.FrozenReason = emptyStringPtr(frozenOut[0].ResData.FrozenReason)
|
||
res.Info.FrozenOperTime = emptyStringPtr(frozenOut[0].ResData.OperTime)
|
||
})
|
||
setUpstreamSuccess("frozen", frozenOut[0].Cached, frozenOut[0].UpdatedAt)
|
||
} else if frozenOut[0].Code == 5000 {
|
||
setModule("frozen", "skipped", frozenOut[0].Message, false, "")
|
||
} else {
|
||
status, message := diagnoseUpstreamFailure("上游冻结状态查询失败", frozenOut[0].Code, frozenOut[0].Message)
|
||
setModule("frozen", status, message, false, "")
|
||
}
|
||
} else {
|
||
setModule("frozen", "error", "上游冻结状态未返回有效结果", false, "")
|
||
}
|
||
})
|
||
wg.Wait()
|
||
s.applyDiagnoseCurrentFlowUsage(ctx, diagCtx, res, cardInfo.ChanKey)
|
||
|
||
return res, nil
|
||
}
|
||
|
||
// Flow 查询SIM卡使用流量
|
||
func (s *sIotTSimDiagnose) Flow(ctx context.Context, in *model.TSimDiagnoseFlowReq) (res *model.TSimDiagnoseFlowOutput, err error) {
|
||
cardInfo, err := s.resolveDiagnoseCardInfo(ctx, in.QueryCode, "查询SIM卡使用流量")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
switch in.QueryType {
|
||
case "7d", "30d":
|
||
return s.queryDailyFlow(ctx, cardInfo.Iccid, in.QueryType)
|
||
case "6M":
|
||
return s.queryMonthlyFlow(ctx, cardInfo.Iccid)
|
||
default:
|
||
return nil, gerror.New("查询SIM卡使用流量失败,查询类型不正确")
|
||
}
|
||
}
|
||
|
||
// emptyStringPtr 返回非空字符串的指针,空字符串返回nil
|
||
func emptyStringPtr(value string) *string {
|
||
if strings.TrimSpace(value) == "" {
|
||
return nil
|
||
}
|
||
return &value
|
||
}
|
||
|
||
func diagnoseFlowFloatPtr(ok bool, value float64) *float64 {
|
||
if !ok {
|
||
return nil
|
||
}
|
||
if value < 0 {
|
||
value = 0
|
||
}
|
||
return &value
|
||
}
|
||
|
||
func applyDiagnoseRemainFlow(info *model.TSimDiagnoseInfoItem) {
|
||
if info == nil || info.PackageTotalFlowMb == nil || info.UsedFlowMb == nil {
|
||
return
|
||
}
|
||
remain := *info.PackageTotalFlowMb - *info.UsedFlowMb
|
||
if remain < 0 {
|
||
remain = 0
|
||
}
|
||
info.RemainFlowMb = &remain
|
||
}
|
||
|
||
func diagnoseLookupCardValue(simKey *model.SimKey) (string, string) {
|
||
if simKey == nil {
|
||
return "", ""
|
||
}
|
||
for _, item := range []struct {
|
||
cardValueType string
|
||
cardValue string
|
||
}{
|
||
{cardValueType: "iccid", cardValue: simKey.ICCID},
|
||
{cardValueType: "imsi", cardValue: simKey.IMSI},
|
||
{cardValueType: "msisdn", cardValue: simKey.MSISDN},
|
||
} {
|
||
if value := strings.TrimSpace(item.cardValue); value != "" {
|
||
return item.cardValueType, value
|
||
}
|
||
}
|
||
return "", ""
|
||
}
|
||
|
||
func (s *sIotTSimDiagnose) applyDiagnoseCurrentFlowUsage(ctx context.Context, diagCtx context.Context, res *model.TSimDiagnoseInfoOutput, chanKey string) {
|
||
if res == nil || res.Info == nil {
|
||
return
|
||
}
|
||
simKey := &model.SimKey{ChanKey: strings.TrimSpace(chanKey)}
|
||
if res.Info.Iccid != nil {
|
||
simKey.ICCID = strings.TrimSpace(*res.Info.Iccid)
|
||
}
|
||
if res.Info.Imsi != nil {
|
||
simKey.IMSI = strings.TrimSpace(*res.Info.Imsi)
|
||
}
|
||
if res.Info.Msisdn != nil {
|
||
simKey.MSISDN = strings.TrimSpace(*res.Info.Msisdn)
|
||
}
|
||
if simKey.ChanKey == "" {
|
||
setDiagnoseModule(res, "flowUsage", "error", "未找到卡对应的上游通道", false, "")
|
||
return
|
||
}
|
||
|
||
out, err := service.UpstreamChan().QuerySimDatausage(diagCtx, &model.SimDataUsageInput{
|
||
SimKeys: []*model.SimKey{simKey},
|
||
ReqData: model.SimInputReqNullDate{},
|
||
})
|
||
if err != nil {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.Info, query current flow usage failed, simKey %+v, err %+v", simKey, err)
|
||
setDiagnoseModule(res, "flowUsage", "error", err.Error(), false, "")
|
||
return
|
||
}
|
||
if len(out) == 0 {
|
||
setDiagnoseModule(res, "flowUsage", "error", "上游实时流量未返回有效结果", false, "")
|
||
return
|
||
}
|
||
item := out[0]
|
||
if item.Code == 5000 || isDiagnoseUnsupportedMessage(item.Message) {
|
||
setDiagnoseModule(res, "flowUsage", "skipped", item.Message, false, "")
|
||
return
|
||
}
|
||
if item.Code != 0 {
|
||
status, message := diagnoseUpstreamFailure("上游实时流量查询失败", item.Code, item.Message)
|
||
setDiagnoseModule(res, "flowUsage", status, message, false, "")
|
||
return
|
||
}
|
||
|
||
res.Info.UsedFlowMb = diagnoseFlowFloatPtr(true, item.ResData.DataUsage)
|
||
syncTime := gtime.Now().Format("Y-m-d H:i:s")
|
||
if item.Cached && strings.TrimSpace(item.UpdatedAt) != "" {
|
||
syncTime = strings.TrimSpace(item.UpdatedAt)
|
||
}
|
||
res.Info.FlowSyncTime = &syncTime
|
||
applyDiagnoseRemainFlow(res.Info)
|
||
status, message := diagnoseCachedModule(item.Cached)
|
||
setDiagnoseModule(res, "flowUsage", status, message, item.Cached, item.UpdatedAt)
|
||
}
|
||
|
||
func diagnoseDataServiceStatus(items []model.SimApnInfoItem) *string {
|
||
return diagnoseDataServiceStatusForChannel(items, "sim_yd", "")
|
||
}
|
||
|
||
func diagnoseDataServiceStatusForChannel(items []model.SimApnInfoItem, tempKey string, chanKey string) *string {
|
||
mobile := diagnoseIsMobileChannel(tempKey, chanKey)
|
||
var selected *string
|
||
for _, item := range items {
|
||
if mobile && !strings.EqualFold(strings.TrimSpace(item.ApnName), consts.SimCardDataApnName) {
|
||
continue
|
||
}
|
||
status := strings.TrimSpace(item.Status)
|
||
if strings.TrimSpace(item.ApnName) == "" || (status != "0" && status != "1") {
|
||
return nil
|
||
}
|
||
if selected != nil && *selected != status {
|
||
return nil
|
||
}
|
||
selected = emptyStringPtr(status)
|
||
}
|
||
return selected
|
||
}
|
||
|
||
func diagnoseIsMobileChannel(tempKey string, chanKey string) bool {
|
||
value := strings.ToLower(strings.TrimSpace(tempKey + " " + chanKey))
|
||
return strings.Contains(value, "sim_yd") || strings.Contains(value, "yd_mock")
|
||
}
|
||
func diagnoseCardStatusPtr(status int) *string {
|
||
switch status {
|
||
case 0, 1, 2, 4, 6, 7, 8, 9:
|
||
default:
|
||
return nil
|
||
}
|
||
value := strconv.Itoa(status)
|
||
return &value
|
||
}
|
||
|
||
func mapDiagnoseCardStatus(status string) *string {
|
||
switch strings.TrimSpace(status) {
|
||
case "":
|
||
return nil
|
||
case "0", "未知":
|
||
return diagnoseCardStatusPtr(0)
|
||
case "1", "待激活":
|
||
return diagnoseCardStatusPtr(1)
|
||
case "2", "已激活", "正常", "在用":
|
||
return diagnoseCardStatusPtr(2)
|
||
case "4", "停机", "已停机":
|
||
return diagnoseCardStatusPtr(4)
|
||
case "6", "可测试", "测试期":
|
||
return diagnoseCardStatusPtr(6)
|
||
case "7", "库存":
|
||
return diagnoseCardStatusPtr(7)
|
||
case "8", "预销户":
|
||
return diagnoseCardStatusPtr(8)
|
||
case "9", "已销户", "销户":
|
||
return diagnoseCardStatusPtr(9)
|
||
default:
|
||
return nil
|
||
}
|
||
}
|
||
|
||
func isMobileDiagnoseChannel(tempKey string, chanKey string) bool {
|
||
value := strings.ToLower(strings.TrimSpace(tempKey + " " + chanKey))
|
||
return strings.Contains(value, "sim_yd") || strings.Contains(value, "yd_mock")
|
||
}
|
||
|
||
func isTelecomDiagnoseChannel(tempKey string, chanKey string) bool {
|
||
value := strings.ToLower(strings.TrimSpace(tempKey + " " + chanKey))
|
||
return strings.Contains(value, "sim_dx") || strings.Contains(value, "dx_")
|
||
}
|
||
|
||
func shouldCacheDiagnoseRegionLimit(tempKey string, chanKey string) bool {
|
||
value := strings.ToLower(strings.TrimSpace(tempKey + " " + chanKey))
|
||
return isMobileDiagnoseChannel(tempKey, chanKey) ||
|
||
strings.Contains(value, "onelink") ||
|
||
strings.Contains(value, "shared_token")
|
||
}
|
||
|
||
func normalizeDiagnoseStopReason(stopReason string) string {
|
||
switch strings.TrimSpace(stopReason) {
|
||
case "", "000000000000":
|
||
return "未停机或系统暂无停机原因"
|
||
default:
|
||
return strings.TrimSpace(stopReason)
|
||
}
|
||
}
|
||
|
||
func diagnoseUpstreamMessage(prefix string, code int, message string) string {
|
||
message = strings.TrimSpace(message)
|
||
if message != "" {
|
||
if isDiagnoseUnsupportedMessage(message) {
|
||
return ""
|
||
}
|
||
if normalized := normalizeDiagnoseCustomerMessage(message); normalized != "" {
|
||
return normalized
|
||
}
|
||
}
|
||
|
||
prefix = strings.TrimSpace(prefix)
|
||
if prefix == "" {
|
||
prefix = "上游查询失败"
|
||
}
|
||
|
||
if isDiagnoseUnsupportedMessage(prefix) {
|
||
return ""
|
||
}
|
||
return normalizeDiagnoseCustomerMessage(prefix)
|
||
}
|
||
|
||
func diagnoseUpstreamFailure(prefix string, code int, message string) (status string, displayMessage string) {
|
||
if code == 5000 || isDiagnoseUnsupportedMessage(message) {
|
||
return "skipped", message
|
||
}
|
||
return "error", diagnoseUpstreamMessage(prefix, code, message)
|
||
}
|
||
|
||
const simDiagnoseRetryMessage = "查询暂时未完成,请稍后重试"
|
||
|
||
func isDiagnoseUnsupportedMessage(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 normalizeDiagnoseCustomerMessage(message string) string {
|
||
message = strings.TrimSpace(message)
|
||
if message == "" {
|
||
return ""
|
||
}
|
||
|
||
lower := strings.ToLower(message)
|
||
switch {
|
||
case strings.Contains(message, "同一卡号60分钟"):
|
||
return "区域限制查询受运营商频率限制,暂未取得有效结果,请在上次查询60分钟后再试"
|
||
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 simDiagnoseRetryMessage
|
||
case strings.Contains(message, "上游"),
|
||
strings.Contains(message, "缓存"),
|
||
strings.Contains(message, "60分钟"),
|
||
strings.Contains(message, "未返回有效结果"),
|
||
strings.Contains(message, "通道未实例化"):
|
||
return simDiagnoseRetryMessage
|
||
case isDiagnoseUnsupportedMessage(message):
|
||
return ""
|
||
default:
|
||
return message
|
||
}
|
||
}
|
||
|
||
func setDiagnoseModule(res *model.TSimDiagnoseInfoOutput, key string, status string, message string, cached bool, updatedAt string) {
|
||
if res == nil {
|
||
return
|
||
}
|
||
if res.Modules == nil {
|
||
res.Modules = make(map[string]*model.TSimDiagnoseModuleItem)
|
||
}
|
||
reason := diagnoseModuleReason(status, message)
|
||
message = normalizeDiagnoseCustomerMessage(message)
|
||
res.Modules[key] = &model.TSimDiagnoseModuleItem{
|
||
Reason: reason,
|
||
Status: status,
|
||
Message: strings.TrimSpace(message),
|
||
Cached: cached,
|
||
UpdatedAt: updatedAt,
|
||
}
|
||
}
|
||
|
||
func diagnoseModuleReason(status string, message string) string {
|
||
switch {
|
||
case status == "error" && (strings.Contains(message, "60分钟") || strings.Contains(message, "频率限制")):
|
||
return "rate_limited"
|
||
case status == "error":
|
||
return "query_error"
|
||
case strings.Contains(message, "未订购"), strings.Contains(message, "未开通"), strings.Contains(message, "通道未申请"):
|
||
return "not_subscribed"
|
||
case status == "skipped" && (strings.Contains(message, "未返回") || strings.Contains(message, "未查询到") || strings.Contains(message, "无数据")):
|
||
return "no_data"
|
||
case status == "skipped":
|
||
return "unsupported"
|
||
default:
|
||
return ""
|
||
}
|
||
}
|
||
|
||
func diagnoseCachedModule(cached bool) (status string, message string) {
|
||
if cached {
|
||
return "cached", "上游查询存在频率限制,当前展示最近一次缓存结果。"
|
||
}
|
||
return "success", ""
|
||
}
|
||
|
||
func diagnoseRegionCacheKey(simKey *model.SimKey) string {
|
||
if simKey == nil {
|
||
return "iotlcm:sim-diagnose:region:unknown"
|
||
}
|
||
parts := []string{
|
||
safeDiagnoseCachePart(simKey.ChanKey),
|
||
safeDiagnoseCachePart(simKey.ICCID),
|
||
safeDiagnoseCachePart(simKey.IMSI),
|
||
safeDiagnoseCachePart(simKey.MSISDN),
|
||
}
|
||
return "iotlcm:sim-diagnose:region:" + strings.Join(parts, ":")
|
||
}
|
||
|
||
func diagnoseRegionLockKey(simKey *model.SimKey) string {
|
||
return diagnoseRegionCacheKey(simKey) + ":lock"
|
||
}
|
||
|
||
func queryDiagnoseRegionLimit(ctx context.Context, diagCtx context.Context, simKey *model.SimKey, useCache bool) (*simDiagnoseRegionQueryResult, error) {
|
||
if !useCache {
|
||
return fetchDiagnoseRegionLimit(diagCtx, simKey)
|
||
}
|
||
if cached, cachedAt, ok := getCachedDiagnoseRegionLimit(ctx, simKey); ok {
|
||
return &simDiagnoseRegionQueryResult{Item: cached, CachedAt: cachedAt, Cached: true}, nil
|
||
}
|
||
|
||
value, err, _ := simDiagnoseRegionFlight.Do(diagnoseRegionCacheKey(simKey), func() (any, error) {
|
||
if cached, cachedAt, ok := getCachedDiagnoseRegionLimit(ctx, simKey); ok {
|
||
return &simDiagnoseRegionQueryResult{Item: cached, CachedAt: cachedAt, Cached: true}, nil
|
||
}
|
||
return queryDiagnoseRegionLimitWithLock(ctx, diagCtx, simKey)
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
result, ok := value.(*simDiagnoseRegionQueryResult)
|
||
if !ok || result == nil {
|
||
return nil, gerror.New("区域限制查询未返回有效结果")
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func queryDiagnoseRegionLimitWithLock(ctx context.Context, diagCtx context.Context, simKey *model.SimKey) (*simDiagnoseRegionQueryResult, error) {
|
||
lockKey := diagnoseRegionLockKey(simKey)
|
||
lockValue := uuid32.S()
|
||
acquired, err := acquireDiagnoseRegionLock(ctx, lockKey, lockValue)
|
||
if err != nil {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.Info, acquire region cache lock failed, key %s, err %+v", lockKey, err)
|
||
return fetchAndCacheDiagnoseRegionLimit(ctx, diagCtx, simKey)
|
||
}
|
||
if acquired {
|
||
defer releaseDiagnoseRegionLock(lockKey, lockValue)
|
||
if cached, cachedAt, ok := getCachedDiagnoseRegionLimit(ctx, simKey); ok {
|
||
return &simDiagnoseRegionQueryResult{Item: cached, CachedAt: cachedAt, Cached: true}, nil
|
||
}
|
||
return fetchAndCacheDiagnoseRegionLimit(ctx, diagCtx, simKey)
|
||
}
|
||
|
||
deadline := time.NewTimer(simDiagnoseRegionLockWait)
|
||
ticker := time.NewTicker(simDiagnoseRegionLockPoll)
|
||
defer deadline.Stop()
|
||
defer ticker.Stop()
|
||
for {
|
||
select {
|
||
case <-ctx.Done():
|
||
return nil, ctx.Err()
|
||
case <-deadline.C:
|
||
return nil, gerror.New("区域限制查询正在处理中,请稍后重试")
|
||
case <-ticker.C:
|
||
if cached, cachedAt, ok := getCachedDiagnoseRegionLimit(ctx, simKey); ok {
|
||
return &simDiagnoseRegionQueryResult{Item: cached, CachedAt: cachedAt, Cached: true}, nil
|
||
}
|
||
acquired, lockErr := acquireDiagnoseRegionLock(ctx, lockKey, lockValue)
|
||
if lockErr != nil {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.Info, retry region cache lock failed, key %s, err %+v", lockKey, lockErr)
|
||
continue
|
||
}
|
||
if acquired {
|
||
defer releaseDiagnoseRegionLock(lockKey, lockValue)
|
||
return fetchAndCacheDiagnoseRegionLimit(ctx, diagCtx, simKey)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func fetchDiagnoseRegionLimit(ctx context.Context, simKey *model.SimKey) (*simDiagnoseRegionQueryResult, error) {
|
||
regionOut, err := service.UpstreamChan().QuerySimRegionLimitStatus(ctx, &model.SimRegionLimitStatusInput{
|
||
SimKeys: []*model.SimKey{simKey},
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if len(regionOut) == 0 {
|
||
return nil, nil
|
||
}
|
||
return &simDiagnoseRegionQueryResult{Item: ®ionOut[0], CachedAt: gtime.Now().Format("Y-m-d H:i:s")}, nil
|
||
}
|
||
|
||
func fetchAndCacheDiagnoseRegionLimit(ctx context.Context, diagCtx context.Context, simKey *model.SimKey) (*simDiagnoseRegionQueryResult, error) {
|
||
result, err := fetchDiagnoseRegionLimit(diagCtx, simKey)
|
||
if err != nil || result == nil || result.Item == nil {
|
||
return result, err
|
||
}
|
||
if isDiagnoseRegionLimitCacheable(result.Item) {
|
||
setCachedDiagnoseRegionLimit(ctx, simKey, *result.Item, result.CachedAt)
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func diagnoseRegionModule(result *simDiagnoseRegionQueryResult) (status string, message string, updatedAt string) {
|
||
if result == nil || result.Item == nil {
|
||
return "error", "区域限制未返回有效数据,请稍后重试", ""
|
||
}
|
||
if result.Cached && isDiagnoseRegionLimitCacheable(result.Item) {
|
||
updatedAt, hint := describeDiagnoseRegionCache(result.CachedAt)
|
||
return "cached", "运营商一小时内只能查询一次,当前展示最近一次查询结果。" + hint, updatedAt
|
||
}
|
||
if isDiagnoseRegionLimitCacheable(result.Item) {
|
||
updatedAt, hint := describeDiagnoseRegionCache(result.CachedAt)
|
||
return "success", hint, updatedAt
|
||
}
|
||
if result.Item.Code == 5000 {
|
||
return "skipped", result.Item.Message, ""
|
||
}
|
||
rawMessage := strings.TrimSpace(result.Item.Message)
|
||
message = normalizeDiagnoseCustomerMessage(rawMessage)
|
||
if message == "" {
|
||
message = "区域限制未返回有效数据,请稍后重试"
|
||
}
|
||
return "error", message, ""
|
||
}
|
||
|
||
func acquireDiagnoseRegionLock(ctx context.Context, key string, value string) (bool, error) {
|
||
res, err := g.Redis().Do(ctx, "SET", key, value, "NX", "PX", simDiagnoseRegionLockTTL.Milliseconds())
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
return strings.EqualFold(strings.TrimSpace(res.String()), "OK"), nil
|
||
}
|
||
|
||
func releaseDiagnoseRegionLock(key string, value string) {
|
||
const script = `if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end`
|
||
releaseCtx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||
defer cancel()
|
||
if _, err := g.Redis().Do(releaseCtx, "EVAL", script, 1, key, value); err != nil {
|
||
g.Log().Warningf(releaseCtx, "sIotTSimDiagnose.Info, release region cache lock failed, key %s, err %+v", key, err)
|
||
}
|
||
}
|
||
|
||
func safeDiagnoseCachePart(value string) string {
|
||
value = strings.TrimSpace(value)
|
||
if value == "" {
|
||
return "-"
|
||
}
|
||
replacer := strings.NewReplacer(":", "_", "\n", "_", "\r", "_", "\t", "_", " ", "_")
|
||
return replacer.Replace(value)
|
||
}
|
||
|
||
func getCachedDiagnoseRegionLimit(ctx context.Context, simKey *model.SimKey) (*model.SimBaseRes[model.SimRegionLimitStatusItem], string, bool) {
|
||
cacheKey := diagnoseRegionCacheKey(simKey)
|
||
value, err := g.Redis().Get(ctx, cacheKey)
|
||
if err != nil || value == nil || strings.TrimSpace(value.String()) == "" {
|
||
return nil, "", false
|
||
}
|
||
var payload simDiagnoseRegionCachePayload
|
||
if err = json.Unmarshal([]byte(value.String()), &payload); err != nil {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.Info, unmarshal region cache failed, key %s, err %+v", cacheKey, err)
|
||
return nil, "", false
|
||
}
|
||
if !isDiagnoseRegionLimitCacheable(&payload.Item) {
|
||
_, _ = g.Redis().Do(ctx, "DEL", cacheKey)
|
||
return nil, "", false
|
||
}
|
||
return &payload.Item, payload.CachedAt, true
|
||
}
|
||
|
||
func setCachedDiagnoseRegionLimit(ctx context.Context, simKey *model.SimKey, item model.SimBaseRes[model.SimRegionLimitStatusItem], cachedAt string) {
|
||
if !isDiagnoseRegionLimitCacheable(&item) {
|
||
return
|
||
}
|
||
if strings.TrimSpace(cachedAt) == "" {
|
||
cachedAt = gtime.Now().Format("Y-m-d H:i:s")
|
||
}
|
||
payload := simDiagnoseRegionCachePayload{
|
||
Item: item,
|
||
CachedAt: cachedAt,
|
||
}
|
||
raw, err := json.Marshal(payload)
|
||
if err != nil {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.Info, marshal region cache failed, simKey %+v, err %+v", simKey, err)
|
||
return
|
||
}
|
||
if _, err = g.Redis().Do(ctx, "SETEX", diagnoseRegionCacheKey(simKey), int(simDiagnoseRegionCacheTTL.Seconds()), string(raw)); err != nil {
|
||
g.Log().Warningf(ctx, "sIotTSimDiagnose.Info, set region cache failed, simKey %+v, err %+v", simKey, err)
|
||
}
|
||
}
|
||
|
||
func hasDiagnoseRegionLimitData(item *model.SimBaseRes[model.SimRegionLimitStatusItem]) bool {
|
||
if item == nil {
|
||
return false
|
||
}
|
||
data := item.ResData
|
||
return strings.TrimSpace(data.RegionLimitStatus) != "" ||
|
||
strings.TrimSpace(data.OrderStatus) != "" ||
|
||
strings.TrimSpace(data.LimitStatus) != "" ||
|
||
strings.TrimSpace(data.LimitType) != "" ||
|
||
strings.TrimSpace(data.LimitArea) != ""
|
||
}
|
||
|
||
func isDiagnoseRegionLimitCacheable(item *model.SimBaseRes[model.SimRegionLimitStatusItem]) bool {
|
||
if item == nil || item.Code != 0 {
|
||
return false
|
||
}
|
||
|
||
data := item.ResData
|
||
return isKnownDiagnoseRegionStatus(data.RegionLimitStatus) ||
|
||
isKnownDiagnoseRegionStatus(data.OrderStatus) ||
|
||
isKnownDiagnoseRegionStatus(data.LimitStatus)
|
||
}
|
||
|
||
func isKnownDiagnoseRegionStatus(value string) bool {
|
||
value = strings.TrimSpace(value)
|
||
return value == "0" || value == "1"
|
||
}
|
||
|
||
func cachedDiagnoseRegionModule(item *model.SimBaseRes[model.SimRegionLimitStatusItem]) (status string, message string) {
|
||
if item == nil {
|
||
return "error", "区域限制暂无有效缓存数据,请稍后重试"
|
||
}
|
||
if isDiagnoseRegionLimitCacheable(item) {
|
||
return "success", ""
|
||
}
|
||
if item.Code == 5000 {
|
||
return "error", "区域限制暂无有效缓存数据,请稍后重试"
|
||
}
|
||
return "error", normalizeDiagnoseCustomerMessage(item.Message)
|
||
}
|
||
|
||
func describeDiagnoseRegionCache(cachedAt string) (updatedAt string, hint string) {
|
||
cachedAt = strings.TrimSpace(cachedAt)
|
||
if cachedAt == "" {
|
||
return "", ""
|
||
}
|
||
|
||
parsed, err := time.ParseInLocation("2006-01-02 15:04:05", cachedAt, time.Local)
|
||
if err != nil {
|
||
return cachedAt, ""
|
||
}
|
||
|
||
now := time.Now()
|
||
updatedAt = parsed.Format("15:04")
|
||
if sameDayTime(parsed, now) {
|
||
updatedAt += fmt.Sprintf("(%s)", regionCacheAgoText(now.Sub(parsed)))
|
||
} else {
|
||
updatedAt = parsed.Format("01-02 15:04") + fmt.Sprintf("(%s)", regionCacheAgoText(now.Sub(parsed)))
|
||
}
|
||
|
||
nextCheckAt := parsed.Add(simDiagnoseRegionCacheTTL)
|
||
if sameDayTime(parsed, nextCheckAt) {
|
||
hint = nextCheckAt.Format("15:04") + "后可再次查看"
|
||
} else {
|
||
hint = nextCheckAt.Format("01-02 15:04") + "后可再次查看"
|
||
}
|
||
return updatedAt, hint
|
||
}
|
||
|
||
func regionCacheAgoText(d time.Duration) string {
|
||
if d < time.Minute {
|
||
return "刚刚"
|
||
}
|
||
if d < time.Hour {
|
||
minutes := int(d.Minutes())
|
||
if minutes <= 0 {
|
||
minutes = 1
|
||
}
|
||
return fmt.Sprintf("%d分钟前", minutes)
|
||
}
|
||
hours := int(d.Hours())
|
||
if hours < 24 {
|
||
return fmt.Sprintf("%d小时前", hours)
|
||
}
|
||
return fmt.Sprintf("%d天前", hours/24)
|
||
}
|
||
|
||
func sameDayTime(left time.Time, right time.Time) bool {
|
||
leftYear, leftMonth, leftDay := left.Date()
|
||
rightYear, rightMonth, rightDay := right.Date()
|
||
return leftYear == rightYear && leftMonth == rightMonth && leftDay == rightDay
|
||
}
|
||
func applyDiagnoseRegionLimit(res *model.TSimDiagnoseInfoOutput, item *model.SimBaseRes[model.SimRegionLimitStatusItem]) {
|
||
if res == nil || res.Info == nil || item == nil {
|
||
return
|
||
}
|
||
if !hasDiagnoseRegionLimitData(item) {
|
||
return
|
||
}
|
||
if item.Code != 0 {
|
||
return
|
||
}
|
||
|
||
data := item.ResData
|
||
orderStatus := strings.TrimSpace(data.OrderStatus)
|
||
if orderStatus == "1" {
|
||
res.Info.RegionLimitStatus = emptyStringPtr("2")
|
||
res.Info.RegionLimitOrderStatus = emptyStringPtr("1")
|
||
res.Info.RegionLimitTriggerStatus = nil
|
||
res.Info.RegionLimitType = nil
|
||
res.Info.RegionLimitArea = nil
|
||
return
|
||
}
|
||
if orderStatus == "0" {
|
||
res.Info.RegionLimitOrderStatus = emptyStringPtr("0")
|
||
switch strings.TrimSpace(data.LimitStatus) {
|
||
case "0":
|
||
res.Info.RegionLimitStatus = emptyStringPtr("1")
|
||
res.Info.RegionLimitTriggerStatus = emptyStringPtr("0")
|
||
case "1":
|
||
res.Info.RegionLimitStatus = emptyStringPtr("0")
|
||
res.Info.RegionLimitTriggerStatus = emptyStringPtr("1")
|
||
case "-":
|
||
res.Info.RegionLimitStatus = emptyStringPtr("2")
|
||
res.Info.RegionLimitTriggerStatus = emptyStringPtr("-")
|
||
default:
|
||
res.Info.RegionLimitStatus = emptyStringPtr("2")
|
||
res.Info.RegionLimitTriggerStatus = nil
|
||
}
|
||
switch strings.TrimSpace(data.LimitType) {
|
||
case "1", "2", "3", "4":
|
||
res.Info.RegionLimitType = emptyStringPtr(data.LimitType)
|
||
default:
|
||
res.Info.RegionLimitType = nil
|
||
}
|
||
res.Info.RegionLimitArea = emptyStringPtr(data.LimitArea)
|
||
return
|
||
}
|
||
|
||
res.Info.RegionLimitStatus = mapRegionLimitStatus(data.RegionLimitStatus)
|
||
res.Info.RegionLimitOrderStatus = emptyStringPtr(data.OrderStatus)
|
||
res.Info.RegionLimitTriggerStatus = emptyStringPtr(data.LimitStatus)
|
||
res.Info.RegionLimitType = emptyStringPtr(data.LimitType)
|
||
res.Info.RegionLimitArea = emptyStringPtr(data.LimitArea)
|
||
}
|
||
|
||
func applyDiagnoseRateLimit(res *model.TSimDiagnoseInfoOutput, item *model.SimBaseRes[model.SimDataDiagnosisItem]) {
|
||
if res == nil || res.Info == nil || item == nil {
|
||
return
|
||
}
|
||
if item.Code == 5000 {
|
||
res.Info.RateLimitStatus = nil
|
||
res.Info.RateLimitReason = nil
|
||
return
|
||
}
|
||
if item.Code != 0 {
|
||
return
|
||
}
|
||
res.Info.RateLimitStatus = mapRateLimitStatus(item.ResData.RateLimitStatus)
|
||
res.Info.RateLimitReason = emptyStringPtr(item.ResData.RateLimitReason)
|
||
res.Info.RateLimitDetail = emptyStringPtr(item.ResData.RateLimitDetail)
|
||
res.Info.RateLimitApnDetails = make([]model.TSimDiagnoseRateLimitApnItem, 0, len(item.ResData.ApnDetails))
|
||
for _, apn := range item.ResData.ApnDetails {
|
||
pccDetails := make([]model.TSimDiagnoseRateLimitPccItem, 0, len(apn.PccDetails))
|
||
for _, pcc := range apn.PccDetails {
|
||
pccDetails = append(pccDetails, model.TSimDiagnoseRateLimitPccItem{
|
||
PccServiceCode: pcc.PccServiceCode,
|
||
PccName: pcc.PccName,
|
||
PccPolicyStatus: pcc.PccPolicyStatus,
|
||
PccServiceUsageState: pcc.PccServiceUsageState,
|
||
ModifyTime: pcc.ModifyTime,
|
||
})
|
||
}
|
||
res.Info.RateLimitApnDetails = append(res.Info.RateLimitApnDetails, model.TSimDiagnoseRateLimitApnItem{
|
||
ApnName: apn.ApnName,
|
||
ApnStatus: apn.ApnStatus,
|
||
ModifyTime: apn.ModifyTime,
|
||
OverUseDetails: apn.OverUseDetails,
|
||
ApnServiceUsageState: apn.ApnServiceUsageState,
|
||
PccDetails: pccDetails,
|
||
})
|
||
}
|
||
}
|
||
|
||
// timeStringPtr 格式化时间并返回字符串指针
|
||
func timeStringPtr(value *gtime.Time) *string {
|
||
if value == nil || value.IsZero() {
|
||
return nil
|
||
}
|
||
text := value.Format("Y-m-d H:i:s")
|
||
return &text
|
||
}
|
||
|
||
// durationMonthsPtr 计算两个时间之间的月数
|
||
func durationMonthsPtr(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
|
||
}
|
||
|
||
// mapOnlineStatus 映射在线状态
|
||
func diagnoseSessionStatus(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 ""
|
||
}
|
||
}
|
||
|
||
func mapOnlineStatus(status string) *string {
|
||
switch diagnoseSessionStatus(status) {
|
||
case "0":
|
||
return emptyStringPtr("1")
|
||
case "-1":
|
||
return emptyStringPtr("0")
|
||
case "-2":
|
||
return emptyStringPtr("2")
|
||
default:
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// mapOperatingStatus 映射运营商终端开关机状态
|
||
func mapOperatingStatus(status string) *string {
|
||
switch strings.TrimSpace(status) {
|
||
case "0":
|
||
value := "0"
|
||
return &value
|
||
case "1":
|
||
value := "1"
|
||
return &value
|
||
default:
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// mapDeviceStatus 映射设备状态
|
||
func mapDeviceStatus(status string) *string {
|
||
var value string
|
||
switch strings.TrimSpace(status) {
|
||
case "0":
|
||
value = "0"
|
||
case "1":
|
||
value = "1"
|
||
default:
|
||
return nil
|
||
}
|
||
return &value
|
||
}
|
||
|
||
// mapImeiStatus 映射机卡状态
|
||
func mapImeiStatusByBindResult(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
|
||
}
|
||
|
||
// mapRegionLimitStatus 映射地域限制状态
|
||
func mapRegionLimitStatus(status string) *string {
|
||
switch strings.TrimSpace(status) {
|
||
case "0":
|
||
value := "0"
|
||
return &value
|
||
case "1":
|
||
value := "1"
|
||
return &value
|
||
case "2":
|
||
value := "2"
|
||
return &value
|
||
default:
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// mapHumanLimitStatus 映射人联网限制状态
|
||
func mapHumanLimitStatus(status string) *string {
|
||
var value string
|
||
switch strings.TrimSpace(status) {
|
||
case "1":
|
||
value = "0"
|
||
case "0":
|
||
value = "1"
|
||
default:
|
||
return nil
|
||
}
|
||
return &value
|
||
}
|
||
|
||
// mapRateLimitStatus 映射达量限速状态
|
||
func mapRateLimitStatus(status string) *string {
|
||
var value string
|
||
switch strings.TrimSpace(status) {
|
||
case "0":
|
||
value = "0"
|
||
case "1":
|
||
value = "1"
|
||
default:
|
||
return nil
|
||
}
|
||
return &value
|
||
}
|
||
|
||
// mapPackageEffectiveStatus 映射套餐生效状态
|
||
func mapPackageEffectiveStatus(status int) *string {
|
||
var value string
|
||
switch status {
|
||
case 1:
|
||
value = "0"
|
||
case 2:
|
||
value = "1"
|
||
case 3:
|
||
value = "2"
|
||
default:
|
||
return nil
|
||
}
|
||
return &value
|
||
}
|
||
|
||
func (s *sIotTSimDiagnose) queryDailyFlow(ctx context.Context, iccid string, queryType string) (res *model.TSimDiagnoseFlowOutput, err error) {
|
||
days := 7
|
||
if queryType == "30d" {
|
||
days = 30
|
||
}
|
||
|
||
now := time.Now()
|
||
startTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).AddDate(0, 0, -(days - 1))
|
||
endTime := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location())
|
||
|
||
list, _, err := service.TrafficUsage().QueryDaily(ctx, model.TrafficUsageQueryInput{
|
||
Iccid: iccid,
|
||
StartTime: startTime,
|
||
EndTime: endTime,
|
||
Asc: true,
|
||
})
|
||
if err != nil {
|
||
return nil, gerror.Wrap(err, "查询SIM卡使用流量失败,请稍后重试!")
|
||
}
|
||
|
||
usageMap := make(map[string]float64, len(list))
|
||
for _, item := range list {
|
||
usageMap[item.Ts.Format("2006-01-02")] = item.UsageMb
|
||
}
|
||
|
||
res = &model.TSimDiagnoseFlowOutput{
|
||
Times: make([]string, 0, days),
|
||
Flow: make([]float64, 0, days),
|
||
FlowAvailable: make([]bool, 0, days),
|
||
Unit: "MB",
|
||
}
|
||
for i := 0; i < days; i++ {
|
||
current := startTime.AddDate(0, 0, i)
|
||
key := current.Format("2006-01-02")
|
||
res.Times = append(res.Times, current.Format("01-02"))
|
||
usage, ok := usageMap[key]
|
||
res.Flow = append(res.Flow, usage)
|
||
res.FlowAvailable = append(res.FlowAvailable, ok)
|
||
}
|
||
return res, nil
|
||
}
|
||
|
||
func (s *sIotTSimDiagnose) queryMonthlyFlow(ctx context.Context, iccid string) (res *model.TSimDiagnoseFlowOutput, err error) {
|
||
now := time.Now()
|
||
currentMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
|
||
startMonth := currentMonth.AddDate(0, -5, 0)
|
||
endTime := time.Date(now.Year(), now.Month()+1, 0, 23, 59, 59, 0, now.Location())
|
||
|
||
list, _, err := service.TrafficUsage().QueryMonthly(ctx, model.TrafficUsageQueryInput{
|
||
Iccid: iccid,
|
||
StartTime: startMonth,
|
||
EndTime: endTime,
|
||
Asc: true,
|
||
})
|
||
if err != nil {
|
||
return nil, gerror.Wrap(err, "查询SIM卡使用流量失败,请稍后重试!")
|
||
}
|
||
|
||
sort.Slice(list, func(i, j int) bool {
|
||
return list[i].Ts.Before(list[j].Ts)
|
||
})
|
||
|
||
usageMap := make(map[string]float64, len(list))
|
||
for _, item := range list {
|
||
usageMap[item.Ts.Format("2006-01")] = item.UsageMb
|
||
}
|
||
|
||
res = &model.TSimDiagnoseFlowOutput{
|
||
Times: make([]string, 0, 6),
|
||
Flow: make([]float64, 0, 6),
|
||
FlowAvailable: make([]bool, 0, 6),
|
||
Unit: "MB",
|
||
}
|
||
for i := 0; i < 6; i++ {
|
||
current := startMonth.AddDate(0, i, 0)
|
||
key := current.Format("2006-01")
|
||
res.Times = append(res.Times, key)
|
||
usage, ok := usageMap[key]
|
||
res.Flow = append(res.Flow, usage)
|
||
res.FlowAvailable = append(res.FlowAvailable, ok)
|
||
}
|
||
return res, nil
|
||
}
|