修复初始化完成后的服务切换
Some checks are pending
Security Checks / security (push) Waiting to run

This commit is contained in:
smail 2026-08-30 21:14:15 +08:00
parent 961d9ff222
commit 492d511551
2 changed files with 215 additions and 36 deletions

View File

@ -12,6 +12,8 @@ import {Plus} from '@element-plus/icons-vue'
import {http} from "@/utils/axios";
import {buildFinalURL as buildSetupFinalURL, probeService} from './SetupNavigation.js'
const setupRequest = (payload) => http.post("/api/v1/setup", payload).then((response) => {
if (!response || typeof response.errorNo !== "number") {
throw new Error(lang.setup_failed)
@ -374,48 +376,78 @@ export function usePage() {
return null
})
}
const buildFinalURL = (sslData) => {
const scheme = "https:"
const configuredDomain = String(domainSettings.web_domain || "").trim()
if (!configuredDomain) {
return window.location.href
}
const buildFinalURL = (sslData) => buildSetupFinalURL(sslData, domainSettings.web_domain)
let target
try {
target = new URL(`${scheme}//${configuredDomain}`)
} catch (error) {
return window.location.href
}
let statusTimer = null
let statusCheckRunning = false
let statusCheckStartedAt = 0
const statusCheckTimeout = 10 * 60 * 1000
const currentHost = window.location.hostname.toLowerCase()
const targetHost = target.hostname.toLowerCase()
const currentPort = window.location.port
const isSameHost = currentHost === targetHost
const targetPort = Number(sslData?.httpsPort) || 443
const proxyMode = sslData?.tlsMode === "proxy"
if (isSameHost && currentPort && !proxyMode) {
target.port = targetPort > 0 ? String(targetPort) : currentPort
} else if (targetPort > 0 && ((scheme === "http:" && targetPort !== 80) || (scheme === "https:" && targetPort !== 443))) {
target.port = String(targetPort)
const stopStatusCheck = () => {
if (statusTimer !== null) {
window.clearTimeout(statusTimer)
statusTimer = null
}
target.pathname = window.location.pathname
target.search = window.location.search
target.hash = window.location.hash
return target.toString()
statusCheckRunning = false
statusCheckStartedAt = 0
}
const checkStatus = () => {
setupRequest({"action": "get", "step": "ssl"}).then((res) => {
if (res.errorNo === 0 && res.data.ready) {
window.location.href = buildFinalURL(res.data)
return
const navigateToFormalService = (sslData) => {
stopStatusCheck()
fullscreenLoading.value = false
const target = buildFinalURL(sslData)
if (target && target !== window.location.href) {
window.location.href = target
}
}
const scheduleStatusCheck = () => {
if (statusTimer !== null) {
window.clearTimeout(statusTimer)
}
statusTimer = window.setTimeout(() => {
statusTimer = null
checkStatus()
}, 1000)
}
const checkStatus = async () => {
if (statusCheckRunning) {
return
}
if (!statusCheckStartedAt) {
statusCheckStartedAt = Date.now()
}
if (Date.now() - statusCheckStartedAt > statusCheckTimeout) {
stopStatusCheck()
fullscreenLoading.value = false
ElMessage.error(lang.setup_failed)
return
}
statusCheckRunning = true
let sslData = sslRuntime.value || {}
try {
const response = await setupRequest({"action": "get", "step": "ssl"})
if (response.errorNo === 0) {
sslData = response.data || sslData
sslRuntime.value = sslData
if (sslData.ready) {
navigateToFormalService(sslData)
return
}
}
setTimeout(checkStatus, 1000)
}).catch(() => {
setTimeout(checkStatus, 1000)
})
} catch (error) {
// 服务切换期间失败属于预期状态。
} finally {
statusCheckRunning = false
}
if (await probeService(sslData, domainSettings.web_domain)) {
navigateToFormalService(sslData)
return
}
scheduleStatusCheck()
}
const setDomainConfig = () => {

View File

@ -0,0 +1,147 @@
const DEFAULT_HTTPS_PORT = 443
const REDIRECT_STATUS_MIN = 300
const REDIRECT_STATUS_MAX = 399
const currentLocation = () => {
if (typeof window === 'undefined' || !window.location) {
return null
}
return window.location
}
const normalizeDomain = (value) => {
const domain = String(value || '').trim()
if (!domain) {
return ''
}
return domain.replace(/^[a-z][a-z\d+.-]*:\/\//i, '').replace(/\/.*$/, '')
}
const isNonDefaultPort = (protocol, port) => {
if (!port) {
return false
}
return protocol === 'https:' ? port !== String(DEFAULT_HTTPS_PORT) : port !== '80'
}
export const isProxyTLSMode = (sslData) => sslData?.tlsMode === 'proxy'
export const buildFinalURL = (sslData, webDomain, location = currentLocation()) => {
if (!location) {
return ''
}
const configuredDomain = normalizeDomain(webDomain)
if (!configuredDomain) {
return location.href
}
let target
try {
target = new URL(`https://${configuredDomain}`)
} catch (error) {
return location.href
}
const proxyMode = isProxyTLSMode(sslData)
const configuredPort = target.port
const targetPort = Number(sslData?.httpsPort)
if (!proxyMode) {
target.port = targetPort > 0 && targetPort !== DEFAULT_HTTPS_PORT ? String(targetPort) : ''
} else if (!isNonDefaultPort(target.protocol, configuredPort)) {
target.port = ''
}
target.pathname = location.pathname
target.search = location.search
target.hash = location.hash
return target.toString()
}
const createURL = (value, path) => {
try {
const url = new URL(path, value)
url.search = ''
url.hash = ''
return url.toString()
} catch (error) {
return ''
}
}
export const getServiceProbeURLs = (sslData, webDomain, location = currentLocation()) => {
if (!location) {
return []
}
const urls = []
const add = (value, path) => {
const url = createURL(value, path)
if (url && !urls.includes(url)) {
urls.push(url)
}
}
add(location.href, '/api/ping')
add(location.href, '/api/v1/ping')
const finalURL = buildFinalURL(sslData, webDomain, location)
if (finalURL) {
add(finalURL, '/api/ping')
add(finalURL, '/api/v1/ping')
}
return urls
}
export const isServiceProbeSuccess = (response) => {
if (!response) {
return false
}
return response.status >= 200 && response.status < 300
}
export const isServiceRedirect = (response, location = currentLocation()) => {
if (!response || !location) {
return false
}
if (response.status < REDIRECT_STATUS_MIN || response.status > REDIRECT_STATUS_MAX) {
return false
}
try {
return new URL(response.url).origin === location.origin
} catch (error) {
return false
}
}
export const probeService = async (sslData, webDomain, location = currentLocation()) => {
if (!location || typeof fetch !== 'function') {
return false
}
const proxyMode = isProxyTLSMode(sslData)
const urls = getServiceProbeURLs(sslData, webDomain, location)
for (const url of urls) {
try {
const response = await fetch(url, {
method: 'GET',
credentials: 'include',
cache: 'no-store',
redirect: 'manual',
headers: {
Accept: 'application/json'
}
})
if (isServiceProbeSuccess(response)) {
return true
}
if (!proxyMode && isServiceRedirect(response, location)) {
return true
}
} catch (error) {
// 服务切换期间失败属于预期状态。
}
}
return false
}