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

544 lines
17 KiB
Go

package app
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
type appOneLinkMockConfig struct {
Enabled bool `json:"enabled"`
BaseURL string `json:"baseUrl"`
AppID string `json:"appid"`
Password string `json:"password"`
Timeout string `json:"timeout"`
}
type appOneLinkMockClient struct {
cfg *appOneLinkMockConfig
httpClient *http.Client
token string
}
type appOneLinkMockTokenResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Result []struct {
Token string `json:"token"`
TTL string `json:"ttl"`
} `json:"result"`
}
type appOneLinkMockChangeStatusResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Result []struct {
Msisdn string `json:"msisdn"`
OrderNum string `json:"orderNum"`
} `json:"result"`
}
type appOneLinkMockBasicInfoResponse struct {
Status string `json:"status"`
Code int `json:"code"`
Message string `json:"message"`
Result json.RawMessage `json:"result"`
Data []struct {
Code int `json:"code"`
Message string `json:"message"`
ResData json.RawMessage `json:"resData"`
} `json:"data"`
}
type appOneLinkMockSimSessionResponse struct {
Status string `json:"status"`
Code int `json:"code"`
Message string `json:"message"`
Result json.RawMessage `json:"result"`
Data []struct {
Code int `json:"code"`
Message string `json:"message"`
ResData appOneLinkMockSimSessionItem `json:"resData"`
} `json:"data"`
}
type appOneLinkMockSimSessionItem struct {
ApnId string `json:"apnId"`
Rat string `json:"rat"`
Imei string `json:"imei"`
Ipv4 string `json:"ipv4"`
Ipv6 string `json:"ipv6"`
Ipv6Prefix string `json:"ipv6Prefix"`
Status string `json:"status"`
CreateTime string `json:"createTime"`
}
type appOneLinkMockStatusOutcome = appSimStatusChangeOutcome
func newAppOneLinkMockClient(ctx context.Context) (*appOneLinkMockClient, error) {
cfg, err := appLoadOneLinkMockConfig(ctx)
if err != nil {
return nil, err
}
timeout := 10 * time.Second
if cfg.Timeout != "" {
if parsed, err := time.ParseDuration(cfg.Timeout); err == nil && parsed > 0 {
timeout = parsed
}
}
return &appOneLinkMockClient{
cfg: cfg,
httpClient: &http.Client{Timeout: timeout},
}, nil
}
func appLoadOneLinkMockConfig(ctx context.Context) (*appOneLinkMockConfig, error) {
cfg := &appOneLinkMockConfig{
BaseURL: "https://example.com/onelink-mock/v5",
Timeout: "10s",
}
if err := g.Cfg().MustGet(ctx, "upstream.onelinkMock").Scan(cfg); err != nil {
return nil, gerror.Wrap(err, "读取OneLink挡板配置失败")
}
cfg.BaseURL = strings.TrimRight(strings.TrimSpace(cfg.BaseURL), "/")
cfg.AppID = strings.TrimSpace(cfg.AppID)
cfg.Password = strings.TrimSpace(cfg.Password)
cfg.Timeout = strings.TrimSpace(cfg.Timeout)
if !cfg.Enabled {
return nil, gerror.New("OneLink挡板未启用")
}
if cfg.BaseURL == "" {
return nil, gerror.New("OneLink挡板地址未配置")
}
if cfg.AppID == "" || cfg.Password == "" {
return nil, gerror.New("OneLink挡板appid或password未配置")
}
return cfg, nil
}
func (c *appOneLinkMockClient) ChangeSimStatus(ctx context.Context, operation appCardLifecycleOperation, row *appCardLifecycleExecutionRow) (appOneLinkMockStatusOutcome, error) {
if c == nil || c.cfg == nil {
return appOneLinkMockStatusOutcome{}, gerror.New("OneLink挡板客户端未初始化")
}
if row == nil {
return appOneLinkMockStatusOutcome{}, gerror.New("卡片操作行不能为空")
}
msisdn := strings.TrimSpace(row.Msisdn)
if msisdn == "" && row.Card != nil {
msisdn = strings.TrimSpace(row.Card.Msisdn)
}
if msisdn == "" {
return appOneLinkMockStatusOutcome{}, gerror.New("卡片缺少MSISDN,不能请求OneLink挡板")
}
operType, err := appOneLinkMockOperType(operation)
if err != nil {
return appOneLinkMockStatusOutcome{}, err
}
token, err := c.ensureToken(ctx)
if err != nil {
return appOneLinkMockStatusOutcome{}, err
}
params := url.Values{}
params.Set("appid", c.cfg.AppID)
params.Set("transid", appOneLinkMockTransID(c.cfg.AppID))
params.Set("token", token)
params.Set("msisdn", msisdn)
params.Set("operType", operType)
body, err := c.get(ctx, c.cfg.BaseURL+"/ec/change/sim-status?"+params.Encode())
if err != nil {
return appOneLinkMockStatusOutcome{}, err
}
var response appOneLinkMockChangeStatusResponse
if err = json.Unmarshal(body, &response); err != nil {
return appOneLinkMockStatusOutcome{}, gerror.Wrap(err, "解析OneLink挡板状态变更响应失败")
}
if strings.TrimSpace(response.Status) != "0" {
message := strings.TrimSpace(response.Message)
if message == "" {
message = "OneLink挡板状态变更失败"
}
return appOneLinkMockStatusOutcome{Code: -1, Message: message}, nil
}
orderNum := ""
if len(response.Result) > 0 {
orderNum = strings.TrimSpace(response.Result[0].OrderNum)
}
return appOneLinkMockStatusOutcome{
Success: true,
Code: 0,
Message: appOneLinkMockSuccessMessage(response.Message),
OrderNum: orderNum,
}, nil
}
func (c *appOneLinkMockClient) QuerySimBasicInfo(ctx context.Context, input appSimBasicInfoInput) (*appSimBasicInfoOutput, error) {
if c == nil || c.cfg == nil {
return nil, gerror.New("OneLink鎸℃澘瀹㈡埛绔湭鍒濆鍖?")
}
token, err := c.ensureToken(ctx)
if err != nil {
return nil, err
}
params := url.Values{}
params.Set("appid", c.cfg.AppID)
params.Set("transid", appOneLinkMockTransID(c.cfg.AppID))
params.Set("token", token)
appOneLinkMockAddBasicInfoQueryParam(params, input)
if params.Get("msisdn") == "" && params.Get("iccid") == "" && params.Get("imsi") == "" && params.Get("imei") == "" {
return nil, gerror.New("鍗$墖缂哄皯鏌ヨ鏍囪瘑锛屼笉鑳借姹侽neLink鎸℃澘")
}
body, err := c.get(ctx, c.cfg.BaseURL+"/ec/query/sim-basic-info?"+params.Encode())
if err != nil {
return nil, err
}
var response appOneLinkMockBasicInfoResponse
if err = json.Unmarshal(body, &response); err != nil {
return nil, gerror.Wrap(err, "瑙f瀽OneLink鎸℃澘鍩烘湰淇℃伅鍝嶅簲澶辫触")
}
if status := strings.TrimSpace(response.Status); status != "" && status != "0" {
message := strings.TrimSpace(response.Message)
if message == "" {
message = "OneLink鎸℃澘鍩烘湰淇℃伅鏌ヨ澶辫触"
}
return nil, gerror.New(message)
}
if response.Code != 0 {
message := strings.TrimSpace(response.Message)
if message == "" {
message = "OneLink鎸℃澘鍩烘湰淇℃伅鏌ヨ澶辫触"
}
return nil, gerror.New(message)
}
if output := appOneLinkMockParseBasicInfoOutput(response.Result, input); output != nil {
return output, nil
}
for _, item := range response.Data {
if item.Code != 0 {
continue
}
if output := appOneLinkMockParseBasicInfoOutput(item.ResData, input); output != nil {
return output, nil
}
}
return nil, nil
}
func (c *appOneLinkMockClient) QuerySimSession(ctx context.Context, card *appCardQueryRow) (*appOneLinkMockSimSessionItem, error) {
if c == nil || c.cfg == nil {
return nil, gerror.New("OneLink挡板客户端未初始化")
}
if card == nil {
return nil, gerror.New("卡片信息为空,不能查询会话")
}
params := url.Values{}
params.Set("appid", c.cfg.AppID)
params.Set("transid", appOneLinkMockTransID(c.cfg.AppID))
if token, err := c.ensureToken(ctx); err != nil {
return nil, err
} else {
params.Set("token", token)
}
if value := strings.TrimSpace(card.Msisdn); value != "" {
params.Set("msisdn", value)
}
if value := strings.TrimSpace(card.Iccid); value != "" {
params.Set("iccid", value)
}
if value := strings.TrimSpace(card.Imsi); value != "" {
params.Set("imsi", value)
}
if params.Get("msisdn") == "" && params.Get("iccid") == "" && params.Get("imsi") == "" {
return nil, gerror.New("卡片缺少查询会话所需标识")
}
body, err := c.get(ctx, c.cfg.BaseURL+"/ec/query/sim-session?"+params.Encode())
if err != nil {
return nil, err
}
var response appOneLinkMockSimSessionResponse
if err = json.Unmarshal(body, &response); err != nil {
return nil, gerror.Wrap(err, "解析OneLink挡板会话响应失败")
}
if strings.TrimSpace(response.Status) != "" && strings.TrimSpace(response.Status) != "0" {
message := strings.TrimSpace(response.Message)
if message == "" {
message = "OneLink挡板会话查询失败"
}
return nil, gerror.New(message)
}
if response.Code != 0 {
message := strings.TrimSpace(response.Message)
if message == "" {
message = "OneLink挡板会话查询失败"
}
return nil, gerror.New(message)
}
for _, item := range appOneLinkMockParseSessionResult(response.Result) {
if !item.isZero() {
return &item, nil
}
}
for _, item := range response.Data {
if item.Code == 0 && !item.ResData.isZero() {
session := item.ResData
return &session, nil
}
}
return nil, nil
}
func appOneLinkMockAddBasicInfoQueryParam(params url.Values, input appSimBasicInfoInput) {
if params == nil {
return
}
if value := strings.TrimSpace(input.Msisdn); value != "" {
params.Set("msisdn", value)
}
if value := strings.TrimSpace(input.Iccid); value != "" {
params.Set("iccid", value)
}
if value := strings.TrimSpace(input.Imsi); value != "" {
params.Set("imsi", value)
}
if value := strings.TrimSpace(input.Imei); value != "" {
params.Set("imei", value)
}
if params.Get("msisdn") == "" && params.Get("iccid") == "" && params.Get("imsi") == "" && params.Get("imei") == "" {
if value := strings.TrimSpace(input.CardValue); value != "" {
params.Set("msisdn", value)
}
}
}
func appOneLinkMockParseBasicInfoOutput(raw json.RawMessage, input appSimBasicInfoInput) *appSimBasicInfoOutput {
payload := appOneLinkMockFirstPayload(raw)
if len(payload) == 0 {
return nil
}
output := &appSimBasicInfoOutput{
Provider: appUpstreamProviderMock,
Iccid: appFirstNotEmpty(appOneLinkMockMapString(payload, "iccid", "Iccid"), strings.TrimSpace(input.Iccid)),
Msisdn: appFirstNotEmpty(appOneLinkMockMapString(payload, "msisdn", "Msisdn"), strings.TrimSpace(input.Msisdn), strings.TrimSpace(input.CardValue)),
Imsi: appFirstNotEmpty(appOneLinkMockMapString(payload, "imsi", "Imsi"), strings.TrimSpace(input.Imsi)),
Imei: appFirstNotEmpty(appOneLinkMockMapString(payload, "imei", "Imei"), strings.TrimSpace(input.Imei)),
OpenDate: appOneLinkMockMapString(payload, "openDate", "open_date", "OpenDate"),
ActiveDate: appOneLinkMockMapString(payload, "activeDate", "active_date", "ActiveDate"),
Remark: appOneLinkMockMapString(payload, "remark", "Remark"),
LastChangeDate: appOneLinkMockMapString(payload, "lastChangeDate", "last_change_date", "LastChangeDate"),
}
statusText := appOneLinkMockMapString(payload, "cardStatus", "card_status", "status", "Status")
if statusText != "" {
if status, err := strconv.Atoi(statusText); err == nil {
output.CardStatus = status
}
if output.CardStatus == 0 {
switch strings.ToLower(statusText) {
case "active", "activated", "normal", "ok", "success":
output.CardStatus = consts.V3ApiSimBossCardStatusActivated
case "stop", "stopped", "disable", "disabled":
output.CardStatus = consts.V3ApiSimBossCardStatusStopped
}
}
}
if output.Iccid == "" && output.Msisdn == "" && output.Imsi == "" && output.Imei == "" && output.OpenDate == "" && output.ActiveDate == "" && output.Remark == "" && output.LastChangeDate == "" && output.CardStatus == 0 {
return nil
}
return output
}
func appOneLinkMockFirstPayload(raw json.RawMessage) map[string]any {
if len(raw) == 0 || string(raw) == "null" {
return nil
}
var payload map[string]any
if err := json.Unmarshal(raw, &payload); err == nil && len(payload) > 0 {
if nested, ok := payload["resData"]; ok && nested != nil {
if nestedRaw, err := json.Marshal(nested); err == nil {
if nestedPayload := appOneLinkMockFirstPayload(nestedRaw); len(nestedPayload) > 0 {
return nestedPayload
}
}
}
if nested, ok := payload["data"]; ok && nested != nil {
if nestedRaw, err := json.Marshal(nested); err == nil {
if nestedPayload := appOneLinkMockFirstPayload(nestedRaw); len(nestedPayload) > 0 {
return nestedPayload
}
}
}
return payload
}
var payloadList []map[string]any
if err := json.Unmarshal(raw, &payloadList); err == nil {
for _, item := range payloadList {
if len(item) > 0 {
if nested, ok := item["resData"]; ok && nested != nil {
if nestedRaw, err := json.Marshal(nested); err == nil {
if nestedPayload := appOneLinkMockFirstPayload(nestedRaw); len(nestedPayload) > 0 {
return nestedPayload
}
}
}
return item
}
}
}
var payloadWrapper struct {
ResData json.RawMessage `json:"resData"`
}
if err := json.Unmarshal(raw, &payloadWrapper); err == nil && len(payloadWrapper.ResData) > 0 {
return appOneLinkMockFirstPayload(payloadWrapper.ResData)
}
return nil
}
func appOneLinkMockMapString(data map[string]any, keys ...string) string {
if len(data) == 0 {
return ""
}
for _, key := range keys {
if value, ok := data[key]; ok && value != nil {
text := strings.TrimSpace(fmt.Sprint(value))
if text != "" && text != "<nil>" {
return text
}
}
}
return ""
}
func (c *appOneLinkMockClient) ensureToken(ctx context.Context) (string, error) {
if strings.TrimSpace(c.token) != "" {
return c.token, nil
}
params := url.Values{}
params.Set("appid", c.cfg.AppID)
params.Set("password", c.cfg.Password)
params.Set("transid", appOneLinkMockTransID(c.cfg.AppID))
body, err := c.get(ctx, c.cfg.BaseURL+"/ec/get/token?"+params.Encode())
if err != nil {
return "", err
}
var response appOneLinkMockTokenResponse
if err = json.Unmarshal(body, &response); err != nil {
return "", gerror.Wrap(err, "解析OneLink挡板token响应失败")
}
if strings.TrimSpace(response.Status) != "0" || len(response.Result) == 0 || strings.TrimSpace(response.Result[0].Token) == "" {
message := strings.TrimSpace(response.Message)
if message == "" {
message = "获取OneLink挡板token失败"
}
return "", gerror.New(message)
}
c.token = strings.TrimSpace(response.Result[0].Token)
return c.token, nil
}
func (c *appOneLinkMockClient) get(ctx context.Context, requestURL string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil)
if err != nil {
return nil, err
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, gerror.Wrap(err, "请求OneLink挡板失败")
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, gerror.Wrap(err, "读取OneLink挡板响应失败")
}
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
return nil, gerror.Newf("OneLink挡板HTTP状态异常:%d", resp.StatusCode)
}
return body, nil
}
func appOneLinkMockParseSessionResult(raw json.RawMessage) []appOneLinkMockSimSessionItem {
if len(raw) == 0 || string(raw) == "null" {
return nil
}
var directList []appOneLinkMockSimSessionItem
if err := json.Unmarshal(raw, &directList); err == nil && len(directList) > 0 {
return directList
}
var direct appOneLinkMockSimSessionItem
if err := json.Unmarshal(raw, &direct); err == nil && !direct.isZero() {
return []appOneLinkMockSimSessionItem{direct}
}
var wrappedList []struct {
Code int `json:"code"`
ResData appOneLinkMockSimSessionItem `json:"resData"`
}
if err := json.Unmarshal(raw, &wrappedList); err == nil && len(wrappedList) > 0 {
items := make([]appOneLinkMockSimSessionItem, 0, len(wrappedList))
for _, wrapped := range wrappedList {
if wrapped.Code == 0 && !wrapped.ResData.isZero() {
items = append(items, wrapped.ResData)
}
}
return items
}
var wrapped struct {
Code int `json:"code"`
ResData appOneLinkMockSimSessionItem `json:"resData"`
}
if err := json.Unmarshal(raw, &wrapped); err == nil && wrapped.Code == 0 && !wrapped.ResData.isZero() {
return []appOneLinkMockSimSessionItem{wrapped.ResData}
}
return nil
}
func (item appOneLinkMockSimSessionItem) isZero() bool {
return strings.TrimSpace(item.ApnId) == "" &&
strings.TrimSpace(item.Rat) == "" &&
strings.TrimSpace(item.Imei) == "" &&
strings.TrimSpace(item.Ipv4) == "" &&
strings.TrimSpace(item.Ipv6) == "" &&
strings.TrimSpace(item.Ipv6Prefix) == "" &&
strings.TrimSpace(item.Status) == "" &&
strings.TrimSpace(item.CreateTime) == ""
}
func appOneLinkMockOperType(operation appCardLifecycleOperation) (string, error) {
switch operation.Code {
case "stop":
return consts.SimCardStatusOperTypeStop, nil
case "start":
return consts.SimCardStatusOperTypeStart, nil
default:
return "", gerror.New("OneLink挡板暂不支持该卡片操作")
}
}
func appOneLinkMockTransID(appID string) string {
random := time.Now().UnixNano() % 100000000
return fmt.Sprintf("%s%s%08d", strings.TrimSpace(appID), gtime.Now().Format("YmdHis"), random)
}
func appOneLinkMockSuccessMessage(message string) string {
message = strings.TrimSpace(message)
if message == "" {
return "OneLink挡板处理成功"
}
return message
}