gongxiangp2/lib/network.ts
chenpei 4e89b57798 feat(upload): optimize upload UI and performance
- 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
2026-01-28 15:07:50 +08:00

202 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 网络管理:智能检测内外网
// 判断是否为内网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)
}
}
})
}