package app import ( "context" "encoding/json" "fmt" "net/http" "strings" "time" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/gclient" "github.com/gogf/gf/v2/net/gsvc" ) type appUpstreamDefaultConfig struct { ClientName string `json:"clientName"` Timeout string `json:"timeout"` } type appDiagnosisProxyContextKey struct{} type appUpstreamSimKey struct { ChanKey string `json:"chanKey,omitempty"` ICCID string `json:"iccid,omitempty"` IMSI string `json:"imsi,omitempty"` MSISDN string `json:"msisdn,omitempty"` } type appUpstreamSimQueryInput struct { SimKeys []*appUpstreamSimKey `json:"simKeys"` ReqData any `json:"reqData"` } type appUpstreamSimQueryItem struct { Code int `json:"code"` Message string `json:"message"` SimKey *appUpstreamSimKey `json:"simKey"` ResData json.RawMessage `json:"resData"` } type appUpstreamSimQueryResponse struct { Code *int `json:"code"` Message string `json:"message"` Data []appUpstreamSimQueryItem `json:"data"` } func queryAppCardRealtimeSession(ctx context.Context, row *appCardQueryRow) (*appOneLinkMockSimSessionItem, string, error) { simKey, err := appUpstreamSimKeyFromRow(row) if err != nil { return nil, "upstream", err } return queryAppCardRealtimeSessionWithKey(ctx, row, simKey) } func queryAppCardRealtimeSessionWithKey(ctx context.Context, row *appCardQueryRow, simKey *appUpstreamSimKey) (*appOneLinkMockSimSessionItem, string, error) { session, err := queryAppCardRealtimeSessionByUpstreamWithKey(ctx, simKey) if err == nil { return session, "upstream", nil } if ctx.Value(appDiagnosisProxyContextKey{}) == true || !appUpstreamMockFallbackEnabled(ctx) { return nil, "upstream", err } g.Log().Debugf(ctx, "app card session upstream query failed, fallback to onelink mock, iccid:%s, err:%+v", appCardQueryLogIccid(row), err) session, mockErr := queryAppCardRealtimeSessionByMock(ctx, row) if mockErr != nil { return nil, "onelink_mock", gerror.Wrapf(mockErr, "查询OneLink挡板会话失败,上游网关错误:%v", err) } return session, "onelink_mock", nil } func queryAppCardRealtimeSessionByUpstream(ctx context.Context, row *appCardQueryRow) (*appOneLinkMockSimSessionItem, error) { simKey, err := appUpstreamSimKeyFromRow(row) if err != nil { return nil, err } return queryAppCardRealtimeSessionByUpstreamWithKey(ctx, simKey) } func queryAppCardRealtimeSessionByUpstreamWithKey(ctx context.Context, simKey *appUpstreamSimKey) (*appOneLinkMockSimSessionItem, error) { items, err := queryAppUpstreamSimWithKey(ctx, simKey, "/sim/query/sim-session", map[string]any{}) if err != nil { return nil, err } for _, item := range items { if item.Code != 0 || len(item.ResData) == 0 { continue } session := appOneLinkMockSimSessionItem{} if err = json.Unmarshal(item.ResData, &session); err != nil { return nil, gerror.Wrap(err, "解析上游会话响应失败") } appNormalizeUpstreamSession(&session, item.ResData) if !session.isZero() { return &session, nil } } return nil, nil } func appNormalizeUpstreamSession(session *appOneLinkMockSimSessionItem, raw json.RawMessage) { if session == nil { return } if session.ApnId == "" { session.ApnId = appDiagnosisRawString(raw, "apnId", "apn") } if session.Rat == "" { session.Rat = appDiagnosisRawString(raw, "rat", "accessType", "netModel") } if session.Imei == "" { session.Imei = appDiagnosisRawString(raw, "imei", "IMEI") } if session.Ipv4 == "" { session.Ipv4 = appDiagnosisRawString(raw, "ipv4", "ipv4Address", "ip") } if session.Ipv6 == "" { session.Ipv6 = appDiagnosisRawString(raw, "ipv6", "ipv6Address") } if session.Ipv6Prefix == "" { session.Ipv6Prefix = appDiagnosisRawString(raw, "ipv6Prefix") } if session.CreateTime == "" { session.CreateTime = appDiagnosisRawString(raw, "createTime", "startTime") } if session.Status == "" { session.Status = appDiagnosisRawString(raw, "status", "onlineStatus", "sessionStatus") } if session.Status == "1" && appDiagnosisRawString(raw, "onlineStatus", "sessionStatus") != "" { session.Status = "-1" } if session.Status == "2" { session.Status = "-2" } } func queryAppCardRealtimeSessionByMock(ctx context.Context, row *appCardQueryRow) (*appOneLinkMockSimSessionItem, error) { client, err := newAppOneLinkMockClient(ctx) if err != nil { return nil, err } return client.QuerySimSession(ctx, row) } func appUpstreamMockFallbackEnabled(ctx context.Context) bool { return g.Cfg().MustGet(ctx, "upstream.onelinkMock.enabled", false).Bool() } func appCardQueryLogIccid(row *appCardQueryRow) string { if row == nil { return "" } return row.Iccid } func queryAppUpstreamSim(ctx context.Context, row *appCardQueryRow, path string, reqData any) ([]appUpstreamSimQueryItem, error) { simKey, err := appUpstreamSimKeyFromRow(row) if err != nil { return nil, err } return queryAppUpstreamSimWithKey(ctx, simKey, path, reqData) } func queryAppUpstreamSimWithKey(ctx context.Context, simKey *appUpstreamSimKey, path string, reqData any) ([]appUpstreamSimQueryItem, error) { if simKey == nil { return nil, gerror.New("卡片信息为空,不能查询上游") } cleanKey := &appUpstreamSimKey{ ChanKey: strings.TrimSpace(simKey.ChanKey), ICCID: strings.TrimSpace(simKey.ICCID), IMSI: strings.TrimSpace(simKey.IMSI), MSISDN: strings.TrimSpace(simKey.MSISDN), } if cleanKey.ICCID == "" && cleanKey.IMSI == "" && cleanKey.MSISDN == "" { return nil, gerror.New("卡片缺少上游查询标识") } cfg := appUpstreamDefaultConfig{} if err := g.Cfg().MustGet(ctx, "upstream.default").Scan(&cfg); err != nil { return nil, gerror.Wrap(err, "读取上游代理配置失败") } cfg.ClientName = strings.TrimSpace(cfg.ClientName) cfg.Timeout = strings.TrimSpace(cfg.Timeout) if cfg.ClientName == "" { return nil, gerror.New("上游代理地址未配置") } timeout := 10 * time.Second if cfg.Timeout != "" { if parsed, err := time.ParseDuration(cfg.Timeout); err == nil && parsed > 0 { timeout = parsed } } if reqData == nil { reqData = map[string]any{} } input := appUpstreamSimQueryInput{ SimKeys: []*appUpstreamSimKey{cleanKey}, ReqData: reqData, } body, err := json.Marshal(input) if err != nil { return nil, gerror.Wrap(err, "组装上游查询参数失败") } requestURL := appUpstreamURL(cfg.ClientName, path) client := g.Client() client.SetDiscovery(gsvc.GetRegistry()) appSetUpstreamClientTimeout(client, timeout) client.SetHeader("Content-Type", "application/json") if ctx.Value(appDiagnosisProxyContextKey{}) == true { token := strings.TrimSpace(g.Cfg().MustGet(ctx, "upstream.appDiagnosisToken").String()) if token == "" { return nil, gerror.New("诊断代理内部令牌未配置") } requestURL = appUpstreamURL(cfg.ClientName, "/internal/app-diagnosis"+path) client.SetHeader("X-App-Diagnosis-Token", token) } resp, err := client.Post(ctx, requestURL, string(body)) if err != nil { return nil, gerror.Wrap(err, "请求上游信息失败") } defer resp.Close() respBody := resp.ReadAll() if ctx.Value(appDiagnosisProxyContextKey{}) == true && resp.StatusCode == http.StatusNotFound && strings.Contains(string(respBody), "诊断通道不可用") { return nil, gerror.New(appDiagnosisNotIncludedMessage) } output := appUpstreamSimQueryResponse{} if err = json.Unmarshal(respBody, &output); err != nil { return nil, gerror.Wrap(err, "解析上游响应失败") } if resp.StatusCode < 200 || resp.StatusCode >= 300 || output.Code == nil || *output.Code != 0 { message := strings.TrimSpace(output.Message) if message == "" { message = "上游查询失败" } return nil, gerror.New(message) } return output.Data, nil } func appSetUpstreamClientTimeout(client *gclient.Client, timeout time.Duration) { client.SetTimeout(timeout) if transport, ok := client.Transport.(*http.Transport); ok { transport.ResponseHeaderTimeout = timeout } } func appUpstreamSimKeyFromRow(row *appCardQueryRow) (*appUpstreamSimKey, error) { if row == nil { return nil, gerror.New("卡片信息为空,不能查询上游") } simKey := &appUpstreamSimKey{ ChanKey: strings.TrimSpace(row.ChanKey), ICCID: strings.TrimSpace(row.Iccid), IMSI: strings.TrimSpace(row.Imsi), MSISDN: strings.TrimSpace(row.Msisdn), } if simKey.ICCID == "" && simKey.IMSI == "" && simKey.MSISDN == "" { return nil, gerror.New("卡片缺少上游查询标识") } return simKey, nil } func appUpstreamURL(clientName string, path string) string { clientName = strings.TrimRight(strings.TrimSpace(clientName), "/") path = "/" + strings.TrimLeft(path, "/") if strings.HasPrefix(clientName, "http://") || strings.HasPrefix(clientName, "https://") { return clientName + path } return fmt.Sprintf("http://%s%s", clientName, path) }