96b1edff45
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
252 lines
12 KiB
Go
252 lines
12 KiB
Go
package upstreamapi
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"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/errors/gerror"
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
)
|
|
|
|
const (
|
|
defaultChannelTestTimeout = 2 * time.Minute
|
|
defaultChannelTestItemTimeout = 10 * time.Second
|
|
)
|
|
|
|
type channelTestOperation struct {
|
|
key string
|
|
name string
|
|
execute func(context.Context, model.SimCardChannelI, *model.SimKey, time.Time) (any, int, string, bool, error)
|
|
}
|
|
|
|
func (u *sUpstreamAPI) TestChannel(ctx context.Context, input *model.ChannelTestInput) (*model.ChannelTestOutput, error) {
|
|
if input == nil {
|
|
return nil, gerror.New("channel test input is required")
|
|
}
|
|
if input.ChannelID <= 0 || strings.TrimSpace(input.TenantID) == "" {
|
|
return nil, gerror.New("channel is not accessible")
|
|
}
|
|
|
|
operations, err := selectChannelTestOperations(input.Items)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
totalTimeout := g.Cfg().MustGet(ctx, "upstreamapi.channel_test.timeout", defaultChannelTestTimeout).Duration()
|
|
if totalTimeout <= 0 {
|
|
totalTimeout = defaultChannelTestTimeout
|
|
}
|
|
itemTimeout := g.Cfg().MustGet(ctx, "upstreamapi.channel_test.item_timeout", defaultChannelTestItemTimeout).Duration()
|
|
if itemTimeout <= 0 {
|
|
itemTimeout = defaultChannelTestItemTimeout
|
|
}
|
|
testCtx, cancel := context.WithTimeout(ctx, totalTimeout)
|
|
defer cancel()
|
|
|
|
config, err := service.ChannelConfig().LoadChannelForTest(testCtx, input.ChannelID, input.TenantID)
|
|
if err != nil {
|
|
if errors.Is(err, model.ErrChannelNotAccessible) {
|
|
return nil, model.ErrChannelNotAccessible
|
|
}
|
|
return nil, gerror.Wrap(err, "load channel test configuration")
|
|
}
|
|
instance, err := u.newTestChannelInstance(testCtx, config)
|
|
if err != nil {
|
|
return nil, gerror.Wrap(err, "channel test configuration is invalid")
|
|
}
|
|
completionCtx, completionCancel := context.WithTimeout(testCtx, itemTimeout)
|
|
simKey, err := resolveChannelTestSimKey(completionCtx, instance, config.Channel.ChanKey, input)
|
|
completionCancel()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
results := make([]*model.ChannelTestItem, 0, len(operations))
|
|
now := time.Now()
|
|
for _, operation := range operations {
|
|
if testCtx.Err() != nil {
|
|
results = append(results, channelTestTimeoutResult(operation))
|
|
continue
|
|
}
|
|
results = append(results, executeChannelTestOperation(testCtx, itemTimeout, operation, instance, simKey, now))
|
|
}
|
|
return &model.ChannelTestOutput{Items: results}, nil
|
|
}
|
|
|
|
func resolveChannelTestSimKey(ctx context.Context, instance model.SimCardChannelI, chanKey string, input *model.ChannelTestInput) (*model.SimKey, error) {
|
|
mode := strings.ToLower(strings.TrimSpace(input.InputMode))
|
|
if mode == "" {
|
|
mode = model.ChannelTestInputModeComplete
|
|
}
|
|
cardType := strings.ToLower(strings.TrimSpace(input.CardValueType))
|
|
cardValue := strings.TrimSpace(input.CardValue)
|
|
if mode == model.ChannelTestInputModeComplete {
|
|
if strings.TrimSpace(input.ICCID) == "" || strings.TrimSpace(input.IMSI) == "" || strings.TrimSpace(input.MSISDN) == "" {
|
|
return nil, gerror.New("complete mode requires iccid, imsi and msisdn")
|
|
}
|
|
return &model.SimKey{ChanKey: chanKey, ICCID: input.ICCID, IMSI: input.IMSI, MSISDN: input.MSISDN}, nil
|
|
}
|
|
if mode != model.ChannelTestInputModeSingle && mode != model.ChannelTestInputModeDirectSingle {
|
|
return nil, gerror.New("unsupported channel test input mode")
|
|
}
|
|
if cardValue == "" || !validChannelTestCardType(cardType) {
|
|
return nil, gerror.New("card value type and value are required")
|
|
}
|
|
key := &model.SimKey{ChanKey: chanKey}
|
|
setChannelTestCardValue(key, cardType, cardValue)
|
|
if mode == model.ChannelTestInputModeDirectSingle {
|
|
return key, nil
|
|
}
|
|
|
|
// Card completion uses the same request-scoped instance as the selected
|
|
// checks; it never falls back to chanMap or creates a second instance.
|
|
basic, err := instance.QuerySimBasicInfo(ctx, &model.SimBasicInfoInput{SimKeys: []*model.SimKey{key}})
|
|
if err != nil {
|
|
return nil, gerror.Wrap(err, "complete card identifiers")
|
|
}
|
|
if len(basic) == 0 || basic[0] == nil || basic[0].Code != consts.ApiStatusSuccess {
|
|
if len(basic) > 0 && basic[0] != nil && basic[0].Message != "" {
|
|
return nil, gerror.New(basic[0].Message)
|
|
}
|
|
return nil, gerror.New("carrier did not return card identifiers")
|
|
}
|
|
completed := basic[0].ResData
|
|
if strings.TrimSpace(completed.ICCID) == "" || strings.TrimSpace(completed.IMSI) == "" || strings.TrimSpace(completed.MSISDN) == "" {
|
|
return nil, gerror.New("carrier did not return complete card identifiers")
|
|
}
|
|
return &model.SimKey{ChanKey: chanKey, ICCID: completed.ICCID, IMSI: completed.IMSI, MSISDN: completed.MSISDN}, nil
|
|
}
|
|
|
|
func validChannelTestCardType(valueType string) bool {
|
|
return valueType == "iccid" || valueType == "imsi" || valueType == "msisdn"
|
|
}
|
|
|
|
func setChannelTestCardValue(key *model.SimKey, valueType, value string) {
|
|
switch valueType {
|
|
case "iccid":
|
|
key.ICCID = value
|
|
case "imsi":
|
|
key.IMSI = value
|
|
case "msisdn":
|
|
key.MSISDN = value
|
|
}
|
|
}
|
|
|
|
func selectChannelTestOperations(requested []string) ([]channelTestOperation, error) {
|
|
all := newChannelTestOperations()
|
|
if len(requested) == 0 {
|
|
return all, nil
|
|
}
|
|
wanted := make(map[string]struct{}, len(requested))
|
|
for _, key := range requested {
|
|
key = strings.TrimSpace(key)
|
|
if _, ok := model.ChannelTestOperationNames[key]; !ok {
|
|
return nil, gerror.Newf("unsupported channel test item: %s", key)
|
|
}
|
|
wanted[key] = struct{}{}
|
|
}
|
|
selected := make([]channelTestOperation, 0, len(wanted))
|
|
for _, operation := range all {
|
|
if _, ok := wanted[operation.key]; ok {
|
|
selected = append(selected, operation)
|
|
}
|
|
}
|
|
return selected, nil
|
|
}
|
|
|
|
func executeChannelTestOperation(parent context.Context, itemTimeout time.Duration, operation channelTestOperation, instance model.SimCardChannelI, simKey *model.SimKey, now time.Time) *model.ChannelTestItem {
|
|
started := time.Now()
|
|
callCtx, cancel := context.WithTimeout(parent, itemTimeout)
|
|
defer cancel()
|
|
data, errorCode, message, success, err := operation.execute(callCtx, instance, simKey, now)
|
|
result := &model.ChannelTestItem{Key: operation.key, Name: operation.name, Status: "failed", Data: data, ErrorCode: errorCode, DurationMs: time.Since(started).Milliseconds()}
|
|
if callCtx.Err() != nil {
|
|
result.Status = "timeout"
|
|
result.Message = "channel test item timed out"
|
|
return result
|
|
}
|
|
if err != nil {
|
|
result.Message = err.Error()
|
|
return result
|
|
}
|
|
if !success {
|
|
result.Message = message
|
|
return result
|
|
}
|
|
result.Status = "success"
|
|
result.Message = message
|
|
return result
|
|
}
|
|
|
|
func channelTestTimeoutResult(operation channelTestOperation) *model.ChannelTestItem {
|
|
return &model.ChannelTestItem{Key: operation.key, Name: operation.name, Status: "timeout", Message: "channel test request timed out"}
|
|
}
|
|
|
|
func newChannelTestOperations() []channelTestOperation {
|
|
testType := 0
|
|
return []channelTestOperation{
|
|
newChannelTestOperation("simBasicInfo", func(ctx context.Context, instance model.SimCardChannelI, key *model.SimKey, _ time.Time) ([]*model.SimBaseRes[model.SimBasicInfoItem], error) {
|
|
return instance.QuerySimBasicInfo(ctx, &model.SimBasicInfoInput{SimKeys: []*model.SimKey{key}})
|
|
}),
|
|
newChannelTestOperation("simSession", func(ctx context.Context, instance model.SimCardChannelI, key *model.SimKey, _ time.Time) ([]*model.SimBaseRes[model.SimSessionItem], error) {
|
|
return instance.QuerySimSession(ctx, &model.SimSessionInput{SimKeys: []*model.SimKey{key}})
|
|
}),
|
|
newChannelTestOperation("simStatus", func(ctx context.Context, instance model.SimCardChannelI, key *model.SimKey, _ time.Time) ([]*model.SimBaseRes[model.SimCardStatusItem], error) {
|
|
return instance.QuerySimCardStatus(ctx, &model.SimCardStatusInput{SimKeys: []*model.SimKey{key}})
|
|
}),
|
|
newChannelTestOperation("simStopReason", func(ctx context.Context, instance model.SimCardChannelI, key *model.SimKey, _ time.Time) ([]*model.SimBaseRes[model.SimStopReasonItem], error) {
|
|
return instance.QuerySimStopReason(ctx, &model.SimStopReasonInput{SimKeys: []*model.SimKey{key}})
|
|
}),
|
|
newChannelTestOperation("simFrozenState", func(ctx context.Context, instance model.SimCardChannelI, key *model.SimKey, _ time.Time) ([]*model.SimBaseRes[model.SimFrozenStateItem], error) {
|
|
return instance.QuerySimFrozenState(ctx, &model.SimFrozenStateInput{SimKeys: []*model.SimKey{key}})
|
|
}),
|
|
newChannelTestOperation("simCardBindStatus", func(ctx context.Context, instance model.SimCardChannelI, key *model.SimKey, _ time.Time) ([]*model.SimBaseRes[model.SimCardBindStatusItem], error) {
|
|
return instance.QuerySimCardBindStatus(ctx, &model.SimCardBindStatusInput{SimKeys: []*model.SimKey{key}, ReqData: model.SimCardBindStatusInputItem{TestType: &testType}})
|
|
}),
|
|
newChannelTestOperation("simDeviceStatus", func(ctx context.Context, instance model.SimCardChannelI, key *model.SimKey, _ time.Time) ([]*model.SimBaseRes[model.SimDeviceStatusItem], error) {
|
|
return instance.QuerySimDeviceStatus(ctx, &model.SimDeviceStatusInput{SimKeys: []*model.SimKey{key}})
|
|
}),
|
|
newChannelTestOperation("simRegionLimitStatus", func(ctx context.Context, instance model.SimCardChannelI, key *model.SimKey, _ time.Time) ([]*model.SimBaseRes[model.SimRegionLimitStatusItem], error) {
|
|
return instance.QuerySimRegionLimitStatus(ctx, &model.SimRegionLimitStatusInput{SimKeys: []*model.SimKey{key}})
|
|
}),
|
|
newChannelTestOperation("simImei", func(ctx context.Context, instance model.SimCardChannelI, key *model.SimKey, _ time.Time) ([]*model.SimBaseRes[model.SimImeiItem], error) {
|
|
return instance.QuerySimImei(ctx, &model.SimImeiInput{SimKeys: []*model.SimKey{key}})
|
|
}),
|
|
newChannelTestOperation("simDataUsage", func(ctx context.Context, instance model.SimCardChannelI, key *model.SimKey, _ time.Time) ([]*model.SimBaseRes[model.SimDataUsageItem], error) {
|
|
return instance.QuerySimDataUsage(ctx, &model.SimDataUsageInput{SimKeys: []*model.SimKey{key}})
|
|
}),
|
|
newChannelTestOperation("simDataUsageMonthly", func(ctx context.Context, instance model.SimCardChannelI, key *model.SimKey, now time.Time) ([]*model.SimBaseRes[model.SimDataUsageMonthlyOutputItem], error) {
|
|
last := now.AddDate(0, -1, 0)
|
|
return instance.QuerySimDataUsageMonthly(ctx, &model.SimDataUsageMonthlyInput{SimKeys: []*model.SimKey{key}, ReqData: model.SimDataUsageMonthlyInputItem{QueryDate: last.Format("200601")}})
|
|
}),
|
|
newChannelTestOperation("simDataUsageDaily", func(ctx context.Context, instance model.SimCardChannelI, key *model.SimKey, now time.Time) ([]*model.SimBaseRes[model.SimDataUsageDailyOutputItem], error) {
|
|
yesterday := now.AddDate(0, 0, -1)
|
|
return instance.QuerySimDataUsageDaily(ctx, &model.SimDataUsageDailyInput{SimKeys: []*model.SimKey{key}, ReqData: model.SimDataUsageDailyInputItem{QueryDate: yesterday.Format("20060102")}})
|
|
}),
|
|
newChannelTestOperation("simApnInfo", func(ctx context.Context, instance model.SimCardChannelI, key *model.SimKey, _ time.Time) ([]*model.SimBaseRes[model.SimApnInfoItemList], error) {
|
|
return instance.QuerySimApnInfo(ctx, &model.SimApnInfoInput{SimKeys: []*model.SimKey{key}})
|
|
}),
|
|
}
|
|
}
|
|
|
|
func newChannelTestOperation[T any](key string, query func(context.Context, model.SimCardChannelI, *model.SimKey, time.Time) ([]*model.SimBaseRes[T], error)) channelTestOperation {
|
|
return channelTestOperation{key: key, name: model.ChannelTestOperationNames[key], execute: func(ctx context.Context, instance model.SimCardChannelI, simKey *model.SimKey, now time.Time) (any, int, string, bool, error) {
|
|
output, err := query(ctx, instance, simKey, now)
|
|
if err != nil {
|
|
return nil, 0, "", false, err
|
|
}
|
|
if len(output) == 0 || output[0] == nil {
|
|
return nil, 0, "carrier returned no result", false, nil
|
|
}
|
|
first := output[0]
|
|
if first.Code != consts.ApiStatusSuccess {
|
|
return first.ResData, first.Code, first.Message, false, nil
|
|
}
|
|
return first.ResData, 0, first.Message, true, nil
|
|
}}
|
|
}
|