chore: 并入 iotlcm-admin-svr 历史到 admin-svr/(原提交 49818c6)

This commit is contained in:
MiFaZhan
2026-11-02 13:27:59 +08:00
1250 changed files with 154111 additions and 0 deletions
@@ -0,0 +1,42 @@
package upstream
import (
"context"
"encoding/json"
"math"
"net/http"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/gclient"
)
func readQueryResponse(ctx context.Context, response *gclient.Response) ([]byte, error) {
defer response.Close()
body := response.ReadAll()
if preserveUpstreamResults(ctx) {
if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices {
return nil, gerror.Newf("上游查询失败,HTTP状态码:%d", response.StatusCode)
}
var envelope struct {
Code *int `json:"code"`
}
if err := json.Unmarshal(body, &envelope); err != nil || envelope.Code == nil {
return nil, gerror.New("上游响应缺少有效状态码")
}
}
return body, nil
}
func diagnosisNumericValuePresent(body []byte, index int, field string) bool {
var envelope struct {
Data []struct {
ResData map[string]json.RawMessage `json:"resData"`
} `json:"data"`
}
if json.Unmarshal(body, &envelope) != nil || index >= len(envelope.Data) {
return false
}
raw := envelope.Data[index].ResData[field]
var value *float64
return json.Unmarshal(raw, &value) == nil && value != nil && !math.IsNaN(*value) && !math.IsInf(*value, 0) && *value >= 0
}
@@ -0,0 +1,355 @@
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)
}
}
@@ -0,0 +1,918 @@
package upstream
import (
"context"
"encoding/json"
"fmt"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
"net/http"
"net/url"
"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 sUpstreamChan struct {
}
type upstreamConfigSceneKey struct{}
func diagnosisQueryURL(ctx context.Context, client *gclient.Client, address string, path string) string {
if scene, _ := ctx.Value(upstreamConfigSceneKey{}).(string); scene == upstreamConfigSceneDiagnose {
client.SetContentType("application/json")
if token := strings.TrimSpace(g.Cfg().MustGet(ctx, "upstream.appDiagnosisToken").String()); token != "" {
client.SetHeader("X-App-Diagnosis-Token", token)
path = "/internal/app-diagnosis" + path
}
}
return fmt.Sprintf("http://%s%s", address, path)
}
type preserveUpstreamResultsKey struct{}
const upstreamConfigSceneDiagnose = "diagnose"
const upstreamConfigSceneGateway = "gateway"
const upstreamRefreshMaxTimeout = 15 * time.Second
// WithDiagnoseUpstreamConfig selects the authenticated diagnosis proxy endpoint.
func WithDiagnoseUpstreamConfig(ctx context.Context) context.Context {
return context.WithValue(ctx, upstreamConfigSceneKey{}, upstreamConfigSceneDiagnose)
}
// WithGatewayUpstreamConfig marks calls that must use the configured gateway
// endpoint without falling back to the proxy endpoint.
func WithGatewayUpstreamConfig(ctx context.Context) context.Context {
return context.WithValue(ctx, upstreamConfigSceneKey{}, upstreamConfigSceneGateway)
}
// WithPreservedUpstreamResults keeps failed per-card results for diagnostic callers.
// Normal business callers continue to receive only successful items.
func WithPreservedUpstreamResults(ctx context.Context) context.Context {
return context.WithValue(ctx, preserveUpstreamResultsKey{}, true)
}
func preserveUpstreamResults(ctx context.Context) bool {
preserve, _ := ctx.Value(preserveUpstreamResultsKey{}).(bool)
return preserve
}
func setClientTimeout(client *gclient.Client, timeout time.Duration) {
client.SetTimeout(timeout)
if transport, ok := client.Transport.(*http.Transport); ok {
transport.ResponseHeaderTimeout = timeout
}
}
func NewUpstreamChan() *sUpstreamChan {
return &sUpstreamChan{}
}
func init() {
service.RegisterUpstreamChan(NewUpstreamChan())
}
func (s *sUpstreamChan) GetUpstreamChanConfig(ctx context.Context) (output model.UpstreamChanConfig, err error) {
if scene, _ := ctx.Value(upstreamConfigSceneKey{}).(string); scene == upstreamConfigSceneDiagnose {
value, getErr := g.Cfg().Get(ctx, "upstream.appDiagnosisToken")
if getErr != nil {
return output, gerror.Wrap(getErr, "读取智能诊断代理凭据失败")
}
if value == nil || strings.TrimSpace(value.String()) == "" {
return output, gerror.New("智能诊断代理凭据未配置")
}
return s.getUpstreamChanConfigFromPaths(ctx, "upstream.proxy")
}
if scene, _ := ctx.Value(upstreamConfigSceneKey{}).(string); scene == upstreamConfigSceneGateway {
return s.getUpstreamChanConfigFromPaths(ctx, "upstream.gateway")
}
return s.getUpstreamChanConfigFromPaths(ctx, "upstream.proxy")
}
func (s *sUpstreamChan) getUpstreamChanConfigFromPaths(ctx context.Context, paths ...string) (output model.UpstreamChanConfig, err error) {
var lastErr error
for _, path := range paths {
value, getErr := g.Cfg().Get(ctx, path)
if getErr != nil {
lastErr = getErr
continue
}
if value == nil {
continue
}
var cfg model.UpstreamChanConfig
if scanErr := value.Scan(&cfg); scanErr != nil {
lastErr = scanErr
continue
}
if strings.TrimSpace(cfg.Address) == "" {
continue
}
if cfg.Timeout <= 0 {
cfg.Timeout = 10 * time.Second
}
return cfg, nil
}
if lastErr != nil {
return output, lastErr
}
return output, gerror.New("upstream chan config not found")
}
type upstreamRefreshResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}
func (s *sUpstreamChan) RefreshGatewayChannel(ctx context.Context, chanKey string) error {
return s.refreshChannel(ctx, upstreamConfigSceneGateway, chanKey)
}
func (s *sUpstreamChan) RefreshProxyChannel(ctx context.Context, chanKey string) error {
return s.refreshChannel(ctx, "", chanKey)
}
func (s *sUpstreamChan) TestChannel(ctx context.Context, input *model.UpstreamChannelTestRequest) (*model.TSimChannelsTestOutput, error) {
if input == nil || input.ChannelID <= 0 || strings.TrimSpace(input.TenantID) == "" {
return nil, gerror.New("通道不存在或无权访问")
}
config, err := s.getUpstreamChanConfigFromPaths(ctx, "upstream.gateway")
if err != nil {
return nil, gerror.Wrap(err, "获取上游通道配置失败")
}
requestCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
client := g.Client()
client.SetDiscovery(gsvc.GetRegistry())
// The upstream endpoint applies its own per-item timeout and executes items
// sequentially. Keep the client alive for the whole bounded request.
setClientTimeout(client, 2*time.Minute)
body, err := json.Marshal(input)
if err != nil {
return nil, gerror.Wrap(err, "序列化通道测试请求失败")
}
client.SetHeader("Content-Type", "application/json")
response, err := client.Post(requestCtx, fmt.Sprintf("http://%s/internal/channel/test", config.Address), body)
if err != nil {
return nil, gerror.Wrap(err, "请求上游通道测试失败")
}
defer response.Close()
responseBody := response.ReadAll()
var envelope struct {
Code int `json:"code"`
Message string `json:"message"`
Data model.TSimChannelsTestOutput `json:"data"`
}
if err := json.Unmarshal(responseBody, &envelope); err != nil {
return nil, &model.TSimChannelsTestError{
Err: gerror.Wrap(err, "解析上游通道测试响应失败"),
GatewayResponseRaw: string(responseBody),
}
}
if envelope.Code != 0 {
if envelope.Message == "" {
envelope.Message = "上游通道测试失败"
}
return nil, &model.TSimChannelsTestError{
Err: gerror.New(envelope.Message),
GatewayResponseRaw: string(responseBody),
}
}
envelope.Data.GatewayResponseRaw = string(responseBody)
for _, item := range envelope.Data.Items {
if item != nil {
item.ResponseJSON = string(responseBody)
}
}
return &envelope.Data, nil
}
func (s *sUpstreamChan) refreshChannel(ctx context.Context, scene string, chanKey string) error {
if strings.TrimSpace(chanKey) == "" {
return gerror.New("chanKey is required")
}
refreshCtx := ctx
if scene == upstreamConfigSceneGateway {
refreshCtx = WithGatewayUpstreamConfig(ctx)
}
chanConfig, err := s.GetUpstreamChanConfig(refreshCtx)
if err != nil {
return err
}
client := g.Client()
client.SetDiscovery(gsvc.GetRegistry())
timeout := chanConfig.Timeout
if timeout <= 0 || timeout > upstreamRefreshMaxTimeout {
timeout = upstreamRefreshMaxTimeout
}
requestCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
setClientTimeout(client, timeout)
refreshURL := fmt.Sprintf("http://%s/internal/channel/refresh?chanKey=%s", chanConfig.Address, url.QueryEscape(chanKey))
resp, err := client.Post(requestCtx, refreshURL, nil)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.refreshChannel, post error %+v, scene %s, url %s", err, scene, refreshURL)
return err
}
var output upstreamRefreshResponse
if err := json.Unmarshal(resp.ReadAll(), &output); err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.refreshChannel, unmarshal error %+v, scene %s, url %s", err, scene, refreshURL)
return err
}
if output.Code != 0 {
if output.Message == "" {
output.Message = "refresh channel failed"
}
return gerror.New(output.Message)
}
return nil
}
// 获取卡基本信息
func (s *sUpstreamChan) QuerySimBasicInfo(ctx context.Context, input *model.SimBasicInfoInput) ([]model.SimBaseRes[model.SimBasicInfoItem], error) {
chanConfig, err := s.GetUpstreamChanConfig(ctx)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimBasicInfo, get upstream chan config error %+v, path %s, input %+v, ", err, "/sim/query/sim-basic-info", input)
return nil, gerror.New("获取上游通道配置失败")
}
client := g.Client()
client.SetDiscovery(gsvc.GetRegistry())
setClientTimeout(client, chanConfig.Timeout*time.Duration(len(input.SimKeys)))
url := diagnosisQueryURL(ctx, client, chanConfig.Address, "/sim/query/sim-basic-info")
resp, err := client.Post(ctx, url, input)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimBasicInfo, post error %+v,url %s, params %+v, ", err, url, input)
return nil, gerror.New("请求失败")
}
body, err := readQueryResponse(ctx, resp)
if err != nil {
return nil, err
}
output := model.SimBasicInfoOutput{}
err = json.Unmarshal([]byte(body), &output)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimBasicInfo, unmarshal error %+v,url %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New("解析响应失败")
}
if output.Code != 0 {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimBasicInfo, query sim basic info error %+v, path %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New(output.Message)
}
var basicInfo []model.SimBaseRes[model.SimBasicInfoItem]
for _, item := range output.Data {
if item.Code != 0 && !preserveUpstreamResults(ctx) {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimBasicInfo, query sim basic info error %+v, path %s, params %+v, body %s, ", err, url, input, body)
continue
}
basicInfo = append(basicInfo, item)
}
return basicInfo, nil
}
// 查询会话信息
func (s *sUpstreamChan) QuerySimSession(ctx context.Context, input *model.SimSessionInput) ([]model.SimBaseRes[model.SimSessionItem], error) {
chanConfig, err := s.GetUpstreamChanConfig(ctx)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimSession, get upstream chan config error %+v, path %s, input %+v, ", err, "/sim/query/sim-session", input)
return nil, gerror.New("获取上游通道配置失败")
}
client := g.Client()
client.SetDiscovery(gsvc.GetRegistry())
setClientTimeout(client, chanConfig.Timeout)
url := diagnosisQueryURL(ctx, client, chanConfig.Address, "/sim/query/sim-session")
resp, err := client.Post(ctx, url, input)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimSession, post error %+v,url %s, params %+v, ", err, url, input)
return nil, gerror.New("请求失败")
}
body, err := readQueryResponse(ctx, resp)
if err != nil {
return nil, err
}
output := model.SimSessionOutput{}
err = json.Unmarshal([]byte(body), &output)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimSession, unmarshal error %+v,url %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New("解析响应失败")
}
if output.Code != 0 {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimSession, query sim session error %+v, path %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New(output.Message)
}
var session []model.SimBaseRes[model.SimSessionItem]
for _, item := range output.Data {
if item.Code != 0 && !preserveUpstreamResults(ctx) {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimSession, query sim session error %+v, path %s, params %+v, body %s, ", err, url, input, body)
continue
}
session = append(session, item)
}
return session, nil
}
// 查询状态
func (s *sUpstreamChan) QuerySimCardStatus(ctx context.Context, input *model.SimCardStatusInput) ([]model.SimBaseRes[model.SimCardStatusItem], error) {
chanConfig, err := s.GetUpstreamChanConfig(ctx)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimCardStatus, get upstream chan config error %+v, path %s", err, "/sim/query/sim-card-status")
return nil, gerror.Wrap(err, "获取上游通道配置失败")
}
client := g.Client()
client.SetDiscovery(gsvc.GetRegistry())
setClientTimeout(client, chanConfig.Timeout*time.Duration(len(input.SimKeys)))
url := diagnosisQueryURL(ctx, client, chanConfig.Address, "/sim/query/sim-status")
resp, err := client.Post(ctx, url, input)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimCardStatus, post error %+v, url %s, count %d", err, url, len(input.SimKeys))
return nil, gerror.Wrap(err, "请求失败")
}
body, err := readQueryResponse(ctx, resp)
if err != nil {
return nil, err
}
cardStatus, err := parseSimCardStatusResponse(body)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimCardStatus, response error %+v, url %s, count %d", err, url, len(input.SimKeys))
return nil, err
}
if preserveUpstreamResults(ctx) {
for index := range cardStatus {
if cardStatus[index].Code == 0 && !diagnosisNumericValuePresent(body, index, "cardStatus") {
cardStatus[index].Code = 500
cardStatus[index].Message = "卡状态未返回有效数值"
}
}
}
return cardStatus, nil
}
func parseSimCardStatusResponse(body []byte) ([]model.SimBaseRes[model.SimCardStatusItem], error) {
output := model.SimCardStatusOutput{}
if err := json.Unmarshal(body, &output); err != nil {
return nil, gerror.Wrap(err, "解析响应失败")
}
if output.Code != 0 {
return nil, gerror.New(output.Message)
}
return output.Data, nil
}
func normalizeSimCardBindStatus(item model.SimCardBindStatusItem) string {
if status := strings.TrimSpace(item.BindStatus); status != "" {
switch status {
case "0", "1", "2":
return status
}
}
cardBindingType := strings.TrimSpace(item.CardBindingType)
switch cardBindingType {
case "1", "2", "3", "4", "5":
return "1"
case "", "-1":
return "2"
default:
return "2"
}
}
// 查询IMEI
func (s *sUpstreamChan) QuerySimStopReason(ctx context.Context, input *model.SimStopReasonInput) ([]model.SimBaseRes[model.SimStopReasonItem], error) {
chanConfig, err := s.GetUpstreamChanConfig(ctx)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimStopReason, get upstream chan config error %+v, path %s", err, "/sim/query/sim-stop-reason")
return nil, gerror.Wrap(err, "获取上游通道配置失败")
}
client := g.Client()
client.SetDiscovery(gsvc.GetRegistry())
setClientTimeout(client, chanConfig.Timeout*time.Duration(len(input.SimKeys)))
url := diagnosisQueryURL(ctx, client, chanConfig.Address, "/sim/query/sim-stop-reason")
resp, err := client.Post(ctx, url, input)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimStopReason, post error %+v, url %s, count %d", err, url, len(input.SimKeys))
return nil, gerror.Wrap(err, "请求失败")
}
body, err := readQueryResponse(ctx, resp)
if err != nil {
return nil, err
}
output := model.SimStopReasonOutput{}
if err = json.Unmarshal([]byte(body), &output); err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimStopReason, unmarshal error %+v,url %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New("解析响应失败")
}
if output.Code != 0 {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimStopReason, query sim stop reason error %+v, path %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New(output.Message)
}
var stopReason []model.SimBaseRes[model.SimStopReasonItem]
for _, item := range output.Data {
if item.Code != 0 && !preserveUpstreamResults(ctx) {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimStopReason, query sim stop reason error %+v, path %s, params %+v, body %s, ", err, url, input, body)
continue
}
stopReason = append(stopReason, item)
}
return stopReason, nil
}
func (s *sUpstreamChan) QuerySimFrozenState(ctx context.Context, input *model.SimFrozenStateInput) ([]model.SimBaseRes[model.SimFrozenStateItem], error) {
chanConfig, err := s.GetUpstreamChanConfig(ctx)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimFrozenState, get upstream chan config error %+v, path %s", err, "/sim/query/sim-frozen-state")
return nil, gerror.Wrap(err, "获取上游通道配置失败")
}
client := g.Client()
client.SetDiscovery(gsvc.GetRegistry())
setClientTimeout(client, chanConfig.Timeout*time.Duration(len(input.SimKeys)))
url := diagnosisQueryURL(ctx, client, chanConfig.Address, "/sim/query/sim-frozen-state")
resp, err := client.Post(ctx, url, input)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimFrozenState, post error %+v, url %s, count %d", err, url, len(input.SimKeys))
return nil, gerror.Wrap(err, "请求失败")
}
body, err := readQueryResponse(ctx, resp)
if err != nil {
return nil, err
}
output := model.SimFrozenStateOutput{}
if err = json.Unmarshal([]byte(body), &output); err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimFrozenState, unmarshal error %+v,url %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New("解析响应失败")
}
if output.Code != 0 {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimFrozenState, query sim frozen state error %+v, path %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New(output.Message)
}
var frozenState []model.SimBaseRes[model.SimFrozenStateItem]
for _, item := range output.Data {
if item.Code != 0 && !preserveUpstreamResults(ctx) {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimFrozenState, query sim frozen state error %+v, path %s, params %+v, body %s, ", err, url, input, body)
continue
}
frozenState = append(frozenState, item)
}
return frozenState, nil
}
func (s *sUpstreamChan) QuerySimImei(ctx context.Context, input *model.SimImeiInput) ([]model.SimBaseRes[model.SimImeiItem], error) {
chanConfig, err := s.GetUpstreamChanConfig(ctx)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimImei, get upstream chan config error %+v, path %s, input %+v, ", err, "/sim/query/sim-imei", input)
return nil, gerror.New("获取上游通道配置失败")
}
client := g.Client()
client.SetDiscovery(gsvc.GetRegistry())
setClientTimeout(client, chanConfig.Timeout*time.Duration(len(input.SimKeys)))
url := diagnosisQueryURL(ctx, client, chanConfig.Address, "/sim/query/sim-imei")
resp, err := client.Post(ctx, url, input)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimImei, post error %+v,url %s, params %+v, ", err, url, input)
return nil, gerror.New("请求失败")
}
body, err := readQueryResponse(ctx, resp)
if err != nil {
return nil, err
}
output := model.SimImeiOutput{}
err = json.Unmarshal([]byte(body), &output)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimImei, unmarshal error %+v,url %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New("解析响应失败")
}
if output.Code != 0 {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimImei, query sim imei error %+v, path %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New(output.Message)
}
var imei []model.SimBaseRes[model.SimImeiItem]
for _, item := range output.Data {
if item.Code != 0 && !preserveUpstreamResults(ctx) {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimImei, query sim imei error %+v, path %s, params %+v, body %s, ", err, url, input, body)
continue
}
imei = append(imei, item)
}
return imei, nil
}
// 查询流量
func (s *sUpstreamChan) QuerySimDatausage(ctx context.Context, input *model.SimDataUsageInput) ([]model.SimBaseRes[model.SimDataUsageItem], error) {
chanConfig, err := s.GetUpstreamChanConfig(ctx)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimDataUsage, get upstream chan config error %+v, path %s, input %+v, ", err, "/sim/query/sim-datausage", input)
return nil, gerror.New("获取上游通道配置失败")
}
client := g.Client()
client.SetDiscovery(gsvc.GetRegistry())
setClientTimeout(client, chanConfig.Timeout*time.Duration(len(input.SimKeys)))
url := diagnosisQueryURL(ctx, client, chanConfig.Address, "/sim/query/sim-datausage")
resp, err := client.Post(ctx, url, input)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimDataUsage, post error %+v,url %s, params %+v, ", err, url, input)
return nil, gerror.New("请求失败")
}
body, err := readQueryResponse(ctx, resp)
if err != nil {
return nil, err
}
dataUsage, err := parseSimDataUsageResponse(body)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimDataUsage, response error %+v,url %s, params %+v, body %s, ", err, url, input, body)
return nil, err
}
if preserveUpstreamResults(ctx) {
for index := range dataUsage {
if dataUsage[index].Code == 0 && !diagnosisNumericValuePresent(body, index, "dataUsage") {
dataUsage[index].Code = 500
dataUsage[index].Message = "实时流量未返回有效数值"
}
}
}
return dataUsage, nil
}
func parseSimDataUsageResponse(body []byte) ([]model.SimBaseRes[model.SimDataUsageItem], error) {
output := model.SimDataUsageOutput{}
if err := json.Unmarshal(body, &output); err != nil {
return nil, gerror.Wrap(err, "解析响应失败")
}
if output.Code != 0 {
return nil, gerror.New(output.Message)
}
return output.Data, nil
}
// 查询达量限速档位
func (s *sUpstreamChan) QuerySimDataDiagnosis(ctx context.Context, input *model.SimDataDiagnosisInput) ([]model.SimBaseRes[model.SimDataDiagnosisItem], error) {
chanConfig, err := s.GetUpstreamChanConfig(ctx)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimDataDiagnosis, get upstream chan config error %+v, path %s, input %+v, ", err, "/sim/query/sim-data-diagnosis", input)
return nil, gerror.New("获取上游通道配置失败")
}
client := g.Client()
client.SetDiscovery(gsvc.GetRegistry())
setClientTimeout(client, chanConfig.Timeout*time.Duration(len(input.SimKeys)))
url := diagnosisQueryURL(ctx, client, chanConfig.Address, "/sim/query/sim-data-diagnosis")
resp, err := client.Post(ctx, url, input)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimDataDiagnosis, post error %+v,url %s, params %+v, ", err, url, input)
return nil, gerror.New("请求失败")
}
body, err := readQueryResponse(ctx, resp)
if err != nil {
return nil, err
}
output := model.SimResponseData[json.RawMessage]{}
if err = json.Unmarshal([]byte(body), &output); err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimDataDiagnosis, unmarshal error %+v,url %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New("解析响应失败")
}
if output.Code != 0 {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimDataDiagnosis, query sim data diagnosis error %+v, path %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New(output.Message)
}
items := make([]model.SimBaseRes[model.SimDataDiagnosisItem], 0, len(output.Data))
for _, item := range output.Data {
switch item.Code {
case 5000:
items = append(items, model.SimBaseRes[model.SimDataDiagnosisItem]{
Code: item.Code,
Message: item.Message,
SimKey: item.SimKey,
ResData: model.SimDataDiagnosisItem{
RateLimitStatus: "3",
RateLimitReason: item.Message,
},
})
case 0:
parsed, parseErr := parseYdDataDiagnosisBody(item.ResData)
if parseErr != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimDataDiagnosis, parse body error %+v,url %s, params %+v, body %s, ", parseErr, url, input, item.ResData)
items = append(items, model.SimBaseRes[model.SimDataDiagnosisItem]{
Code: 1,
Message: "解析响应失败",
SimKey: item.SimKey,
ResData: model.SimDataDiagnosisItem{RateLimitStatus: "2"},
})
continue
}
items = append(items, model.SimBaseRes[model.SimDataDiagnosisItem]{
Code: item.Code,
Message: item.Message,
SimKey: item.SimKey,
ResData: parsed,
})
default:
items = append(items, model.SimBaseRes[model.SimDataDiagnosisItem]{
Code: item.Code,
Message: item.Message,
SimKey: item.SimKey,
ResData: model.SimDataDiagnosisItem{RateLimitStatus: "2"},
})
}
}
return items, nil
}
// 查询月流量
func (s *sUpstreamChan) QuerySimDatausageMonthly(ctx context.Context, input *model.SimDataUsageMonthlyInput) ([]model.SimBaseRes[model.SimDataUsageMonthlyOutputItem], error) {
chanConfig, err := s.GetUpstreamChanConfig(ctx)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimDatausageMonthly, get upstream chan config error %+v, path %s, input %+v, ", err, "/sim/query/sim-datausage-monthly", input)
return nil, gerror.New("获取上游通道配置失败")
}
client := g.Client()
client.SetDiscovery(gsvc.GetRegistry())
g.Log().Debug(ctx, chanConfig.Timeout*time.Duration(len(input.SimKeys)))
setClientTimeout(client, chanConfig.Timeout*time.Duration(len(input.SimKeys)))
url := diagnosisQueryURL(ctx, client, chanConfig.Address, "/sim/query/sim-datausage-monthly")
resp, err := client.Post(ctx, url, input)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimDatausageMonthly, post error %+v,url %s, params %+v, ", err, url, input)
return nil, gerror.New("请求失败")
}
body := resp.ReadAll()
output := model.SimDataUsageMonthlyOutput{}
err = json.Unmarshal([]byte(body), &output)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimDatausageMonthly, unmarshal error %+v,url %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New("解析响应失败")
}
if output.Code != 0 {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimDatausageMonthly, query sim datausage monthly error %+v, path %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New(output.Message)
}
var dataUsage []model.SimBaseRes[model.SimDataUsageMonthlyOutputItem]
for _, item := range output.Data {
if item.Code != 0 && !preserveUpstreamResults(ctx) {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimDatausageMonthly, query sim datausage monthly error %+v, path %s, params %+v, body %s, ", err, url, input, body)
continue
}
dataUsage = append(dataUsage, item)
}
return dataUsage, nil
}
// 查询日流量
func (s *sUpstreamChan) QuerySimDatausageDaily(ctx context.Context, input *model.SimDataUsageDailyInput) ([]model.SimBaseRes[model.SimDataUsageDailyOutputItem], error) {
chanConfig, err := s.GetUpstreamChanConfig(ctx)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimDatausageDaily, get upstream chan config error %+v, path %s, input %+v, ", err, "/sim/query/sim-datausage-daily", input)
return nil, gerror.New("获取上游通道配置失败")
}
client := g.Client()
client.SetDiscovery(gsvc.GetRegistry())
setClientTimeout(client, chanConfig.Timeout*time.Duration(len(input.SimKeys)))
url := diagnosisQueryURL(ctx, client, chanConfig.Address, "/sim/query/sim-datausage-daily")
resp, err := client.Post(ctx, url, input)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimDatausageDaily, post error %+v,url %s, params %+v, ", err, url, input)
return nil, gerror.New("请求失败")
}
body := resp.ReadAll()
output := model.SimDataUsageDailyOutput{}
err = json.Unmarshal([]byte(body), &output)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimDatausageDaily, unmarshal error %+v,url %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New("解析响应失败")
}
if output.Code != 0 {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimDatausageDaily, query sim datausage daily error %+v, path %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New(output.Message)
}
var dataUsage []model.SimBaseRes[model.SimDataUsageDailyOutputItem]
for _, item := range output.Data {
if item.Code != 0 && !preserveUpstreamResults(ctx) {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimDatausageDaily, query sim datausage daily error %+v, path %s, params %+v, body %s, ", err, url, input, body)
continue
}
dataUsage = append(dataUsage, item)
}
return dataUsage, nil
}
// 查询已开通APN信息
func (s *sUpstreamChan) QuerySimApnInfo(ctx context.Context, input *model.SimApnInfoInput) ([]model.SimBaseRes[model.SimApnInfoItemList], error) {
chanConfig, err := s.GetUpstreamChanConfig(ctx)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimApnInfo, get upstream chan config error %+v, path %s, input %+v, ", err, "/sim/query/sim-apn-info", input)
return nil, gerror.New("获取上游通道配置失败")
}
client := g.Client()
client.SetDiscovery(gsvc.GetRegistry())
setClientTimeout(client, chanConfig.Timeout*time.Duration(len(input.SimKeys)))
url := diagnosisQueryURL(ctx, client, chanConfig.Address, "/sim/query/sim-apn-info")
resp, err := client.Post(ctx, url, input)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimApnInfo, post error %+v,url %s, params %+v, ", err, url, input)
return nil, gerror.New("请求失败")
}
body, err := readQueryResponse(ctx, resp)
if err != nil {
return nil, err
}
output := model.SimApnInfoOutput{}
err = json.Unmarshal([]byte(body), &output)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimApnInfo, unmarshal error %+v,url %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New("解析响应失败")
}
if output.Code != 0 {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimApnInfo, query sim apn info error %+v, path %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New(output.Message)
}
var apnInfo []model.SimBaseRes[model.SimApnInfoItemList]
for _, item := range output.Data {
if item.Code != 0 && !preserveUpstreamResults(ctx) {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimApnInfo, query sim apn info error %+v, path %s, params %+v, body %s, ", err, url, input, body)
continue
}
apnInfo = append(apnInfo, item)
}
return apnInfo, nil
}
// 卡状态变更
func (s *sUpstreamChan) ChangeSimStatus(ctx context.Context, input *model.SimCardStatusChangeInput) ([]model.SimBaseRes[model.SimCardStatusChangeItem], error) {
chanConfig, err := s.GetUpstreamChanConfig(ctx)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.ChangeSimStatus, get upstream chan config error %+v, path %s, input %+v, ", err, "/sim/change/sim-status", input)
return nil, gerror.New("获取上游通道配置失败")
}
client := g.Client()
client.SetDiscovery(gsvc.GetRegistry())
setClientTimeout(client, chanConfig.Timeout*time.Duration(len(input.SimKeys)))
url := diagnosisQueryURL(ctx, client, chanConfig.Address, "/sim/change/sim-status")
resp, err := client.Post(ctx, url, input)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.ChangeSimStatus, post error %+v,url %s, params %+v, ", err, url, input)
return nil, gerror.New("请求失败")
}
body := resp.ReadAll()
output := model.SimCardStatusChangeOutput{}
err = json.Unmarshal([]byte(body), &output)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.ChangeSimStatus, unmarshal error %+v,url %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New("解析响应失败")
}
if output.Code != 0 {
g.Log().Debugf(ctx, "sUpstreamChan.ChangeSimStatus, change sim status error %+v, path %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New(output.Message)
}
var statusList []model.SimBaseRes[model.SimCardStatusChangeItem]
for _, item := range output.Data {
if item.Code != 0 {
g.Log().Debugf(ctx, "sUpstreamChan.ChangeSimStatus, change sim status error %+v, path %s, params %+v, body %s, ", err, url, input, body)
}
statusList = append(statusList, item)
}
return statusList, nil
}
// 卡APN功能开停
func (s *sUpstreamChan) ChangeSimApnFunction(ctx context.Context, input *model.SimCardOperateChangeApnInput) ([]model.SimBaseRes[model.SimCardOperateChangeApnItem], error) {
chanConfig, err := s.GetUpstreamChanConfig(ctx)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.ChangeSimApnFunction, get upstream chan config error %+v, path %s, input %+v, ", err, "/sim/change/sim-apn", input)
return nil, gerror.New("获取上游通道配置失败")
}
client := g.Client()
client.SetDiscovery(gsvc.GetRegistry())
setClientTimeout(client, chanConfig.Timeout*time.Duration(len(input.SimKeys)))
url := diagnosisQueryURL(ctx, client, chanConfig.Address, "/sim/change/sim-apn")
resp, err := client.Post(ctx, url, input)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.ChangeSimApnFunction, post error %+v,url %s, params %+v, ", err, url, input)
return nil, gerror.New("请求失败")
}
body := resp.ReadAll()
output := model.SimCardOperateChangeApnOutput{}
err = json.Unmarshal([]byte(body), &output)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.ChangeSimApnFunction, unmarshal error %+v,url %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New("解析响应失败")
}
if output.Code != 0 {
g.Log().Debugf(ctx, "sUpstreamChan.ChangeSimApnFunction, change sim apn function error %+v, path %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New(output.Message)
}
var statusList []model.SimBaseRes[model.SimCardOperateChangeApnItem]
for _, item := range output.Data {
if item.Code != 0 {
g.Log().Debugf(ctx, "sUpstreamChan.ChangeSimApnFunction, change sim apn function error %+v, path %s, params %+v, body %s, ", err, url, input, body)
continue
}
statusList = append(statusList, item)
}
return statusList, nil
}
// 查询机卡绑定状态
func (s *sUpstreamChan) QuerySimCardBindStatus(ctx context.Context, input *model.SimCardBindStatusInput) ([]model.SimBaseRes[model.SimCardBindStatusItem], error) {
chanConfig, err := s.GetUpstreamChanConfig(ctx)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimCardBindStatus, get upstream chan config error %+v, path %s, input %+v, ", err, "/sim/query/sim-card-bind-status", input)
return nil, gerror.New("获取上游通道配置失败")
}
client := g.Client()
client.SetDiscovery(gsvc.GetRegistry())
setClientTimeout(client, chanConfig.Timeout*time.Duration(len(input.SimKeys)))
url := diagnosisQueryURL(ctx, client, chanConfig.Address, "/sim/query/sim-card-bind-status")
resp, err := client.Post(ctx, url, input)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimCardBindStatus, post error %+v,url %s, params %+v, ", err, url, input)
return nil, gerror.New("请求失败")
}
body, err := readQueryResponse(ctx, resp)
if err != nil {
return nil, err
}
output := model.SimCardBindStatusOutput{}
err = json.Unmarshal([]byte(body), &output)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimCardBindStatus, unmarshal error %+v,url %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New("解析响应失败")
}
if output.Code != 0 {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimCardBindStatus, query sim card bind status error %+v, path %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New(output.Message)
}
for i := range output.Data {
if output.Data[i].Code != 0 {
continue
}
output.Data[i].ResData.BindStatus = normalizeSimCardBindStatus(output.Data[i].ResData)
}
return output.Data, nil
}
// 查询设备状态
func (s *sUpstreamChan) QuerySimDeviceStatus(ctx context.Context, input *model.SimDeviceStatusInput) ([]model.SimBaseRes[model.SimDeviceStatusItem], error) {
chanConfig, err := s.GetUpstreamChanConfig(ctx)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimDeviceStatus, get upstream chan config error %+v, path %s, input %+v, ", err, "/sim/query/sim-device-status", input)
return nil, gerror.New("获取上游通道配置失败")
}
client := g.Client()
client.SetDiscovery(gsvc.GetRegistry())
setClientTimeout(client, chanConfig.Timeout*time.Duration(len(input.SimKeys)))
url := diagnosisQueryURL(ctx, client, chanConfig.Address, "/sim/query/sim-device-status")
resp, err := client.Post(ctx, url, input)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimDeviceStatus, post error %+v,url %s, params %+v, ", err, url, input)
return nil, gerror.New("请求失败")
}
body, err := readQueryResponse(ctx, resp)
if err != nil {
return nil, err
}
output := model.SimDeviceStatusOutput{}
err = json.Unmarshal([]byte(body), &output)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimDeviceStatus, unmarshal error %+v,url %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New("解析响应失败")
}
if output.Code != 0 {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimDeviceStatus, query sim device status error %+v, path %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New(output.Message)
}
return output.Data, nil
}
// 查询地域限制状态
func (s *sUpstreamChan) QuerySimRegionLimitStatus(ctx context.Context, input *model.SimRegionLimitStatusInput) ([]model.SimBaseRes[model.SimRegionLimitStatusItem], error) {
chanConfig, err := s.GetUpstreamChanConfig(ctx)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimRegionLimitStatus, get upstream chan config error %+v, path %s, input %+v, ", err, "/sim/query/sim-region-limit-status", input)
return nil, gerror.New("获取上游通道配置失败")
}
client := g.Client()
client.SetDiscovery(gsvc.GetRegistry())
setClientTimeout(client, chanConfig.Timeout*time.Duration(len(input.SimKeys)))
url := diagnosisQueryURL(ctx, client, chanConfig.Address, "/sim/query/sim-region-limit-status")
resp, err := client.Post(ctx, url, input)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimRegionLimitStatus, post error %+v,url %s, params %+v, ", err, url, input)
return nil, gerror.New("请求失败")
}
body, err := readQueryResponse(ctx, resp)
if err != nil {
return nil, err
}
output := model.SimRegionLimitStatusOutput{}
err = json.Unmarshal([]byte(body), &output)
if err != nil {
g.Log().Errorf(ctx, "sUpstreamChan.QuerySimRegionLimitStatus, unmarshal error %+v,url %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New("解析响应失败")
}
if output.Code != 0 {
g.Log().Debugf(ctx, "sUpstreamChan.QuerySimRegionLimitStatus, query sim region limit status error %+v, path %s, params %+v, body %s, ", err, url, input, body)
return nil, gerror.New(output.Message)
}
return output.Data, nil
}
@@ -0,0 +1,139 @@
package upstream
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcfg"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
)
func TestTestChannelPostsOneTenantBoundRequestToGateway(t *testing.T) {
const gatewayBody = `{"code":0,"message":"success","data":{"items":[{"key":"simStatus","name":"卡状态","status":"success"}]}}`
var requestBody model.UpstreamChannelTestRequest
requestCount := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/internal/channel/test" || r.Method != http.MethodPost {
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
}
requestCount++
if r.Header.Get("X-Internal-Token") != "" {
t.Errorf("unexpected internal token header")
}
if err := json.NewDecoder(r.Body).Decode(&requestBody); err != nil {
t.Fatal(err)
}
_, _ = w.Write([]byte(gatewayBody))
}))
defer server.Close()
oldAdapter := g.Cfg().GetAdapter()
adapter, err := gcfg.NewAdapterContent(`upstream:
gateway:
address: "` + strings.TrimPrefix(server.URL, "http://") + `"
timeout: 1s
`)
if err != nil {
t.Fatal(err)
}
g.Cfg().SetAdapter(adapter)
t.Cleanup(func() { g.Cfg().SetAdapter(oldAdapter) })
output, err := NewUpstreamChan().TestChannel(context.Background(), &model.UpstreamChannelTestRequest{
ChannelID: 42, TenantID: "tenant-a", InputMode: "complete",
ICCID: "iccid", IMSI: "imsi", MSISDN: "msisdn", Items: []string{"simStatus"},
})
if err != nil {
t.Fatal(err)
}
if requestCount != 1 {
t.Fatalf("request count=%d, want one", requestCount)
}
if requestBody.ChannelID != 42 || requestBody.TenantID != "tenant-a" || len(requestBody.Items) != 1 {
t.Fatalf("unexpected request body: %+v", requestBody)
}
if output == nil || len(output.Items) != 1 || output.Items[0].Status != "success" {
t.Fatalf("unexpected output: %+v", output)
}
if output.GatewayResponseRaw != gatewayBody {
t.Fatalf("raw response=%q, want %q", output.GatewayResponseRaw, gatewayBody)
}
}
func TestTestChannelPreservesGatewayErrorResponses(t *testing.T) {
responseBody := ""
responseStatus := http.StatusOK
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(responseStatus)
_, _ = w.Write([]byte(responseBody))
}))
defer server.Close()
oldAdapter := g.Cfg().GetAdapter()
adapter, err := gcfg.NewAdapterContent(`upstream:
gateway:
address: "` + strings.TrimPrefix(server.URL, "http://") + `"
timeout: 1s
`)
if err != nil {
t.Fatal(err)
}
g.Cfg().SetAdapter(adapter)
t.Cleanup(func() { g.Cfg().SetAdapter(oldAdapter) })
for _, tc := range []struct {
name string
body string
status int
wantMessage string
}{
{"business error", `{"code":13000,"message":"系统忙","data":{"items":[]}}`, http.StatusOK, "系统忙"},
{"completion error", `{"code":50,"message":"carrier did not return complete card identifiers","data":null}`, http.StatusOK, "carrier did not return complete card identifiers"},
{"empty message", `{"code":50,"message":"","data":null}`, http.StatusOK, "上游通道测试失败"},
{"HTTP error", `{"code":13000,"message":"网关故障"}`, http.StatusBadGateway, "网关故障"},
{"invalid JSON", `gateway unavailable`, http.StatusOK, "解析上游通道测试响应失败"},
{"schema mismatch", `{"code":0,"data":{"items":"invalid"}}`, http.StatusOK, "解析上游通道测试响应失败"},
} {
t.Run(tc.name, func(t *testing.T) {
responseBody = tc.body
responseStatus = tc.status
output, err := NewUpstreamChan().TestChannel(context.Background(), &model.UpstreamChannelTestRequest{
ChannelID: 42, TenantID: "tenant-a", InputMode: "complete",
ICCID: "iccid", IMSI: "imsi", MSISDN: "msisdn",
})
if err == nil || output != nil {
t.Fatalf("failure must remain an error: output=%+v err=%v", output, err)
}
var diagnostic *model.TSimChannelsTestError
if !errors.As(err, &diagnostic) || diagnostic.GatewayResponseRaw != tc.body {
t.Fatalf("missing raw error response: %+v", err)
}
if !strings.Contains(err.Error(), tc.wantMessage) || gerror.Code(err) != gcode.CodeNil {
t.Fatalf("original error changed: %v code=%v", err, gerror.Code(err))
}
if err.Error() != diagnostic.Err.Error() || !errors.Is(err, diagnostic.Err) {
t.Fatalf("original error chain changed: %v", err)
}
})
}
t.Run("individual failures remain results", func(t *testing.T) {
responseStatus = http.StatusOK
responseBody = `{"code":0,"message":"success","data":{"items":[{"key":"simStatus","status":"failed","errorCode":9999,"message":"carrier rejected"},{"key":"simSession","status":"timeout","message":"channel test item timed out"}]}}`
output, err := NewUpstreamChan().TestChannel(context.Background(), &model.UpstreamChannelTestRequest{ChannelID: 42, TenantID: "tenant-a"})
if err != nil || output == nil {
t.Fatalf("single-item failures must not fail the whole request: %v", err)
}
if output.GatewayResponseRaw != responseBody || len(output.Items) != 2 || output.Items[0].ErrorCode != 9999 || output.Items[0].Message != "carrier rejected" || output.Items[1].Status != "timeout" {
t.Fatalf("item results changed: %+v", output)
}
})
}