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

1789 lines
57 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"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
const (
gUrlBaseYDMock = "https://api.iot.10086.cn/v5/"
gChanTemplateYdMock = "sim_yd_mock_v5"
)
type sSimYDMock struct {
channelConfig *model.SimChannelConfigItemCache
seqId atomic.Int64
timeout time.Duration
}
func NewSimYDMock() *sSimYDMock {
return &sSimYDMock{}
}
func init() {
s := NewSimYDMock()
service.UpstreamAPI().RegisterTemp(gChanTemplateYdMock, s)
service.RegisterSimYDMock(s)
}
func (s *sSimYDMock) NewTemp(ctx context.Context, channelConfig *model.SimChannelConfigItemCache) model.SimCardChannelI {
return &sSimYDMock{
channelConfig: channelConfig,
timeout: resolveUpstreamHTTPTimeout(ctx, templateConfigJSON(channelConfig)),
}
}
func (s *sSimYDMock) getUrl(ctx context.Context, serviceUrl string) string {
baseUrl := ""
if s.channelConfig != nil && s.channelConfig.Template != nil {
var template struct {
API struct {
BaseURL string `json:"baseUrl"`
} `json:"api"`
}
if err := json.Unmarshal([]byte(s.channelConfig.Template.TempConfig), &template); err == nil {
baseUrl = strings.TrimSpace(template.API.BaseURL)
}
}
if baseUrl == "" {
g.Log().Warningf(ctx, "using legacy Mock OneLink URL configuration")
baseUrl = g.Cfg().MustGet(ctx, "dependencies."+gChanTemplateYdMock+".url", gUrlBaseYDMock).String()
}
if strings.HasSuffix(baseUrl, "/") {
return baseUrl + serviceUrl
}
return baseUrl + "/" + serviceUrl
}
func (s *sSimYDMock) refreshToken(ctx context.Context) string {
chanKey := s.channelConfig.Channel.ChanKey
url := s.getUrl(ctx, "ec/get/token")
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 any `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 = gconv.Int(ydResp.Result[0].Ttl)
}
if token == "" {
g.Log().Warningf(ctx, "refreshToken empty token in result chanKey=%s", chanKey)
return ""
}
if ttl <= 0 {
g.Log().Warningf(ctx, "refreshToken invalid ttl=%v, use default ttl=3600, chanKey=%s", ydResp.Result[0].Ttl, chanKey)
ttl = 3600
}
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 *sSimYDMock) getToken(ctx context.Context) string {
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 *sSimYDMock) recoverToken(ctx context.Context, rejectedToken string) (string, error) {
return recoverYdToken(ctx, s.channelConfig.Channel.ChanKey, s.channelConfig.Channel, rejectedToken, s.refreshToken)
}
func (s *sSimYDMock) 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) {
resp, err := client.Get(ctx, url, requestParams)
if err != nil {
return "", err
}
return resp.ReadAllString(), nil
})
}
func (s *sSimYDMock) httpTimeout() time.Duration {
if s != nil && s.timeout > 0 {
return s.timeout
}
return defaultUpstreamHTTPTimeout
}
func (s *sSimYDMock) getTransId(ctx context.Context) string {
appid := s.channelConfig.Channel.AppKey
return appid + time.Now().Format("20060102150405") + fmt.Sprintf("%08d", s.seqId.Add(1))
}
// 单卡基本信息查询
// http://api.iot.10086.cn/v5/ec/query/sim-basic-info?transid=REPLACE_WITH_VALUE&token=REPLACE_WITH_VALUE&iccid=89860000000000000004
func (s *sSimYDMock) 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 *sSimYDMock) 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,
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 *sSimYDMock) 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 *sSimYDMock) 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 *sSimYDMock) 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 *sSimYDMock) 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 *sSimYDMock) 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 *sSimYDMock) 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 *sSimYDMock) 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 *sSimYDMock) 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 *sSimYDMock) 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, "SimYDMock.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, "SimYDMock.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 *sSimYDMock) 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 *sSimYDMock) 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 *sSimYDMock) 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 := ""
if len(respData.Result) > 0 {
bindStatus = pickYdCardBindStatus(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,
},
})
}
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 *sSimYDMock) 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
}
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 *sSimYDMock) 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
}