feat: sync host-based public app links

This commit is contained in:
A 2026-07-22 21:43:56 +08:00
parent 10de462191
commit 8cfb6f74c1
19 changed files with 375 additions and 26 deletions

View file

@ -458,7 +458,7 @@ func (r *Router) connectedBusinessBotPeerSettings(ctx context.Context, ownerUser
settings.BusinessBotManageURL = r.connectedBusinessBotManageURL(botUser)
}
if settings.BusinessBotManageURL == "" {
settings.BusinessBotManageURL = "telesrv://business-bot"
settings.BusinessBotManageURL = r.publicAppLink("business-bot")
}
return settings, nil
}

View file

@ -0,0 +1,32 @@
package rpc
import (
"testing"
"github.com/iamxvbaba/td/clock"
"go.uber.org/zap"
)
func TestRouterPublicAppLinkUsesConfiguredBaseAndLegacyDefault(t *testing.T) {
legacy := New(Config{}, Deps{}, zap.NewNop(), clock.System)
if got, want := legacy.publicAppLink("business-bot"), "telesrv://business-bot"; got != want {
t.Fatalf("legacy business bot link = %q, want %q", got, want)
}
hosted := New(Config{
PublicAppScheme: "telesrv",
PublicAppLinkBase: "owpg://tenant.example.test",
}, Deps{}, zap.NewNop(), clock.System)
if got, want := hosted.publicAppLink("business-bot"), "owpg://tenant.example.test/business-bot"; got != want {
t.Fatalf("hosted business bot link = %q, want %q", got, want)
}
}
func TestRouterRejectsInvalidPublicAppLinkConfig(t *testing.T) {
defer func() {
if recover() == nil {
t.Fatal("New did not fail fast for an invalid public app link base")
}
}()
_ = New(Config{PublicAppLinkBase: "owpg://tenant.example.test/root"}, Deps{}, zap.NewNop(), clock.System)
}

View file

@ -22,6 +22,10 @@ func (r *Router) publicLinkHost() string {
return links.Host(r.cfg.PublicBaseURL)
}
func (r *Router) publicAppLink(route string) string {
return r.appLinks.Build(route, nil)
}
func publicLinkWithBaseURL(baseURL, path string) string {
return links.Build(baseURL, path, nil)
}

View file

