import type { TenantContext } from '~/app/types' type RuntimeConfig = ReturnType /** * 从 Host 解析租户上下文 * * 策略: * 1. 如果 host 匹配 `{prefix}-{tenantId}.{baseDomain}` 模式(前缀走白名单、 * 主域走配置清单),从子域名提取 tenantId * - 白名单前缀:site / shop / store / mp / app / oa(由 subdomainPrefixes 配置) * - 主域清单:shoplnk.cn / sitelink.cn / wsdns.cn(由 baseDomains 配置) * 2. 否则返回环境变量默认值(后续可由域名绑定表查询覆盖) * * 注意:子域名解析仅为「兜底」来源(优先级低于 appDomain 绑定表与 * app_product.domain),因此后台绑定的合法自定义域名天然优先,不会被子域名覆盖。 */ export function resolveTenantFromHost( host: string, baseDomains: string[] | string, prefixes: string[] | string, defaultTenantId: string, defaultAppId: string, defaultTemplateId: string ): TenantContext { const hostname = host.split(':')[0] // 防御性类型保障:runtimeConfig 序列化可能将数组退化为字符串 const safeDomains = Array.isArray(baseDomains) ? baseDomains : String(baseDomains || '').split(',').map((s: string) => s.trim()).filter(Boolean) const safePrefixes = Array.isArray(prefixes) ? prefixes : String(prefixes || '').split(',').map((s: string) => s.trim()).filter(Boolean) const domains = (safeDomains && safeDomains.length ? safeDomains : ['shoplnk.cn'] ).map(d => escapeRegex(d.trim())).filter(Boolean).join('|') const prefixList = (safePrefixes && safePrefixes.length ? safePrefixes : ['site'] ).map(p => escapeRegex(p.trim())).filter(Boolean).join('|') // 匹配 {site|shop|store|mp|app|oa}-{tenantId}.{baseDomain} const subdomainPattern = new RegExp( `^(?:${prefixList})-(\\d+)\\.(?:${domains})$`, 'i' ) const match = hostname.match(subdomainPattern) if (match && match[1]) { return { tenantId: match[1], appId: defaultAppId, templateId: defaultTemplateId, source: 'subdomain', host } } // 默认使用环境变量 return { tenantId: defaultTenantId, appId: defaultAppId, templateId: defaultTemplateId, source: 'env', host } } /** * 从 event.context 获取租户上下文 * 如果不存在则回退到运行时配置默认值 */ export function getTenantFromContext( event: Parameters[0]>[0], config: RuntimeConfig ): TenantContext { const ctx = event.context?.tenant as TenantContext | undefined if (ctx?.tenantId) { return ctx } return { tenantId: config.public.tenantId as string, appId: config.public.appId as string, templateId: config.public.templateId as string, source: 'env' } } function escapeRegex(str: string): string { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') }