| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315 |
- import {
- getDomainParts,
- setDomainParts,
- getDefaultRootDomain,
- DEFAULT_DOMAIN,
- } from '@/config/domainState'
- import {
- DOMAIN_CONFIG_URL,
- DOMAIN_INIT_TIMEOUT,
- DOMAIN_PROBE_PATH,
- DOMAIN_PROBE_TIMEOUT,
- DOMAIN_HEARTBEAT_INTERVAL,
- } from '@/config/domainConstants'
- let resolving = null
- let domainReady = false
- let domainReadyPromise = null
- let domainReadyResolve = null
- let initTimeoutId = null
- let heartbeatTimer = null
- function createDomainReadyPromise() {
- domainReadyPromise = new Promise((resolve) => {
- domainReadyResolve = resolve
- })
- }
- function markDomainReady() {
- if (domainReady) return
- domainReady = true
- if (initTimeoutId) {
- clearTimeout(initTimeoutId)
- initTimeoutId = null
- }
- domainReadyResolve?.()
- }
- function buildActiveSecureUrl() {
- const { ht, ho, dt } = getDomainParts()
- return `${ht}//secure.${ho}.${dt}`
- }
- function notifyDomainChanged(parts) {
- try {
- uni.$emit('domain-changed', {
- ...parts,
- activeSecureUrl: parts.secureUrl || buildActiveSecureUrl(),
- })
- } catch (e) {
- // ignore
- }
- }
- /** 业务请求需等待域名就绪(仅 APP) */
- export function whenDomainReady() {
- // #ifndef APP-PLUS
- return Promise.resolve()
- // #endif
- if (domainReady) return Promise.resolve()
- if (!domainReadyPromise) createDomainReadyPromise()
- return domainReadyPromise
- }
- /** 当前域名状态(调试用) */
- export function getDomainStatus() {
- return {
- ready: domainReady,
- parts: getDomainParts(),
- activeSecureUrl: buildActiveSecureUrl(),
- defaultRoot: getDefaultRootDomain(),
- defaultSecureUrl: `https://secure.${DEFAULT_DOMAIN.ho}.${DEFAULT_DOMAIN.dt}`,
- }
- }
- /**
- * 解析根域名配置项
- * 支持:cwgbroker.club / secure.cwgbroker.club / https://secure.cwgbroker.club
- */
- export function parseRootDomain(input) {
- if (!input || typeof input !== 'string') {
- throw new Error('无效的域名')
- }
- let host = input.trim()
- host = host.replace(/^https?:\/\//i, '')
- host = host.split('/')[0]
- host = host.replace(/^secure\./i, '')
- const parts = host.split('.')
- if (parts.length < 2) {
- throw new Error(`域名格式不正确: ${input}`)
- }
- const dt = parts[parts.length - 1]
- const ho = parts[parts.length - 2]
- const ht = 'https:'
- return {
- ho,
- dt,
- ht,
- root: `${ho}.${dt}`,
- secureUrl: `${ht}//secure.${ho}.${dt}`,
- }
- }
- /** @deprecated 兼容旧调用 */
- export function parseSecureUrl(url) {
- return parseRootDomain(url)
- }
- function parseResponseData(data) {
- if (!data) return null
- if (typeof data === 'string') {
- try {
- return JSON.parse(data)
- } catch (e) {
- return null
- }
- }
- return data
- }
- function requestJson(url, timeout = 8000) {
- return new Promise((resolve, reject) => {
- uni.request({
- url,
- method: 'GET',
- timeout,
- success: (res) => {
- if (res.statusCode !== 200) {
- reject(new Error(`请求失败: ${res.statusCode}`))
- return
- }
- const body = parseResponseData(res.data)
- if (!body) {
- reject(new Error('配置解析失败'))
- return
- }
- resolve(body)
- },
- fail: (err) => reject(err || new Error('网络请求失败')),
- })
- })
- }
- /** 探测 secure 子域是否可用 */
- export function probeSecureUrl(secureUrl, timeout = DOMAIN_PROBE_TIMEOUT) {
- const url = `${secureUrl.replace(/\/$/, '')}${DOMAIN_PROBE_PATH}?_t=${Date.now()}`
- return new Promise((resolve) => {
- uni.request({
- url,
- method: 'GET',
- timeout,
- success: (res) => resolve(res.statusCode === 200),
- fail: () => resolve(false),
- })
- })
- }
- async function pickActiveRootDomain(remoteConfig) {
- const candidates = []
- if (remoteConfig?.primary) {
- candidates.push({ root: remoteConfig.primary, priority: 0 })
- }
- ;(remoteConfig?.backup || []).forEach((root, index) => {
- if (root) candidates.push({ root, priority: index + 1 })
- })
- if (!candidates.length) {
- throw new Error('远程域名配置为空')
- }
- const checks = await Promise.all(
- candidates.map(async (item) => {
- const parsed = parseRootDomain(item.root)
- return {
- ...item,
- parsed,
- ok: await probeSecureUrl(parsed.secureUrl),
- }
- })
- )
- const winner = checks
- .filter((item) => item.ok)
- .sort((a, b) => a.priority - b.priority)[0]
- if (!winner) {
- console.warn('[dynamicDomain] primary/backup 均不可用,回退代码默认域名')
- return getDefaultRootDomain()
- }
- return winner.root
- }
- function applyRootDomain(root, meta = {}) {
- const parsed = parseRootDomain(root)
- const before = getDomainParts()
- setDomainParts(parsed)
- const changed = before.ho !== parsed.ho || before.dt !== parsed.dt
- if (changed) {
- notifyDomainChanged(parsed)
- console.log('[dynamicDomain] 域名已切换', {
- from: `${before.ho}.${before.dt}`,
- to: parsed.root,
- secureUrl: parsed.secureUrl,
- version: meta.version,
- fallback: meta.fallback || false,
- })
- } else {
- console.log('[dynamicDomain] 域名可用', parsed.secureUrl)
- }
- return parsed
- }
- /** primary/backup 均不可用或拉配置失败时,回退代码内置默认域名 */
- function applyDefaultDomain(meta = {}) {
- return applyRootDomain(getDefaultRootDomain(), { ...meta, fallback: true })
- }
- async function fetchRemoteConfig() {
- const data = await requestJson(`${DOMAIN_CONFIG_URL}?_t=${Date.now()}`)
- if (!data?.primary) {
- throw new Error('远程配置缺少 primary 字段')
- }
- return data
- }
- /** 拉取配置并探测可用根域名 */
- export async function resolveDynamicDomain() {
- if (resolving) return resolving
- resolving = (async () => {
- try {
- const remoteConfig = await fetchRemoteConfig()
- const activeRoot = await pickActiveRootDomain(remoteConfig)
- return applyRootDomain(activeRoot, { version: remoteConfig.version })
- } catch (error) {
- console.warn('[dynamicDomain] 解析失败,回退代码默认域名', error)
- return applyDefaultDomain()
- } finally {
- resolving = null
- }
- })()
- return resolving
- }
- function scheduleInitTimeout() {
- if (initTimeoutId) return
- initTimeoutId = setTimeout(() => {
- console.warn(`[dynamicDomain] 初始化超过 ${DOMAIN_INIT_TIMEOUT}ms,放行业务请求`)
- markDomainReady()
- }, DOMAIN_INIT_TIMEOUT)
- }
- /** 启动时解析域名(main.js 调用) */
- export function initDynamicDomain() {
- // #ifndef APP-PLUS
- return Promise.resolve(null)
- // #endif
- if (!domainReadyPromise) createDomainReadyPromise()
- try {
- uni.removeStorageSync('app-dynamic-domain')
- } catch (e) {
- // ignore
- }
- scheduleInitTimeout()
- return resolveDynamicDomain()
- .finally(() => {
- markDomainReady()
- startDomainHeartbeat()
- console.log('[dynamicDomain] 域名就绪', buildActiveSecureUrl())
- })
- }
- /** 每次 App 进入前台时重新检测 */
- export function refreshDynamicDomainOnShow() {
- // #ifndef APP-PLUS
- return Promise.resolve(null)
- // #endif
- if (!domainReady) {
- return whenDomainReady()
- }
- return resolveDynamicDomain().catch((e) => {
- console.warn('[dynamicDomain] 前台刷新失败', e)
- })
- }
- /** App 运行期间定时心跳检测(10 分钟) */
- export function startDomainHeartbeat() {
- // #ifndef APP-PLUS
- return
- // #endif
- stopDomainHeartbeat()
- heartbeatTimer = setInterval(() => {
- console.log('[dynamicDomain] 心跳检测')
- resolveDynamicDomain().catch((e) => {
- console.warn('[dynamicDomain] 心跳检测失败', e)
- })
- }, DOMAIN_HEARTBEAT_INTERVAL)
- }
- export function stopDomainHeartbeat() {
- if (heartbeatTimer) {
- clearInterval(heartbeatTimer)
- heartbeatTimer = null
- }
- }
|