1136 lines
38 KiB
Go
1136 lines
38 KiB
Go
package upstreamimpl
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/md5"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"github.com/gogf/gf/v2/util/gconv"
|
|
"golang.org/x/sync/singleflight"
|
|
"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"
|
|
)
|
|
|
|
const (
|
|
tibiotDefaultBaseURL = "https://api.tibiot.com"
|
|
tibiotTimeLayout = "20060102150405"
|
|
tibiotUsageCacheTTL = 30 * time.Second
|
|
)
|
|
|
|
var tibiotEast8 = time.FixedZone("UTC+8", 8*60*60)
|
|
|
|
type sSimTibiot struct {
|
|
channelConfig *model.SimChannelConfigItemCache
|
|
baseURL string
|
|
userName string
|
|
password string
|
|
http *http.Client
|
|
usageCache sync.Map
|
|
usageFlight singleflight.Group
|
|
}
|
|
|
|
type tibiotUsageCacheEntry struct {
|
|
Usage float64
|
|
UpdatedAt string
|
|
ExpiresAt time.Time
|
|
}
|
|
|
|
type tibiotUsageResult struct {
|
|
Usage float64
|
|
Cached bool
|
|
UpdatedAt string
|
|
BusinessError *tibiotResponse
|
|
}
|
|
|
|
type tibiotResponse struct {
|
|
Status any `json:"status"`
|
|
Message string `json:"message"`
|
|
Data json.RawMessage `json:"data"`
|
|
}
|
|
|
|
type tibiotCardInfo struct {
|
|
ICCID string `json:"iccid"`
|
|
IMSI string `json:"imsi"`
|
|
MSISDN string `json:"msisdn"`
|
|
IMEI string `json:"imei"`
|
|
CardStatus any `json:"cardStatus"`
|
|
PackageName string `json:"packageName"`
|
|
TotalFlow string `json:"totalFlow"`
|
|
CardFlow string `json:"cardFlow"`
|
|
LeftFlow string `json:"leftFlow"`
|
|
OpenCardTime string `json:"openCardTime"`
|
|
LastActivateTime string `json:"lastActivateTime"`
|
|
ActivateTime string `json:"activateTime"`
|
|
PackageTime string `json:"packageTime"`
|
|
Remark string `json:"remark"`
|
|
ChannelID string `json:"channelId"`
|
|
PackageID string `json:"packageId"`
|
|
CardType any `json:"cardType"`
|
|
PoolID string `json:"poolId"`
|
|
}
|
|
|
|
type tibiotOnlineInfo struct {
|
|
ICCID string `json:"iccid"`
|
|
MSISDN string `json:"msisdn"`
|
|
CardStatus string `json:"cardStatus"`
|
|
NetworkStatus string `json:"netWorkStatus"`
|
|
EquipmentStatus string `json:"equipmentStatus"`
|
|
OnlineStatus string `json:"onlineStatus"`
|
|
SeparationStatus string `json:"separationStatus"`
|
|
NetworkType string `json:"networkType"`
|
|
RegionLimitStatus string `json:"regionLimitStatus"`
|
|
APN string `json:"apn"`
|
|
IP string `json:"ip"`
|
|
IMEI string `json:"imei"`
|
|
}
|
|
|
|
type tibiotMonthUsage struct {
|
|
Years string `json:"years"`
|
|
CardFlow string `json:"cardFlow"`
|
|
ICCID string `json:"iccid"`
|
|
MSISDN string `json:"msisdn"`
|
|
SyncTime string `json:"syncTime"`
|
|
}
|
|
|
|
type tibiotDailyUsage struct {
|
|
SyncStatus string `json:"syncStatus"`
|
|
CardFlow string `json:"cardFlow"`
|
|
CreateTime string `json:"createTime"`
|
|
SyncTime string `json:"syncTime"`
|
|
ICCID string `json:"iccid"`
|
|
IMSI string `json:"imsi"`
|
|
MSISDN string `json:"msisdn"`
|
|
QueryDate string `json:"queryTime"`
|
|
}
|
|
|
|
type tibiotBatchCardData struct {
|
|
PageNum int `json:"pageNum"`
|
|
PageSize int `json:"pageSize"`
|
|
TotalCount int `json:"totalCount"`
|
|
PageCount int `json:"pageCount"`
|
|
Records []tibiotCardInfo `json:"records"`
|
|
}
|
|
|
|
func NewSimTibiot() *sSimTibiot {
|
|
return &sSimTibiot{}
|
|
}
|
|
|
|
func init() {
|
|
service.UpstreamAPI().RegisterTemp("sim_tibiot_v1", NewSimTibiot())
|
|
}
|
|
|
|
func (s *sSimTibiot) NewTemp(ctx context.Context, channelConfig *model.SimChannelConfigItemCache) model.SimCardChannelI {
|
|
baseURL := tibiotDefaultBaseURL
|
|
if channelConfig != nil && channelConfig.Template != nil {
|
|
var cfg struct {
|
|
API struct {
|
|
BaseURL string `json:"baseUrl"`
|
|
} `json:"api"`
|
|
}
|
|
if err := json.Unmarshal([]byte(channelConfig.Template.TempConfig), &cfg); err != nil {
|
|
g.Log().Warningf(ctx, "parse Tibiot template config failed: %v", err)
|
|
} else if strings.TrimSpace(cfg.API.BaseURL) != "" {
|
|
baseURL = strings.TrimRight(strings.TrimSpace(cfg.API.BaseURL), "/")
|
|
}
|
|
}
|
|
|
|
var credentials struct {
|
|
UserName string `json:"userName"`
|
|
Password string `json:"password"`
|
|
}
|
|
if channelConfig != nil && channelConfig.Channel != nil {
|
|
if err := json.Unmarshal([]byte(channelConfig.Channel.ChanConfig), &credentials); err != nil {
|
|
g.Log().Warningf(ctx, "parse Tibiot channel config failed, chanKey=%s err=%v", channelConfig.Channel.ChanKey, err)
|
|
}
|
|
}
|
|
|
|
return &sSimTibiot{
|
|
channelConfig: channelConfig,
|
|
baseURL: baseURL,
|
|
userName: strings.TrimSpace(credentials.UserName),
|
|
password: credentials.Password,
|
|
http: &http.Client{Timeout: resolveUpstreamHTTPTimeout(ctx, templateConfigJSON(channelConfig))},
|
|
}
|
|
}
|
|
|
|
func tibiotSignature(password, tKey string) string {
|
|
first := md5.Sum([]byte(password))
|
|
firstText := hex.EncodeToString(first[:])
|
|
second := md5.Sum([]byte(firstText + tKey))
|
|
return hex.EncodeToString(second[:])
|
|
}
|
|
|
|
func tibiotAuthFields(userName, password string, now time.Time) map[string]any {
|
|
tKey := now.In(tibiotEast8).Format(tibiotTimeLayout)
|
|
return map[string]any{
|
|
"userName": userName,
|
|
"passWord": tibiotSignature(password, tKey),
|
|
"tKey": tKey,
|
|
}
|
|
}
|
|
|
|
func (s *sSimTibiot) post(ctx context.Context, endpoint string, params map[string]any) (tibiotResponse, error) {
|
|
if s == nil {
|
|
return tibiotResponse{}, fmt.Errorf("Tibiot client is nil")
|
|
}
|
|
if strings.TrimSpace(s.userName) == "" || s.password == "" {
|
|
return tibiotResponse{}, fmt.Errorf("Tibiot credentials are not configured")
|
|
}
|
|
payload := tibiotAuthFields(s.userName, s.password, time.Now())
|
|
for key, value := range params {
|
|
payload[key] = value
|
|
}
|
|
body, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return tibiotResponse{}, fmt.Errorf("marshal Tibiot request: %w", err)
|
|
}
|
|
url := strings.TrimRight(s.baseURL, "/") + "/" + strings.TrimLeft(endpoint, "/")
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
|
if err != nil {
|
|
return tibiotResponse{}, fmt.Errorf("create Tibiot request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
client := s.http
|
|
if client == nil {
|
|
client = &http.Client{Timeout: defaultUpstreamHTTPTimeout}
|
|
}
|
|
if endpoint == "/api/v1/card/cardNetWork" {
|
|
transport := http.DefaultTransport.(*http.Transport).Clone()
|
|
if original, ok := client.Transport.(*http.Transport); ok {
|
|
transport = original.Clone()
|
|
}
|
|
transport.DisableKeepAlives = true
|
|
defer transport.CloseIdleConnections()
|
|
writeClient := *client
|
|
writeClient.Transport = transport
|
|
writeClient.CheckRedirect = func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }
|
|
client = &writeClient
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return tibiotResponse{}, fmt.Errorf("Tibiot request %s: %w", endpoint, err)
|
|
}
|
|
defer resp.Body.Close()
|
|
responseBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return tibiotResponse{}, fmt.Errorf("read Tibiot response %s: %w", endpoint, err)
|
|
}
|
|
rawResponse := strings.TrimSpace(string(responseBody))
|
|
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
|
g.Log().Warningf(ctx, "Tibiot HTTP error endpoint=%s status=%d response=%s", endpoint, resp.StatusCode, rawResponse)
|
|
return tibiotResponse{}, fmt.Errorf("Tibiot HTTP status %d", resp.StatusCode)
|
|
}
|
|
var output tibiotResponse
|
|
if err := json.Unmarshal(responseBody, &output); err != nil {
|
|
g.Log().Warningf(ctx, "decode Tibiot response failed endpoint=%s response=%s err=%v", endpoint, rawResponse, err)
|
|
return tibiotResponse{}, fmt.Errorf("decode Tibiot response %s: %w", endpoint, err)
|
|
}
|
|
if !tibiotSuccess(output) {
|
|
g.Log().Warningf(ctx, "Tibiot business error endpoint=%s status=%d message=%s response=%s", endpoint, tibiotCode(output), output.Message, rawResponse)
|
|
} else {
|
|
g.Log().Infof(ctx, "Tibiot response endpoint=%s status=%d response=%s", endpoint, tibiotCode(output), rawResponse)
|
|
}
|
|
return output, nil
|
|
}
|
|
|
|
func tibiotCode(resp tibiotResponse) int {
|
|
return gconv.Int(resp.Status)
|
|
}
|
|
|
|
func tibiotSuccess(resp tibiotResponse) bool {
|
|
return tibiotCode(resp) == 0
|
|
}
|
|
|
|
func tibiotCardIdentifier(simKey *model.SimKey) (string, string) {
|
|
if simKey == nil {
|
|
return "", ""
|
|
}
|
|
if value := strings.TrimSpace(simKey.ICCID); value != "" {
|
|
return "iccid", value
|
|
}
|
|
if value := strings.TrimSpace(simKey.MSISDN); value != "" {
|
|
return "msisdn", value
|
|
}
|
|
if value := strings.TrimSpace(simKey.IMSI); value != "" {
|
|
return "imsi", value
|
|
}
|
|
return "", ""
|
|
}
|
|
|
|
func tibiotParams(simKey *model.SimKey) (map[string]any, error) {
|
|
name, value := tibiotCardIdentifier(simKey)
|
|
if name == "" {
|
|
return nil, fmt.Errorf("missing iccid/msisdn/imsi")
|
|
}
|
|
return map[string]any{name: value}, nil
|
|
}
|
|
|
|
func tibiotUnsupported[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 tibiotError[T any](simKey *model.SimKey, err error) *model.SimBaseRes[T] {
|
|
return &model.SimBaseRes[T]{SimKey: simKey, Code: consts.ApiStatusError, Message: err.Error()}
|
|
}
|
|
|
|
func tibiotBusinessError[T any](simKey *model.SimKey, resp tibiotResponse) *model.SimBaseRes[T] {
|
|
return &model.SimBaseRes[T]{
|
|
SimKey: simKey,
|
|
Code: tibiotCode(resp),
|
|
ErrCode: strconv.Itoa(tibiotCode(resp)),
|
|
Message: resp.Message,
|
|
}
|
|
}
|
|
|
|
func decodeTibiotData[T any](resp tibiotResponse, target *T) error {
|
|
if len(resp.Data) == 0 || string(resp.Data) == "null" {
|
|
return fmt.Errorf("Tibiot response data is empty")
|
|
}
|
|
if err := json.Unmarshal(resp.Data, target); err != nil {
|
|
return fmt.Errorf("decode Tibiot response data: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func decodeTibiotBatchCards(resp tibiotResponse) (records []tibiotCardInfo, pageCount int, err error) {
|
|
var pageData tibiotBatchCardData
|
|
if err = decodeTibiotData(resp, &pageData); err == nil && pageData.Records != nil {
|
|
return pageData.Records, pageData.PageCount, nil
|
|
}
|
|
var list []tibiotCardInfo
|
|
if listErr := decodeTibiotData(resp, &list); listErr != nil {
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
return nil, 0, listErr
|
|
}
|
|
return list, 1, nil
|
|
}
|
|
|
|
func parseTibiotFlow(value string) float64 {
|
|
value = strings.ToUpper(strings.TrimSpace(value))
|
|
if value == "" {
|
|
return 0
|
|
}
|
|
for _, unit := range []struct {
|
|
suffix string
|
|
factor float64
|
|
}{
|
|
{suffix: "EB", factor: 1024 * 1024 * 1024 * 1024},
|
|
{suffix: "PB", factor: 1024 * 1024 * 1024},
|
|
{suffix: "TB", factor: 1024 * 1024},
|
|
{suffix: "GB", factor: 1024},
|
|
{suffix: "MB", factor: 1},
|
|
{suffix: "KB", factor: 1.0 / 1024},
|
|
} {
|
|
if strings.HasSuffix(value, unit.suffix) {
|
|
return gconv.Float64(strings.TrimSpace(strings.TrimSuffix(value, unit.suffix))) * unit.factor
|
|
}
|
|
}
|
|
return gconv.Float64(value)
|
|
}
|
|
|
|
func tibiotSessionStatus(value string) string {
|
|
switch strings.ToUpper(strings.TrimSpace(value)) {
|
|
case "1", "ONLINE":
|
|
return consts.CardSessionStatusOnline
|
|
case "2", "OFFLINE":
|
|
return consts.CardSessionStatusOffline
|
|
default:
|
|
return consts.CardSessionStatusUnknown
|
|
}
|
|
}
|
|
|
|
func tibiotDeviceStatus(value string) string {
|
|
switch strings.ToUpper(strings.TrimSpace(value)) {
|
|
case "1", "ON", "ONLINE":
|
|
return "1"
|
|
case "2", "0", "OFF", "OFFLINE":
|
|
return "0"
|
|
default:
|
|
return consts.CardSessionStatusUnknown
|
|
}
|
|
}
|
|
|
|
func (s *sSimTibiot) 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 {
|
|
params, err := tibiotParams(simKey)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimBasicInfoItem](simKey, err))
|
|
continue
|
|
}
|
|
resp, err := s.post(ctx, "/api/v1/card/queryCardInfo", params)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimBasicInfoItem](simKey, err))
|
|
continue
|
|
}
|
|
if !tibiotSuccess(resp) {
|
|
items = append(items, tibiotBusinessError[model.SimBasicInfoItem](simKey, resp))
|
|
continue
|
|
}
|
|
var data tibiotCardInfo
|
|
if err := decodeTibiotData(resp, &data); err != nil {
|
|
items = append(items, tibiotError[model.SimBasicInfoItem](simKey, err))
|
|
continue
|
|
}
|
|
items = append(items, &model.SimBaseRes[model.SimBasicInfoItem]{
|
|
SimKey: simKey,
|
|
Code: consts.ApiStatusSuccess,
|
|
Message: resp.Message,
|
|
ResData: model.SimBasicInfoItem{
|
|
ICCID: data.ICCID,
|
|
IMSI: data.IMSI,
|
|
MSISDN: data.MSISDN,
|
|
ActiveDate: firstNonEmpty(data.ActivateTime, data.LastActivateTime),
|
|
OpenDate: firstNonEmpty(data.OpenCardTime, data.ActivateTime),
|
|
Remark: data.Remark,
|
|
SimStatus: strconv.Itoa(convertTibiotCardStatus(context.Background(), gconv.Int(data.CardStatus))),
|
|
MainOfferName: data.PackageName,
|
|
},
|
|
})
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (s *sSimTibiot) QuerySimSession(ctx context.Context, input *model.SimSessionInput) (model.SimSessionOutput, error) {
|
|
if input == nil {
|
|
return make(model.SimSessionOutput, 0), nil
|
|
}
|
|
items := make(model.SimSessionOutput, 0, len(input.SimKeys))
|
|
for _, simKey := range input.SimKeys {
|
|
params, err := tibiotParams(simKey)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimSessionItem](simKey, err))
|
|
continue
|
|
}
|
|
resp, err := s.post(ctx, "/api/v1/card/cardOnlineInformation", params)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimSessionItem](simKey, err))
|
|
continue
|
|
}
|
|
if !tibiotSuccess(resp) {
|
|
items = append(items, tibiotBusinessError[model.SimSessionItem](simKey, resp))
|
|
continue
|
|
}
|
|
var data tibiotOnlineInfo
|
|
if err := decodeTibiotData(resp, &data); err != nil {
|
|
items = append(items, tibiotError[model.SimSessionItem](simKey, err))
|
|
continue
|
|
}
|
|
items = append(items, &model.SimBaseRes[model.SimSessionItem]{
|
|
SimKey: simKey,
|
|
Code: consts.ApiStatusSuccess,
|
|
Message: resp.Message,
|
|
ResData: model.SimSessionItem{
|
|
Imei: data.IMEI,
|
|
Rat: data.NetworkType,
|
|
Ipv4: data.IP,
|
|
ApnId: data.APN,
|
|
Status: tibiotSessionStatus(data.OnlineStatus),
|
|
},
|
|
})
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (s *sSimTibiot) 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 {
|
|
params, err := tibiotParams(simKey)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimCardStatusItem](simKey, err))
|
|
continue
|
|
}
|
|
resp, err := s.post(ctx, "/api/v1/card/queryCardInfo", params)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimCardStatusItem](simKey, err))
|
|
continue
|
|
}
|
|
if !tibiotSuccess(resp) {
|
|
items = append(items, tibiotBusinessError[model.SimCardStatusItem](simKey, resp))
|
|
continue
|
|
}
|
|
var data tibiotCardInfo
|
|
if err := decodeTibiotData(resp, &data); err != nil {
|
|
items = append(items, tibiotError[model.SimCardStatusItem](simKey, err))
|
|
continue
|
|
}
|
|
items = append(items, &model.SimBaseRes[model.SimCardStatusItem]{
|
|
SimKey: simKey,
|
|
Code: consts.ApiStatusSuccess,
|
|
Message: resp.Message,
|
|
ResData: model.SimCardStatusItem{CardStatus: convertTibiotCardStatus(ctx, gconv.Int(data.CardStatus)), LastChangeDate: data.LastActivateTime},
|
|
})
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (s *sSimTibiot) QuerySimStopReason(ctx context.Context, input *model.SimStopReasonInput) (model.SimStopReasonOutput, error) {
|
|
if input == nil {
|
|
return make(model.SimStopReasonOutput, 0), nil
|
|
}
|
|
items := make(model.SimStopReasonOutput, 0, len(input.SimKeys))
|
|
for _, simKey := range input.SimKeys {
|
|
params, err := tibiotParams(simKey)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimStopReasonItem](simKey, err))
|
|
continue
|
|
}
|
|
resp, err := s.post(ctx, "/api/v1/card/getCardStopReason", params)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimStopReasonItem](simKey, err))
|
|
continue
|
|
}
|
|
if !tibiotSuccess(resp) {
|
|
items = append(items, tibiotBusinessError[model.SimStopReasonItem](simKey, resp))
|
|
continue
|
|
}
|
|
var reasons []string
|
|
if err := decodeTibiotData(resp, &reasons); err != nil {
|
|
items = append(items, tibiotError[model.SimStopReasonItem](simKey, err))
|
|
continue
|
|
}
|
|
items = append(items, &model.SimBaseRes[model.SimStopReasonItem]{SimKey: simKey, Code: 0, Message: resp.Message, ResData: model.SimStopReasonItem{StopReason: strings.Join(reasons, ";")}})
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (s *sSimTibiot) QuerySimFrozenState(ctx context.Context, input *model.SimFrozenStateInput) (model.SimFrozenStateOutput, error) {
|
|
if input == nil {
|
|
return make(model.SimFrozenStateOutput, 0), nil
|
|
}
|
|
return tibiotUnsupported[model.SimFrozenStateItem](input.SimKeys, "Tibiot does not support frozen state query"), nil
|
|
}
|
|
|
|
func (s *sSimTibiot) QuerySimCardBindStatus(ctx context.Context, input *model.SimCardBindStatusInput) (model.SimCardBindStatusOutput, error) {
|
|
if input == nil {
|
|
return make(model.SimCardBindStatusOutput, 0), nil
|
|
}
|
|
items := make(model.SimCardBindStatusOutput, 0, len(input.SimKeys))
|
|
for _, simKey := range input.SimKeys {
|
|
params, err := tibiotParams(simKey)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimCardBindStatusItem](simKey, err))
|
|
continue
|
|
}
|
|
resp, err := s.post(ctx, "/api/v1/card/queryCardBindStatus", params)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimCardBindStatusItem](simKey, err))
|
|
continue
|
|
}
|
|
if !tibiotSuccess(resp) {
|
|
items = append(items, tibiotBusinessError[model.SimCardBindStatusItem](simKey, resp))
|
|
continue
|
|
}
|
|
var value string
|
|
if err := decodeTibiotData(resp, &value); err != nil {
|
|
items = append(items, tibiotError[model.SimCardBindStatusItem](simKey, err))
|
|
continue
|
|
}
|
|
items = append(items, &model.SimBaseRes[model.SimCardBindStatusItem]{SimKey: simKey, Code: 0, Message: resp.Message, ResData: model.SimCardBindStatusItem{BindStatus: value}})
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (s *sSimTibiot) QuerySimDeviceStatus(ctx context.Context, input *model.SimDeviceStatusInput) (model.SimDeviceStatusOutput, error) {
|
|
if input == nil {
|
|
return make(model.SimDeviceStatusOutput, 0), nil
|
|
}
|
|
items := make(model.SimDeviceStatusOutput, 0, len(input.SimKeys))
|
|
for _, simKey := range input.SimKeys {
|
|
params, err := tibiotParams(simKey)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimDeviceStatusItem](simKey, err))
|
|
continue
|
|
}
|
|
resp, err := s.post(ctx, "/api/v1/card/cardOnlineInformation", params)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimDeviceStatusItem](simKey, err))
|
|
continue
|
|
}
|
|
if !tibiotSuccess(resp) {
|
|
items = append(items, tibiotBusinessError[model.SimDeviceStatusItem](simKey, resp))
|
|
continue
|
|
}
|
|
var data tibiotOnlineInfo
|
|
if err := decodeTibiotData(resp, &data); err != nil {
|
|
items = append(items, tibiotError[model.SimDeviceStatusItem](simKey, err))
|
|
continue
|
|
}
|
|
items = append(items, &model.SimBaseRes[model.SimDeviceStatusItem]{SimKey: simKey, Code: 0, Message: resp.Message, ResData: model.SimDeviceStatusItem{DeviceStatus: tibiotDeviceStatus(data.EquipmentStatus)}})
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (s *sSimTibiot) QuerySimRegionLimitStatus(ctx context.Context, input *model.SimRegionLimitStatusInput) (model.SimRegionLimitStatusOutput, error) {
|
|
if input == nil {
|
|
return make(model.SimRegionLimitStatusOutput, 0), nil
|
|
}
|
|
items := make(model.SimRegionLimitStatusOutput, 0, len(input.SimKeys))
|
|
for _, simKey := range input.SimKeys {
|
|
params, err := tibiotParams(simKey)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimRegionLimitStatusItem](simKey, err))
|
|
continue
|
|
}
|
|
resp, err := s.post(ctx, "/api/v1/card/cardOnlineInformation", params)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimRegionLimitStatusItem](simKey, err))
|
|
continue
|
|
}
|
|
if !tibiotSuccess(resp) {
|
|
items = append(items, tibiotBusinessError[model.SimRegionLimitStatusItem](simKey, resp))
|
|
continue
|
|
}
|
|
var data tibiotOnlineInfo
|
|
if err := decodeTibiotData(resp, &data); err != nil {
|
|
items = append(items, tibiotError[model.SimRegionLimitStatusItem](simKey, err))
|
|
continue
|
|
}
|
|
items = append(items, &model.SimBaseRes[model.SimRegionLimitStatusItem]{SimKey: simKey, Code: 0, Message: resp.Message, ResData: model.SimRegionLimitStatusItem{RegionLimitStatus: data.RegionLimitStatus}})
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (s *sSimTibiot) QuerySimImei(ctx context.Context, input *model.SimImeiInput) (model.SimImeiOutput, error) {
|
|
if input == nil {
|
|
return make(model.SimImeiOutput, 0), nil
|
|
}
|
|
items := make(model.SimImeiOutput, 0, len(input.SimKeys))
|
|
for _, simKey := range input.SimKeys {
|
|
params, err := tibiotParams(simKey)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimImeiItem](simKey, err))
|
|
continue
|
|
}
|
|
resp, err := s.post(ctx, "/api/v1/card/querySimIMEI", params)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimImeiItem](simKey, err))
|
|
continue
|
|
}
|
|
if !tibiotSuccess(resp) {
|
|
items = append(items, tibiotBusinessError[model.SimImeiItem](simKey, resp))
|
|
continue
|
|
}
|
|
var data struct {
|
|
IMEI string `json:"imei"`
|
|
}
|
|
if err := decodeTibiotData(resp, &data); err != nil {
|
|
items = append(items, tibiotError[model.SimImeiItem](simKey, err))
|
|
continue
|
|
}
|
|
items = append(items, &model.SimBaseRes[model.SimImeiItem]{SimKey: simKey, Code: 0, Message: resp.Message, ResData: model.SimImeiItem{Imei: data.IMEI}})
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (s *sSimTibiot) 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 {
|
|
result, err := s.querySimDataUsage(ctx, simKey)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimDataUsageItem](simKey, err))
|
|
continue
|
|
}
|
|
if result.BusinessError != nil {
|
|
items = append(items, tibiotBusinessError[model.SimDataUsageItem](simKey, *result.BusinessError))
|
|
continue
|
|
}
|
|
items = append(items, &model.SimBaseRes[model.SimDataUsageItem]{
|
|
SimKey: simKey,
|
|
Code: 0,
|
|
Cached: result.Cached,
|
|
UpdatedAt: result.UpdatedAt,
|
|
ResData: model.SimDataUsageItem{DataUsage: result.Usage},
|
|
})
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (s *sSimTibiot) querySimDataUsage(ctx context.Context, simKey *model.SimKey) (*tibiotUsageResult, error) {
|
|
cacheKey := tibiotUsageCacheKey(simKey)
|
|
if cached, ok := s.cachedUsage(cacheKey); ok {
|
|
return cached, nil
|
|
}
|
|
value, err, _ := s.usageFlight.Do(cacheKey, func() (any, error) {
|
|
if cached, ok := s.cachedUsage(cacheKey); ok {
|
|
return cached, nil
|
|
}
|
|
params, paramsErr := tibiotParams(simKey)
|
|
if paramsErr != nil {
|
|
return nil, paramsErr
|
|
}
|
|
params["flowUnit"] = "MB"
|
|
|
|
var (
|
|
lastResp tibiotResponse
|
|
lastErr error
|
|
usage float64
|
|
)
|
|
for attempt := 0; attempt < 2; attempt++ {
|
|
if waitErr := tibiotSimDataMarginLimiter.wait(ctx); waitErr != nil {
|
|
lastErr = waitErr
|
|
break
|
|
}
|
|
lastResp, lastErr = s.post(ctx, "/api/v1/card/simDataMargin", params)
|
|
if lastErr != nil {
|
|
if errors.Is(lastErr, context.Canceled) || errors.Is(lastErr, context.DeadlineExceeded) {
|
|
break
|
|
}
|
|
continue
|
|
}
|
|
if !tibiotSuccess(lastResp) {
|
|
continue
|
|
}
|
|
var value string
|
|
value, lastErr = decodeTibiotUsage(lastResp)
|
|
if lastErr == nil {
|
|
usage = parseTibiotFlow(value)
|
|
break
|
|
}
|
|
}
|
|
if lastErr != nil {
|
|
return nil, lastErr
|
|
}
|
|
if !tibiotSuccess(lastResp) {
|
|
return &tibiotUsageResult{BusinessError: &lastResp}, nil
|
|
}
|
|
updatedAt := time.Now().Format("2006-01-02 15:04:05")
|
|
s.usageCache.Store(cacheKey, tibiotUsageCacheEntry{Usage: usage, UpdatedAt: updatedAt, ExpiresAt: time.Now().Add(tibiotUsageCacheTTL)})
|
|
return &tibiotUsageResult{Usage: usage, UpdatedAt: updatedAt}, nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result, ok := value.(*tibiotUsageResult)
|
|
if !ok || result == nil {
|
|
return nil, fmt.Errorf("Tibiot usage query returned invalid result")
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *sSimTibiot) cachedUsage(cacheKey string) (*tibiotUsageResult, bool) {
|
|
value, ok := s.usageCache.Load(cacheKey)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
entry, ok := value.(tibiotUsageCacheEntry)
|
|
if !ok || time.Now().After(entry.ExpiresAt) {
|
|
s.usageCache.Delete(cacheKey)
|
|
return nil, false
|
|
}
|
|
return &tibiotUsageResult{Usage: entry.Usage, Cached: true, UpdatedAt: entry.UpdatedAt}, true
|
|
}
|
|
|
|
func tibiotUsageCacheKey(simKey *model.SimKey) string {
|
|
if simKey == nil {
|
|
return "unknown"
|
|
}
|
|
for _, value := range []string{simKey.ICCID, simKey.IMSI, simKey.MSISDN} {
|
|
if value = strings.TrimSpace(value); value != "" {
|
|
return value
|
|
}
|
|
}
|
|
return "unknown"
|
|
}
|
|
|
|
func decodeTibiotUsage(resp tibiotResponse) (string, error) {
|
|
var value string
|
|
if err := decodeTibiotData(resp, &value); err == nil {
|
|
if strings.TrimSpace(value) == "" {
|
|
return "", fmt.Errorf("Tibiot usage data is empty")
|
|
}
|
|
return value, nil
|
|
} else {
|
|
var number float64
|
|
if numberErr := decodeTibiotData(resp, &number); numberErr != nil {
|
|
return "", err
|
|
}
|
|
return strconv.FormatFloat(number, 'f', -1, 64), nil
|
|
}
|
|
}
|
|
|
|
func (s *sSimTibiot) QuerySimDataDiagnosis(ctx context.Context, input *model.SimDataDiagnosisInput) (model.SimDataDiagnosisOutput, error) {
|
|
if input == nil {
|
|
return make(model.SimDataDiagnosisOutput, 0), nil
|
|
}
|
|
return tibiotUnsupported[any](input.SimKeys, "Tibiot does not support data diagnosis"), nil
|
|
}
|
|
|
|
func (s *sSimTibiot) QuerySimDataUsageMonthly(ctx context.Context, input *model.SimDataUsageMonthlyInput) (model.SimDataUsageMonthlyOutput, error) {
|
|
if input == nil {
|
|
return make(model.SimDataUsageMonthlyOutput, 0), nil
|
|
}
|
|
queryMonth, err := tibiotMonth(input.ReqData.QueryDate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items := make(model.SimDataUsageMonthlyOutput, 0, len(input.SimKeys))
|
|
for _, simKey := range input.SimKeys {
|
|
params, err := tibiotParams(simKey)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimDataUsageMonthlyOutputItem](simKey, err))
|
|
continue
|
|
}
|
|
params["flowUnit"] = "MB"
|
|
resp, err := s.post(ctx, "/api/v1/card/queryCardMonthData", params)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimDataUsageMonthlyOutputItem](simKey, err))
|
|
continue
|
|
}
|
|
if !tibiotSuccess(resp) {
|
|
items = append(items, tibiotBusinessError[model.SimDataUsageMonthlyOutputItem](simKey, resp))
|
|
continue
|
|
}
|
|
var data []tibiotMonthUsage
|
|
if err := decodeTibiotData(resp, &data); err != nil {
|
|
items = append(items, tibiotError[model.SimDataUsageMonthlyOutputItem](simKey, err))
|
|
continue
|
|
}
|
|
var selected *tibiotMonthUsage
|
|
for i := range data {
|
|
dataMonth, parseErr := tibiotMonth(data[i].Years)
|
|
if queryMonth == "" || (parseErr == nil && dataMonth == queryMonth) {
|
|
selected = &data[i]
|
|
break
|
|
}
|
|
}
|
|
if selected == nil {
|
|
items = append(items, &model.SimBaseRes[model.SimDataUsageMonthlyOutputItem]{SimKey: simKey, Code: consts.ApiStatusError, Message: "monthly usage data not found"})
|
|
continue
|
|
}
|
|
if strings.TrimSpace(selected.CardFlow) == "" {
|
|
items = append(items, tibiotError[model.SimDataUsageMonthlyOutputItem](simKey, fmt.Errorf("Tibiot monthly usage data is empty")))
|
|
continue
|
|
}
|
|
items = append(items, &model.SimBaseRes[model.SimDataUsageMonthlyOutputItem]{SimKey: simKey, Code: 0, Message: resp.Message, ResData: model.SimDataUsageMonthlyOutputItem{DeadLine: selected.Years, DataAmount: parseTibiotFlow(selected.CardFlow)}})
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func tibiotDate(queryDate string) (string, error) {
|
|
queryDate = strings.TrimSpace(queryDate)
|
|
if queryDate == "" {
|
|
return "", fmt.Errorf("queryDate is required")
|
|
}
|
|
if parsed, err := time.Parse("20060102", queryDate); err == nil {
|
|
return parsed.Format("2006-01-02"), nil
|
|
}
|
|
if parsed, err := time.Parse("2006-01-02", queryDate); err == nil {
|
|
return parsed.Format("2006-01-02"), nil
|
|
}
|
|
return "", fmt.Errorf("invalid queryDate: %s", queryDate)
|
|
}
|
|
|
|
func tibiotMonth(queryDate string) (string, error) {
|
|
queryDate = strings.TrimSpace(queryDate)
|
|
if queryDate == "" {
|
|
return "", nil
|
|
}
|
|
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 monthly queryDate: %s", queryDate)
|
|
}
|
|
|
|
func (s *sSimTibiot) QuerySimDataUsageDaily(ctx context.Context, input *model.SimDataUsageDailyInput) (model.SimDataUsageDailyOutput, error) {
|
|
if input == nil {
|
|
return make(model.SimDataUsageDailyOutput, 0), nil
|
|
}
|
|
items := make(model.SimDataUsageDailyOutput, 0, len(input.SimKeys))
|
|
queryDate, err := tibiotDate(input.ReqData.QueryDate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, simKey := range input.SimKeys {
|
|
params, err := tibiotParams(simKey)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimDataUsageDailyOutputItem](simKey, err))
|
|
continue
|
|
}
|
|
params["queryTime"] = queryDate
|
|
params["flowUnit"] = "MB"
|
|
resp, err := s.post(ctx, "/api/v1/card/newIotData", params)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimDataUsageDailyOutputItem](simKey, err))
|
|
continue
|
|
}
|
|
if !tibiotSuccess(resp) {
|
|
items = append(items, tibiotBusinessError[model.SimDataUsageDailyOutputItem](simKey, resp))
|
|
continue
|
|
}
|
|
var data tibiotDailyUsage
|
|
if err := decodeTibiotData(resp, &data); err != nil {
|
|
items = append(items, tibiotError[model.SimDataUsageDailyOutputItem](simKey, err))
|
|
continue
|
|
}
|
|
if strings.TrimSpace(data.CardFlow) == "" {
|
|
items = append(items, tibiotError[model.SimDataUsageDailyOutputItem](simKey, fmt.Errorf("Tibiot daily usage data is empty")))
|
|
continue
|
|
}
|
|
items = append(items, &model.SimBaseRes[model.SimDataUsageDailyOutputItem]{SimKey: simKey, Code: 0, Message: resp.Message, ResData: model.SimDataUsageDailyOutputItem{QueryDate: input.ReqData.QueryDate, DataAmount: parseTibiotFlow(data.CardFlow)}})
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (s *sSimTibiot) QuerySimApnInfo(ctx context.Context, input *model.SimApnInfoInput) (model.SimApnInfoOutput, error) {
|
|
if input == nil {
|
|
return make(model.SimApnInfoOutput, 0), nil
|
|
}
|
|
return tibiotUnsupported[model.SimApnInfoItemList](input.SimKeys, "Tibiot does not support APN information query"), nil
|
|
}
|
|
|
|
func (s *sSimTibiot) ChangeSimStatus(ctx context.Context, input *model.SimCardStatusChangeInput) (model.SimCardStatusChangeOutput, error) {
|
|
if input == nil {
|
|
return make(model.SimCardStatusChangeOutput, 0), nil
|
|
}
|
|
return tibiotUnsupported[model.SimCardStatusChangeItem](input.SimKeys, "Tibiot does not support SIM card status changes"), nil
|
|
}
|
|
|
|
func (s *sSimTibiot) ChangeSimApnFunction(ctx context.Context, input *model.SimCardOperateChangeApnInput) (model.SimCardOperateChangeApnOutput, error) {
|
|
if input == nil {
|
|
return make(model.SimCardOperateChangeApnOutput, 0), nil
|
|
}
|
|
operateType := 0
|
|
switch strings.TrimSpace(input.ReqData.OperType) {
|
|
case consts.CardApnOperTypeOpen:
|
|
operateType = 1
|
|
case consts.CardApnOperTypeStop:
|
|
operateType = 2
|
|
default:
|
|
return tibiotUnsupported[model.SimCardOperateChangeApnItem](input.SimKeys, "unsupported operType, expected 0(open) or 1(stop)"), nil
|
|
}
|
|
items := make(model.SimCardOperateChangeApnOutput, 0, len(input.SimKeys))
|
|
for _, simKey := range input.SimKeys {
|
|
params, err := tibiotParams(simKey)
|
|
if err != nil {
|
|
items = append(items, tibiotError[model.SimCardOperateChangeApnItem](simKey, err))
|
|
continue
|
|
}
|
|
params["operateType"] = operateType
|
|
resp, err := s.post(ctx, "/api/v1/card/cardNetWork", params)
|
|
if err != nil {
|
|
item := tibiotError[model.SimCardOperateChangeApnItem](simKey, err)
|
|
item.ResData.Uncertain = true
|
|
items = append(items, item)
|
|
continue
|
|
}
|
|
statusText := strings.TrimSpace(fmt.Sprint(resp.Status))
|
|
status, statusErr := strconv.Atoi(statusText)
|
|
if statusErr != nil || (status == 0 && statusText != "0") {
|
|
items = append(items, &model.SimBaseRes[model.SimCardOperateChangeApnItem]{
|
|
SimKey: simKey, Code: consts.ApiStatusError,
|
|
Message: "天比高 APN 开停响应状态无效,办理结果未确认:" + statusText,
|
|
ResData: model.SimCardOperateChangeApnItem{Uncertain: true},
|
|
})
|
|
continue
|
|
}
|
|
if status != 0 {
|
|
items = append(items, tibiotBusinessError[model.SimCardOperateChangeApnItem](simKey, resp))
|
|
continue
|
|
}
|
|
items = append(items, &model.SimBaseRes[model.SimCardOperateChangeApnItem]{SimKey: simKey, Code: 0, Message: resp.Message, ResData: model.SimCardOperateChangeApnItem{OrderNum: ""}})
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (s *sSimTibiot) QuerySimCardStatusBatch(ctx context.Context, input *model.SimCardStatusInput) (model.SimCardStatusOutput, error) {
|
|
// Tibiot's batch endpoint returns paged card records. Query pages until all
|
|
// requested identifiers are found, preserving the caller's input order.
|
|
if input == nil || len(input.SimKeys) == 0 {
|
|
return make(model.SimCardStatusOutput, 0), nil
|
|
}
|
|
requested := make(map[string]*model.SimKey, len(input.SimKeys)*2)
|
|
for _, key := range input.SimKeys {
|
|
if key == nil {
|
|
continue
|
|
}
|
|
if key.ICCID != "" {
|
|
requested["iccid:"+strings.ToUpper(strings.TrimSpace(key.ICCID))] = key
|
|
}
|
|
if key.MSISDN != "" {
|
|
requested["msisdn:"+strings.TrimSpace(key.MSISDN)] = key
|
|
}
|
|
if key.IMSI != "" {
|
|
requested["imsi:"+strings.ToUpper(strings.TrimSpace(key.IMSI))] = key
|
|
}
|
|
}
|
|
found := make(map[*model.SimKey]tibiotCardInfo, len(input.SimKeys))
|
|
for page := 1; page <= 100 && len(found) < len(input.SimKeys); page++ {
|
|
params := map[string]any{"cardType": 1, "pageNum": page, "pageSize": 100, "flowUnit": "MB"}
|
|
if len(input.SimKeys) == 1 {
|
|
if name, value := tibiotCardIdentifier(input.SimKeys[0]); name != "" {
|
|
params[name] = value
|
|
}
|
|
}
|
|
resp, err := s.post(ctx, "/api/v1/card/batchQueryCardInfo", params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !tibiotSuccess(resp) {
|
|
return nil, fmt.Errorf("Tibiot batch status failed: code=%d message=%s", tibiotCode(resp), resp.Message)
|
|
}
|
|
records, pageCount, err := decodeTibiotBatchCards(resp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, item := range records {
|
|
if key := requested["iccid:"+strings.ToUpper(strings.TrimSpace(item.ICCID))]; key != nil {
|
|
found[key] = item
|
|
}
|
|
if key := requested["msisdn:"+strings.TrimSpace(item.MSISDN)]; key != nil {
|
|
found[key] = item
|
|
}
|
|
if key := requested["imsi:"+strings.ToUpper(strings.TrimSpace(item.IMSI))]; key != nil {
|
|
found[key] = item
|
|
}
|
|
}
|
|
if pageCount > 0 && page >= pageCount {
|
|
break
|
|
}
|
|
if len(records) < 100 {
|
|
break
|
|
}
|
|
}
|
|
items := make(model.SimCardStatusOutput, 0, len(input.SimKeys))
|
|
for _, simKey := range input.SimKeys {
|
|
data, ok := found[simKey]
|
|
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: 0, ResData: model.SimCardStatusItem{CardStatus: convertTibiotCardStatus(ctx, gconv.Int(data.CardStatus)), LastChangeDate: data.LastActivateTime}})
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (s *sSimTibiot) QuerySimDataUsageMonthlyBatch(ctx context.Context, input *model.SimDataUsageMonthlyInput) (model.SimDataUsageMonthlyOutput, error) {
|
|
return s.QuerySimDataUsageMonthly(ctx, input)
|
|
}
|
|
|
|
func (s *sSimTibiot) QuerySimDataUsageDailyBatch(ctx context.Context, input *model.SimDataUsageDailyInput) (model.SimDataUsageDailyOutput, error) {
|
|
if input == nil || len(input.SimKeys) == 0 {
|
|
return make(model.SimDataUsageDailyOutput, 0), nil
|
|
}
|
|
queryDate, err := tibiotDate(input.ReqData.QueryDate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
params := map[string]any{"queryTime": queryDate, "flowUnit": "MB"}
|
|
useICCID := true
|
|
for _, key := range input.SimKeys {
|
|
if key == nil || strings.TrimSpace(key.ICCID) == "" {
|
|
useICCID = false
|
|
break
|
|
}
|
|
}
|
|
if useICCID {
|
|
values := make([]string, 0, len(input.SimKeys))
|
|
for _, key := range input.SimKeys {
|
|
values = append(values, strings.TrimSpace(key.ICCID))
|
|
}
|
|
params["iccids"] = strings.Join(values, ",")
|
|
} else {
|
|
values := make([]string, 0, len(input.SimKeys))
|
|
for _, key := range input.SimKeys {
|
|
if key == nil || strings.TrimSpace(key.MSISDN) == "" {
|
|
return nil, fmt.Errorf("batch daily query requires all ICCIDs or MSISDNs")
|
|
}
|
|
values = append(values, strings.TrimSpace(key.MSISDN))
|
|
}
|
|
params["msisdns"] = strings.Join(values, ",")
|
|
}
|
|
resp, err := s.post(ctx, "/api/v1/card/batchNewIotData", params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !tibiotSuccess(resp) {
|
|
return nil, fmt.Errorf("Tibiot batch daily failed: code=%d message=%s", tibiotCode(resp), resp.Message)
|
|
}
|
|
var data []tibiotDailyUsage
|
|
if err := decodeTibiotData(resp, &data); err != nil {
|
|
return nil, err
|
|
}
|
|
byID := make(map[string]tibiotDailyUsage, len(data)*2)
|
|
for _, item := range data {
|
|
if item.ICCID != "" {
|
|
byID["iccid:"+strings.ToUpper(strings.TrimSpace(item.ICCID))] = item
|
|
}
|
|
if item.IMSI != "" {
|
|
byID["imsi:"+strings.ToUpper(strings.TrimSpace(item.IMSI))] = item
|
|
}
|
|
if item.MSISDN != "" {
|
|
byID["msisdn:"+strings.TrimSpace(item.MSISDN)] = item
|
|
}
|
|
}
|
|
items := make(model.SimDataUsageDailyOutput, 0, len(input.SimKeys))
|
|
for _, key := range input.SimKeys {
|
|
var item tibiotDailyUsage
|
|
var ok bool
|
|
if key != nil {
|
|
for _, identifier := range []struct {
|
|
name string
|
|
value string
|
|
}{
|
|
{name: "iccid", value: key.ICCID},
|
|
{name: "msisdn", value: key.MSISDN},
|
|
{name: "imsi", value: key.IMSI},
|
|
} {
|
|
value := strings.TrimSpace(identifier.value)
|
|
if value == "" {
|
|
continue
|
|
}
|
|
lookup := identifier.name + ":" + value
|
|
if identifier.name != "msisdn" {
|
|
lookup = identifier.name + ":" + strings.ToUpper(value)
|
|
}
|
|
if item, ok = byID[lookup]; ok {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !ok {
|
|
items = append(items, &model.SimBaseRes[model.SimDataUsageDailyOutputItem]{SimKey: key, Code: consts.ApiStatusError, Message: "batch response missing card", ResData: model.SimDataUsageDailyOutputItem{QueryDate: input.ReqData.QueryDate}})
|
|
continue
|
|
}
|
|
if strings.TrimSpace(item.CardFlow) == "" {
|
|
items = append(items, &model.SimBaseRes[model.SimDataUsageDailyOutputItem]{SimKey: key, Code: consts.ApiStatusError, Message: "batch response card flow is empty", ResData: model.SimDataUsageDailyOutputItem{QueryDate: input.ReqData.QueryDate}})
|
|
continue
|
|
}
|
|
items = append(items, &model.SimBaseRes[model.SimDataUsageDailyOutputItem]{SimKey: key, Code: 0, Message: resp.Message, ResData: model.SimDataUsageDailyOutputItem{QueryDate: input.ReqData.QueryDate, DataAmount: parseTibiotFlow(item.CardFlow)}})
|
|
}
|
|
return items, nil
|
|
}
|