Files
iotlcm/server/internal/logic/upstream/sim_data_diagnosis.go
T
MiFaZhan 59eb34976a chore: 初始开源发布
iotlcm-admin-svr:管理后台服务。

- Go module:iotlcm → git.5iot.com/iotlcm-public/iotlcm-admin-svr(729 文件)
- 凭据与地址脱敏:JWT signingKey(含代码里的 MustGet 默认值)、
  MySQL DSN、Redis 口令、告警邮件发件人邮箱全部改为占位;
  私网 IP → 文档地址段;库名 xiujie_admin → iotlcm
- 真实手机号/邮箱 → 合法 11 位编造号 / example.com
- 移除内部资产:CI 触发器、过时 DDL(sql/db_iotlcm.sql)、
  私有依赖 go get 行、明文 registry 登录口令
- 启动 banner 统一 IOTLCM 方块字 + 社区惯例署名
- Dockerfile 改用公共基础镜像
- 拼错的 RADEME.md → README.md,README 重写
- 需要真实数据库的用例改为可用 IOTLCM_TEST_WITH_DB=1 显式启用
- 补 Apache-2.0 LICENSE,LF 行尾 + .gitattributes

数据库 schema 不随本仓发布(生产 DDL 已过时),README 已说明。
2026-10-23 14:48:30 +08:00

