package app import ( "encoding/json" "fmt" "strings" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model" ) type appYdDataDiagnosisResp struct { Status string `json:"status"` Message string `json:"message"` Result []appYdDataDiagnosisResult `json:"result"` } type appYdDataDiagnosisResult struct { ApnList json.RawMessage `json:"apnList"` } type appYdDataDiagnosisApnInfo struct { ApnName string `json:"apnName"` ApnStatus string `json:"apnStatus"` ModifyTime string `json:"modifyTime"` OverUseDetails string `json:"overUseDetails"` ApnServiceUsageState string `json:"apnServiceUsageState"` PccList json.RawMessage `json:"pccList"` } type appYdDataDiagnosisPccInfo struct { PccServiceCode string `json:"pccServiceCode"` PccName string `json:"pccName"` PccNameLower string `json:"pccname"` PccPolicyStatus string `json:"pccPolicyStatus"` PccServiceUsageState string `json:"pccServiceUsageState"` ModifyTime string `json:"modifyTime"` } func appParseYdDataDiagnosisBody(body json.RawMessage) (appYdDataDiagnosisItem, error) { var ydResp appYdDataDiagnosisResp if err := json.Unmarshal(body, &ydResp); err != nil { return appYdDataDiagnosisItem{}, err } return appBuildYdDataDiagnosisItem(ydResp.Result), nil } func appBuildYdDataDiagnosisItem(results []appYdDataDiagnosisResult) appYdDataDiagnosisItem { apnInfos := make([]appYdDataDiagnosisApnInfo, 0) for _, result := range results { apnInfos = append(apnInfos, appDecodeYdDataDiagnosisApnInfos(result.ApnList)...) } if len(apnInfos) == 0 { return appYdDataDiagnosisItem{ RateLimitStatus: "2", RateLimitReason: "上游未返回达量限速明细", } } triggered := false reasons := make([]string, 0) details := make([]string, 0, len(apnInfos)) apnDetails := make([]model.AppDiagnosisRateLimitApnItem, 0, len(apnInfos)) for _, apn := range apnInfos { apnName := strings.TrimSpace(apn.ApnName) displayApn := apnName if displayApn == "" { displayApn = "未命名APN" } if overUseText := appMapYdOverUseDetailsText(apn.OverUseDetails); overUseText != "" { triggered = true reasons = append(reasons, fmt.Sprintf("%s:%s", displayApn, overUseText)) } if stateText, limited := appMapYdUsageStateText(apn.ApnServiceUsageState); limited { triggered = true reasons = append(reasons, fmt.Sprintf("%s:%s", displayApn, stateText)) } pccInfos := appDecodeYdDataDiagnosisPccInfos(apn.PccList) pccDetails := make([]model.AppDiagnosisRateLimitPccItem, 0, len(pccInfos)) pccText := make([]string, 0, len(pccInfos)) for _, pcc := range pccInfos { pccName := appFirstNonEmpty(pcc.PccName, pcc.PccNameLower, pcc.PccServiceCode) if pccName == "" { pccName = "未命名PCC策略" } if stateText, limited := appMapYdUsageStateText(pcc.PccServiceUsageState); limited { triggered = true reasons = append(reasons, fmt.Sprintf("%s/%s:%s", displayApn, pccName, stateText)) } pccText = append(pccText, fmt.Sprintf("%s %s %s", pccName, appMapYdPccPolicyStatusText(pcc.PccPolicyStatus), appMapYdUsageStateTextForDetail(pcc.PccServiceUsageState))) pccDetails = append(pccDetails, model.AppDiagnosisRateLimitPccItem{ PccServiceCode: strings.TrimSpace(pcc.PccServiceCode), PccName: appFirstNonEmpty(pcc.PccName, pcc.PccNameLower), PccPolicyStatus: strings.TrimSpace(pcc.PccPolicyStatus), PccServiceUsageState: strings.TrimSpace(pcc.PccServiceUsageState), ModifyTime: strings.TrimSpace(pcc.ModifyTime), }) } detailParts := []string{fmt.Sprintf("%s %s %s", displayApn, appMapYdApnStatusText(apn.ApnStatus), appMapYdUsageStateTextForDetail(apn.ApnServiceUsageState))} if overUseText := appMapYdOverUseDetailsText(apn.OverUseDetails); overUseText != "" { detailParts = append(detailParts, "超停原因:"+overUseText) } if len(pccText) > 0 { detailParts = append(detailParts, "PCC:"+strings.Join(pccText, ";")) } details = append(details, strings.Join(detailParts, ";")) apnDetails = append(apnDetails, model.AppDiagnosisRateLimitApnItem{ ApnName: apnName, ApnStatus: strings.TrimSpace(apn.ApnStatus), ModifyTime: strings.TrimSpace(apn.ModifyTime), OverUseDetails: strings.TrimSpace(apn.OverUseDetails), ApnServiceUsageState: strings.TrimSpace(apn.ApnServiceUsageState), PccDetails: pccDetails, }) } status := "1" reason := "未发现达量限速或流量关停" if triggered { status = "0" reason = strings.Join(appUniqueNonEmptyStrings(reasons), ";") } return appYdDataDiagnosisItem{ RateLimitStatus: status, RateLimitReason: reason, RateLimitDetail: strings.Join(appUniqueNonEmptyStrings(details), ";"), ApnDetails: apnDetails, } } func appDecodeYdDataDiagnosisApnInfos(raw json.RawMessage) []appYdDataDiagnosisApnInfo { raw = appTrimJSONRaw(raw) if len(raw) == 0 { return nil } if raw[0] == '{' { var wrapper struct { ApnInfo json.RawMessage `json:"apnInfo"` } if json.Unmarshal(raw, &wrapper) == nil && len(appTrimJSONRaw(wrapper.ApnInfo)) > 0 { return appDecodeYdDataDiagnosisApnInfoList(wrapper.ApnInfo) } var item appYdDataDiagnosisApnInfo if json.Unmarshal(raw, &item) == nil { return []appYdDataDiagnosisApnInfo{item} } return nil } if raw[0] == '[' { var wrappers []struct { ApnInfo json.RawMessage `json:"apnInfo"` } if json.Unmarshal(raw, &wrappers) == nil { items := make([]appYdDataDiagnosisApnInfo, 0) for _, wrapper := range wrappers { items = append(items, appDecodeYdDataDiagnosisApnInfoList(wrapper.ApnInfo)...) } if len(items) > 0 { return items } } return appDecodeYdDataDiagnosisApnInfoList(raw) } return nil } func appDecodeYdDataDiagnosisApnInfoList(raw json.RawMessage) []appYdDataDiagnosisApnInfo { raw = appTrimJSONRaw(raw) if len(raw) == 0 { return nil } if raw[0] == '[' { var items []appYdDataDiagnosisApnInfo if json.Unmarshal(raw, &items) == nil && len(items) > 0 { return items } } if raw[0] == '{' { var item appYdDataDiagnosisApnInfo if json.Unmarshal(raw, &item) == nil { return []appYdDataDiagnosisApnInfo{item} } } return nil } func appDecodeYdDataDiagnosisPccInfos(raw json.RawMessage) []appYdDataDiagnosisPccInfo { raw = appTrimJSONRaw(raw) if len(raw) == 0 { return nil } if raw[0] == '{' { var wrapper struct { PccInfo json.RawMessage `json:"pccInfo"` } if json.Unmarshal(raw, &wrapper) == nil && len(appTrimJSONRaw(wrapper.PccInfo)) > 0 { return appDecodeYdDataDiagnosisPccInfoList(wrapper.PccInfo) } var item appYdDataDiagnosisPccInfo if json.Unmarshal(raw, &item) == nil { return []appYdDataDiagnosisPccInfo{item} } return nil } if raw[0] == '[' { var wrappers []struct { PccInfo json.RawMessage `json:"pccInfo"` } if json.Unmarshal(raw, &wrappers) == nil { items := make([]appYdDataDiagnosisPccInfo, 0) for _, wrapper := range wrappers { items = append(items, appDecodeYdDataDiagnosisPccInfoList(wrapper.PccInfo)...) } if len(items) > 0 { return items } } return appDecodeYdDataDiagnosisPccInfoList(raw) } return nil } func appDecodeYdDataDiagnosisPccInfoList(raw json.RawMessage) []appYdDataDiagnosisPccInfo { raw = appTrimJSONRaw(raw) if len(raw) == 0 { return nil } if raw[0] == '[' { var items []appYdDataDiagnosisPccInfo if json.Unmarshal(raw, &items) == nil && len(items) > 0 { return items } } if raw[0] == '{' { var item appYdDataDiagnosisPccInfo if json.Unmarshal(raw, &item) == nil { return []appYdDataDiagnosisPccInfo{item} } } return nil } func appTrimJSONRaw(raw json.RawMessage) json.RawMessage { return json.RawMessage(strings.TrimSpace(string(raw))) } func appFirstNonEmpty(values ...string) string { for _, value := range values { if value = strings.TrimSpace(value); value != "" { return value } } return "" } func appUniqueNonEmptyStrings(values []string) []string { seen := make(map[string]struct{}, len(values)) result := make([]string, 0, len(values)) for _, value := range values { value = strings.TrimSpace(value) if value == "" { continue } if _, ok := seen[value]; ok { continue } seen[value] = struct{}{} result = append(result, value) } return result } func appMapYdOverUseDetailsText(value string) string { parts := strings.FieldsFunc(strings.TrimSpace(value), func(r rune) bool { return r == '|' || r == ',' || r == ';' || r == ',' || r == ';' }) texts := make([]string, 0, len(parts)) for _, part := range parts { switch strings.TrimSpace(part) { case "12": texts = append(texts, "流量用尽关停") case "17": texts = append(texts, "限额管控关停") case "18": texts = append(texts, "超量关停") case "": default: texts = append(texts, strings.TrimSpace(part)) } } return strings.Join(appUniqueNonEmptyStrings(texts), ";") } func appMapYdUsageStateText(value string) (text string, limited bool) { switch strings.TrimSpace(value) { case "", "1": return "正常速率", false case "91": return "限速至2Mbps(月初不自动恢复)", true case "92": return "限速至2Mbps(月初自动恢复)", true case "95": return "限速至1Mbps(月初不自动恢复)", true case "96": return "限速至1Mbps(月初自动恢复)", true case "97": return "达量限速2M", true case "98": return "达量限速1M", true default: value = strings.TrimSpace(value) if value == "" { return "", false } return value, value != "0" } } func appMapYdUsageStateTextForDetail(value string) string { text, _ := appMapYdUsageStateText(value) return text } func appMapYdApnStatusText(value string) string { switch strings.TrimSpace(value) { case "", "0", "1": return "APN已停用" case "2": return "APN已激活" case "3": return "APN已删除" case "4": return "APN已暂停" default: return strings.TrimSpace(value) } } func appMapYdPccPolicyStatusText(value string) string { switch strings.TrimSpace(value) { case "", "0", "1": return "策略已停用" case "2": return "策略已激活" case "3": return "策略已删除" case "4": return "策略已暂停" default: return strings.TrimSpace(value) } } type appYdDataDiagnosisItem struct { RateLimitStatus string `json:"rateLimitStatus"` RateLimitReason string `json:"rateLimitReason"` RateLimitDetail string `json:"rateLimitDetail"` ApnDetails []model.AppDiagnosisRateLimitApnItem `json:"apnDetails"` } func appDiagnosisNormalizeRateLimitBody(raw json.RawMessage) json.RawMessage { if appDiagnosisRawString(raw, "rateLimitStatus", "rate_limit_status") != "" || len(appDiagnosisRateLimitApnDetails(raw)) > 0 { return raw } var payload map[string]json.RawMessage if json.Unmarshal(raw, &payload) != nil || len(payload["result"]) == 0 { return raw } parsed, err := appParseYdDataDiagnosisBody(raw) if err != nil || len(parsed.ApnDetails) == 0 { return raw } normalized, err := json.Marshal(parsed) if err != nil { return raw } return normalized }