Files
iotlcm/upstream-chan/internal/logic/upstreamimpl/sim_yd.go
T

1872 lines
60 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package upstreamimpl
import (
"context"
"encoding/json"
"fmt"
"strings"
"sync/atomic"
"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"
sharedtoken "git.5iot.com/iotlcm-public/iotlcm-upstream-chan/internal/token"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
const (
gUrlBase = "https://api.iot.10086.cn/v5/"
)
type ydDataUsageResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Result []struct {
DataAmount string `json:"dataAmount"`
ApnUseAmountList []struct {
ApnName string `json:"apnName"`
ApnUseAmount string `json:"apnUseAmount"`
PccCodeUseAmountList []any `json:"pccCodeUseAmountList"`
} `json:"apnUseAmountList"`
} `json:"result"`
}
func decodeYdDataUsageResponse(body string) (ydDataUsageResponse, error) {
var response ydDataUsageResponse
err := json.Unmarshal([]byte(body), &response)
return response, err
}
type sSimYD struct {
channelConfig *model.SimChannelConfigItemCache
seqId atomic.Int64
tokenProvider ydTokenProvider
apiConfig *oneLinkAPIConfig
}
func NewSimYD() *sSimYD {
return &sSimYD{}
}
func init() {
s := NewSimYD()
service.UpstreamAPI().RegisterTemp("sim_yd_v5", s)
service.RegisterSimYD(s)
}
func (s *sSimYD) NewTemp(ctx context.Context, channelConfig *model.SimChannelConfigItemCache) model.SimCardChannelI {
apiConfig := parseOneLinkTemplateAPI(ctx, channelConfig)
if apiConfig == nil {
apiConfig = &oneLinkAPIConfig{BaseURL: gUrlBase, Timeout: loadDefaultUpstreamHTTPTimeout(ctx)}
}
return &sSimYD{
channelConfig: channelConfig,
apiConfig: apiConfig,
}
}
// GetSharedToken exposes the shared token for channel types that support it.
// Shared-token channels carry a sharedTokenManagerProvider; other channel
// types return an error.
func (s *sSimYD) GetSharedToken(ctx context.Context) (sharedtoken.OneLinkToken, error) {
provider, ok := s.tokenProvider.(interface {
GetSharedToken(ctx context.Context) (sharedtoken.OneLinkToken, error)
})
if !ok {
return sharedtoken.OneLinkToken{}, fmt.Errorf("通道不支持共享Token")
}
return provider.GetSharedToken(ctx)
}
func (s *sSimYD) getUrl(ctx context.Context, serviceUrl string) string {
baseURL := gUrlBase
if s.apiConfig != nil && s.apiConfig.BaseURL != "" {
baseURL = s.apiConfig.BaseURL
}
if strings.HasSuffix(baseURL, "/") {
return baseURL + serviceUrl
}
return baseURL + "/" + serviceUrl
}
func (s *sSimYD) refreshToken(ctx context.Context) string {
chanKey := s.channelConfig.Channel.ChanKey
tokenPath := "ec/get/token"
if s.apiConfig != nil && s.apiConfig.TokenPath != "" {
tokenPath = strings.TrimPrefix(s.apiConfig.TokenPath, "/")
}
url := s.getUrl(ctx, tokenPath)
client := g.Client()
client.SetTimeout(s.httpTimeout())
// {"status":"0","message":"正确","result":[{"token":"50af5334a72a5a51ad8fe72d5c0e77a3e00a7a5588d301be143479310d32b609","ttl":3600}]}
type ydTokenResp struct {
Status string `json:"status"`
Message string `json:"message"`
Result []struct {
Token string `json:"token"`
Ttl int `json:"ttl"`
} `json:"result"`
}
now := gtime.Now()
oldToken := s.channelConfig.Channel.AccessToken
// TODO: 暂时不刷新,影响老系统,实际上需要刷新的
// FIX: 暂时不刷新,影响老系统,实际上需要刷新的
params := g.Map{
"appid": s.channelConfig.Channel.AppKey,
"password": s.channelConfig.Channel.AppSecret,
"transid": s.getTransId(ctx),
"refresh": "0", // 0: 不刷新, 1: 刷新
}
resp, err := client.Get(ctx, url, params)
if err != nil {
g.Log().Errorf(ctx, "refreshToken get error %+v, chanKey %s", err, chanKey)
return ""
}
body := resp.ReadAllString()
var ydResp ydTokenResp
err = json.Unmarshal([]byte(body), &ydResp)
if err != nil {
g.Log().Errorf(ctx, "refreshToken unmarshal error %+v, chanKey %s", err, chanKey)
return ""
}
if ydResp.Status != "0" {
g.Log().Warningf(ctx, "refreshToken upstream error status=%s message=%s chanKey=%s", ydResp.Status, ydResp.Message, chanKey)
return ""
}
token := ""
ttl := 0
if len(ydResp.Result) > 0 {
token = ydResp.Result[0].Token
ttl = ydResp.Result[0].Ttl
}
if token == "" {
g.Log().Warningf(ctx, "refreshToken empty token in result chanKey=%s", chanKey)
return ""
}
now2 := gtime.New(now)
accessTokenExpire := now2.Add(time.Duration(ttl) * time.Second)
err = service.ChannelUpdate().UpdateAccessToken(ctx, &model.UpdateAccessTokenInput{
ChanKey: s.channelConfig.Channel.ChanKey,
AccessToken: token,
AccessTokenCreated: now,
AccessTokenExpire: accessTokenExpire,
RefreshToken: "",
RefreshTokenExpire: nil,
})
if err != nil {
g.Log().Errorf(ctx, "refreshToken update access token error %+v, chanKey %s, ", err, chanKey)
return ""
}
s.channelConfig.Channel.AccessToken = token
s.channelConfig.Channel.AccessTokenExpire = accessTokenExpire
updateYdTokenState(chanKey, token, now.Time, accessTokenExpire.Time)
g.Log().Infof(ctx, "refreshToken success, tokenChanged %v, ttl %d, expire %s, chanKey %s", oldToken != token, ttl, accessTokenExpire.Format("2006-01-02 15:04:05"), chanKey)
return token
}
func (s *sSimYD) getToken(ctx context.Context) string {
if s.tokenProvider != nil {
token, err := s.tokenProvider.GetToken(ctx)
if err != nil {
g.Log().Errorf(ctx, "shared token get error: %v", err)
return ""
}
return token
}
chanKey := s.channelConfig.Channel.ChanKey
policy := loadYdTokenRefreshPolicy(ctx)
return withYdTokenGuard(chanKey, func() string {
now := time.Now()
seedYdTokenState(s.channelConfig.Channel)
state := ydTokenStateFor(chanKey)
state.mu.RLock()
current, expiresAt := state.token, state.expiresAt
state.mu.RUnlock()
usable := current != "" && !expiresAt.IsZero() && now.Before(expiresAt)
if policy.mode == ydTokenRefreshModeProactive && usable {
usable = now.Before(expiresAt.Add(-time.Duration(policy.skewSecond) * time.Second))
}
if usable {
s.channelConfig.Channel.AccessToken = current
s.channelConfig.Channel.AccessTokenExpire = gtime.New(expiresAt)
return current
}
g.Log().Debugf(ctx, "getToken refresh needed, mode %s, chanKey %s", policy.mode, chanKey)
return s.refreshToken(ctx)
})
}
func (s *sSimYD) recoverToken(ctx context.Context, rejectedToken string) (string, error) {
if s.tokenProvider != nil {
token, err := s.tokenProvider.RecoverToken(ctx, rejectedToken)
if err != nil {
return token, err
}
if token == "" {
return "", fmt.Errorf("shared token recovery returned an empty token")
}
if token == rejectedToken {
return token, errYdTokenUnchanged
}
return token, nil
}
return recoverYdToken(ctx, s.channelConfig.Channel.ChanKey, s.channelConfig.Channel, rejectedToken, s.refreshToken)
}
func (s *sSimYD) requestYd(ctx context.Context, operation, url string, params g.Map) (*ydHTTPResponse, error) {
client := g.Client()
client.SetTimeout(s.httpTimeout())
return doYdRequest(ctx, operation, s.channelConfig.Channel.ChanKey, s.getToken, s.recoverToken, params, func(requestParams g.Map) (string, error) {
if t, _ := requestParams["token"].(string); strings.TrimSpace(t) == "" {
// Sending an empty token yields OneLink 11000 "请求参数不正确",
// which hides the real cause (token acquisition failure).
return "", gerror.New("token is empty, skip upstream request")
}
if s.tokenProvider != nil {
// transid is protocol-owned and must be regenerated for a replay.
requestParams["transid"] = s.getTransId(ctx)
if provider, ok := s.tokenProvider.(interface {
GetDeviceToken(context.Context) (string, error)
}); ok {
deviceToken, deviceErr := provider.GetDeviceToken(ctx)
if deviceErr != nil {
return "", fmt.Errorf("shared device token get error: %w", deviceErr)
}
if strings.TrimSpace(deviceToken) != "" {
requestParams["token_device"] = deviceToken
}
}
}
resp, err := client.Get(ctx, url, requestParams)
if err != nil {
return "", err
}
return resp.ReadAllString(), nil
})
}
func (s *sSimYD) getTransId(ctx context.Context) string {
prefix := s.channelConfig.Channel.AppKey
if s.apiConfig != nil && s.apiConfig.transIDPrefix != "" {
prefix = s.apiConfig.transIDPrefix
}
return prefix + time.Now().Format("20060102150405") + fmt.Sprintf("%08d", s.seqId.Add(1))
}
func (s *sSimYD) httpTimeout() time.Duration {
if s != nil && s.apiConfig != nil && s.apiConfig.Timeout > 0 {
return s.apiConfig.Timeout
}
return defaultUpstreamHTTPTimeout
}
// 单卡基本信息查询
// http://api.iot.10086.cn/v5/ec/query/sim-basic-info?transid=REPLACE_WITH_VALUE&token=REPLACE_WITH_VALUE&iccid=89860000000000000004
func (s *sSimYD) QuerySimBasicInfo(ctx context.Context, input *model.SimBasicInfoInput) (model.SimBasicInfoOutput, error) {
// {
// "status": "0",
// "message": "正确",
// "result": [
// {
// "msisdn": "1440000000002",
// "imsi": "460000000000002",
// "iccid": "89860000000000000004",
// "activeDate": "2023-05-11 15:07:06", // 激活日期
// "openDate": "2023-05-11 14:53:24", // 开卡日期
// "remark": ""
// }
// ]
// }
type ydResp struct {
Status string `json:"status"`
Message string `json:"message"`
Result []struct {
Msisdn string `json:"msisdn"`
Imsi string `json:"imsi"`
Iccid string `json:"iccid"`
ActiveDate string `json:"activeDate"`
OpenDate string `json:"openDate"`
Remark string `json:"remark"`
} `json:"result"`
}
url := s.getUrl(ctx, "ec/query/sim-basic-info")
items := make(model.SimBasicInfoOutput, 0)
for _, simKey := range input.SimKeys {
params := g.Map{
"transid": s.getTransId(ctx),
"iccid": simKey.ICCID,
}
resp, err := s.requestYd(ctx, "QuerySimBasicInfo", url, params)
if err != nil {
g.Log().Errorf(ctx, "QuerySimBasicInfo get error %+v,url %s, params %+v, ", err, url, params)
items = append(items, &model.SimBaseRes[model.SimBasicInfoItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
ResData: model.SimBasicInfoItem{
ActiveDate: "",
OpenDate: "",
Remark: "",
},
})
continue
}
body := resp.ReadAllString()
var ydResp ydResp
err = json.Unmarshal([]byte(body), &ydResp)
if err != nil {
g.Log().Errorf(ctx, "QuerySimBasicInfo unmarshal error %+v,url %s, params %+v, body %s, ", err, url, params, body)
items = append(items, &model.SimBaseRes[model.SimBasicInfoItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
ResData: model.SimBasicInfoItem{
ActiveDate: "",
OpenDate: "",
Remark: "",
},
})
continue
}
if ydResp.Status != "0" {
g.Log().Warningf(ctx, "QuerySimBasicInfo error %s,url %s, params %+v, body %s, ", ydResp.Message, url, params, body)
items = append(items, &model.SimBaseRes[model.SimBasicInfoItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ResData: model.SimBasicInfoItem{
ActiveDate: "",
OpenDate: "",
Remark: "",
},
})
continue
}
if len(ydResp.Result) == 0 {
items = append(items, &model.SimBaseRes[model.SimBasicInfoItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: firstNonEmpty(ydResp.Message, "上游未返回SIM卡信息"),
ResData: model.SimBasicInfoItem{
ActiveDate: "",
OpenDate: "",
Remark: "",
},
})
continue
}
hasIdentity := false
for _, session := range ydResp.Result {
if ydBasicInfoHasIdentity(session.Iccid, session.Msisdn, session.Imsi) {
hasIdentity = true
break
}
}
if !hasIdentity {
message := strings.TrimSpace(ydResp.Message)
if message == "" || message == "正常" || message == "正确" {
message = "上游未返回三码信息"
}
items = append(items, &model.SimBaseRes[model.SimBasicInfoItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: message,
ResData: model.SimBasicInfoItem{
ActiveDate: "",
OpenDate: "",
Remark: "",
},
})
continue
}
for _, session := range ydResp.Result {
items = append(items, &model.SimBaseRes[model.SimBasicInfoItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ResData: model.SimBasicInfoItem{
ICCID: session.Iccid,
IMSI: session.Imsi,
MSISDN: session.Msisdn,
ActiveDate: session.ActiveDate,
OpenDate: session.OpenDate,
Remark: session.Remark,
},
})
}
}
return items, nil
}
// 5.7.2 CMIOT_API25M01-单卡在线信息实时查询
func (s *sSimYD) QuerySimSession(ctx context.Context, input *model.SimSessionInput) (model.SimSessionOutput, error) {
// {
// "status": "0",
// "message": "正确",
// "result": [
// {
// "simSessionList":[
// {
// "apnId":"CMIOT",
// "status":"01",
// "ip":"127.0.0.1",
// "ipv6Prefix":"",
// "ipv6":"",
// "createDate":"2018-08-23 17:17:27",
// "rat":"6"
// }
// ]
// }
// ]
// }
type ydResp struct {
Status string `json:"status"`
Message string `json:"message"`
Result []struct {
InfoList []struct {
Apn string `json:"apn"`
SessionStatus string `json:"sessionStatus"`
Ipv4 string `json:"ipv4"`
Ipv6Prefix string `json:"ipv6Prefix"`
Ipv6 string `json:"ipv6"`
CreateTime string `json:"createTime"`
AccessType string `json:"accessType"`
} `json:"infoList"`
SimSessionList []struct {
ApnId string `json:"apnId"`
Status string `json:"status"`
Ip string `json:"ip"`
Ipv6Prefix string `json:"ipv6Prefix"`
Ipv6 string `json:"ipv6"`
CreateDate string `json:"createDate"`
Rat string `json:"rat"`
} `json:"simSessionList"`
} `json:"result"`
}
url := s.getUrl(ctx, "ec/query/sim-apn-session-info")
items := make(model.SimSessionOutput, 0)
for _, simKey := range input.SimKeys {
params := g.Map{
"transid": s.getTransId(ctx),
}
paramName, paramValue := pickSimQueryParam(simKey)
if paramName == "" {
items = append(items, &model.SimBaseRes[model.SimSessionItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: "missing iccid/msisdn/imsi",
ResData: model.SimSessionItem{
Status: "-2",
},
})
continue
}
params[paramName] = paramValue
resp, err := s.requestYd(ctx, "QuerySimSession", url, params)
if err != nil {
g.Log().Errorf(ctx, "QuerySimSession get error %+v,url %s, params %+v, ", err, url, params)
items = append(items, &model.SimBaseRes[model.SimSessionItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
ResData: model.SimSessionItem{
Status: "-2",
},
})
continue
}
body := resp.ReadAllString()
var ydResp ydResp
err = json.Unmarshal([]byte(body), &ydResp)
if err != nil {
g.Log().Errorf(ctx, "QuerySimSession unmarshal error %+v,url %s, params %+v, body %s, ", err, url, params, body)
items = append(items, &model.SimBaseRes[model.SimSessionItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
ResData: model.SimSessionItem{
Status: "-2",
},
})
continue
}
if ydResp.Status != "0" {
g.Log().Warningf(ctx, "QuerySimSession error %s,url %s, params %+v, body %s, ", ydResp.Message, url, params, body)
items = append(items, &model.SimBaseRes[model.SimSessionItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ResData: model.SimSessionItem{
Status: "-2",
},
})
continue
}
if len(ydResp.Result) == 0 || (len(ydResp.Result[0].InfoList) == 0 && len(ydResp.Result[0].SimSessionList) == 0) {
items = append(items, &model.SimBaseRes[model.SimSessionItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: ydResp.Message,
ResData: model.SimSessionItem{
Status: "-2",
},
})
continue
}
for _, session := range ydResp.Result[0].InfoList {
items = append(items, &model.SimBaseRes[model.SimSessionItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ResData: model.SimSessionItem{
ApnId: session.Apn,
Rat: session.AccessType,
Ipv4: session.Ipv4,
Ipv6: session.Ipv6,
Ipv6Prefix: session.Ipv6Prefix,
Status: convertYdSessionStatus(ctx, session.SessionStatus),
CreateTime: session.CreateTime,
},
})
}
for _, session := range ydResp.Result[0].SimSessionList {
items = append(items, &model.SimBaseRes[model.SimSessionItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ResData: model.SimSessionItem{
ApnId: session.ApnId,
Rat: session.Rat,
Ipv4: session.Ip,
Ipv6: session.Ipv6,
Ipv6Prefix: session.Ipv6Prefix,
Status: convertYdSessionStatus(ctx, session.Status),
CreateTime: session.CreateDate,
},
})
}
}
return items, nil
}
// 5.1.6 单卡状态查询
// https://api.iot.10086.cn/v5/ec/query/sim-status?transid=REPLACE_WITH_VALUE&token=REPLACE_WITH_VALUE&iccid=89860000000000000004
func (s *sSimYD) QuerySimCardStatus(ctx context.Context, input *model.SimCardStatusInput) (model.SimCardStatusOutput, error) {
url := s.getUrl(ctx, "ec/query/sim-status")
// {
// "status":"0",
// "message":"正确",
// "result":[
// {
// "cardStatus":"1",
// "lastChangeDate":"2018-04-21 08:00:00"
// }
// ]
// }
// {"status":"0","message":"正确","result":[{"cardStatus":"2","lastChangeDate":"2023-05-11 15:07:08"}]}
type ydResp struct {
Status string `json:"status"`
Message string `json:"message"`
Result []struct {
CardStatus string `json:"cardStatus"`
LastChangeDate string `json:"lastChangeDate"`
} `json:"result"`
}
items := make(model.SimCardStatusOutput, 0)
for _, simKey := range input.SimKeys {
params := g.Map{
"transid": s.getTransId(ctx),
"iccid": simKey.ICCID,
}
resp, err := s.requestYd(ctx, "QuerySimCardStatus", url, params)
if err != nil {
g.Log().Errorf(ctx, "QuerySimCardStatus get error %+v,url %s, params %+v, ", err, url, params)
items = append(items, &model.SimBaseRes[model.SimCardStatusItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
})
continue
}
body := resp.ReadAllString()
var ydResp ydResp
err = json.Unmarshal([]byte(body), &ydResp)
if err != nil {
g.Log().Errorf(ctx, "QuerySimCardStatus unmarshal error %+v,url %s, params %+v, body %s, ", err, url, params, body)
items = append(items, &model.SimBaseRes[model.SimCardStatusItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
})
continue
}
if ydResp.Status != "0" {
g.Log().Warningf(ctx, "QuerySimCardStatus error %s,url %s, params %+v, body %s, ", ydResp.Message, url, params, body)
items = append(items, &model.SimBaseRes[model.SimCardStatusItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ResData: model.SimCardStatusItem{
CardStatus: 0,
LastChangeDate: "",
},
})
continue
}
if len(ydResp.Result) == 0 {
items = append(items, &model.SimBaseRes[model.SimCardStatusItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: ydResp.Message,
ResData: model.SimCardStatusItem{
CardStatus: 0,
LastChangeDate: "",
},
})
continue
}
if len(ydResp.Result) > 0 {
result := ydResp.Result[0]
items = append(items, &model.SimBaseRes[model.SimCardStatusItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ResData: model.SimCardStatusItem{
CardStatus: gconv.Int(result.CardStatus),
LastChangeDate: result.LastChangeDate,
},
})
} else {
items = append(items, &model.SimBaseRes[model.SimCardStatusItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: ydResp.Message,
ResData: model.SimCardStatusItem{
CardStatus: 0,
LastChangeDate: "",
},
})
}
}
return items, nil
}
// 物联网卡IMEI查询
// https://api.iot.10086.cn/v5/ec/query/sim-imei?transid=REPLACE_WITH_VALUE&token=REPLACE_WITH_VALUE&iccid=89860000000000000005
func (s *sSimYD) QuerySimStopReason(ctx context.Context, input *model.SimStopReasonInput) (model.SimStopReasonOutput, error) {
url := s.getUrl(ctx, "ec/query/sim-stop-reason")
type ydResp struct {
Status string `json:"status"`
Message string `json:"message"`
Result []struct {
Msisdn string `json:"msisdn"`
PlatformType string `json:"platformType"`
StopReason string `json:"stopReason"`
} `json:"result"`
}
items := make(model.SimStopReasonOutput, 0, len(input.SimKeys))
for _, simKey := range input.SimKeys {
paramKey, paramValue := pickSimQueryParam(simKey)
if paramKey == "" {
items = append(items, &model.SimBaseRes[model.SimStopReasonItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: "missing iccid/msisdn/imsi",
ResData: model.SimStopReasonItem{},
})
continue
}
params := g.Map{
"transid": s.getTransId(ctx),
paramKey: paramValue,
}
resp, err := s.requestYd(ctx, "QuerySimStopReason", url, params)
if err != nil {
g.Log().Errorf(ctx, "QuerySimStopReason get error %+v,url %s, params %+v, ", err, url, params)
items = append(items, &model.SimBaseRes[model.SimStopReasonItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
ResData: model.SimStopReasonItem{},
})
continue
}
body := resp.ReadAllString()
var ydResp ydResp
if err = json.Unmarshal([]byte(body), &ydResp); err != nil {
g.Log().Errorf(ctx, "QuerySimStopReason unmarshal error %+v,url %s, params %+v, body %s, ", err, url, params, body)
items = append(items, &model.SimBaseRes[model.SimStopReasonItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
ResData: model.SimStopReasonItem{},
})
continue
}
if ydResp.Status != "0" {
g.Log().Warningf(ctx, "QuerySimStopReason error %s,url %s, params %+v, body %s, ", ydResp.Message, url, params, body)
items = append(items, &model.SimBaseRes[model.SimStopReasonItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ResData: model.SimStopReasonItem{},
})
continue
}
stopReason := "未停机"
platformType := ""
if len(ydResp.Result) > 0 {
stopReason = mapYdStopReasonText(ydResp.Result[0].StopReason)
if stopReason == "" {
stopReason = "未停机"
}
platformType = strings.TrimSpace(ydResp.Result[0].PlatformType)
}
items = append(items, &model.SimBaseRes[model.SimStopReasonItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ResData: model.SimStopReasonItem{
StopReason: stopReason,
PlatformType: platformType,
},
})
}
return items, nil
}
func (s *sSimYD) QuerySimFrozenState(ctx context.Context, input *model.SimFrozenStateInput) (model.SimFrozenStateOutput, error) {
url := s.getUrl(ctx, "ec/query/sim-frozen-state-info")
type ydResp struct {
Status string `json:"status"`
Message string `json:"message"`
Result []struct {
Msisdn string `json:"msisdn"`
FrozenStatus string `json:"frozenStatus"`
FrozenReason string `json:"frozenReason"`
OperTime string `json:"operTime"`
} `json:"result"`
}
items := make(model.SimFrozenStateOutput, 0, len(input.SimKeys))
for _, simKey := range input.SimKeys {
paramKey, paramValue := pickSimQueryParam(simKey)
if paramKey == "" {
items = append(items, &model.SimBaseRes[model.SimFrozenStateItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: "missing iccid/msisdn/imsi",
ResData: model.SimFrozenStateItem{},
})
continue
}
params := g.Map{
"transid": s.getTransId(ctx),
paramKey: paramValue,
}
resp, err := s.requestYd(ctx, "QuerySimFrozenState", url, params)
if err != nil {
g.Log().Errorf(ctx, "QuerySimFrozenState get error %+v,url %s, params %+v, ", err, url, params)
items = append(items, &model.SimBaseRes[model.SimFrozenStateItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
ResData: model.SimFrozenStateItem{},
})
continue
}
body := resp.ReadAllString()
var ydResp ydResp
if err = json.Unmarshal([]byte(body), &ydResp); err != nil {
g.Log().Errorf(ctx, "QuerySimFrozenState unmarshal error %+v,url %s, params %+v, body %s, ", err, url, params, body)
items = append(items, &model.SimBaseRes[model.SimFrozenStateItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
ResData: model.SimFrozenStateItem{},
})
continue
}
if ydResp.Status != "0" {
g.Log().Warningf(ctx, "QuerySimFrozenState error %s,url %s, params %+v, body %s, ", ydResp.Message, url, params, body)
items = append(items, &model.SimBaseRes[model.SimFrozenStateItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ResData: model.SimFrozenStateItem{},
})
continue
}
frozenStatus := "未冻结"
frozenReason := ""
operTime := ""
if len(ydResp.Result) > 0 {
frozenStatus = mapYdFrozenStatusText(ydResp.Result[0].FrozenStatus)
if frozenStatus == "" {
frozenStatus = "未冻结"
}
frozenReason = strings.TrimSpace(ydResp.Result[0].FrozenReason)
operTime = strings.TrimSpace(ydResp.Result[0].OperTime)
}
items = append(items, &model.SimBaseRes[model.SimFrozenStateItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ResData: model.SimFrozenStateItem{
FrozenStatus: frozenStatus,
FrozenReason: frozenReason,
OperTime: operTime,
},
})
}
return items, nil
}
func (s *sSimYD) QuerySimImei(ctx context.Context, input *model.SimImeiInput) (model.SimImeiOutput, error) {
url := s.getUrl(ctx, "ec/query/sim-terminal-device-info")
// {"status":"0","message":"正确","result":[{"imei":"865466070817587"}]}
type ydResp struct {
Status string `json:"status"`
Message string `json:"message"`
Result []struct {
Imei string `json:"imei"`
TerminalDeviceImei string `json:"terminalDeviceImei"`
} `json:"result"`
}
items := make(model.SimImeiOutput, 0)
for _, simKey := range input.SimKeys {
key, value := pickSimQueryParam(simKey)
if key == "" {
items = append(items, &model.SimBaseRes[model.SimImeiItem]{SimKey: simKey, Code: consts.ApiStatusError, Message: "missing iccid, msisdn or imsi"})
continue
}
params := g.Map{
"transid": s.getTransId(ctx),
key: value,
}
resp, err := s.requestYd(ctx, "QuerySimImei", url, params)
if err != nil {
g.Log().Errorf(ctx, "QuerySimImei get error %+v,url %s, params %+v, ", err, url, params)
items = append(items, &model.SimBaseRes[model.SimImeiItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
})
continue
}
body := resp.ReadAllString()
var ydResp ydResp
err = json.Unmarshal([]byte(body), &ydResp)
if err != nil {
g.Log().Errorf(ctx, "QuerySimImei unmarshal error %+v,url %s, params %+v, body %s, ", err, url, params, body)
items = append(items, &model.SimBaseRes[model.SimImeiItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
})
continue
}
if ydResp.Status != "0" {
g.Log().Warningf(ctx, "QuerySimImei error %s,url %s, params %+v, body %s, ", ydResp.Message, url, params, body)
items = append(items, &model.SimBaseRes[model.SimImeiItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
})
continue
}
if len(ydResp.Result) == 0 {
items = append(items, &model.SimBaseRes[model.SimImeiItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
})
continue
}
if len(ydResp.Result) == 1 {
result := ydResp.Result[0]
items = append(items, &model.SimBaseRes[model.SimImeiItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ResData: model.SimImeiItem{
Imei: firstNonEmpty(result.TerminalDeviceImei, result.Imei),
},
})
} else {
items = append(items, &model.SimBaseRes[model.SimImeiItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
})
}
}
return items, nil
}
// 单卡本月流量累计使用量查询
// https://api.iot.10086.cn/v5/ec/query/sim-data-usage-inpool?transid=REPLACE_WITH_VALUE&token=REPLACE_WITH_VALUE&msisdn=1440000000003
func (s *sSimYD) QuerySimDataUsageInPool(ctx context.Context, input *model.SimDataUsageInPoolInput) (model.SimDataUsageInPoolOutput, error) {
url := s.getUrl(ctx, "ec/query/sim-data-usage-inpool")
// {"status":"0","message":"正确","result":[{"apnList":[{"apnName":"CMIOT","apnUseAmount":"5994479616"}]}]}
type ydResp struct {
Status string `json:"status"`
Message string `json:"message"`
Result []struct {
ApnList []struct {
ApnName string `json:"apnName"`
ApnUseAmount string `json:"apnUseAmount"`
} `json:"apnList"`
} `json:"result"`
}
items := make(model.SimDataUsageInPoolOutput, 0)
for _, simKey := range input.SimKeys {
params := g.Map{
"transid": s.getTransId(ctx),
"iccid": simKey.ICCID,
}
resp, err := s.requestYd(ctx, "QuerySimDataUsageInPool", url, params)
if err != nil {
g.Log().Errorf(ctx, "QuerySimDataUsageInPool get error %+v,url %s, params %+v, ", err, url, params)
items = append(items, &model.SimBaseRes[model.SimDataUsageInPoolItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
})
continue
}
body := resp.ReadAllString()
var ydResp ydResp
err = json.Unmarshal([]byte(body), &ydResp)
if err != nil {
g.Log().Errorf(ctx, "QuerySimDataUsageInPool unmarshal error %+v,url %s, params %+v, body %s, ", err, url, params, body)
items = append(items, &model.SimBaseRes[model.SimDataUsageInPoolItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
})
continue
}
if ydResp.Status != "0" {
g.Log().Warningf(ctx, "QuerySimDataUsageInPool error %s,url %s, params %+v, body %s, ", ydResp.Message, url, params, body)
items = append(items, &model.SimBaseRes[model.SimDataUsageInPoolItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
})
continue
}
if len(ydResp.Result) == 0 {
items = append(items, &model.SimBaseRes[model.SimDataUsageInPoolItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ResData: model.SimDataUsageInPoolItem{
DataUsage: 0.0,
},
})
continue
}
if len(ydResp.Result) == 1 {
result := ydResp.Result[0]
dataUsage := 0.0
if len(result.ApnList) != 0 {
for _, apn := range result.ApnList {
dataUsage += gconv.Float64(apn.ApnUseAmount)
}
}
items = append(items, &model.SimBaseRes[model.SimDataUsageInPoolItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ResData: model.SimDataUsageInPoolItem{
DataUsage: dataUsage / 1024,
},
})
} else {
items = append(items, &model.SimBaseRes[model.SimDataUsageInPoolItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ResData: model.SimDataUsageInPoolItem{
DataUsage: 0.0,
},
})
}
}
return items, nil
}
// 单卡本月流量累计使用量查询
// https://api.iot.10086.cn/v5/ec/query/sim-data-usage?transid=REPLACE_WITH_VALUE&token=REPLACE_WITH_VALUE&msisdn=1440000000003
func (s *sSimYD) QuerySimDataUsage(ctx context.Context, input *model.SimDataUsageInput) (model.SimDataUsageOutput, error) {
url := s.getUrl(ctx, "ec/query/sim-data-usage")
// {"status":"0","message":"正确","result":[{"dataAmount":"710569.00","apnUseAmountList":[{"apnName":"CMIOT","apnUseAmount":"710569.00","pccCodeUseAmountList":[]}]}]}
items := make(model.SimDataUsageOutput, 0)
for _, simKey := range input.SimKeys {
params := g.Map{
"transid": s.getTransId(ctx),
"iccid": simKey.ICCID,
}
resp, fetchErr := s.requestYd(ctx, "QuerySimDataUsage", url, params)
if fetchErr != nil {
g.Log().Errorf(ctx, "QuerySimDataUsage request failed, chanKey:%s, err:%+v", simKey.ChanKey, fetchErr)
items = append(items, &model.SimBaseRes[model.SimDataUsageItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: fetchErr.Error(),
})
continue
}
body := resp.ReadAllString()
ydResp, decodeErr := decodeYdDataUsageResponse(body)
if fetchErr = decodeErr; fetchErr != nil {
g.Log().Errorf(ctx, "QuerySimDataUsage unmarshal error, chanKey:%s, err:%+v", simKey.ChanKey, fetchErr)
items = append(items, &model.SimBaseRes[model.SimDataUsageItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: fetchErr.Error(),
})
continue
}
if ydResp.Status != "0" {
g.Log().Warningf(ctx, "QuerySimDataUsage upstream error, chanKey:%s, status:%s, message:%s", simKey.ChanKey, ydResp.Status, ydResp.Message)
items = append(items, &model.SimBaseRes[model.SimDataUsageItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ErrCode: ydResp.Status,
})
continue
}
if len(ydResp.Result) == 0 {
items = append(items, &model.SimBaseRes[model.SimDataUsageItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ResData: model.SimDataUsageItem{
DataUsage: 0.0,
},
})
continue
} else {
items = append(items, &model.SimBaseRes[model.SimDataUsageItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ResData: model.SimDataUsageItem{
DataUsage: gconv.Float64(ydResp.Result[0].DataAmount) / 1024,
},
})
}
}
return items, nil
}
// 物联卡单月 GPRS 流量使用量批量查询,单批最多 100 张卡
func (s *sSimYD) QuerySimDataUsageMonthly(ctx context.Context, input *model.SimDataUsageMonthlyInput) (model.SimDataUsageMonthlyOutput, error) {
url := s.getUrl(ctx, "ec/query/sim-data-usage-monthly/batch")
type YdResp struct {
Status string `json:"status"`
Message string `json:"message"`
Result []struct {
DeadLine string `json:"deadLine"`
DataAmountList []struct {
DataAmount string `json:"dataAmount"`
Iccid string `json:"iccid"`
Imsi string `json:"imsi"`
Msisdn string `json:"msisdn"`
} `json:"dataAmountList"`
} `json:"result"`
}
items := make(model.SimDataUsageMonthlyOutput, 0, len(input.SimKeys))
const batchSize = 100
for start := 0; start < len(input.SimKeys); start += batchSize {
end := start + batchSize
if end > len(input.SimKeys) {
end = len(input.SimKeys)
}
batch := input.SimKeys[start:end]
iccids := make([]string, 0, len(batch))
simKeyMap := make(map[string]*model.SimKey, len(batch)*3)
for _, simKey := range batch {
if simKey == nil {
iccids = append(iccids, "")
continue
}
iccids = append(iccids, strings.TrimSpace(simKey.ICCID))
if value := strings.TrimSpace(simKey.ICCID); value != "" {
simKeyMap["iccid:"+strings.ToUpper(value)] = simKey
}
if value := strings.TrimSpace(simKey.IMSI); value != "" {
simKeyMap["imsi:"+strings.ToUpper(value)] = simKey
}
if value := strings.TrimSpace(simKey.MSISDN); value != "" {
simKeyMap["msisdn:"+value] = simKey
}
}
params := g.Map{
"transid": s.getTransId(ctx),
"iccids": strings.Join(iccids, "_"),
"queryDate": input.ReqData.QueryDate,
}
resp, err := s.requestYd(ctx, "QuerySimDataUsageMonthly", url, params)
if err != nil {
g.Log().Errorf(ctx, "QuerySimDataUsageMonthly get error %+v,url %s, params %+v", err, url, params)
for _, simKey := range batch {
items = append(items, &model.SimBaseRes[model.SimDataUsageMonthlyOutputItem]{SimKey: simKey, Code: consts.ApiStatusError, Message: err.Error()})
}
continue
}
body := resp.ReadAllString()
var ydResp YdResp
if err = json.Unmarshal([]byte(body), &ydResp); err != nil {
g.Log().Errorf(ctx, "QuerySimDataUsageMonthly unmarshal error %+v,url %s, params %+v, body %s", err, url, params, body)
for _, simKey := range batch {
items = append(items, &model.SimBaseRes[model.SimDataUsageMonthlyOutputItem]{SimKey: simKey, Code: consts.ApiStatusError, Message: err.Error()})
}
continue
}
if ydResp.Status != "0" {
g.Log().Warningf(ctx, "QuerySimDataUsageMonthly error %s,url %s, params %+v, body %s", ydResp.Message, url, params, body)
for _, simKey := range batch {
items = append(items, &model.SimBaseRes[model.SimDataUsageMonthlyOutputItem]{SimKey: simKey, Code: gconv.Int(ydResp.Status), Message: ydResp.Message})
}
continue
}
resultMap := make(map[*model.SimKey]struct {
DeadLine string
DataAmount string
}, len(batch))
for _, result := range ydResp.Result {
for _, dataAmount := range result.DataAmountList {
var simKey *model.SimKey
if value := strings.TrimSpace(dataAmount.Iccid); value != "" {
simKey = simKeyMap["iccid:"+strings.ToUpper(value)]
}
if simKey == nil {
if value := strings.TrimSpace(dataAmount.Imsi); value != "" {
simKey = simKeyMap["imsi:"+strings.ToUpper(value)]
}
}
if simKey == nil {
if value := strings.TrimSpace(dataAmount.Msisdn); value != "" {
simKey = simKeyMap["msisdn:"+value]
}
}
if simKey == nil && len(batch) == 1 {
simKey = batch[0]
}
if simKey != nil {
resultMap[simKey] = struct {
DeadLine string
DataAmount string
}{DeadLine: result.DeadLine, DataAmount: dataAmount.DataAmount}
}
}
}
for _, simKey := range batch {
result, ok := resultMap[simKey]
if !ok {
items = append(items, &model.SimBaseRes[model.SimDataUsageMonthlyOutputItem]{SimKey: simKey, Code: gconv.Int(ydResp.Status), Message: "查询信息为空"})
continue
}
items = append(items, &model.SimBaseRes[model.SimDataUsageMonthlyOutputItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
ResData: model.SimDataUsageMonthlyOutputItem{
DeadLine: result.DeadLine,
DataAmount: gconv.Float64(strings.TrimSpace(result.DataAmount)) / 1024,
},
})
}
}
return items, nil
}
// 物联卡单日 GPRS 流量使用量批量查询,单批最多 100 张卡
// 上游返回 dataAmount 单位为 KB,对外统一转换为 MB
func (s *sSimYD) QuerySimDataUsageDaily(ctx context.Context, input *model.SimDataUsageDailyInput) (model.SimDataUsageDailyOutput, error) {
url := s.getUrl(ctx, "ec/query/sim-data-usage-daily/batch")
type YdResp struct {
Status string `json:"status"`
Message string `json:"message"`
Result []struct {
QueryDate string `json:"queryDate"`
DeadLine string `json:"deadLine"`
DataAmount string `json:"dataAmount"`
DataAmountList []struct {
QueryDate string `json:"queryDate"`
DeadLine string `json:"deadLine"`
Msisdn string `json:"msisdn"`
Iccid string `json:"iccid"`
Imsi string `json:"imsi"`
DataAmount string `json:"dataAmount"`
} `json:"dataAmountList"`
} `json:"result"`
}
items := make(model.SimDataUsageDailyOutput, 0, len(input.SimKeys))
const batchSize = 100
for start := 0; start < len(input.SimKeys); start += batchSize {
end := start + batchSize
if end > len(input.SimKeys) {
end = len(input.SimKeys)
}
batch := input.SimKeys[start:end]
iccids := make([]string, 0, len(batch))
simKeyMap := make(map[string]*model.SimKey, len(batch)*3)
for _, simKey := range batch {
if simKey == nil {
iccids = append(iccids, "")
continue
}
iccids = append(iccids, strings.TrimSpace(simKey.ICCID))
if value := strings.TrimSpace(simKey.ICCID); value != "" {
simKeyMap["iccid:"+strings.ToUpper(value)] = simKey
}
if value := strings.TrimSpace(simKey.IMSI); value != "" {
simKeyMap["imsi:"+strings.ToUpper(value)] = simKey
}
if value := strings.TrimSpace(simKey.MSISDN); value != "" {
simKeyMap["msisdn:"+value] = simKey
}
}
params := g.Map{
"transid": s.getTransId(ctx),
"iccids": strings.Join(iccids, "_"),
"queryDate": input.ReqData.QueryDate,
}
resp, err := s.requestYd(ctx, "QuerySimDataUsageDaily", url, params)
if err != nil {
g.Log().Errorf(ctx, "QuerySimDataUsageDaily get error %+v,url %s, params %+v", err, url, params)
for _, simKey := range batch {
items = append(items, &model.SimBaseRes[model.SimDataUsageDailyOutputItem]{SimKey: simKey, Code: consts.ApiStatusError, Message: err.Error()})
}
continue
}
body := resp.ReadAllString()
var ydResp YdResp
if err = json.Unmarshal([]byte(body), &ydResp); err != nil {
g.Log().Errorf(ctx, "QuerySimDataUsageDaily unmarshal error %+v,url %s, params %+v, body %s", err, url, params, body)
for _, simKey := range batch {
items = append(items, &model.SimBaseRes[model.SimDataUsageDailyOutputItem]{SimKey: simKey, Code: consts.ApiStatusError, Message: err.Error()})
}
continue
}
if ydResp.Status != "0" {
g.Log().Warningf(ctx, "QuerySimDataUsageDaily error %s,url %s, params %+v, body %s", ydResp.Message, url, params, body)
for _, simKey := range batch {
items = append(items, &model.SimBaseRes[model.SimDataUsageDailyOutputItem]{SimKey: simKey, Code: gconv.Int(ydResp.Status), Message: ydResp.Message, ResData: model.SimDataUsageDailyOutputItem{QueryDate: input.ReqData.QueryDate}})
}
continue
}
resultMap := make(map[*model.SimKey]struct {
QueryDate string
DeadLine string
DataAmount string
}, len(batch))
for _, result := range ydResp.Result {
if len(result.DataAmountList) == 0 {
if len(batch) == 1 {
resultMap[batch[0]] = struct {
QueryDate string
DeadLine string
DataAmount string
}{QueryDate: result.QueryDate, DeadLine: result.DeadLine, DataAmount: result.DataAmount}
}
continue
}
for _, dataAmount := range result.DataAmountList {
var simKey *model.SimKey
if value := strings.TrimSpace(dataAmount.Iccid); value != "" {
simKey = simKeyMap["iccid:"+strings.ToUpper(value)]
}
if simKey == nil {
if value := strings.TrimSpace(dataAmount.Imsi); value != "" {
simKey = simKeyMap["imsi:"+strings.ToUpper(value)]
}
}
if simKey == nil {
if value := strings.TrimSpace(dataAmount.Msisdn); value != "" {
simKey = simKeyMap["msisdn:"+value]
}
}
if simKey == nil && len(batch) == 1 {
simKey = batch[0]
}
if simKey != nil {
queryDate := dataAmount.QueryDate
if queryDate == "" {
queryDate = dataAmount.DeadLine
}
if queryDate == "" {
queryDate = result.QueryDate
}
if queryDate == "" {
queryDate = result.DeadLine
}
resultMap[simKey] = struct {
QueryDate string
DeadLine string
DataAmount string
}{QueryDate: queryDate, DeadLine: dataAmount.DeadLine, DataAmount: dataAmount.DataAmount}
}
}
}
for _, simKey := range batch {
result, ok := resultMap[simKey]
if !ok {
items = append(items, &model.SimBaseRes[model.SimDataUsageDailyOutputItem]{SimKey: simKey, Code: gconv.Int(ydResp.Status), Message: "查询信息为空", ResData: model.SimDataUsageDailyOutputItem{QueryDate: input.ReqData.QueryDate}})
continue
}
queryDate := result.QueryDate
if queryDate == "" {
queryDate = result.DeadLine
}
if queryDate == "" {
queryDate = input.ReqData.QueryDate
}
items = append(items, &model.SimBaseRes[model.SimDataUsageDailyOutputItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
ResData: model.SimDataUsageDailyOutputItem{
QueryDate: queryDate,
DataAmount: gconv.Float64(strings.TrimSpace(result.DataAmount)) / 1024,
},
})
}
}
return items, nil
}
// 单卡已开通APN信息查询
// https://api.iot.10086.cn/v5/ec/query/apn-info?transid=REPLACE_WITH_VALUE&token=REPLACE_WITH_VALUE&iccid=89860000000000000003
func (s *sSimYD) QuerySimApnInfo(ctx context.Context, input *model.SimApnInfoInput) (model.SimApnInfoOutput, error) {
url := s.getUrl(ctx, "ec/query/apn-info")
// {"status":"0","message":"正确","result":[{"apnList":[{"apnName":"CMIOT","status":"1"}]}]}
type YdResp struct {
Status string `json:"status"`
Message string `json:"message"`
Result []struct {
ApnList []struct {
ApnName string `json:"apnName"`
Status string `json:"status"`
} `json:"apnList"`
} `json:"result"`
}
items := make(model.SimApnInfoOutput, 0)
for _, simKey := range input.SimKeys {
params := g.Map{
"transid": s.getTransId(ctx),
"iccid": simKey.ICCID,
}
resp, err := s.requestYd(ctx, "QuerySimApnInfo", url, params)
if err != nil {
g.Log().Errorf(ctx, "QuerySimApnInfo get error %+v,url %s, params %+v, ", err, url, params)
items = append(items, &model.SimBaseRes[model.SimApnInfoItemList]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
ResData: model.SimApnInfoItemList{
ApnList: make([]model.SimApnInfoItem, 0),
},
})
continue
}
body := resp.ReadAllString()
var ydResp YdResp
err = json.Unmarshal([]byte(body), &ydResp)
if err != nil {
g.Log().Errorf(ctx, "QuerySimApnInfo unmarshal error %+v,url %s, params %+v, body %s, ", err, url, params, body)
items = append(items, &model.SimBaseRes[model.SimApnInfoItemList]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
ResData: model.SimApnInfoItemList{
ApnList: make([]model.SimApnInfoItem, 0),
},
})
continue
}
if ydResp.Status != "0" {
g.Log().Warningf(ctx, "QuerySimApnInfo error %s,url %s, params %+v, body %s, ", ydResp.Message, url, params, body)
items = append(items, &model.SimBaseRes[model.SimApnInfoItemList]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ResData: model.SimApnInfoItemList{
ApnList: make([]model.SimApnInfoItem, 0),
},
})
continue
}
if len(ydResp.Result) == 0 {
items = append(items, &model.SimBaseRes[model.SimApnInfoItemList]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: "查询信息为空",
ResData: model.SimApnInfoItemList{
ApnList: make([]model.SimApnInfoItem, 0),
},
})
continue
}
if len(ydResp.Result) == 1 {
result := ydResp.Result[0]
if len(result.ApnList) != 0 {
for _, apn := range result.ApnList {
items = append(items, &model.SimBaseRes[model.SimApnInfoItemList]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: "",
ResData: model.SimApnInfoItemList{
ApnList: []model.SimApnInfoItem{
{
ApnName: apn.ApnName,
Status: convertYdApnStatus(ctx, apn.Status),
},
},
},
})
}
} else {
g.Log().Errorf(ctx, "SimYD.QuerySimApnInfo, Error in returning information,ydResp: %+v, input: %+v, ", ydResp, input)
items = append(items, &model.SimBaseRes[model.SimApnInfoItemList]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: "查询信息为空",
ResData: model.SimApnInfoItemList{
ApnList: make([]model.SimApnInfoItem, 0),
},
})
continue
}
} else {
g.Log().Errorf(ctx, "SimYD.QuerySimApnInfo, Error in returning information,ydResp: %+v, input: %+v, ", ydResp, input)
items = append(items, &model.SimBaseRes[model.SimApnInfoItemList]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: "查询信息为空",
ResData: model.SimApnInfoItemList{
ApnList: make([]model.SimApnInfoItem, 0),
},
})
continue
}
}
return items, nil
}
// 单卡状态变更
// 平台 operType: 0=申请停机(已激活转已停机), 1=申请复机(已停机转已激活)
// https://api.iot.10086.cn/v5/ec/change/sim-status?transid=REPLACE_WITH_VALUE&token=REPLACE_WITH_VALUE&msisdn=1440000000003&operType=0
func (s *sSimYD) ChangeSimStatus(ctx context.Context, input *model.SimCardStatusChangeInput) (model.SimCardStatusChangeOutput, error) {
operType, ok := normalizeCardStatusChangeOperType(ctx, input.ReqData.OperType)
if !ok {
return newInvalidCardStatusChangeOutput(input.SimKeys), nil
}
url := s.getUrl(ctx, "ec/change/sim-status")
// {"status":"0","message":"正确","result":[{"iccid":"89860000000000000004","orderNum":"995000011515720993"}]}
type YdResp struct {
Status string `json:"status"`
Message string `json:"message"`
Result []struct {
Iccid string `json:"iccid"`
OrderNum string `json:"orderNum"`
} `json:"result"`
}
items := make(model.SimCardStatusChangeOutput, 0, len(input.SimKeys))
for _, simKey := range input.SimKeys {
params := g.Map{
"transid": s.getTransId(ctx),
"iccid": simKey.ICCID,
"operType": operType,
}
resp, err := s.requestYd(ctx, "ChangeSimStatus", url, params)
if err != nil {
g.Log().Errorf(ctx, "ChangeSimStatus get error %+v,url %s, params %+v, ", err, url, params)
items = append(items, &model.SimBaseRes[model.SimCardStatusChangeItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
ResData: model.SimCardStatusChangeItem{
OrderNum: "",
},
})
continue
}
body := resp.ReadAllString()
var ydResp YdResp
err = json.Unmarshal([]byte(body), &ydResp)
if err != nil {
g.Log().Errorf(ctx, "ChangeSimStatus unmarshal error %+v,url %s, params %+v, body %s, ", err, url, params, body)
items = append(items, &model.SimBaseRes[model.SimCardStatusChangeItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
ResData: model.SimCardStatusChangeItem{
OrderNum: "",
},
})
continue
}
if ydResp.Status != "0" {
g.Log().Warningf(ctx, "ChangeSimStatus error %s,url %s, params %+v, body %s, ", ydResp.Message, url, params, body)
items = append(items, &model.SimBaseRes[model.SimCardStatusChangeItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: ydResp.Message,
ResData: model.SimCardStatusChangeItem{
OrderNum: "",
},
})
continue
}
if len(ydResp.Result) == 0 {
items = append(items, &model.SimBaseRes[model.SimCardStatusChangeItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: "查询信息为空",
ResData: model.SimCardStatusChangeItem{
OrderNum: "",
},
})
continue
}
if len(ydResp.Result) == 1 {
result := ydResp.Result[0]
if len(result.OrderNum) != 0 {
items = append(items, &model.SimBaseRes[model.SimCardStatusChangeItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: "变更成功",
ResData: model.SimCardStatusChangeItem{
OrderNum: result.OrderNum,
},
})
}
} else {
items = append(items, &model.SimBaseRes[model.SimCardStatusChangeItem]{
SimKey: simKey,
Code: gconv.Int(ydResp.Status),
Message: "查询信息为空",
ResData: model.SimCardStatusChangeItem{
OrderNum: "",
},
})
continue
}
}
return items, nil
}
// 开通/停用移动数据(APN)功能(开通数据 dataStart / 停用数据 dataStop / 达量断网 dataCut)。
// 2026 接口改版:已从旧的 ec/operate/sim-apn-function 迁移到
// 7.2.1 单卡 sim-communication-service 与 7.2.2 批量 sim-communication-function/batch,
// 具体分流与编码转换见 sim_yd_apn_communication.go。
func (s *sSimYD) ChangeSimApnFunction(ctx context.Context, input *model.SimCardOperateChangeApnInput) (model.SimCardOperateChangeApnOutput, error) {
return changeSimApnFunctionYD(ctx, s, input)
}
// 单卡机卡绑定状态查询
// https://api.iot.10086.cn/v5/ec/query/card-bind-status
// 文档位置:中移物联网OneLink能力调用参考文档V25.11.1(CT)- 6.1.1 CMIOT_API23A04
// 请求参数:msisdn、testType
// 上游返回 result 中的 bindStatus/cardBindStatus 作为机卡绑定状态
func (s *sSimYD) QuerySimCardBindStatus(ctx context.Context, input *model.SimCardBindStatusInput) (model.SimCardBindStatusOutput, error) {
url := s.getUrl(ctx, "ec/query/card-bind-status")
type ydResp struct {
Status string `json:"status"`
Message string `json:"message"`
Result []map[string]any `json:"result"`
}
items := make(model.SimCardBindStatusOutput, 0, len(input.SimKeys))
for _, simKey := range input.SimKeys {
msisdn := strings.TrimSpace(simKey.MSISDN)
if msisdn == "" {
g.Log().Warningf(ctx, "QuerySimCardBindStatus missing msisdn,url %s, simKey %+v, reqData %+v", url, simKey, input.ReqData)
items = append(items, &model.SimBaseRes[model.SimCardBindStatusItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: "missing msisdn",
})
continue
}
testType := 1
if input.ReqData.TestType != nil {
testType = *input.ReqData.TestType
}
params := g.Map{
"transid": s.getTransId(ctx),
"msisdn": msisdn,
"testType": testType,
}
resp, err := s.requestYd(ctx, "QuerySimCardBindStatus", url, params)
if err != nil {
g.Log().Errorf(ctx, "QuerySimCardBindStatus get error %+v,url %s, params %+v", err, url, params)
items = append(items, &model.SimBaseRes[model.SimCardBindStatusItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
})
continue
}
body := resp.ReadAllString()
var respData ydResp
if err = json.Unmarshal([]byte(body), &respData); err != nil {
g.Log().Errorf(ctx, "QuerySimCardBindStatus unmarshal error %+v,url %s, params %+v, body %s", err, url, params, body)
items = append(items, &model.SimBaseRes[model.SimCardBindStatusItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
})
continue
}
if respData.Status != "0" {
g.Log().Warningf(ctx, "QuerySimCardBindStatus error %s,url %s, params %+v, body %s", respData.Message, url, params, body)
items = append(items, &model.SimBaseRes[model.SimCardBindStatusItem]{
SimKey: simKey,
Code: gconv.Int(respData.Status),
Message: respData.Message,
})
continue
}
bindStatus := ""
var errorCode string
var errorDes string
if len(respData.Result) > 0 {
bindStatus = pickYdCardBindStatus(respData.Result[0])
errorCode = pickYdCardBindStatusErrorCode(respData.Result[0])
errorDes = pickYdCardBindStatusErrorDes(respData.Result[0])
}
if bindStatus == "" {
g.Log().Warningf(ctx, "QuerySimCardBindStatus empty bind status,url %s, params %+v, body %s", url, params, body)
items = append(items, &model.SimBaseRes[model.SimCardBindStatusItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: "sim card bind status is empty",
})
continue
}
items = append(items, &model.SimBaseRes[model.SimCardBindStatusItem]{
SimKey: simKey,
Code: gconv.Int(respData.Status),
Message: respData.Message,
ResData: model.SimCardBindStatusItem{
BindStatus: bindStatus,
ErrorCode: errorCode,
ErrorDes: errorDes,
},
})
}
return items, nil
}
// 单卡设备状态查询
// https://api.iot.10086.cn/v5/ec/query/sim-operating-status?transid=REPLACE_WITH_VALUE&token=REPLACE_WITH_VALUE&iccid=xxx
// 上游返回 result 中的 operatingStatus/onOffStatus/status 作为设备状态
func (s *sSimYD) QuerySimDeviceStatus(ctx context.Context, input *model.SimDeviceStatusInput) (model.SimDeviceStatusOutput, error) {
url := s.getUrl(ctx, "ec/query/sim-operating-status")
type ydResp struct {
Status string `json:"status"`
Message string `json:"message"`
Result []map[string]any `json:"result"`
}
items := make(model.SimDeviceStatusOutput, 0, len(input.SimKeys))
for _, simKey := range input.SimKeys {
params := g.Map{
"transid": s.getTransId(ctx),
}
paramName, paramValue := pickSimQueryParam(simKey)
if paramName == "" {
items = append(items, &model.SimBaseRes[model.SimDeviceStatusItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: "missing imsi/iccid/msisdn",
})
continue
}
params[paramName] = paramValue
resp, err := s.requestYd(ctx, "QuerySimDeviceStatus", url, params)
if err != nil {
g.Log().Errorf(ctx, "QuerySimDeviceStatus get error %+v,url %s, params %+v", err, url, params)
items = append(items, &model.SimBaseRes[model.SimDeviceStatusItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
})
continue
}
body := resp.ReadAllString()
var respData ydResp
if err = json.Unmarshal([]byte(body), &respData); err != nil {
g.Log().Errorf(ctx, "QuerySimDeviceStatus unmarshal error %+v,url %s, params %+v, body %s", err, url, params, body)
items = append(items, &model.SimBaseRes[model.SimDeviceStatusItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
})
continue
}
if respData.Status != "0" {
items = append(items, &model.SimBaseRes[model.SimDeviceStatusItem]{
SimKey: simKey,
Code: gconv.Int(respData.Status),
Message: respData.Message,
})
continue
}
// sim-operating-status only reports the carrier terminal power state.
// It does not provide the unified device health state, so keep the two
// dimensions separate instead of treating shutdown as device failure.
deviceStatus := consts.CardSessionStatusUnknown
operatingStatus := consts.CardSessionStatusUnknown
if len(respData.Result) > 0 {
result := respData.Result[0]
rawStatus := firstNonEmpty(
gconv.String(result["operatingStatus"]),
gconv.String(result["onOffStatus"]),
gconv.String(result["status"]),
)
operatingStatus = mapYdOperatingStatus(rawStatus)
}
items = append(items, &model.SimBaseRes[model.SimDeviceStatusItem]{
SimKey: simKey,
Code: gconv.Int(respData.Status),
Message: respData.Message,
ResData: model.SimDeviceStatusItem{
DeviceStatus: deviceStatus,
OperatingStatus: operatingStatus,
},
})
}
return items, nil
}
// 单卡地域限制状态查询
// https://api.iot.10086.cn/v5/ec/query/region-limit-status
// 上游返回 result 中的 regionLimitStatus/status 作为地域限制状态
func (s *sSimYD) QuerySimRegionLimitStatus(ctx context.Context, input *model.SimRegionLimitStatusInput) (model.SimRegionLimitStatusOutput, error) {
url := s.getUrl(ctx, "ec/query/region-limit-status")
type ydResp struct {
Status string `json:"status"`
Message string `json:"message"`
Result []map[string]any `json:"result"`
}
items := make(model.SimRegionLimitStatusOutput, 0, len(input.SimKeys))
for _, simKey := range input.SimKeys {
params := g.Map{
"transid": s.getTransId(ctx),
}
switch {
case strings.TrimSpace(simKey.ICCID) != "":
params["iccid"] = simKey.ICCID
case strings.TrimSpace(simKey.IMSI) != "":
params["imsi"] = simKey.IMSI
case strings.TrimSpace(simKey.MSISDN) != "":
params["msisdn"] = simKey.MSISDN
default:
items = append(items, &model.SimBaseRes[model.SimRegionLimitStatusItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: "missing iccid/imsi/msisdn",
})
continue
}
resp, err := s.requestYd(ctx, "QuerySimRegionLimitStatus", url, params)
if err != nil {
g.Log().Errorf(ctx, "QuerySimRegionLimitStatus get error %+v,url %s, params %+v", err, url, params)
items = append(items, &model.SimBaseRes[model.SimRegionLimitStatusItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
})
continue
}
body := resp.ReadAllString()
var respData ydResp
if err = json.Unmarshal([]byte(body), &respData); err != nil {
g.Log().Errorf(ctx, "QuerySimRegionLimitStatus unmarshal error %+v,url %s, params %+v, body %s", err, url, params, body)
items = append(items, &model.SimBaseRes[model.SimRegionLimitStatusItem]{
SimKey: simKey,
Code: consts.ApiStatusError,
Message: err.Error(),
})
continue
}
if respData.Status != "0" {
items = append(items, &model.SimBaseRes[model.SimRegionLimitStatusItem]{
SimKey: simKey,
Code: gconv.Int(respData.Status),
Message: respData.Message,
})
continue
}
regionLimitStatus := ""
if len(respData.Result) > 0 {
result := respData.Result[0]
regionLimitStatus = gconv.String(result["regionLimitStatus"])
if regionLimitStatus == "" {
regionLimitStatus = gconv.String(result["status"])
}
}
items = append(items, &model.SimBaseRes[model.SimRegionLimitStatusItem]{
SimKey: simKey,
Code: gconv.Int(respData.Status),
Message: respData.Message,
ResData: model.SimRegionLimitStatusItem{
RegionLimitStatus: regionLimitStatus,
},
})
}
return items, nil
}