Files
iotlcm/server/internal/logic/app/app_upstream_session.go
T

276 lines
8.8 KiB
Go

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)
}