356 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package upstream
import (
"encoding/json"
"fmt"
"strings"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
)
type ydDataDiagnosisResp struct {
Status string `json:"status"`
Message string `json:"message"`
Result []ydDataDiagnosisResult `json:"result"`
}
type ydDataDiagnosisResult struct {
ApnList json.RawMessage `json:"apnList"`
}
type ydDataDiagnosisApnInfo 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 ydDataDiagnosisPccInfo 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 parseYdDataDiagnosisBody(body json.RawMessage) (model.SimDataDiagnosisItem, error) {
var ydResp ydDataDiagnosisResp
if err := json.Unmarshal(body, &ydResp); err != nil {
return model.SimDataDiagnosisItem{}, err
}
return buildYdDataDiagnosisItem(ydResp.Result), nil
}
func buildYdDataDiagnosisItem(results []ydDataDiagnosisResult) model.SimDataDiagnosisItem {
apnInfos := make([]ydDataDiagnosisApnInfo, 0)
for _, result := range results {
apnInfos = append(apnInfos, decodeYdDataDiagnosisApnInfos(result.ApnList)...)
}
if len(apnInfos) == 0 {
return model.SimDataDiagnosisItem{
RateLimitStatus: "2",
RateLimitReason: "上游未返回达量限速明细",
}
}
triggered := false
reasons := make([]string, 0)
details := make([]string, 0, len(apnInfos))
apnDetails := make([]model.SimDataDiagnosisApnDetail, 0, len(apnInfos))
for _, apn := range apnInfos {
apnName := strings.TrimSpace(apn.ApnName)
displayApn := apnName
if displayApn == "" {
displayApn = "未命名APN"
}
if overUseText := mapYdOverUseDetailsText(apn.OverUseDetails); overUseText != "" {
triggered = true
reasons = append(reasons, fmt.Sprintf("%s:%s", displayApn, overUseText))
}
if stateText, limited := mapYdUsageStateText(apn.ApnServiceUsageState); limited {
triggered = true
reasons = append(reasons, fmt.Sprintf("%s:%s", displayApn, stateText))
}
pccInfos := decodeYdDataDiagnosisPccInfos(apn.PccList)
pccDetails := make([]model.SimDataDiagnosisPccDetail, 0, len(pccInfos))
pccText := make([]string, 0, len(pccInfos))
for _, pcc := range pccInfos {
pccName := firstNonEmpty(pcc.PccName, pcc.PccNameLower, pcc.PccServiceCode)
if pccName == "" {
pccName = "未命名PCC策略"
}
if stateText, limited := mapYdUsageStateText(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, mapYdPccPolicyStatusText(pcc.PccPolicyStatus), mapYdUsageStateTextForDetail(pcc.PccServiceUsageState)))
pccDetails = append(pccDetails, model.SimDataDiagnosisPccDetail{
PccServiceCode: strings.TrimSpace(pcc.PccServiceCode),
PccName: firstNonEmpty(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, mapYdApnStatusText(apn.ApnStatus), mapYdUsageStateTextForDetail(apn.ApnServiceUsageState))}
if overUseText := mapYdOverUseDetailsText(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.SimDataDiagnosisApnDetail{
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(uniqueNonEmptyStrings(reasons), ";")
}
return model.SimDataDiagnosisItem{
RateLimitStatus: status,
RateLimitReason: reason,
RateLimitDetail: strings.Join(uniqueNonEmptyStrings(details), ";"),
ApnDetails: apnDetails,
}
}
func decodeYdDataDiagnosisApnInfos(raw json.RawMessage) []ydDataDiagnosisApnInfo {
raw = trimJSONRaw(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(trimJSONRaw(wrapper.ApnInfo)) > 0 {
return decodeYdDataDiagnosisApnInfoList(wrapper.ApnInfo)
}
var item ydDataDiagnosisApnInfo
if json.Unmarshal(raw, &item) == nil {
return []ydDataDiagnosisApnInfo{item}
}
return nil
}
if raw[0] == '[' {
var wrappers []struct {
ApnInfo json.RawMessage `json:"apnInfo"`
}
if json.Unmarshal(raw, &wrappers) == nil {
items := make([]ydDataDiagnosisApnInfo, 0)
for _, wrapper := range wrappers {
items = append(items, decodeYdDataDiagnosisApnInfoList(wrapper.ApnInfo)...)
}
if len(items) > 0 {
return items
}
}
return decodeYdDataDiagnosisApnInfoList(raw)
}
return nil
}
func decodeYdDataDiagnosisApnInfoList(raw json.RawMessage) []ydDataDiagnosisApnInfo {
raw = trimJSONRaw(raw)
if len(raw) == 0 {
return nil
}
if raw[0] == '[' {
var items []ydDataDiagnosisApnInfo
if json.Unmarshal(raw, &items) == nil && len(items) > 0 {
return items
}
}
if raw[0] == '{' {
var item ydDataDiagnosisApnInfo
if json.Unmarshal(raw, &item) == nil {
return []ydDataDiagnosisApnInfo{item}
}
}
return nil
}
func decodeYdDataDiagnosisPccInfos(raw json.RawMessage) []ydDataDiagnosisPccInfo {
raw = trimJSONRaw(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(trimJSONRaw(wrapper.PccInfo)) > 0 {
return decodeYdDataDiagnosisPccInfoList(wrapper.PccInfo)
}
var item ydDataDiagnosisPccInfo
if json.Unmarshal(raw, &item) == nil {
return []ydDataDiagnosisPccInfo{item}
}
return nil
}
if raw[0] == '[' {
var wrappers []struct {
PccInfo json.RawMessage `json:"pccInfo"`
}
if json.Unmarshal(raw, &wrappers) == nil {
items := make([]ydDataDiagnosisPccInfo, 0)
for _, wrapper := range wrappers {
items = append(items, decodeYdDataDiagnosisPccInfoList(wrapper.PccInfo)...)
}
if len(items) > 0 {
return items
}
}
return decodeYdDataDiagnosisPccInfoList(raw)
}
return nil
}
func decodeYdDataDiagnosisPccInfoList(raw json.RawMessage) []ydDataDiagnosisPccInfo {
raw = trimJSONRaw(raw)
if len(raw) == 0 {
return nil
}
if raw[0] == '[' {
var items []ydDataDiagnosisPccInfo
if json.Unmarshal(raw, &items) == nil && len(items) > 0 {
return items
}
}
if raw[0] == '{' {
var item ydDataDiagnosisPccInfo
if json.Unmarshal(raw, &item) == nil {
return []ydDataDiagnosisPccInfo{item}
}
}
return nil
}
func trimJSONRaw(raw json.RawMessage) json.RawMessage {
return json.RawMessage(strings.TrimSpace(string(raw)))
}
func firstNonEmpty(values ...string) string {
for _, value := range values {
if value = strings.TrimSpace(value); value != "" {
return value
}
}
return ""
}
func uniqueNonEmptyStrings(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 mapYdOverUseDetailsText(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(uniqueNonEmptyStrings(texts), ";")
}
func mapYdUsageStateText(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 mapYdUsageStateTextForDetail(value string) string {
text, _ := mapYdUsageStateText(value)
return text
}
func mapYdApnStatusText(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 mapYdPccPolicyStatusText(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)
}
}