96b1edff45
iotlcm-upstream-chan:运营商上游通道适配服务(统一 /sim/* 协议)。 - 内联原私有依赖:yunze-public → internal/pub/(10 包 18 文件), yunze-pb/pb → internal/pb/(9 文件);Consul 注册改用官方 gogf/gf/contrib/registry/consul/v2(API 兼容) - Go module:yz-svr → git.5iot.com/iotlcm-public/iotlcm-upstream-chan - 修复 mingqian 包测试:实现重构后 `adapter.operations` 字段已移除, 测试断言按当前实现语义重写(不改产品行为) - 凭据与内部标识脱敏:MySQL/Redis/Consul 凭据、私网与生产 IP、 内网域名、真实手机号、注释里的真实 token/transid - 移除内部资产:CI 触发器、生产通道种子 SQL、真实凭据测试资源文档 - Dockerfile 改用公共基础镜像,移除写死的 registry 凭据 - 补 Apache-2.0 LICENSE,重写 README,LF 行尾 + .gitattributes
881 lines
29 KiB
Go
881 lines
29 KiB
Go
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 != "<nil>" {
|
|
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
|
|
}
|