package upstreamimpl import ( "context" "encoding/json" "fmt" "strings" "sync" "time" "github.com/gogf/gf/v2/frame/g" "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" ) 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 }) }