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 }