gongxiangp2/lib/disk-space.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

238 lines
6.4 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.

/**
* R38修复:磁盘空间检查工具
*
* 用途:在大文件操作前检查磁盘剩余空间,防止因磁盘写满导致服务崩溃
*
* 支持平台:
* - Linux: 使用 `df` 命令
* - Windows (WSL): 使用 `df` 命令
* - Windows (原生): 使用 PowerShell 的 Get-PSDrive
*/
import { exec } from 'child_process'
import { promisify } from 'util'
const execAsync = promisify(exec)
interface DiskSpaceInfo {
/** 磁盘总容量(字节) */
total: number
/** 已使用空间(字节) */
used: number
/** 可用空间(字节) */
available: number
/** 使用百分比 (0-100) */
usePercent: number
/** 挂载点/盘符 */
mountPoint: string
}
/**
* 检查磁盘空间(Linux/WSL 使用 df 命令)
*/
async function checkDiskSpaceLinux(path: string): Promise<DiskSpaceInfo | null> {
try {
// df -B1: 以字节为单位显示
// --output: 指定输出字段
const { stdout } = await execAsync(`df -B1 --output=size,used,avail,pcent,target "${path}"`, {
timeout: 5000, // 5秒超时
})
const lines = stdout.trim().split('\n')
if (lines.length < 2) {
console.warn('[DiskSpace] Failed to parse df output')
return null
}
// 跳过标题行,解析数据行
const data = lines[1].trim().split(/\s+/)
if (data.length < 5) {
console.warn('[DiskSpace] Invalid df output format')
return null
}
const total = parseInt(data[0], 10)
const used = parseInt(data[1], 10)
const available = parseInt(data[2], 10)
const usePercentStr = data[3].replace('%', '')
const usePercent = parseInt(usePercentStr, 10)
const mountPoint = data[4]
return {
total,
used,
available,
usePercent,
mountPoint,
}
} catch (error) {
console.error('[DiskSpace] Failed to check disk space:', error)
return null
}
}
/**
* 检查磁盘空间(Windows 原生使用 PowerShell)
*/
async function checkDiskSpaceWindows(path: string): Promise<DiskSpaceInfo | null> {
try {
// 获取盘符
const driveLetter = path.charAt(0).toUpperCase()
const drive = driveLetter + ':'
// 使用 PowerShell Get-PSDrive 获取磁盘信息
const { stdout } = await execAsync(
`powershell -Command "Get-PSDrive -Name '${driveLetter}' | Select-Object Used,Free | ConvertTo-Json"`,
{ timeout: 5000 }
)
const result = JSON.parse(stdout)
if (!result) {
return null
}
const used = result.Used || 0
const free = result.Free || 0
const total = used + free
const usePercent = total > 0 ? Math.round((used / total) * 100) : 0
return {
total,
used,
available: free,
usePercent,
mountPoint: drive,
}
} catch (error) {
console.error('[DiskSpace] Failed to check disk space (Windows):', error)
return null
}
}
/**
* 检查磁盘空间(自动检测平台)
* @param path 要检查的路径(默认为临时目录)
* @returns 磁盘空间信息,失败返回 null
*/
export async function checkDiskSpace(path: string = require('os').tmpdir()): Promise<DiskSpaceInfo | null> {
const platform = process.platform
if (platform === 'win32' && !process.env.WSL_DISTRO_NAME) {
// Windows 原生环境(非 WSL)
return checkDiskSpaceWindows(path)
} else {
// Linux、macOS、WSL
return checkDiskSpaceLinux(path)
}
}
/**
* 检查是否有足够磁盘空间
* @param requiredBytes 需要的字节数
* @param path 检查的路径
* @param minFreePercent 最小剩余空间百分比(默认 10%)
* @returns 是否有足够空间
*/
export async function hasEnoughDiskSpace(
requiredBytes: number,
path?: string,
minFreePercent: number = 10
): Promise<boolean> {
const space = await checkDiskSpace(path)
if (!space) {
// 无法检查磁盘空间时,保守处理:假设有足够空间
console.warn('[DiskSpace] Unable to check disk space, assuming sufficient space')
return true
}
// 检查绝对值
if (space.available < requiredBytes) {
console.warn(
`[DiskSpace] Insufficient disk space: need ${formatBytes(requiredBytes)}, available ${formatBytes(space.available)}`
)
return false
}
// 检查百分比(确保不会把磁盘填满)
if (space.usePercent > (100 - minFreePercent)) {
console.warn(
`[DiskSpace] Disk usage too high: ${space.usePercent}% (max allowed: ${100 - minFreePercent}%)`
)
return false
}
return true
}
/**
* R38修复:检查磁盘空间并在不足时抛出错误
* @param requiredBytes 需要的字节数
* @param operation 操作名称(用于错误消息)
* @param path 检查的路径
* @throws Error 当磁盘空间不足时
*/
export async function ensureEnoughDiskSpace(
requiredBytes: number,
operation: string = '操作',
path?: string
): Promise<void> {
const space = await checkDiskSpace(path)
if (!space) {
console.warn(`[DiskSpace] Unable to check disk space before ${operation}, proceeding anyway`)
return
}
// 计算最小保留空间(磁盘总容量的 10%,或至少 5GB)
const minReservedBytes = Math.max(space.total * 0.1, 5 * 1024 * 1024 * 1024)
const requiredWithReserve = requiredBytes + minReservedBytes
if (space.available < requiredWithReserve) {
throw new Error(
`磁盘空间不足,无法${operation}。` +
`需要 ${formatBytes(requiredBytes)},` +
`但只剩余 ${formatBytes(space.available)} ` +
`(使用率 ${space.usePercent}%)。` +
`请清理磁盘空间后再试。`
)
}
console.log(
`[DiskSpace] Disk space check passed for ${operation}: ` +
`need ${formatBytes(requiredBytes)}, available ${formatBytes(space.available)} (${space.usePercent}% used)`
)
}
/**
* 格式化字节数为可读格式
*/
export function formatBytes(bytes: number): string {
const units = ['B', 'KB', 'MB', 'GB', 'TB']
let size = bytes
let unitIndex = 0
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024
unitIndex++
}
return `${size.toFixed(2)} ${units[unitIndex]}`
}
/**
* 获取磁盘使用率警告级别
* @returns 'ok' (正常), 'warning' (>70%), 'critical' (>90%)
*/
export async function getDiskUsageLevel(path?: string): Promise<'ok' | 'warning' | 'critical'> {
const space = await checkDiskSpace(path)
if (!space) {
return 'ok'
}
if (space.usePercent >= 90) {
return 'critical'
} else if (space.usePercent >= 70) {
return 'warning'
}
return 'ok'
}