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
616 lines
28 KiB
Go
616 lines
28 KiB
Go
package upstreamimpl
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"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/frame/g"
|
|
)
|
|
|
|
const (
|
|
sharedOneLinkTengyuTemplateKey = "sim_onelink_tengyu_v1"
|
|
sharedOneLinkXuzhouTemplateKey = "sim_onelink_xuzhou_mobile_v1"
|
|
sharedOneLinkLelianTemplateKey = "sim_onelink_lelian_v1"
|
|
sharedOneLinkQianxunTemplateKey = "sim_onelink_qianxun_v1"
|
|
sharedOneLinkHenanXinjingTemplateKey = "sim_onelink_henan_xinjing_v1"
|
|
sharedOneLinkDaiheTemplateKey = "sim_onelink_daihe_v1"
|
|
sharedOneLinkMobilePublicTemplateKey = "sim_onelink_daliuliang_v1"
|
|
sharedOneLinkMobilePublicPreviousKey = "sim__daliuliang_v1"
|
|
sharedOneLinkMobilePublicLegacyKey = "sim_onelink_mobile_public_v1"
|
|
sharedOneLinkApikaTemplateKey = "sim_onelink_kedang_v1"
|
|
sharedOneLinkYunzeTemplateKey = "sim_onelink_yunze_v1"
|
|
sharedOneLinkLanshuTemplateKey = "sim_onelink_lanshu_v1"
|
|
sharedOneLinkBeixinTemplateKey = "sim_onelink_beixin_v1"
|
|
sharedOneLinkAPIBaseURL = "https://api.iot.10086.cn/v5/"
|
|
sharedOneLinkTransIDPrefix = "ONELINK"
|
|
sharedOneLinkXuzhouTokenTTL = 40 * time.Minute
|
|
)
|
|
|
|
// SharedTokenConfig is the per-channel configuration passed to a provider
|
|
// factory. It intentionally contains no provider implementation details.
|
|
type SharedTokenConfig struct {
|
|
Provider string
|
|
TokenBaseURL string
|
|
LoginPath string
|
|
TokenPath string
|
|
CarrierType int
|
|
PwdKey string
|
|
ChannelNo string
|
|
AppKey string
|
|
AppID string
|
|
AppSecret string
|
|
SubAccount string
|
|
Secret string
|
|
OperatorID string
|
|
UserID string
|
|
TokenType string
|
|
UserName string
|
|
Password string
|
|
PasswayUID string
|
|
Sign string
|
|
ChannelID string
|
|
ChannelPName string
|
|
CardNo string
|
|
CMCCAppID string
|
|
Timeout time.Duration
|
|
}
|
|
|
|
// TokenProviderFactory is the adapter point for the token-core/provider
|
|
// integration. Provider branches can register factories without changing this
|
|
// channel implementation.
|
|
type TokenProviderFactory func(context.Context, *model.SimChannelConfigItemCache, SharedTokenConfig) (ydTokenProvider, error)
|
|
|
|
var sharedTokenFactories = struct {
|
|
sync.RWMutex
|
|
m map[string]TokenProviderFactory
|
|
}{m: make(map[string]TokenProviderFactory)}
|
|
|
|
func RegisterTokenProviderFactory(provider string, factory TokenProviderFactory) {
|
|
provider = strings.ToLower(strings.TrimSpace(provider))
|
|
if provider == "" || factory == nil {
|
|
return
|
|
}
|
|
sharedTokenFactories.Lock()
|
|
sharedTokenFactories.m[provider] = factory
|
|
sharedTokenFactories.Unlock()
|
|
}
|
|
|
|
func tokenProviderFactory(provider string) (TokenProviderFactory, bool) {
|
|
sharedTokenFactories.RLock()
|
|
f, ok := sharedTokenFactories.m[strings.ToLower(strings.TrimSpace(provider))]
|
|
sharedTokenFactories.RUnlock()
|
|
return f, ok
|
|
}
|
|
|
|
type oneLinkAPIConfig struct {
|
|
BaseURL string
|
|
TokenPath string
|
|
Timeout time.Duration
|
|
transIDPrefix string
|
|
}
|
|
|
|
type sharedOneLinkChannelConfig struct {
|
|
PwdKey string `json:"pwdKey"`
|
|
ChannelNo string `json:"channelNo"`
|
|
AppID string `json:"appId"`
|
|
SubAccount string `json:"subAccount"`
|
|
Secret string `json:"secret"`
|
|
OperatorID string `json:"operatorId"`
|
|
UserID string `json:"userId"`
|
|
TokenType string `json:"tokenType"`
|
|
UserName string `json:"userName"`
|
|
Password string `json:"password"`
|
|
PasswayUID string `json:"passwayUid"`
|
|
ChannelID string `json:"channelId"`
|
|
ChannelPName string `json:"channelPname"`
|
|
CardNo string `json:"cardNo"`
|
|
CMCCAppID string `json:"cmccAppId"`
|
|
// Sign is the fixed signature issued by the LanShu open route. It is
|
|
// passed through verbatim and is never derived locally.
|
|
Sign string `json:"sign"`
|
|
// TokenBaseURL lets a channel override the template's token.baseUrl, so
|
|
// accounts living on a different upstream host can share one template.
|
|
TokenBaseURL string `json:"tokenBaseUrl"`
|
|
LegacyCredentials *struct {
|
|
PwdKey string `json:"pwdKey"`
|
|
ChannelNo string `json:"channelNo"`
|
|
AppID string `json:"appId"`
|
|
UserName string `json:"userName"`
|
|
Password string `json:"password"`
|
|
PasswayUID string `json:"passwayUid"`
|
|
} `json:"credentials"`
|
|
}
|
|
|
|
type sharedOneLinkTemplateConfig struct {
|
|
API struct {
|
|
BaseURL string `json:"baseUrl"`
|
|
} `json:"api"`
|
|
Token struct {
|
|
Provider string `json:"provider"`
|
|
BaseURL string `json:"baseUrl"`
|
|
Path string `json:"path"`
|
|
Params struct {
|
|
LoginPath string `json:"loginPath"`
|
|
CarrierType *int `json:"carrierType"`
|
|
} `json:"params"`
|
|
LegacyLoginPath string `json:"loginPath"`
|
|
LegacyCarrierType *int `json:"carrierType"`
|
|
} `json:"token"`
|
|
}
|
|
|
|
type sharedTokenManagerProvider struct {
|
|
manager sharedtoken.TokenManager
|
|
chanKey string
|
|
includeDeviceToken bool
|
|
}
|
|
|
|
func (p *sharedTokenManagerProvider) GetToken(ctx context.Context) (string, error) {
|
|
return p.manager.GetToken(ctx, p.chanKey)
|
|
}
|
|
|
|
func (p *sharedTokenManagerProvider) GetDeviceToken(ctx context.Context) (string, error) {
|
|
if !p.includeDeviceToken {
|
|
return "", nil
|
|
}
|
|
token, err := p.manager.Get(ctx, p.chanKey)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return token.DeviceValue, nil
|
|
}
|
|
|
|
func (p *sharedTokenManagerProvider) RecoverToken(ctx context.Context, rejected string) (string, error) {
|
|
p.manager.InvalidateIfCurrent(p.chanKey, rejected)
|
|
return p.manager.GetToken(ctx, p.chanKey)
|
|
}
|
|
|
|
// GetSharedToken returns the shared token together with its expiry, fetching
|
|
// from the provider on demand when the cached value is missing or expired.
|
|
// Expiry is zero when the provider does not report one.
|
|
func (p *sharedTokenManagerProvider) GetSharedToken(ctx context.Context) (sharedtoken.OneLinkToken, error) {
|
|
return p.manager.Get(ctx, p.chanKey)
|
|
}
|
|
|
|
type tengyuFetcher struct{ provider *TengyuTokenProvider }
|
|
|
|
func (f tengyuFetcher) FetchToken(ctx context.Context, _ string) (sharedtoken.OneLinkToken, error) {
|
|
t, err := f.provider.Fetch(ctx)
|
|
if err != nil {
|
|
return sharedtoken.OneLinkToken{}, err
|
|
}
|
|
return sharedtoken.OneLinkToken{Value: t.Value, CreatedAt: t.CreatedAt, ExpiresAt: t.ExpiresAt}, nil
|
|
}
|
|
|
|
type xuzhouMobileFetcher struct{ provider *XuzhouMobileTokenProvider }
|
|
|
|
func (f xuzhouMobileFetcher) FetchToken(ctx context.Context, _ string) (sharedtoken.OneLinkToken, error) {
|
|
t, err := f.provider.Fetch(ctx)
|
|
if err != nil {
|
|
return sharedtoken.OneLinkToken{}, err
|
|
}
|
|
return sharedtoken.OneLinkToken{Value: t.Value, ExpiresAt: t.ExpiresAt}, nil
|
|
}
|
|
|
|
type henanLelianFetcher struct{ provider *HenanLelianTokenProvider }
|
|
|
|
func (f henanLelianFetcher) FetchToken(ctx context.Context, _ string) (sharedtoken.OneLinkToken, error) {
|
|
t, err := f.provider.Fetch(ctx)
|
|
if err != nil {
|
|
return sharedtoken.OneLinkToken{}, err
|
|
}
|
|
return sharedtoken.OneLinkToken{Value: t.Value, CreatedAt: t.CreatedAt}, nil
|
|
}
|
|
|
|
func parseSharedOneLinkConfig(ctx context.Context, channelConfig *model.SimChannelConfigItemCache) (SharedTokenConfig, *oneLinkAPIConfig, error) {
|
|
if channelConfig == nil || channelConfig.Channel == nil || channelConfig.Template == nil {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("channel and template config are required")
|
|
}
|
|
var template sharedOneLinkTemplateConfig
|
|
if strings.TrimSpace(channelConfig.Template.TempConfig) == "" {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("temp_config is required")
|
|
}
|
|
if err := json.Unmarshal([]byte(channelConfig.Template.TempConfig), &template); err != nil {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("invalid temp_config: %w", err)
|
|
}
|
|
var channel sharedOneLinkChannelConfig
|
|
if strings.TrimSpace(channelConfig.Channel.ChanConfig) != "" {
|
|
if err := json.Unmarshal([]byte(channelConfig.Channel.ChanConfig), &channel); err != nil {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("invalid chan_config: %w", err)
|
|
}
|
|
}
|
|
legacy := channel.LegacyCredentials
|
|
provider := strings.ToLower(strings.TrimSpace(template.Token.Provider))
|
|
if provider == "" {
|
|
provider = "tengyu"
|
|
}
|
|
tokenBaseURL := strings.TrimSpace(template.Token.BaseURL)
|
|
// A channel may point token fetches at a different upstream host than the
|
|
// template default (e.g. the 361 account lives on gxhy.ge-xing.cn while
|
|
// the template points at hywx.xjict.com).
|
|
if override := strings.TrimSpace(channel.TokenBaseURL); override != "" {
|
|
tokenBaseURL = override
|
|
}
|
|
if tokenBaseURL == "" {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("token.baseUrl is required")
|
|
}
|
|
if strings.TrimSpace(template.Token.Path) == "" {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("token.path is required")
|
|
}
|
|
pwdKey := strings.TrimSpace(channel.PwdKey)
|
|
channelNo := strings.TrimSpace(channel.ChannelNo)
|
|
appID := strings.TrimSpace(channel.AppID)
|
|
appSecret := strings.TrimSpace(channelConfig.Channel.AppSecret)
|
|
subAccount := strings.TrimSpace(channel.SubAccount)
|
|
secret := channel.Secret
|
|
operatorID := strings.TrimSpace(channel.OperatorID)
|
|
userID := strings.TrimSpace(channel.UserID)
|
|
tokenType := strings.TrimSpace(channel.TokenType)
|
|
channelID := strings.TrimSpace(channel.ChannelID)
|
|
channelPName := strings.TrimSpace(channel.ChannelPName)
|
|
cardNo := strings.TrimSpace(channel.CardNo)
|
|
cmccAppID := strings.TrimSpace(channel.CMCCAppID)
|
|
sign := strings.TrimSpace(channel.Sign)
|
|
userName := strings.TrimSpace(channel.UserName)
|
|
password := channel.Password
|
|
passwayUID := strings.TrimSpace(channel.PasswayUID)
|
|
if legacy != nil {
|
|
if pwdKey == "" {
|
|
pwdKey = strings.TrimSpace(legacy.PwdKey)
|
|
}
|
|
if channelNo == "" {
|
|
channelNo = strings.TrimSpace(legacy.ChannelNo)
|
|
}
|
|
if appID == "" {
|
|
appID = strings.TrimSpace(legacy.AppID)
|
|
}
|
|
if userName == "" {
|
|
userName = strings.TrimSpace(legacy.UserName)
|
|
}
|
|
if password == "" {
|
|
password = legacy.Password
|
|
}
|
|
if passwayUID == "" {
|
|
passwayUID = strings.TrimSpace(legacy.PasswayUID)
|
|
}
|
|
g.Log().Warningf(ctx, "shared token channel %s uses deprecated chan_config.credentials; migrate fields to the root", channelConfig.Channel.ChanKey)
|
|
}
|
|
carrierType := 1
|
|
if template.Token.Params.CarrierType != nil {
|
|
carrierType = *template.Token.Params.CarrierType
|
|
} else if template.Token.LegacyCarrierType != nil {
|
|
carrierType = *template.Token.LegacyCarrierType
|
|
g.Log().Warningf(ctx, "shared token template %s uses deprecated token.carrierType; move it to token.params.carrierType", channelConfig.Template.TempKey)
|
|
}
|
|
loginPath := strings.TrimSpace(template.Token.Params.LoginPath)
|
|
if loginPath == "" && strings.TrimSpace(template.Token.LegacyLoginPath) != "" {
|
|
loginPath = strings.TrimSpace(template.Token.LegacyLoginPath)
|
|
g.Log().Warningf(ctx, "shared token template %s uses deprecated token.loginPath; move it to token.params.loginPath", channelConfig.Template.TempKey)
|
|
}
|
|
timeout := resolveUpstreamHTTPTimeout(ctx, channelConfig.Template.TempConfig)
|
|
config := SharedTokenConfig{
|
|
Provider: provider, TokenBaseURL: tokenBaseURL, LoginPath: loginPath, TokenPath: template.Token.Path,
|
|
CarrierType: carrierType, PwdKey: pwdKey, ChannelNo: channelNo, AppKey: strings.TrimSpace(channelConfig.Channel.AppKey), AppID: appID,
|
|
AppSecret: appSecret, SubAccount: subAccount, Secret: secret, OperatorID: operatorID, UserID: userID, TokenType: tokenType,
|
|
UserName: userName, Password: password, PasswayUID: passwayUID,
|
|
ChannelID: channelID, ChannelPName: channelPName, CardNo: cardNo, CMCCAppID: cmccAppID,
|
|
Sign: sign, Timeout: timeout,
|
|
}
|
|
if provider == "daihe" && config.AppID == "" {
|
|
config.AppID = strings.TrimSpace(channelConfig.Channel.AppKey)
|
|
}
|
|
if provider == "daihe" && (config.AppID == "" || config.AppSecret == "" || config.CMCCAppID == "") {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("chan_config.appId, app_secret and chan_config.cmccAppId are required for daihe")
|
|
}
|
|
if provider == "zibo" {
|
|
config.AppID = strings.TrimSpace(channelConfig.Channel.AppKey)
|
|
config.AppSecret = strings.TrimSpace(channelConfig.Channel.AppSecret)
|
|
if config.AppID == "" || config.AppSecret == "" {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("app_key and app_secret are required for zibo")
|
|
}
|
|
}
|
|
if provider == "yunze" {
|
|
config.AppID = strings.TrimSpace(channelConfig.Channel.AppKey)
|
|
if config.AppID == "" {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("app_key is required for yunze")
|
|
}
|
|
}
|
|
if provider == "tengyu" && (config.PwdKey == "" || config.ChannelNo == "") {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("chan_config.pwdKey and chan_config.channelNo are required")
|
|
}
|
|
if provider == "xuzhou_mobile" && config.AppID == "" {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("chan_config.appId is required for xuzhou_mobile")
|
|
}
|
|
if provider == "lanshang" && (config.SubAccount == "" || config.Secret == "") {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("chan_config.subAccount and chan_config.secret are required for lanshang")
|
|
}
|
|
if provider == "qianxun" && (config.OperatorID == "" || config.AppID == "" || config.TokenType == "" || config.AppSecret == "") {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("chan_config.operatorId, chan_config.appId, chan_config.tokenType and app_secret are required for qianxun")
|
|
}
|
|
if provider == "lanshu" && (config.AppID == "" || config.Password == "" || config.Sign == "" || config.TokenType == "") {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("chan_config.appId, chan_config.password, chan_config.sign and chan_config.tokenType are required for lanshu")
|
|
}
|
|
if provider == "beixin" && (config.PwdKey == "" || config.ChannelNo == "") {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("chan_config.pwdKey and chan_config.channelNo are required for beixin")
|
|
}
|
|
if provider == "mobile_public" && (config.UserID == "" || config.AppSecret == "") {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("chan_config.userId and app_secret are required for mobile_public")
|
|
}
|
|
if provider == "apika" && (config.AppKey == "" || config.AppSecret == "" || config.AppID == "") {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("app_key, app_secret and chan_config.appId are required for apika")
|
|
}
|
|
if provider == "henan_lelian" {
|
|
if strings.TrimSpace(config.LoginPath) == "" {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("token.params.loginPath is required for henan_lelian")
|
|
}
|
|
if config.UserName == "" || strings.TrimSpace(config.Password) == "" || config.PasswayUID == "" {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("chan_config.userName, chan_config.password and chan_config.passwayUid are required for henan_lelian")
|
|
}
|
|
}
|
|
if provider == "henan_xinjing" {
|
|
if config.AppID == "" || config.AppSecret == "" {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("chan_config.appId (userId) and app_secret (apikey) are required for henan_xinjing")
|
|
}
|
|
if config.ChannelID == "" || config.ChannelPName == "" {
|
|
return SharedTokenConfig{}, nil, fmt.Errorf("chan_config.channelId and chan_config.channelPname are required for henan_xinjing")
|
|
}
|
|
}
|
|
transIDPrefix := sharedOneLinkTransIDPrefix
|
|
if provider == "daihe" {
|
|
transIDPrefix = config.CMCCAppID
|
|
}
|
|
if provider == "zibo" {
|
|
transIDPrefix = config.AppID
|
|
}
|
|
businessBaseURL := strings.TrimSpace(template.API.BaseURL)
|
|
if businessBaseURL == "" {
|
|
businessBaseURL = sharedOneLinkAPIBaseURL
|
|
}
|
|
return config, &oneLinkAPIConfig{
|
|
BaseURL: businessBaseURL, transIDPrefix: transIDPrefix, Timeout: timeout,
|
|
}, nil
|
|
}
|
|
|
|
func parseOneLinkTemplateAPI(ctx context.Context, channelConfig *model.SimChannelConfigItemCache) *oneLinkAPIConfig {
|
|
if channelConfig == nil || channelConfig.Template == nil || strings.TrimSpace(channelConfig.Template.TempConfig) == "" {
|
|
return nil
|
|
}
|
|
var raw sharedOneLinkTemplateConfig
|
|
if err := json.Unmarshal([]byte(channelConfig.Template.TempConfig), &raw); err != nil {
|
|
g.Log().Warningf(ctx, "parse OneLink template config failed: %v", err)
|
|
return nil
|
|
}
|
|
baseURL := strings.TrimSpace(raw.API.BaseURL)
|
|
if baseURL == "" {
|
|
baseURL = gUrlBase
|
|
}
|
|
timeout := resolveUpstreamHTTPTimeout(ctx, channelConfig.Template.TempConfig)
|
|
return &oneLinkAPIConfig{
|
|
BaseURL: baseURL, TokenPath: raw.Token.Path, Timeout: timeout,
|
|
}
|
|
}
|
|
|
|
type sSimOneLinkShared struct{}
|
|
|
|
func NewSimOneLinkShared() *sSimOneLinkShared { return &sSimOneLinkShared{} }
|
|
|
|
func init() {
|
|
shared := NewSimOneLinkShared()
|
|
service.UpstreamAPI().RegisterTemp(sharedOneLinkTengyuTemplateKey, shared)
|
|
service.UpstreamAPI().RegisterTemp(sharedOneLinkXuzhouTemplateKey, shared)
|
|
service.UpstreamAPI().RegisterTemp(sharedOneLinkLelianTemplateKey, shared)
|
|
service.UpstreamAPI().RegisterTemp(sharedOneLinkQianxunTemplateKey, shared)
|
|
service.UpstreamAPI().RegisterTemp(sharedOneLinkHenanXinjingTemplateKey, shared)
|
|
service.UpstreamAPI().RegisterTemp(sharedOneLinkDaiheTemplateKey, shared)
|
|
service.UpstreamAPI().RegisterTemp(sharedOneLinkMobilePublicTemplateKey, shared)
|
|
service.UpstreamAPI().RegisterTemp(sharedOneLinkMobilePublicPreviousKey, shared)
|
|
service.UpstreamAPI().RegisterTemp(sharedOneLinkMobilePublicLegacyKey, shared)
|
|
service.UpstreamAPI().RegisterTemp(sharedOneLinkApikaTemplateKey, shared)
|
|
service.UpstreamAPI().RegisterTemp(sharedOneLinkYunzeTemplateKey, shared)
|
|
service.UpstreamAPI().RegisterTemp(sharedOneLinkLanshuTemplateKey, shared)
|
|
service.UpstreamAPI().RegisterTemp(sharedOneLinkBeixinTemplateKey, shared)
|
|
}
|
|
|
|
func (s *sSimOneLinkShared) NewTemp(ctx context.Context, channelConfig *model.SimChannelConfigItemCache) model.SimCardChannelI {
|
|
providerConfig, apiConfig, err := parseSharedOneLinkConfig(ctx, channelConfig)
|
|
if err != nil {
|
|
g.Log().Errorf(ctx, "create shared_token_onelink failed: %v", err)
|
|
return nil
|
|
}
|
|
factory, ok := tokenProviderFactory(providerConfig.Provider)
|
|
if !ok || factory == nil {
|
|
g.Log().Errorf(ctx, "create shared_token_onelink failed: unsupported provider %s", providerConfig.Provider)
|
|
return nil
|
|
}
|
|
provider, err := factory(ctx, channelConfig, providerConfig)
|
|
if err != nil || provider == nil {
|
|
if err == nil {
|
|
err = fmt.Errorf("factory returned nil provider")
|
|
}
|
|
g.Log().Errorf(ctx, "create shared_token_onelink failed: %v", err)
|
|
return nil
|
|
}
|
|
return &sSimYD{channelConfig: channelConfig, tokenProvider: provider, apiConfig: apiConfig}
|
|
}
|
|
|
|
func init() {
|
|
RegisterTokenProviderFactory("mobile_public", func(ctx context.Context, channelConfig *model.SimChannelConfigItemCache, cfg SharedTokenConfig) (ydTokenProvider, error) {
|
|
if channelConfig == nil || channelConfig.Channel == nil {
|
|
return nil, fmt.Errorf("channel config is required")
|
|
}
|
|
fetcher := mobilePublicFetcher{provider: NewMobilePublicTokenProvider(MobilePublicTokenConfig{
|
|
BaseURL: cfg.TokenBaseURL,
|
|
Path: cfg.TokenPath,
|
|
UserID: cfg.UserID,
|
|
Secret: cfg.AppSecret,
|
|
Timeout: cfg.Timeout,
|
|
})}
|
|
manager := sharedtoken.NewTokenManager(fetcher, sharedtoken.WithRefreshAhead(30*time.Second))
|
|
manager.SeedFromConfig(channelConfig)
|
|
return &sharedTokenManagerProvider{manager: manager, chanKey: channelConfig.Channel.ChanKey}, nil
|
|
})
|
|
RegisterTokenProviderFactory("apika", func(ctx context.Context, channelConfig *model.SimChannelConfigItemCache, cfg SharedTokenConfig) (ydTokenProvider, error) {
|
|
if channelConfig == nil || channelConfig.Channel == nil {
|
|
return nil, fmt.Errorf("channel config is required")
|
|
}
|
|
fetcher := apikaFetcher{provider: NewApikaTokenProvider(ApikaTokenConfig{
|
|
BaseURL: cfg.TokenBaseURL, Path: cfg.TokenPath, AppKey: cfg.AppKey,
|
|
AppSecret: cfg.AppSecret, AppID: cfg.AppID, Timeout: cfg.Timeout,
|
|
})}
|
|
manager := sharedtoken.NewTokenManager(fetcher, sharedtoken.WithRefreshAhead(30*time.Second))
|
|
manager.SeedFromConfig(channelConfig)
|
|
return &sharedTokenManagerProvider{manager: manager, chanKey: channelConfig.Channel.ChanKey}, nil
|
|
})
|
|
RegisterTokenProviderFactory("qianxun", func(ctx context.Context, channelConfig *model.SimChannelConfigItemCache, cfg SharedTokenConfig) (ydTokenProvider, error) {
|
|
if channelConfig == nil || channelConfig.Channel == nil {
|
|
return nil, fmt.Errorf("channel config is required")
|
|
}
|
|
fetcher := qianxunFetcher{provider: NewQianxunTokenProvider(QianxunTokenConfig{BaseURL: cfg.TokenBaseURL, Path: cfg.TokenPath, OperatorID: cfg.OperatorID, AppID: cfg.AppID, TokenType: cfg.TokenType, Key: cfg.AppSecret, Timeout: cfg.Timeout})}
|
|
manager := sharedtoken.NewTokenManager(fetcher, sharedtoken.WithUntilInvalidated())
|
|
manager.SeedFromConfig(channelConfig)
|
|
return &sharedTokenManagerProvider{manager: manager, chanKey: channelConfig.Channel.ChanKey}, nil
|
|
})
|
|
RegisterTokenProviderFactory("henan_xinjing", func(ctx context.Context, channelConfig *model.SimChannelConfigItemCache, cfg SharedTokenConfig) (ydTokenProvider, error) {
|
|
if channelConfig == nil || channelConfig.Channel == nil {
|
|
return nil, fmt.Errorf("channel config is required")
|
|
}
|
|
fetcher := henanXinjingFetcher{provider: NewHenanXinjingTokenProvider(HenanXinjingTokenConfig{
|
|
BaseURL: cfg.TokenBaseURL,
|
|
Path: cfg.TokenPath,
|
|
UserID: cfg.AppID,
|
|
APIKey: cfg.AppSecret,
|
|
ChannelID: cfg.ChannelID,
|
|
ChannelPName: cfg.ChannelPName,
|
|
CardNo: cfg.CardNo,
|
|
Timeout: cfg.Timeout,
|
|
})}
|
|
manager := sharedtoken.NewTokenManager(fetcher)
|
|
manager.SeedFromConfig(channelConfig)
|
|
return &sharedTokenManagerProvider{manager: manager, chanKey: channelConfig.Channel.ChanKey}, nil
|
|
})
|
|
RegisterTokenProviderFactory("tengyu", func(ctx context.Context, channelConfig *model.SimChannelConfigItemCache, cfg SharedTokenConfig) (ydTokenProvider, error) {
|
|
fetcher := tengyuFetcher{provider: NewTengyuTokenProvider(TengyuTokenConfig{BaseURL: cfg.TokenBaseURL, Path: cfg.TokenPath, PwdKey: cfg.PwdKey, ChannelNo: cfg.ChannelNo, Timeout: cfg.Timeout})}
|
|
// Tengyu does not document a refresh-ahead mechanism. Reuse the cached
|
|
// token until its local one-hour validity expires, then fetch on demand.
|
|
manager := sharedtoken.NewTokenManager(fetcher)
|
|
manager.SeedFromConfig(channelConfig)
|
|
return &sharedTokenManagerProvider{manager: manager, chanKey: channelConfig.Channel.ChanKey}, nil
|
|
})
|
|
RegisterTokenProviderFactory("xuzhou_mobile", func(ctx context.Context, channelConfig *model.SimChannelConfigItemCache, cfg SharedTokenConfig) (ydTokenProvider, error) {
|
|
if channelConfig == nil || channelConfig.Channel == nil {
|
|
return nil, fmt.Errorf("channel config is required")
|
|
}
|
|
fetcher := xuzhouMobileFetcher{provider: NewXuzhouMobileTokenProvider(XuzhouMobileTokenConfig{
|
|
BaseURL: cfg.TokenBaseURL, Path: cfg.TokenPath, AppID: cfg.AppID,
|
|
CarrierType: cfg.CarrierType, TTL: sharedOneLinkXuzhouTokenTTL, Timeout: cfg.Timeout,
|
|
})}
|
|
manager := sharedtoken.NewTokenManager(fetcher)
|
|
manager.SeedFromConfig(channelConfig)
|
|
return &sharedTokenManagerProvider{manager: manager, chanKey: channelConfig.Channel.ChanKey}, nil
|
|
})
|
|
RegisterTokenProviderFactory("daihe", func(ctx context.Context, channelConfig *model.SimChannelConfigItemCache, cfg SharedTokenConfig) (ydTokenProvider, error) {
|
|
if channelConfig == nil || channelConfig.Channel == nil {
|
|
return nil, fmt.Errorf("channel config is required")
|
|
}
|
|
fetcher := daiheTokenFetcher{provider: NewDaiheTokenProvider(DaiheTokenConfig{
|
|
BaseURL: cfg.TokenBaseURL, Path: cfg.TokenPath, AppID: cfg.AppID, AppSecret: cfg.AppSecret,
|
|
CMCCAppID: cfg.CMCCAppID, Timeout: cfg.Timeout,
|
|
})}
|
|
manager := sharedtoken.NewTokenManager(fetcher)
|
|
manager.SeedFromConfig(channelConfig)
|
|
return &sharedTokenManagerProvider{manager: manager, chanKey: channelConfig.Channel.ChanKey, includeDeviceToken: true}, nil
|
|
})
|
|
RegisterTokenProviderFactory("zibo", func(ctx context.Context, channelConfig *model.SimChannelConfigItemCache, cfg SharedTokenConfig) (ydTokenProvider, error) {
|
|
if channelConfig == nil || channelConfig.Channel == nil {
|
|
return nil, fmt.Errorf("channel config is required")
|
|
}
|
|
fetcher := ziboFetcher{provider: NewZiboTokenProvider(ZiboTokenConfig{
|
|
BaseURL: cfg.TokenBaseURL,
|
|
Path: cfg.TokenPath,
|
|
AppID: cfg.AppID,
|
|
Password: cfg.AppSecret,
|
|
Timeout: cfg.Timeout,
|
|
})}
|
|
manager := sharedtoken.NewTokenManager(fetcher)
|
|
manager.SeedFromConfig(channelConfig)
|
|
return &sharedTokenManagerProvider{manager: manager, chanKey: channelConfig.Channel.ChanKey}, nil
|
|
})
|
|
RegisterTokenProviderFactory("yunze", func(ctx context.Context, channelConfig *model.SimChannelConfigItemCache, cfg SharedTokenConfig) (ydTokenProvider, error) {
|
|
if channelConfig == nil || channelConfig.Channel == nil {
|
|
return nil, fmt.Errorf("channel config is required")
|
|
}
|
|
fetcher := yunzeFetcher{provider: NewYunzeTokenProvider(YunzeTokenConfig{
|
|
BaseURL: cfg.TokenBaseURL,
|
|
Path: cfg.TokenPath,
|
|
AppID: cfg.AppID,
|
|
Timeout: cfg.Timeout,
|
|
})}
|
|
manager := sharedtoken.NewTokenManager(fetcher, sharedtoken.WithRefreshAhead(30*time.Second))
|
|
manager.SeedFromConfig(channelConfig)
|
|
return &sharedTokenManagerProvider{manager: manager, chanKey: channelConfig.Channel.ChanKey}, nil
|
|
})
|
|
RegisterTokenProviderFactory("lanshu", func(ctx context.Context, channelConfig *model.SimChannelConfigItemCache, cfg SharedTokenConfig) (ydTokenProvider, error) {
|
|
if channelConfig == nil || channelConfig.Channel == nil {
|
|
return nil, fmt.Errorf("channel config is required")
|
|
}
|
|
fetcher := lanshuFetcher{provider: NewLanShuTokenProvider(LanShuTokenConfig{
|
|
BaseURL: cfg.TokenBaseURL, Path: cfg.TokenPath, AppID: cfg.AppID,
|
|
Password: cfg.Password, Sign: cfg.Sign, TokenType: cfg.TokenType, Timeout: cfg.Timeout,
|
|
})}
|
|
// The upstream reports a real expireTime (~19-26 minutes) and issues a
|
|
// new token per request, so refresh proactively inside the window.
|
|
manager := sharedtoken.NewTokenManager(fetcher, sharedtoken.WithRefreshAhead(60*time.Second))
|
|
manager.SeedFromConfig(channelConfig)
|
|
return &sharedTokenManagerProvider{manager: manager, chanKey: channelConfig.Channel.ChanKey}, nil
|
|
})
|
|
RegisterTokenProviderFactory("beixin", func(ctx context.Context, channelConfig *model.SimChannelConfigItemCache, cfg SharedTokenConfig) (ydTokenProvider, error) {
|
|
if channelConfig == nil || channelConfig.Channel == nil {
|
|
return nil, fmt.Errorf("channel config is required")
|
|
}
|
|
fetcher := beixinFetcher{provider: NewBeixinTokenProvider(BeixinTokenConfig{
|
|
BaseURL: cfg.TokenBaseURL, Path: cfg.TokenPath, PwdKey: cfg.PwdKey,
|
|
ChannelNo: cfg.ChannelNo, Timeout: cfg.Timeout,
|
|
})}
|
|
// The upstream returns no expireTime, so the provider holds a local
|
|
// one-hour window (mirroring tengyu). Refresh proactively inside it
|
|
// rather than waiting for a 12021 rejection.
|
|
manager := sharedtoken.NewTokenManager(fetcher, sharedtoken.WithRefreshAhead(30*time.Second))
|
|
manager.SeedFromConfig(channelConfig)
|
|
return &sharedTokenManagerProvider{manager: manager, chanKey: channelConfig.Channel.ChanKey}, nil
|
|
})
|
|
RegisterTokenProviderFactory("lanshang", func(ctx context.Context, channelConfig *model.SimChannelConfigItemCache, cfg SharedTokenConfig) (ydTokenProvider, error) {
|
|
if channelConfig == nil || channelConfig.Channel == nil {
|
|
return nil, fmt.Errorf("channel config is required")
|
|
}
|
|
if cfg.SubAccount == "" || cfg.Secret == "" {
|
|
return nil, fmt.Errorf("lanshang subAccount and secret are required")
|
|
}
|
|
fetcher := lanshangFetcher{provider: NewLanShangTokenProvider(LanShangTokenConfig{
|
|
BaseURL: cfg.TokenBaseURL, Path: cfg.TokenPath,
|
|
SubAccount: cfg.SubAccount, Secret: cfg.Secret, Timeout: cfg.Timeout,
|
|
})}
|
|
manager := sharedtoken.NewTokenManager(fetcher)
|
|
manager.SeedFromConfig(channelConfig)
|
|
return &sharedTokenManagerProvider{manager: manager, chanKey: channelConfig.Channel.ChanKey}, nil
|
|
})
|
|
RegisterTokenProviderFactory("henan_lelian", func(ctx context.Context, channelConfig *model.SimChannelConfigItemCache, cfg SharedTokenConfig) (ydTokenProvider, error) {
|
|
if channelConfig == nil || channelConfig.Channel == nil {
|
|
return nil, fmt.Errorf("channel config is required")
|
|
}
|
|
fetcher := henanLelianFetcher{provider: NewHenanLelianTokenProvider(HenanLelianTokenConfig{
|
|
BaseURL: cfg.TokenBaseURL, LoginPath: cfg.LoginPath, Path: cfg.TokenPath,
|
|
UserName: cfg.UserName, Password: cfg.Password, PasswayUID: cfg.PasswayUID,
|
|
Timeout: cfg.Timeout,
|
|
})}
|
|
manager := sharedtoken.NewTokenManager(fetcher, sharedtoken.WithUntilInvalidated())
|
|
manager.SeedFromConfig(channelConfig)
|
|
return &sharedTokenManagerProvider{manager: manager, chanKey: channelConfig.Channel.ChanKey}, nil
|
|
})
|
|
}
|