@ -20,6 +20,7 @@ import (
"github.com/iamxvbaba/td/tlprofile"
compatandroid "telesrv/internal/compat/android"
"telesrv/internal/domain"
"telesrv/internal/links"
"telesrv/internal/observability/dbtrace"
)
@ -83,6 +84,10 @@ type Config struct {
RtmpIngestURL string
// PublicBaseURL 是所有客户端可见 telesrv 链接的公开根 URL。
PublicBaseURL string
// PublicAppScheme/PublicAppLinkBase 控制客户端 deep linkbase 为空时
// 保持 <scheme>://<route>,非空时生成 <base>/<route>。
PublicAppScheme string
PublicAppLinkBase string
// TempKeyResolveCacheTTL 是 PFS temp→perm auth key 解析的进程内缓存有效期。>0 时同一 temp key
// 在 TTL 内复用上次解析、跳过每帧 ResolveAuthKey 的 PG 查询0默认/测试)关闭=每帧重校验。
// 显式撤销会删除协议 auth key、清缓存并断开活跃连接TTL 只影响自然过期或异常路径下的
@ -99,6 +104,7 @@ type Config struct {
// 剥离 invokeWithLayer / initConnection / invokeWithoutUpdates / invokeAfter*,并兜底未注册 RPC。
type Router struct {
cfg Config
appLinks links.AppLinkBuilder
log *zap.Logger
clock clock.Clock
deps Deps
@ -236,11 +242,15 @@ type authUserCacheEntry struct {
// New 创建 Router由各业务域自行注册其 RPC handlerregisterHelp/Auth/Users/Updates
func New(cfg Config, deps Deps, log *zap.Logger, clk clock.Clock) *Router {
assertNoTypedNilDeps(deps)
appLinks, err := links.NewAppLinkBuilder(cfg.PublicAppScheme, cfg.PublicAppLinkBase)
if err != nil {
panic(fmt.Sprintf("initialize public app links: %v", err))
}
instanceID := cfg.InstanceID
if instanceID == "" {
instanceID = fmt.Sprintf("%016x", randomNonZeroInt64())
}
r := &Router{cfg: cfg, log: log, clock: clk, deps: deps, exactProfiles: make(map[clientInfoSessionKey]exactSessionProfileEntry), authLayerEvidence: make(map[[8]byte]authLayerDefaultEvidence), presence: newPresenceTracker(), callbacks: newCallbackRegistry(deps.BotCallbacks), inlines: newInlineRegistry(botInlineQueryTTL, deps.Inline), webviews: newWebViewRegistry(webViewSessionTTL, deps.Inline), loginTokens: newLoginTokenRegistry(), botAPIUpdates: newBotAPIUpdateNotifier(), tempKeyResolveCache: newTempKeyResolveCache(cfg.TempKeyResolveCacheMaxEntries), storyProjectionCache: newStoryProjectionCache(clk.Now), storyPinnedCache: newStoryPinnedAvailableCache(clk.Now), storyPinnedListCache: newStoryPinnedStoriesCache(clk.Now), channelFullBotCache: newChannelFullBotInfoCache(clk.Now), userFullProjectionCache: newUserFullProjectionCache(clk.Now), peerSettingsProjectionCache: newPeerSettingsProjectionCache(clk.Now), channelFullProjectionCache: newChannelFullProjectionCache(clk.Now), emojiStickers: newEmojiStickerIndex(clk.Now), notifySettings: newNotifySettingsCache(clk.Now), stickerCatalog: newStickerCatalogCache(clk.Now), accountSettings: newAccountSettingsCache(clk.Now), accountFreezeWake: make(chan struct{}, 1), instanceID: instanceID}
r := &Router{cfg: cfg, appLinks: appLinks, log: log, clock: clk, deps: deps, exactProfiles: make(map[clientInfoSessionKey]exactSessionProfileEntry), authLayerEvidence: make(map[[8]byte]authLayerDefaultEvidence), presence: newPresenceTracker(), callbacks: newCallbackRegistry(deps.BotCallbacks), inlines: newInlineRegistry(botInlineQueryTTL, deps.Inline), webviews: newWebViewRegistry(webViewSessionTTL, deps.Inline), loginTokens: newLoginTokenRegistry(), botAPIUpdates: newBotAPIUpdateNotifier(), tempKeyResolveCache: newTempKeyResolveCache(cfg.TempKeyResolveCacheMaxEntries), storyProjectionCache: newStoryProjectionCache(clk.Now), storyPinnedCache: newStoryPinnedAvailableCache(clk.Now), storyPinnedListCache: newStoryPinnedStoriesCache(clk.Now), channelFullBotCache: newChannelFullBotInfoCache(clk.Now), userFullProjectionCache: newUserFullProjectionCache(clk.Now), peerSettingsProjectionCache: newPeerSettingsProjectionCache(clk.Now), channelFullProjectionCache: newChannelFullProjectionCache(clk.Now), emojiStickers: newEmojiStickerIndex(clk.Now), notifySettings: newNotifySettingsCache(clk.Now), stickerCatalog: newStickerCatalogCache(clk.Now), accountSettings: newAccountSettingsCache(clk.Now), accountFreezeWake: make(chan struct{}, 1), instanceID: instanceID}
r.channelFanout = newChannelFanoutDispatcher(r, defaultChannelFanoutShards, defaultChannelFanoutBuffer)
r.botAPIEnqueueQueue = newBotAPIEnqueueDispatcher(log, defaultBotAPIEnqueueBuffer)
r.webPageResolveSem = make(chan struct{}, webPageResolveConcurrency)