chore: 初始开源发布
iotlcm-upstream-chan:运营商上游通道适配服务(统一 /sim/* 协议)。 - 内联原私有依赖:yunze-public → internal/pub/(10 包 18 文件), yunze-pb/pb → internal/pb/(9 文件);Consul 注册改用官方 gogf/gf/contrib/registry/consul/v2(API 兼容) - Go module:yz-svr → git.5iot.com/iotlcm-public/iotlcm-upstream-chan - 修复 mingqian 包测试:实现重构后 `adapter.operations` 字段已移除, 测试断言按当前实现语义重写(不改产品行为) - 凭据与内部标识脱敏:MySQL/Redis/Consul 凭据、私网与生产 IP、 内网域名、真实手机号、注释里的真实 token/transid - 移除内部资产:CI 触发器、生产通道种子 SQL、真实凭据测试资源文档 - Dockerfile 改用公共基础镜像,移除写死的 registry 凭据 - 补 Apache-2.0 LICENSE,重写 README,LF 行尾 + .gitattributes
This commit is contained in:
@@ -0,0 +1,521 @@
|
||||
package upstreamimpl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-upstream-chan/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-upstream-chan/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-upstream-chan/internal/service"
|
||||
)
|
||||
|
||||
const henanXinjingDirectTemplateKey = "sim_henan_xinjing_direct_v1"
|
||||
|
||||
// henanXinjingConfig is the channel-level config for the direct Henan Xinjing
|
||||
// template. The top-level userId/apiKey form is the canonical shape; the
|
||||
// nested `credentials` block is kept for backward compatibility with earlier
|
||||
// drafts of this template.
|
||||
type henanXinjingConfig struct {
|
||||
UserID string `json:"userId"`
|
||||
APIKey string `json:"apiKey"`
|
||||
APIKeyLegacy string `json:"apikey"`
|
||||
API struct {
|
||||
BaseURL string `json:"baseUrl"`
|
||||
Timeout string `json:"timeout"`
|
||||
} `json:"api"`
|
||||
Credentials struct {
|
||||
UserID string `json:"userId"`
|
||||
APIKey string `json:"apiKey"`
|
||||
APIKeyLegacy string `json:"apikey"`
|
||||
} `json:"credentials"`
|
||||
}
|
||||
|
||||
// henanXinjingParseConfig unmarshals one JSON config blob (channel or
|
||||
// template) into henanXinjingConfig. Empty input yields a zero config.
|
||||
func henanXinjingParseConfig(raw string) henanXinjingConfig {
|
||||
var c henanXinjingConfig
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return c
|
||||
}
|
||||
_ = json.Unmarshal([]byte(raw), &c)
|
||||
return c
|
||||
}
|
||||
|
||||
// henanXinjingPickCreds resolves (userId, apiKey) with precedence:
|
||||
// channel.chan_config > channel.app_key/app_secret > template.temp_config.
|
||||
// This matches how the LCM admin stores credentials: per-channel in
|
||||
// t_sim_channels, not in the shared template row.
|
||||
func henanXinjingPickCreds(chanCfg, tplCfg henanXinjingConfig, appKey, appSecret string) (user, key string) {
|
||||
firstNonEmptyStr := func(vals ...string) string {
|
||||
for _, v := range vals {
|
||||
if v = strings.TrimSpace(v); v != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
user = firstNonEmptyStr(
|
||||
chanCfg.UserID, chanCfg.Credentials.UserID,
|
||||
appKey,
|
||||
tplCfg.UserID, tplCfg.Credentials.UserID,
|
||||
)
|
||||
key = firstNonEmptyStr(
|
||||
chanCfg.APIKey, chanCfg.APIKeyLegacy,
|
||||
chanCfg.Credentials.APIKey, chanCfg.Credentials.APIKeyLegacy,
|
||||
appSecret,
|
||||
tplCfg.APIKey, tplCfg.APIKeyLegacy,
|
||||
tplCfg.Credentials.APIKey, tplCfg.Credentials.APIKeyLegacy,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// henanXinjingFlexFloat accepts either a JSON number or a JSON string and
|
||||
// normalizes to float64. The platform returns `used` as a number and
|
||||
// `surplus`/`canused` as strings in some responses.
|
||||
type henanXinjingFlexFloat float64
|
||||
|
||||
func (f *henanXinjingFlexFloat) UnmarshalJSON(data []byte) error {
|
||||
if len(data) == 0 || string(data) == "null" {
|
||||
*f = 0
|
||||
return nil
|
||||
}
|
||||
var n float64
|
||||
if err := json.Unmarshal(data, &n); err == nil {
|
||||
*f = henanXinjingFlexFloat(n)
|
||||
return nil
|
||||
}
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
*f = 0
|
||||
return nil
|
||||
}
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("henan xinjing: parse float %q: %w", s, err)
|
||||
}
|
||||
*f = henanXinjingFlexFloat(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// henanXinjingCard mirrors `data` in `/api/v1/getChaxun`. Only fields we
|
||||
// actually use are decoded; the platform returns many more.
|
||||
//
|
||||
// Documented field meanings (per upstream doc):
|
||||
// - state : string card status code ("00" ok, "02" stopped, etc.)
|
||||
// - status : int online/auth status (NOT the card lifecycle status)
|
||||
// - used : KB (current month)
|
||||
// - surplus : KB
|
||||
// - expired_at / shouchong_time : yyyy-MM-dd HH:mm:ss strings
|
||||
// - gprs_status : 0 unknown 1 on 2 off
|
||||
// - power_status: 0 unknown 1 online 2 offline
|
||||
// - display_status: 1 pending, 2 normal, 3 abnormal, 4 pre-close, 5 closed
|
||||
type henanXinjingCard struct {
|
||||
ICCID string `json:"iccid"`
|
||||
CardNo string `json:"cardno"`
|
||||
IMEI string `json:"imei"`
|
||||
Used henanXinjingFlexFloat `json:"used"`
|
||||
Surplus henanXinjingFlexFloat `json:"surplus"`
|
||||
CanUsed henanXinjingFlexFloat `json:"canused"`
|
||||
State string `json:"state"`
|
||||
Status int `json:"status"`
|
||||
OnlineState int `json:"onlinestate"`
|
||||
GPRSStatus int `json:"gprs_status"`
|
||||
PowerStatus int `json:"power_status"`
|
||||
DisplayStatus int `json:"display_status"`
|
||||
ExpiredAt string `json:"expired_at"`
|
||||
FirstChargeAt string `json:"shouchong_time"`
|
||||
Message string `json:"message"`
|
||||
AutoName string `json:"autoname"`
|
||||
MonthUseds []struct {
|
||||
MonthDate string `json:"month_date"` // yyyyMM
|
||||
UsedFlow string `json:"used_flow"` // MB per upstream doc
|
||||
} `json:"month_useds"`
|
||||
Package []struct {
|
||||
OrderNo string `json:"orderno"`
|
||||
PackageName string `json:"package_name"`
|
||||
State int `json:"state"`
|
||||
ExpiredAt string `json:"expired_at"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Flow string `json:"flow"`
|
||||
MonthlyFlow henanXinjingFlexFloat `json:"monthly_flow"`
|
||||
Surplus henanXinjingFlexFloat `json:"surplus"`
|
||||
Used henanXinjingFlexFloat `json:"used"`
|
||||
} `json:"package"`
|
||||
}
|
||||
|
||||
type sSimHenanXinjingDirect struct {
|
||||
channelConfig *model.SimChannelConfigItemCache
|
||||
client *henanXinjingClient
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.UpstreamAPI().RegisterTemp(henanXinjingDirectTemplateKey, NewSimHenanXinjingDirect())
|
||||
}
|
||||
|
||||
func NewSimHenanXinjingDirect() *sSimHenanXinjingDirect { return &sSimHenanXinjingDirect{} }
|
||||
|
||||
func (s *sSimHenanXinjingDirect) NewTemp(ctx context.Context, cfg *model.SimChannelConfigItemCache) model.SimCardChannelI {
|
||||
chanKey := ""
|
||||
if cfg != nil && cfg.Channel != nil {
|
||||
chanKey = cfg.Channel.ChanKey
|
||||
}
|
||||
if cfg == nil || cfg.Channel == nil || cfg.Template == nil {
|
||||
g.Log().Warningf(ctx, "henan_xinjing_direct.NewTemp: nil cfg/channel/template, chanKey=%s", chanKey)
|
||||
return nil
|
||||
}
|
||||
chanCfg := henanXinjingParseConfig(cfg.Channel.ChanConfig)
|
||||
tplCfg := henanXinjingParseConfig(cfg.Template.TempConfig)
|
||||
user, key := henanXinjingPickCreds(chanCfg, tplCfg, cfg.Channel.AppKey, cfg.Channel.AppSecret)
|
||||
baseURL := strings.TrimSpace(chanCfg.API.BaseURL)
|
||||
if baseURL == "" {
|
||||
baseURL = strings.TrimSpace(tplCfg.API.BaseURL)
|
||||
}
|
||||
if user == "" || key == "" || baseURL == "" {
|
||||
g.Log().Warningf(ctx,
|
||||
"henan_xinjing_direct.NewTemp: missing required config, chanKey=%s userIdEmpty=%t apiKeyEmpty=%t baseURLEmpty=%t",
|
||||
chanKey, user == "", key == "", baseURL == "")
|
||||
return nil
|
||||
}
|
||||
timeout := defaultUpstreamHTTPTimeout
|
||||
for _, raw := range []string{chanCfg.API.Timeout, tplCfg.API.Timeout} {
|
||||
if raw == "" {
|
||||
continue
|
||||
}
|
||||
if d, err := time.ParseDuration(raw); err == nil && d > 0 {
|
||||
timeout = d
|
||||
break
|
||||
}
|
||||
}
|
||||
return &sSimHenanXinjingDirect{
|
||||
channelConfig: cfg,
|
||||
client: newHenanXinjingClient(baseURL, user, key, timeout),
|
||||
}
|
||||
}
|
||||
|
||||
// simKeyCardNo picks the identifier Henan Xinjing accepts for `cardno`.
|
||||
// The platform keys on its own card-number column, which corresponds to
|
||||
// MSISDN for these cards; fall back to ICCID/IMSI when MSISDN is absent.
|
||||
func henanXinjingCardNo(k *model.SimKey) string {
|
||||
if k == nil {
|
||||
return ""
|
||||
}
|
||||
if v := strings.TrimSpace(k.MSISDN); v != "" {
|
||||
return v
|
||||
}
|
||||
if v := strings.TrimSpace(k.ICCID); v != "" {
|
||||
return v
|
||||
}
|
||||
return strings.TrimSpace(k.IMSI)
|
||||
}
|
||||
|
||||
// fetchCard calls /api/v1/getChaxun for a single SimKey.
|
||||
func (s *sSimHenanXinjingDirect) fetchCard(ctx context.Context, k *model.SimKey) (henanXinjingCard, error) {
|
||||
if k == nil {
|
||||
return henanXinjingCard{}, fmt.Errorf("sim key is required")
|
||||
}
|
||||
no := henanXinjingCardNo(k)
|
||||
if no == "" {
|
||||
return henanXinjingCard{}, fmt.Errorf("msisdn/iccid/imsi is required")
|
||||
}
|
||||
var c henanXinjingCard
|
||||
err := s.client.get(ctx, "/api/v1/getChaxun", map[string]string{"cardno": no}, &c)
|
||||
return c, err
|
||||
}
|
||||
|
||||
// henanXinjingKBToMB converts upstream KB to the MB unit SimDataUsageItem
|
||||
// requires. Rounds to 4 decimal places to keep values readable.
|
||||
func henanXinjingKBToMB(kb float64) float64 {
|
||||
if kb <= 0 {
|
||||
return 0
|
||||
}
|
||||
mb := kb / 1024
|
||||
return float64(int(mb*10000+0.5)) / 10000
|
||||
}
|
||||
|
||||
// henanXinjingMapCardStatus translates the platform's `state` string into the
|
||||
// internal CardStatus code used across the gateway.
|
||||
//
|
||||
// Upstream doc:
|
||||
//
|
||||
// 0 unknown, 00 normal, 01 one-way stop, 02 stopped, 03 pre-close,
|
||||
// 04 closed, 05 transfer, 06 dormant, 07 pending, 08 disabled,
|
||||
// 09 inventory, 10 expired, 11 violation stop, 12 lost, 13 user-stop,
|
||||
// 14 testing, 99 not-exists
|
||||
//
|
||||
// Internal consts (from existing adapters like tibiot):
|
||||
//
|
||||
// 1待激活 2已激活 4停机 6可测试 7库存 8预销户 9已销户
|
||||
func henanXinjingMapCardStatus(state string) int {
|
||||
switch strings.TrimSpace(state) {
|
||||
case "00":
|
||||
return 2 // 已激活/正常
|
||||
case "01", "02", "08", "11", "12", "13":
|
||||
return 4 // 停机
|
||||
case "03":
|
||||
return 8 // 预销户
|
||||
case "04", "10":
|
||||
return 9 // 已销户
|
||||
case "06":
|
||||
return 1 // 休眠 → 待激活
|
||||
case "07":
|
||||
return 1 // 待激活
|
||||
case "09":
|
||||
return 7 // 库存
|
||||
case "14":
|
||||
return 6 // 可测试
|
||||
default:
|
||||
return 0 // 未知
|
||||
}
|
||||
}
|
||||
|
||||
func henanXinjingStatusChangePath(operType string) (string, bool) {
|
||||
switch operType {
|
||||
case consts.CardStatusChangeOperTypeApplyStop:
|
||||
return "/api/v1/stop", true
|
||||
case consts.CardStatusChangeOperTypeApplyResume:
|
||||
return "/api/v1/start", true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
func henanXinjingMainOfferName(card henanXinjingCard) string {
|
||||
if name := strings.TrimSpace(card.AutoName); name != "" {
|
||||
return name
|
||||
}
|
||||
for _, item := range card.Package {
|
||||
if name := strings.TrimSpace(item.PackageName); name != "" {
|
||||
return name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *sSimHenanXinjingDirect) QuerySimBasicInfo(ctx context.Context, in *model.SimBasicInfoInput) (out model.SimBasicInfoOutput, err error) {
|
||||
for _, k := range in.SimKeys {
|
||||
c, e := s.fetchCard(ctx, k)
|
||||
r := &model.SimBaseRes[model.SimBasicInfoItem]{SimKey: k}
|
||||
if e != nil {
|
||||
r.Code = consts.ApiStatusError
|
||||
r.Message = e.Error()
|
||||
} else {
|
||||
r.Code = 0
|
||||
r.ResData = model.SimBasicInfoItem{
|
||||
ICCID: firstNonEmpty(c.ICCID, k.ICCID),
|
||||
IMSI: k.IMSI,
|
||||
MSISDN: firstNonEmpty(c.CardNo, k.MSISDN),
|
||||
ActiveDate: c.FirstChargeAt,
|
||||
OpenDate: c.FirstChargeAt, // use 首充时间 as the open/activate timestamp; expired_at goes to remark
|
||||
Remark: c.Message,
|
||||
SimStatus: strconv.Itoa(henanXinjingMapCardStatus(c.State)),
|
||||
MainOfferName: henanXinjingMainOfferName(c),
|
||||
}
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSimHenanXinjingDirect) QuerySimImei(ctx context.Context, in *model.SimImeiInput) (out model.SimImeiOutput, err error) {
|
||||
for _, k := range in.SimKeys {
|
||||
c, e := s.fetchCard(ctx, k)
|
||||
r := &model.SimBaseRes[model.SimImeiItem]{SimKey: k}
|
||||
if e != nil {
|
||||
r.Code = consts.ApiStatusError
|
||||
r.Message = e.Error()
|
||||
} else {
|
||||
r.Code = 0
|
||||
r.ResData.Imei = c.IMEI
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSimHenanXinjingDirect) QuerySimCardStatus(ctx context.Context, in *model.SimCardStatusInput) (out model.SimCardStatusOutput, err error) {
|
||||
for _, k := range in.SimKeys {
|
||||
c, e := s.fetchCard(ctx, k)
|
||||
r := &model.SimBaseRes[model.SimCardStatusItem]{SimKey: k}
|
||||
if e != nil {
|
||||
r.Code = consts.ApiStatusError
|
||||
r.Message = e.Error()
|
||||
} else {
|
||||
r.Code = 0
|
||||
r.ResData.CardStatus = henanXinjingMapCardStatus(c.State)
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSimHenanXinjingDirect) QuerySimDataUsage(ctx context.Context, in *model.SimDataUsageInput) (out model.SimDataUsageOutput, err error) {
|
||||
for _, k := range in.SimKeys {
|
||||
c, e := s.fetchCard(ctx, k)
|
||||
r := &model.SimBaseRes[model.SimDataUsageItem]{SimKey: k}
|
||||
if e != nil {
|
||||
r.Code = consts.ApiStatusError
|
||||
r.Message = e.Error()
|
||||
} else {
|
||||
r.Code = 0
|
||||
r.ResData.DataUsage = henanXinjingKBToMB(float64(c.Used))
|
||||
total := henanXinjingKBToMB(float64(c.Used) + float64(c.Surplus))
|
||||
if total > 0 {
|
||||
r.ResData.TotalFlow = &total
|
||||
}
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSimHenanXinjingDirect) ChangeSimStatus(ctx context.Context, in *model.SimCardStatusChangeInput) (out model.SimCardStatusChangeOutput, err error) {
|
||||
operType, ok := normalizeCardStatusChangeOperType(ctx, in.ReqData.OperType)
|
||||
if !ok {
|
||||
return newInvalidCardStatusChangeOutput(in.SimKeys), nil
|
||||
}
|
||||
for _, k := range in.SimKeys {
|
||||
r := &model.SimBaseRes[model.SimCardStatusChangeItem]{SimKey: k}
|
||||
path, _ := henanXinjingStatusChangePath(operType)
|
||||
no := henanXinjingCardNo(k)
|
||||
var e error
|
||||
if no == "" {
|
||||
e = fmt.Errorf("msisdn/iccid/imsi is required")
|
||||
} else {
|
||||
e = s.client.get(ctx, path, map[string]string{"cardno": no}, nil)
|
||||
}
|
||||
if e != nil {
|
||||
r.Code = consts.ApiStatusError
|
||||
r.Message = e.Error()
|
||||
} else {
|
||||
r.Code = 0
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func henanXinjingUnsupported[T any](keys []*model.SimKey, name string) (out []*model.SimBaseRes[T], err error) {
|
||||
for _, k := range keys {
|
||||
out = append(out, &model.SimBaseRes[T]{SimKey: k, Code: consts.ApiStatusError, Message: "henan xinjing direct does not support " + name})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSimHenanXinjingDirect) QuerySimSession(ctx context.Context, in *model.SimSessionInput) (model.SimSessionOutput, error) {
|
||||
return henanXinjingUnsupported[model.SimSessionItem](in.SimKeys, "session query")
|
||||
}
|
||||
func (s *sSimHenanXinjingDirect) QuerySimStopReason(ctx context.Context, in *model.SimStopReasonInput) (model.SimStopReasonOutput, error) {
|
||||
return henanXinjingUnsupported[model.SimStopReasonItem](in.SimKeys, "stop reason query")
|
||||
}
|
||||
func (s *sSimHenanXinjingDirect) QuerySimFrozenState(ctx context.Context, in *model.SimFrozenStateInput) (model.SimFrozenStateOutput, error) {
|
||||
return henanXinjingUnsupported[model.SimFrozenStateItem](in.SimKeys, "frozen state query")
|
||||
}
|
||||
func (s *sSimHenanXinjingDirect) QuerySimCardBindStatus(ctx context.Context, in *model.SimCardBindStatusInput) (model.SimCardBindStatusOutput, error) {
|
||||
return henanXinjingUnsupported[model.SimCardBindStatusItem](in.SimKeys, "card binding query")
|
||||
}
|
||||
func (s *sSimHenanXinjingDirect) QuerySimDeviceStatus(ctx context.Context, in *model.SimDeviceStatusInput) (model.SimDeviceStatusOutput, error) {
|
||||
out := make(model.SimDeviceStatusOutput, 0, len(in.SimKeys))
|
||||
for _, k := range in.SimKeys {
|
||||
c, e := s.fetchCard(ctx, k)
|
||||
r := &model.SimBaseRes[model.SimDeviceStatusItem]{SimKey: k}
|
||||
if e != nil {
|
||||
r.Code = consts.ApiStatusError
|
||||
r.Message = e.Error()
|
||||
} else {
|
||||
r.Code = 0
|
||||
// gprs_status: 0 unknown 1 power-on 2 power-off
|
||||
// power_status: 0 unknown 1 online 2 offline
|
||||
// DeviceStatus (compatible semantics): 0 abnormal 1 normal.
|
||||
r.ResData.DeviceStatus = henanXinjingMapDeviceStatus(c.GPRSStatus, c.PowerStatus)
|
||||
switch c.GPRSStatus {
|
||||
case 1:
|
||||
r.ResData.OperatingStatus = "1"
|
||||
case 2:
|
||||
r.ResData.OperatingStatus = "0"
|
||||
}
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// henanXinjingMapDeviceStatus merges the platform's gprs_status (power on/off)
|
||||
// and power_status (online/offline) into the gateway's binary device status.
|
||||
// Only a card that is powered on AND online is "normal" (1); anything else,
|
||||
// including unknown, maps to abnormal (0).
|
||||
func henanXinjingMapDeviceStatus(gprsStatus, powerStatus int) string {
|
||||
if gprsStatus == 1 && powerStatus == 1 {
|
||||
return "1"
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
func (s *sSimHenanXinjingDirect) QuerySimRegionLimitStatus(ctx context.Context, in *model.SimRegionLimitStatusInput) (model.SimRegionLimitStatusOutput, error) {
|
||||
return henanXinjingUnsupported[model.SimRegionLimitStatusItem](in.SimKeys, "region limit query")
|
||||
}
|
||||
func (s *sSimHenanXinjingDirect) QuerySimDataDiagnosis(ctx context.Context, in *model.SimDataDiagnosisInput) (model.SimDataDiagnosisOutput, error) {
|
||||
return henanXinjingUnsupported[any](in.SimKeys, "data diagnosis")
|
||||
}
|
||||
func (s *sSimHenanXinjingDirect) QuerySimDataUsageMonthly(ctx context.Context, in *model.SimDataUsageMonthlyInput) (model.SimDataUsageMonthlyOutput, error) {
|
||||
queryMonth := ""
|
||||
if in != nil {
|
||||
queryMonth = strings.TrimSpace(in.ReqData.QueryDate)
|
||||
}
|
||||
out := make(model.SimDataUsageMonthlyOutput, 0, len(in.SimKeys))
|
||||
for _, k := range in.SimKeys {
|
||||
c, e := s.fetchCard(ctx, k)
|
||||
r := &model.SimBaseRes[model.SimDataUsageMonthlyOutputItem]{SimKey: k}
|
||||
if e != nil {
|
||||
r.Code = consts.ApiStatusError
|
||||
r.Message = e.Error()
|
||||
} else {
|
||||
var selected *struct {
|
||||
MonthDate string
|
||||
UsedFlow string
|
||||
}
|
||||
for i := range c.MonthUseds {
|
||||
m := strings.TrimSpace(c.MonthUseds[i].MonthDate)
|
||||
if queryMonth == "" || strings.Contains(m, queryMonth) {
|
||||
selected = &struct {
|
||||
MonthDate string
|
||||
UsedFlow string
|
||||
}{c.MonthUseds[i].MonthDate, c.MonthUseds[i].UsedFlow}
|
||||
break
|
||||
}
|
||||
}
|
||||
if selected == nil {
|
||||
r.Code = consts.ApiStatusError
|
||||
r.Message = "monthly usage data not found"
|
||||
} else {
|
||||
r.Code = 0
|
||||
r.ResData.DeadLine = strings.TrimSpace(selected.MonthDate)
|
||||
// month_useds.used_flow is MB per the platform doc, unlike the
|
||||
// KB used/surplus fields on the same response.
|
||||
if v, perr := strconv.ParseFloat(strings.TrimSpace(selected.UsedFlow), 64); perr == nil {
|
||||
r.ResData.DataAmount = v
|
||||
}
|
||||
}
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (s *sSimHenanXinjingDirect) QuerySimDataUsageDaily(ctx context.Context, in *model.SimDataUsageDailyInput) (model.SimDataUsageDailyOutput, error) {
|
||||
return henanXinjingUnsupported[model.SimDataUsageDailyOutputItem](in.SimKeys, "historical daily usage query")
|
||||
}
|
||||
func (s *sSimHenanXinjingDirect) QuerySimApnInfo(ctx context.Context, in *model.SimApnInfoInput) (model.SimApnInfoOutput, error) {
|
||||
return henanXinjingUnsupported[model.SimApnInfoItemList](in.SimKeys, "APN query")
|
||||
}
|
||||
func (s *sSimHenanXinjingDirect) ChangeSimApnFunction(ctx context.Context, in *model.SimCardOperateChangeApnInput) (model.SimCardOperateChangeApnOutput, error) {
|
||||
return henanXinjingUnsupported[model.SimCardOperateChangeApnItem](in.SimKeys, "APN change")
|
||||
}
|
||||
Reference in New Issue
Block a user