package upstreamimpl import ( "bytes" "context" "crypto/md5" "encoding/hex" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "strconv" "strings" "time" "git.5iot.com/iotlcm-public/iotlcm-upstream-chan/internal/consts" "git.5iot.com/iotlcm-public/iotlcm-upstream-chan/internal/model" "git.5iot.com/iotlcm-public/iotlcm-upstream-chan/internal/service" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/gconv" ) const ( qianxunTempKey = "sim_qianxun_v3" qianxunSuccessCode = "000000" qianxunTimeLayout = "20060102150405" qianxunBatchLimit = 200 ) var qianxunEast8 = time.FixedZone("UTC+8", 8*60*60) type sSimQianxun struct { channelConfig *model.SimChannelConfigItemCache baseURL string appKey string appSecret string userName string http *http.Client } var _ model.SimCardChannelI = (*sSimQianxun)(nil) var _ model.SimCardBatchQueryI = (*sSimQianxun)(nil) type qianxunResponse struct { Status string `json:"status"` Message string `json:"message"` Result json.RawMessage `json:"result"` } type qianxunCardInfo struct { ICCID string `json:"iccid"` IMSI string `json:"imsi"` IMEI string `json:"imei"` MSISDN string `json:"msisdn"` AccessNumber string `json:"accessNumber"` CardNo string `json:"cardNo"` CardStatus any `json:"cardStatus"` Status any `json:"status"` SimStatus any `json:"simStatus"` RealStatus any `json:"realStatus"` StatusName string `json:"statusName"` OpenDate string `json:"openDate"` OpenTime string `json:"openTime"` ActiveDate string `json:"activeDate"` ActiveTime string `json:"activeTime"` ActivateTime string `json:"activateTime"` PackageName string `json:"packageName"` MainOfferName string `json:"mainOfferName"` ProdName string `json:"prodName"` Remark string `json:"remark"` UsageTotal any `json:"usageTotal"` UsedGprs any `json:"usedGprs"` UsedFlow any `json:"usedFlow"` UseFlow any `json:"useFlow"` CardFlow any `json:"cardFlow"` Flow any `json:"flow"` TotalFlow any `json:"totalFlow"` ProdTotal any `json:"prodTotal"` PackageFlow any `json:"packageFlow"` LeftFlow any `json:"leftFlow"` LastChangeDate string `json:"lastChangeDate"` UpdateTime string `json:"updateTime"` } type qianxunFlowHistory struct { ICCID string `json:"iccid"` BillingMonth string `json:"billingMonth"` UsageTotal any `json:"usageTotal"` Status string `json:"status"` } type qianxunBatchCardResult struct { ICCID string `json:"iccid"` ICCIDData json.RawMessage `json:"iccidData"` ICCIDResult json.RawMessage `json:"iccidResult"` } func NewSimQianxun() *sSimQianxun { return &sSimQianxun{} } func init() { service.UpstreamAPI().RegisterTemp(qianxunTempKey, NewSimQianxun()) } func (s *sSimQianxun) NewTemp(ctx context.Context, channelConfig *model.SimChannelConfigItemCache) model.SimCardChannelI { baseURL := "" if channelConfig != nil && channelConfig.Template != nil { var templateConfig struct { API struct { BaseURL string `json:"baseUrl"` } `json:"api"` } if err := json.Unmarshal([]byte(channelConfig.Template.TempConfig), &templateConfig); err != nil { g.Log().Warningf(ctx, "parse Qianxun template config failed: %v", err) } else { baseURL = strings.TrimRight(strings.TrimSpace(templateConfig.API.BaseURL), "/") } } var channelExtra struct { UserName string `json:"userName"` } if channelConfig != nil && channelConfig.Channel != nil { if err := json.Unmarshal([]byte(channelConfig.Channel.ChanConfig), &channelExtra); err != nil { g.Log().Warningf(ctx, "parse Qianxun channel config failed, chanKey=%s err=%v", channelConfig.Channel.ChanKey, err) } } appKey := "" appSecret := "" if channelConfig != nil && channelConfig.Channel != nil { appKey = strings.TrimSpace(channelConfig.Channel.AppKey) appSecret = strings.TrimSpace(channelConfig.Channel.AppSecret) } return &sSimQianxun{ channelConfig: channelConfig, baseURL: baseURL, appKey: appKey, appSecret: appSecret, userName: strings.TrimSpace(channelExtra.UserName), http: &http.Client{Timeout: resolveUpstreamHTTPTimeout(ctx, templateConfigJSON(channelConfig))}, } } func qianxunSign(appKey, appSecret, userName, timestamp string) string { plain := fmt.Sprintf("appKey=%s&timeStamp=%s&userName=%s&appSecret=%s", appKey, timestamp, userName, appSecret) sum := md5.Sum([]byte(plain)) return hex.EncodeToString(sum[:]) } func (s *sSimQianxun) authParams() (url.Values, error) { if s == nil { return nil, fmt.Errorf("Qianxun client is nil") } if strings.TrimSpace(s.baseURL) == "" { return nil, fmt.Errorf("Qianxun baseUrl is not configured") } if strings.TrimSpace(s.appKey) == "" || strings.TrimSpace(s.appSecret) == "" || strings.TrimSpace(s.userName) == "" { return nil, fmt.Errorf("Qianxun credentials are not configured") } timestamp := time.Now().In(qianxunEast8).Format(qianxunTimeLayout) params := url.Values{} params.Set("appKey", s.appKey) params.Set("timeStamp", timestamp) params.Set("userName", s.userName) params.Set("sign", qianxunSign(s.appKey, s.appSecret, s.userName, timestamp)) return params, nil } func (s *sSimQianxun) post(ctx context.Context, endpoint string, params url.Values) (qianxunResponse, error) { authParams, err := s.authParams() if err != nil { return qianxunResponse{}, err } for key, values := range params { for _, value := range values { authParams.Add(key, value) } } target := strings.TrimRight(s.baseURL, "/") + "/" + strings.TrimLeft(endpoint, "/") body := authParams.Encode() req, err := http.NewRequestWithContext(ctx, http.MethodPost, target, bytes.NewBufferString(body)) if err != nil { return qianxunResponse{}, fmt.Errorf("create Qianxun request %s: %w", endpoint, err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") client := s.http if client == nil { client = &http.Client{Timeout: defaultUpstreamHTTPTimeout} } resp, err := client.Do(req) if err != nil { return qianxunResponse{}, fmt.Errorf("Qianxun request %s: %w", endpoint, err) } defer resp.Body.Close() responseBody, err := io.ReadAll(resp.Body) if err != nil { return qianxunResponse{}, fmt.Errorf("read Qianxun response %s: %w", endpoint, err) } rawResponse := strings.TrimSpace(string(responseBody)) if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices { g.Log().Warningf(ctx, "Qianxun HTTP error endpoint=%s status=%d response=%s", endpoint, resp.StatusCode, rawResponse) return qianxunResponse{}, fmt.Errorf("Qianxun HTTP status %d", resp.StatusCode) } var output qianxunResponse if err := json.Unmarshal(responseBody, &output); err != nil { g.Log().Warningf(ctx, "decode Qianxun response failed endpoint=%s response=%s err=%v", endpoint, rawResponse, err) return qianxunResponse{}, fmt.Errorf("decode Qianxun response %s: %w", endpoint, err) } if !qianxunSuccess(output) { g.Log().Warningf(ctx, "Qianxun business error endpoint=%s status=%s message=%s response=%s", endpoint, output.Status, output.Message, rawResponse) } else { g.Log().Infof(ctx, "Qianxun response endpoint=%s status=%s response=%s", endpoint, output.Status, rawResponse) } return output, nil } func qianxunSuccess(resp qianxunResponse) bool { return strings.TrimSpace(resp.Status) == qianxunSuccessCode } func qianxunBusinessError[T any](simKey *model.SimKey, resp qianxunResponse) *model.SimBaseRes[T] { return &model.SimBaseRes[T]{ SimKey: simKey, Code: gconv.Int(resp.Status), ErrCode: resp.Status, Message: resp.Message, } } func qianxunError[T any](simKey *model.SimKey, err error) *model.SimBaseRes[T] { return &model.SimBaseRes[T]{SimKey: simKey, Code: consts.ApiStatusError, Message: err.Error()} } func qianxunUnsupported[T any](simKeys []*model.SimKey, message string) []*model.SimBaseRes[T] { items := make([]*model.SimBaseRes[T], 0, len(simKeys)) for _, simKey := range simKeys { items = append(items, &model.SimBaseRes[T]{ SimKey: simKey, Code: consts.ApiInvokeException, Message: message, }) } return items } func qianxunICCID(simKey *model.SimKey) (string, error) { if simKey == nil || strings.TrimSpace(simKey.ICCID) == "" { return "", fmt.Errorf("ICCID not found") } return strings.TrimSpace(simKey.ICCID), nil } func decodeQianxunData[T any](raw json.RawMessage, target *T) error { if len(raw) == 0 || string(raw) == "null" { return fmt.Errorf("Qianxun response result is empty") } if err := json.Unmarshal(raw, target); err != nil { return fmt.Errorf("decode Qianxun response result: %w", err) } return nil } func decodeQianxunStringJSON(raw json.RawMessage, target any) error { if len(raw) == 0 || string(raw) == "null" { return fmt.Errorf("Qianxun batch item result is empty") } var text string if err := json.Unmarshal(raw, &text); err == nil { text = strings.TrimSpace(text) if !strings.HasPrefix(text, "{") && !strings.HasPrefix(text, "[") { return errors.New(text) } return json.Unmarshal([]byte(text), target) } return json.Unmarshal(raw, target) } func qianxunCardStatus(ctx context.Context, value any) int { status := strings.TrimSpace(gconv.String(value)) switch status { case "": return 0 case "0", "10": return 2 case "1": return 1 case "3", "4", "8": return 4 case "6": return 6 case "7": return 7 case "9": return 9 default: g.Log().Warningf(ctx, "Unknown Qianxun card status %s from upstream", status) return 0 } } func qianxunUsageMB(values ...any) float64 { for _, value := range values { text := strings.TrimSpace(gconv.String(value)) if text != "" && text != "" { return parseDataUsage(text) } } return 0 } func qianxunCardMessage(resp qianxunResponse) string { return firstNonEmpty(resp.Message, "成功") } func qianxunCardToBasic(data qianxunCardInfo) model.SimBasicInfoItem { status := strconv.Itoa(qianxunCardStatus(context.Background(), qianxunStatusValue(data))) return model.SimBasicInfoItem{ ICCID: data.ICCID, IMSI: data.IMSI, MSISDN: firstNonEmpty(data.MSISDN, data.AccessNumber, data.CardNo), ActiveDate: firstNonEmpty(data.ActiveDate, data.ActiveTime, data.ActivateTime), OpenDate: firstNonEmpty(data.OpenDate, data.OpenTime), Remark: data.Remark, SimStatus: status, MainOfferName: firstNonEmpty(data.MainOfferName, data.PackageName, data.ProdName), } } func qianxunStatusValue(data qianxunCardInfo) string { return firstNonEmpty(gconv.String(data.SimStatus), gconv.String(data.CardStatus), gconv.String(data.Status), gconv.String(data.RealStatus)) } func (s *sSimQianxun) queryCardInfo(ctx context.Context, simKey *model.SimKey) (qianxunCardInfo, qianxunResponse, error) { iccid, err := qianxunICCID(simKey) if err != nil { return qianxunCardInfo{}, qianxunResponse{}, err } params := url.Values{} params.Set("iccid", iccid) resp, err := s.post(ctx, "/api/v3/cardInfo", params) if err != nil || !qianxunSuccess(resp) { return qianxunCardInfo{}, resp, err } var data qianxunCardInfo if err := decodeQianxunData(resp.Result, &data); err != nil { return qianxunCardInfo{}, resp, err } return data, resp, nil } func (s *sSimQianxun) QuerySimBasicInfo(ctx context.Context, input *model.SimBasicInfoInput) (model.SimBasicInfoOutput, error) { if input == nil { return make(model.SimBasicInfoOutput, 0), nil } items := make(model.SimBasicInfoOutput, 0, len(input.SimKeys)) for _, simKey := range input.SimKeys { data, resp, err := s.queryCardInfo(ctx, simKey) if err != nil { items = append(items, qianxunError[model.SimBasicInfoItem](simKey, err)) continue } if !qianxunSuccess(resp) { items = append(items, qianxunBusinessError[model.SimBasicInfoItem](simKey, resp)) continue } items = append(items, &model.SimBaseRes[model.SimBasicInfoItem]{ SimKey: simKey, Code: consts.ApiStatusSuccess, Message: qianxunCardMessage(resp), ResData: qianxunCardToBasic(data), }) } return items, nil } func (s *sSimQianxun) QuerySimCardStatus(ctx context.Context, input *model.SimCardStatusInput) (model.SimCardStatusOutput, error) { if input == nil { return make(model.SimCardStatusOutput, 0), nil } items := make(model.SimCardStatusOutput, 0, len(input.SimKeys)) for _, simKey := range input.SimKeys { data, resp, err := s.queryCardInfo(ctx, simKey) if err != nil { items = append(items, qianxunError[model.SimCardStatusItem](simKey, err)) continue } if !qianxunSuccess(resp) { items = append(items, qianxunBusinessError[model.SimCardStatusItem](simKey, resp)) continue } items = append(items, &model.SimBaseRes[model.SimCardStatusItem]{ SimKey: simKey, Code: consts.ApiStatusSuccess, Message: qianxunCardMessage(resp), ResData: model.SimCardStatusItem{ CardStatus: qianxunCardStatus(ctx, qianxunStatusValue(data)), LastChangeDate: firstNonEmpty(data.LastChangeDate, data.UpdateTime), }, }) } return items, nil } func (s *sSimQianxun) QuerySimDataUsage(ctx context.Context, input *model.SimDataUsageInput) (model.SimDataUsageOutput, error) { if input == nil { return make(model.SimDataUsageOutput, 0), nil } items := make(model.SimDataUsageOutput, 0, len(input.SimKeys)) for _, simKey := range input.SimKeys { data, resp, err := s.queryCardInfo(ctx, simKey) if err != nil { items = append(items, qianxunError[model.SimDataUsageItem](simKey, err)) continue } if !qianxunSuccess(resp) { items = append(items, qianxunBusinessError[model.SimDataUsageItem](simKey, resp)) continue } item := model.SimDataUsageItem{ DataUsage: qianxunUsageMB(data.UsedGprs, data.UsageTotal, data.UsedFlow, data.UseFlow, data.CardFlow, data.Flow), } total := qianxunUsageMB(data.ProdTotal, data.TotalFlow, data.PackageFlow) if total > 0 { item.TotalFlow = &total } items = append(items, &model.SimBaseRes[model.SimDataUsageItem]{ SimKey: simKey, Code: consts.ApiStatusSuccess, Message: qianxunCardMessage(resp), ResData: item, }) } return items, nil } func (s *sSimQianxun) QuerySimDataUsageMonthly(ctx context.Context, input *model.SimDataUsageMonthlyInput) (model.SimDataUsageMonthlyOutput, error) { if input == nil { return make(model.SimDataUsageMonthlyOutput, 0), nil } queryMonth, err := qianxunMonth(input.ReqData.QueryDate) if err != nil { return nil, err } items := make(model.SimDataUsageMonthlyOutput, 0, len(input.SimKeys)) for _, simKey := range input.SimKeys { iccid, err := qianxunICCID(simKey) if err != nil { items = append(items, qianxunError[model.SimDataUsageMonthlyOutputItem](simKey, err)) continue } params := url.Values{} params.Set("iccid", iccid) params.Set("billingMonth", queryMonth) resp, err := s.post(ctx, "/api/v3/queryFlowHistory", params) if err != nil { items = append(items, qianxunError[model.SimDataUsageMonthlyOutputItem](simKey, err)) continue } if !qianxunSuccess(resp) { items = append(items, qianxunBusinessError[model.SimDataUsageMonthlyOutputItem](simKey, resp)) continue } var data qianxunFlowHistory if err := decodeQianxunData(resp.Result, &data); err != nil { items = append(items, qianxunError[model.SimDataUsageMonthlyOutputItem](simKey, err)) continue } if strings.EqualFold(strings.TrimSpace(data.Status), "N") { items = append(items, &model.SimBaseRes[model.SimDataUsageMonthlyOutputItem]{ SimKey: simKey, Code: consts.ApiStatusError, Message: "Qianxun monthly usage data not synced", ResData: model.SimDataUsageMonthlyOutputItem{DeadLine: firstNonEmpty(data.BillingMonth, queryMonth)}, }) continue } items = append(items, &model.SimBaseRes[model.SimDataUsageMonthlyOutputItem]{ SimKey: simKey, Code: consts.ApiStatusSuccess, Message: qianxunCardMessage(resp), ResData: model.SimDataUsageMonthlyOutputItem{ DeadLine: firstNonEmpty(data.BillingMonth, queryMonth), DataAmount: qianxunUsageMB(data.UsageTotal), }, }) } return items, nil } func qianxunMonth(queryDate string) (string, error) { queryDate = strings.TrimSpace(queryDate) if queryDate == "" { return "", fmt.Errorf("queryDate is required") } for _, layout := range []string{"200601", "2006-01", "20060102", "2006-01-02"} { if parsed, err := time.Parse(layout, queryDate); err == nil { return parsed.Format("200601"), nil } } return "", fmt.Errorf("invalid queryDate: %s", queryDate) } func (s *sSimQianxun) QuerySimCardStatusBatch(ctx context.Context, input *model.SimCardStatusInput) (model.SimCardStatusOutput, error) { if input == nil || len(input.SimKeys) == 0 { return make(model.SimCardStatusOutput, 0), nil } cards, errs := s.queryCardInfoBatch(ctx, input.SimKeys) items := make(model.SimCardStatusOutput, 0, len(input.SimKeys)) for _, simKey := range input.SimKeys { iccid, err := qianxunICCID(simKey) if err != nil { items = append(items, qianxunError[model.SimCardStatusItem](simKey, err)) continue } if err = errs[iccid]; err != nil { items = append(items, qianxunError[model.SimCardStatusItem](simKey, err)) continue } data, ok := cards[iccid] if !ok { items = append(items, &model.SimBaseRes[model.SimCardStatusItem]{SimKey: simKey, Code: consts.ApiStatusError, Message: "batch response missing card"}) continue } items = append(items, &model.SimBaseRes[model.SimCardStatusItem]{ SimKey: simKey, Code: consts.ApiStatusSuccess, ResData: model.SimCardStatusItem{ CardStatus: qianxunCardStatus(ctx, qianxunStatusValue(data)), LastChangeDate: firstNonEmpty(data.LastChangeDate, data.UpdateTime), }, }) } return items, nil } func (s *sSimQianxun) QuerySimDataUsageMonthlyBatch(ctx context.Context, input *model.SimDataUsageMonthlyInput) (model.SimDataUsageMonthlyOutput, error) { if input == nil || len(input.SimKeys) == 0 { return make(model.SimDataUsageMonthlyOutput, 0), nil } queryMonth, err := qianxunMonth(input.ReqData.QueryDate) if err != nil { return nil, err } results := make(model.SimDataUsageMonthlyOutput, len(input.SimKeys)) for _, chunk := range qianxunChunkSimKeys(input.SimKeys, qianxunBatchLimit) { iccids := make([]string, 0, len(chunk)) for _, item := range chunk { iccid, err := qianxunICCID(item.simKey) if err != nil { results[item.index] = qianxunError[model.SimDataUsageMonthlyOutputItem](item.simKey, err) continue } iccids = append(iccids, iccid) } if len(iccids) == 0 { continue } params := url.Values{} params.Set("iccids", strings.Join(iccids, "_")) params.Set("billingMonth", queryMonth) resp, err := s.post(ctx, "/api/v3/batchQueryFlowHistory", params) if err != nil { for _, item := range chunk { if results[item.index] == nil { results[item.index] = qianxunError[model.SimDataUsageMonthlyOutputItem](item.simKey, err) } } continue } if !qianxunSuccess(resp) { for _, item := range chunk { if results[item.index] == nil { results[item.index] = qianxunBusinessError[model.SimDataUsageMonthlyOutputItem](item.simKey, resp) } } continue } history, itemErrs, err := decodeQianxunBatchHistory(resp.Result) if err != nil { for _, item := range chunk { if results[item.index] == nil { results[item.index] = qianxunError[model.SimDataUsageMonthlyOutputItem](item.simKey, err) } } continue } for _, item := range chunk { if results[item.index] != nil { continue } iccid, _ := qianxunICCID(item.simKey) if itemErrs[iccid] != nil { results[item.index] = qianxunError[model.SimDataUsageMonthlyOutputItem](item.simKey, itemErrs[iccid]) continue } data, ok := history[iccid] if !ok { results[item.index] = &model.SimBaseRes[model.SimDataUsageMonthlyOutputItem]{SimKey: item.simKey, Code: consts.ApiStatusError, Message: "batch response missing card", ResData: model.SimDataUsageMonthlyOutputItem{DeadLine: queryMonth}} continue } if strings.EqualFold(strings.TrimSpace(data.Status), "N") { results[item.index] = &model.SimBaseRes[model.SimDataUsageMonthlyOutputItem]{SimKey: item.simKey, Code: consts.ApiStatusError, Message: "Qianxun monthly usage data not synced", ResData: model.SimDataUsageMonthlyOutputItem{DeadLine: firstNonEmpty(data.BillingMonth, queryMonth)}} continue } results[item.index] = &model.SimBaseRes[model.SimDataUsageMonthlyOutputItem]{ SimKey: item.simKey, Code: consts.ApiStatusSuccess, Message: qianxunCardMessage(resp), ResData: model.SimDataUsageMonthlyOutputItem{ DeadLine: firstNonEmpty(data.BillingMonth, queryMonth), DataAmount: qianxunUsageMB(data.UsageTotal), }, } } } return compactMonthlyBatchResults(results), nil } type qianxunBatchKey struct { index int simKey *model.SimKey } func qianxunChunkSimKeys(simKeys []*model.SimKey, size int) [][]qianxunBatchKey { if size <= 0 { size = 1 } items := make([]qianxunBatchKey, 0, len(simKeys)) for i, simKey := range simKeys { items = append(items, qianxunBatchKey{index: i, simKey: simKey}) } chunks := make([][]qianxunBatchKey, 0, (len(items)+size-1)/size) for start := 0; start < len(items); start += size { end := start + size if end > len(items) { end = len(items) } chunks = append(chunks, items[start:end]) } return chunks } func (s *sSimQianxun) queryCardInfoBatch(ctx context.Context, simKeys []*model.SimKey) (map[string]qianxunCardInfo, map[string]error) { cards := make(map[string]qianxunCardInfo, len(simKeys)) errs := make(map[string]error, len(simKeys)) for _, chunk := range qianxunChunkSimKeys(simKeys, qianxunBatchLimit) { iccids := make([]string, 0, len(chunk)) for _, item := range chunk { iccid, err := qianxunICCID(item.simKey) if err != nil { continue } iccids = append(iccids, iccid) } if len(iccids) == 0 { continue } params := url.Values{} params.Set("iccids", strings.Join(iccids, "_")) resp, err := s.post(ctx, "/api/v3/cardInfos", params) if err != nil { for _, iccid := range iccids { errs[iccid] = err } continue } if !qianxunSuccess(resp) { for _, iccid := range iccids { errs[iccid] = fmt.Errorf("Qianxun batch card info failed: code=%s message=%s", resp.Status, resp.Message) } continue } batchCards, itemErrs, err := decodeQianxunBatchCards(resp.Result) if err != nil { for _, iccid := range iccids { errs[iccid] = err } continue } for iccid, data := range batchCards { cards[iccid] = data } for iccid, itemErr := range itemErrs { errs[iccid] = itemErr } } return cards, errs } func decodeQianxunBatchCards(raw json.RawMessage) (map[string]qianxunCardInfo, map[string]error, error) { cards := make(map[string]qianxunCardInfo) errs := make(map[string]error) var wrapped []qianxunBatchCardResult if err := decodeQianxunData(raw, &wrapped); err == nil && qianxunHasWrappedData(wrapped) { for _, item := range wrapped { iccid := strings.TrimSpace(item.ICCID) var card qianxunCardInfo if err := decodeQianxunStringJSON(firstQianxunRaw(item.ICCIDData, item.ICCIDResult), &card); err != nil { errs[iccid] = err continue } if strings.TrimSpace(card.ICCID) == "" { card.ICCID = iccid } cards[card.ICCID] = card } return cards, errs, nil } var list []qianxunCardInfo if err := decodeQianxunData(raw, &list); err != nil { return nil, nil, err } for _, item := range list { iccid := strings.TrimSpace(item.ICCID) if iccid == "" { continue } cards[iccid] = item } return cards, errs, nil } func decodeQianxunBatchHistory(raw json.RawMessage) (map[string]qianxunFlowHistory, map[string]error, error) { history := make(map[string]qianxunFlowHistory) errs := make(map[string]error) var wrapped []qianxunBatchCardResult if err := decodeQianxunData(raw, &wrapped); err == nil && qianxunHasWrappedData(wrapped) { for _, item := range wrapped { iccid := strings.TrimSpace(item.ICCID) var data qianxunFlowHistory if err := decodeQianxunStringJSON(firstQianxunRaw(item.ICCIDResult, item.ICCIDData), &data); err != nil { errs[iccid] = err continue } if strings.TrimSpace(data.ICCID) == "" { data.ICCID = iccid } history[data.ICCID] = data } return history, errs, nil } var list []qianxunFlowHistory if err := decodeQianxunData(raw, &list); err != nil { return nil, nil, err } for _, item := range list { iccid := strings.TrimSpace(item.ICCID) if iccid == "" { continue } history[iccid] = item } return history, errs, nil } func qianxunHasWrappedData(items []qianxunBatchCardResult) bool { for _, item := range items { if len(item.ICCIDData) > 0 && string(item.ICCIDData) != "null" { return true } if len(item.ICCIDResult) > 0 && string(item.ICCIDResult) != "null" { return true } } return false } func firstQianxunRaw(values ...json.RawMessage) json.RawMessage { for _, value := range values { if len(value) > 0 && string(value) != "null" { return value } } return nil } func (s *sSimQianxun) QuerySimSession(ctx context.Context, input *model.SimSessionInput) (model.SimSessionOutput, error) { if input == nil { return make(model.SimSessionOutput, 0), nil } return qianxunUnsupported[model.SimSessionItem](input.SimKeys, "千寻通道暂不支持在线信息查询"), nil } func (s *sSimQianxun) QuerySimStopReason(ctx context.Context, input *model.SimStopReasonInput) (model.SimStopReasonOutput, error) { if input == nil { return make(model.SimStopReasonOutput, 0), nil } return qianxunUnsupported[model.SimStopReasonItem](input.SimKeys, "千寻通道暂不支持停机原因查询"), nil } func (s *sSimQianxun) QuerySimFrozenState(ctx context.Context, input *model.SimFrozenStateInput) (model.SimFrozenStateOutput, error) { if input == nil { return make(model.SimFrozenStateOutput, 0), nil } return qianxunUnsupported[model.SimFrozenStateItem](input.SimKeys, "千寻通道暂不支持冻结状态查询"), nil } func (s *sSimQianxun) QuerySimCardBindStatus(ctx context.Context, input *model.SimCardBindStatusInput) (model.SimCardBindStatusOutput, error) { if input == nil { return make(model.SimCardBindStatusOutput, 0), nil } return qianxunUnsupported[model.SimCardBindStatusItem](input.SimKeys, "千寻通道暂不支持机卡绑定状态查询"), nil } func (s *sSimQianxun) QuerySimDeviceStatus(ctx context.Context, input *model.SimDeviceStatusInput) (model.SimDeviceStatusOutput, error) { if input == nil { return make(model.SimDeviceStatusOutput, 0), nil } return qianxunUnsupported[model.SimDeviceStatusItem](input.SimKeys, "千寻通道暂不支持设备状态查询"), nil } func (s *sSimQianxun) QuerySimRegionLimitStatus(ctx context.Context, input *model.SimRegionLimitStatusInput) (model.SimRegionLimitStatusOutput, error) { if input == nil { return make(model.SimRegionLimitStatusOutput, 0), nil } return qianxunUnsupported[model.SimRegionLimitStatusItem](input.SimKeys, "千寻通道暂不支持地域限制状态查询"), nil } func (s *sSimQianxun) QuerySimImei(ctx context.Context, input *model.SimImeiInput) (model.SimImeiOutput, error) { if input == nil { return make(model.SimImeiOutput, 0), nil } return qianxunUnsupported[model.SimImeiItem](input.SimKeys, "千寻通道暂不支持IMEI查询"), nil } func (s *sSimQianxun) QuerySimDataDiagnosis(ctx context.Context, input *model.SimDataDiagnosisInput) (model.SimDataDiagnosisOutput, error) { if input == nil { return make(model.SimDataDiagnosisOutput, 0), nil } return qianxunUnsupported[any](input.SimKeys, "千寻通道暂不支持达量限速档位查询"), nil } func (s *sSimQianxun) QuerySimDataUsageDaily(ctx context.Context, input *model.SimDataUsageDailyInput) (model.SimDataUsageDailyOutput, error) { if input == nil { return make(model.SimDataUsageDailyOutput, 0), nil } return qianxunUnsupported[model.SimDataUsageDailyOutputItem](input.SimKeys, "千寻通道暂不支持单日流量查询"), nil } func (s *sSimQianxun) QuerySimDataUsageDailyBatch(ctx context.Context, input *model.SimDataUsageDailyInput) (model.SimDataUsageDailyOutput, error) { return s.QuerySimDataUsageDaily(ctx, input) } func (s *sSimQianxun) QuerySimApnInfo(ctx context.Context, input *model.SimApnInfoInput) (model.SimApnInfoOutput, error) { if input == nil { return make(model.SimApnInfoOutput, 0), nil } return qianxunUnsupported[model.SimApnInfoItemList](input.SimKeys, "千寻通道暂不支持APN信息查询"), nil } func (s *sSimQianxun) ChangeSimStatus(ctx context.Context, input *model.SimCardStatusChangeInput) (model.SimCardStatusChangeOutput, error) { if input == nil { return make(model.SimCardStatusChangeOutput, 0), nil } return qianxunUnsupported[model.SimCardStatusChangeItem](input.SimKeys, "千寻通道暂不支持卡状态变更"), nil } func (s *sSimQianxun) ChangeSimApnFunction(ctx context.Context, input *model.SimCardOperateChangeApnInput) (model.SimCardOperateChangeApnOutput, error) { if input == nil { return make(model.SimCardOperateChangeApnOutput, 0), nil } return qianxunUnsupported[model.SimCardOperateChangeApnItem](input.SimKeys, "千寻通道暂不支持APN功能开停"), nil }