- Expand batch upload list area and optimize preview image layout - Improve upload speed tracking and display (server-side & client-side) - Reduce network detection log noise - Optimize Docker build process (caching, env validation) - Add shadow environment deployment support
202 lines
6.1 KiB
TypeScript
202 lines
6.1 KiB
TypeScript
// 网络管理:智能检测内外网
|
||
|
||
// 判断是否为内网IP地址
|
||
function isInternalIP(hostname: string): boolean {
|
||
// 如果是localhost
|
||
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
||
return true
|
||
}
|
||
|
||
// 判断是否为内网IP段
|
||
// 10.0.0.0 - 10.255.255.255
|
||
// 172.16.0.0 - 172.31.255.255
|
||
// 192.168.0.0 - 192.168.255.255
|
||
const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/
|
||
const match = hostname.match(ipv4Regex)
|
||
|
||
if (match) {
|
||
const [, a, b] = match.map(Number)
|
||
|
||
// 10.x.x.x
|
||
if (a === 10) return true
|
||
|
||
// 172.16.x.x - 172.31.x.x
|
||
if (a === 172 && b >= 16 && b <= 31) return true
|
||
|
||
// 192.168.x.x
|
||
if (a === 192 && b === 168) return true
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
function getCurrentHost(): string {
|
||
if (typeof window === 'undefined') return ''
|
||
return window.location.host
|
||
}
|
||
|
||
function getCurrentHostname(): string {
|
||
if (typeof window === 'undefined') return ''
|
||
return window.location.hostname
|
||
}
|
||
|
||
export interface NetworkStatus {
|
||
isInternal: boolean
|
||
host: string
|
||
lastChecked?: number // 上次检测时间戳
|
||
}
|
||
|
||
let currentNetwork: NetworkStatus | null = null
|
||
let networkChangeListeners: ((status: NetworkStatus) => void)[] = []
|
||
|
||
// 网络状态缓存有效期(1分钟,缩短以便更快响应网络变化)
|
||
const NETWORK_CACHE_TTL = 60 * 1000
|
||
|
||
// 初始化网络检测(优先使用后端API获取准确状态)
|
||
export async function initializeNetwork(forceRefresh: boolean = false): Promise<NetworkStatus> {
|
||
if (typeof window === 'undefined') {
|
||
return { isInternal: false, host: '' }
|
||
}
|
||
|
||
// 如果已经检测过且缓存未过期,直接返回缓存结果
|
||
if (!forceRefresh && currentNetwork && currentNetwork.lastChecked) {
|
||
const cacheAge = Date.now() - currentNetwork.lastChecked
|
||
if (cacheAge < NETWORK_CACHE_TTL) {
|
||
return currentNetwork
|
||
}
|
||
}
|
||
|
||
const host = getCurrentHost()
|
||
const hostname = getCurrentHostname()
|
||
const previousStatus = currentNetwork?.isInternal
|
||
|
||
// 优先调用后端API获取准确的网络状态(后端可以通过请求头获取真实的客户端IP)
|
||
try {
|
||
const response = await fetch('/api/network-status', {
|
||
cache: 'no-store', // 不缓存,确保获取最新状态
|
||
})
|
||
|
||
if (response.ok) {
|
||
const data = await response.json()
|
||
if (data.success) {
|
||
currentNetwork = {
|
||
isInternal: data.isIntranet,
|
||
host,
|
||
lastChecked: Date.now()
|
||
}
|
||
|
||
// 使用 debug 级别,生产环境默认不显示
|
||
console.debug(`[Network] ${data.isIntranet ? '内网' : '公网'}访问`)
|
||
|
||
// 如果网络状态发生变化,通知监听器(保留 warn 级别,重要信息)
|
||
if (previousStatus !== undefined && previousStatus !== data.isIntranet) {
|
||
console.warn(`[Network] 网络切换: ${previousStatus ? '内网' : '外网'} → ${data.isIntranet ? '内网' : '外网'}`)
|
||
notifyNetworkChange(currentNetwork)
|
||
}
|
||
|
||
return currentNetwork
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.warn('后端网络检测失败,使用前端判断:', error)
|
||
}
|
||
|
||
// 如果后端API调用失败,回退到前端判断(基于hostname)
|
||
const isInternal = isInternalIP(hostname)
|
||
currentNetwork = {
|
||
isInternal,
|
||
host,
|
||
lastChecked: Date.now()
|
||
}
|
||
|
||
// 使用 debug 级别,生产环境默认不显示
|
||
console.debug(`[Network] ${isInternal ? '内网' : '公网'}访问 (前端检测)`)
|
||
|
||
// 如果网络状态发生变化,通知监听器(保留 warn 级别,重要信息)
|
||
if (previousStatus !== undefined && previousStatus !== isInternal) {
|
||
console.warn(`[Network] 网络切换: ${previousStatus ? '内网' : '外网'} → ${isInternal ? '内网' : '外网'}`)
|
||
notifyNetworkChange(currentNetwork)
|
||
}
|
||
|
||
return currentNetwork
|
||
}
|
||
|
||
// 获取当前网络状态
|
||
export function getNetworkStatus(): NetworkStatus {
|
||
if (!currentNetwork) {
|
||
const host = getCurrentHost()
|
||
const hostname = getCurrentHostname()
|
||
const isInternal = isInternalIP(hostname)
|
||
return { isInternal, host }
|
||
}
|
||
return currentNetwork
|
||
}
|
||
|
||
// 获取API基础URL
|
||
export function getApiBaseUrl(): string {
|
||
if (!currentNetwork) {
|
||
return '' // 使用相对路径
|
||
}
|
||
return currentNetwork.isInternal ? `http://${currentNetwork.host}` : ''
|
||
}
|
||
|
||
// 清除缓存(用于刷新网络状态)
|
||
export function clearNetworkCache() {
|
||
currentNetwork = null
|
||
console.debug('[Network] 缓存已清除')
|
||
}
|
||
|
||
// 手动失效缓存并重新检测(用于用户主动刷新)
|
||
export async function invalidateAndRefresh(): Promise<NetworkStatus> {
|
||
console.debug('[Network] 手动刷新')
|
||
clearNetworkCache()
|
||
return await initializeNetwork(true)
|
||
}
|
||
|
||
// 添加网络状态变化监听器
|
||
export function addNetworkChangeListener(listener: (status: NetworkStatus) => void) {
|
||
networkChangeListeners.push(listener)
|
||
return () => {
|
||
networkChangeListeners = networkChangeListeners.filter(l => l !== listener)
|
||
}
|
||
}
|
||
|
||
// 通知所有监听器网络状态变化
|
||
function notifyNetworkChange(status: NetworkStatus) {
|
||
networkChangeListeners.forEach(listener => {
|
||
try {
|
||
listener(status)
|
||
} catch (error) {
|
||
console.error('网络状态变化监听器执行失败:', error)
|
||
}
|
||
})
|
||
}
|
||
|
||
// 设置网络变化监听(监听浏览器online/offline事件)
|
||
export function setupNetworkChangeDetection() {
|
||
if (typeof window === 'undefined') return
|
||
|
||
// 监听浏览器网络状态变化
|
||
window.addEventListener('online', () => {
|
||
console.debug('[Network] 网络恢复,重新检测')
|
||
initializeNetwork(true)
|
||
})
|
||
|
||
window.addEventListener('offline', () => {
|
||
console.warn('[Network] 网络断开')
|
||
})
|
||
|
||
// 监听页面可见性变化(用户切换回页面时重新检测)
|
||
document.addEventListener('visibilitychange', () => {
|
||
if (document.visibilityState === 'visible' && currentNetwork) {
|
||
const cacheAge = Date.now() - (currentNetwork.lastChecked || 0)
|
||
// 如果缓存超过1分钟,重新检测
|
||
if (cacheAge > 60 * 1000) {
|
||
console.debug('[Network] 页面可见,刷新状态')
|
||
initializeNetwork(true)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|