本文目录导读:

在PHP项目中拦截超额存储(超出磁盘配额或存储限制)的上传文件,可以通过以下几种方案实现:
服务器层配额控制
PHP配置文件限制
// php.ini 配置 upload_max_filesize = 50M post_max_size = 55M max_file_uploads = 10
磁盘配额管理(Linux)
# 创建配额限制 sudo setquota -u www-data 500M 600M /var/www/html # 检查配额 sudo repquota -a
程序层实时监控
检查磁盘空间
<?php
class FileStorageGuard {
private $maxStorage;
private $uploadDir;
public function __construct($maxStorage = 1073741824) { // 1GB
$this->maxStorage = $maxStorage;
$this->uploadDir = '/path/to/uploads';
}
/**
* 检查剩余空间
*/
public function checkAvailableSpace($fileSize) {
$freeSpace = disk_free_space($this->uploadDir);
$usedSpace = $this->getUsedSpace();
$remainingSpace = $this->maxStorage - $usedSpace;
if ($remainingSpace <= 0) {
throw new Exception('存储空间已满');
}
if ($fileSize > $remainingSpace) {
throw new Exception('文件过大,剩余空间不足');
}
return $remainingSpace;
}
/**
* 计算已用空间
*/
private function getUsedSpace() {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->uploadDir,
RecursiveDirectoryIterator::SKIP_DOTS)
);
$totalSize = 0;
foreach ($iterator as $file) {
if ($file->isFile()) {
$totalSize += $file->getSize();
}
}
return $totalSize;
}
/**
* 处理上传文件
*/
public function handleUpload($file, $destination) {
// 检查文件大小
if ($file['size'] > $this->checkAvailableSpace($file['size'])) {
return false;
}
// 移动文件
return move_uploaded_file($file['tmp_name'], $destination);
}
}
// 使用示例
$guard = new FileStorageGuard(5 * 1024 * 1024 * 1024); // 5GB
try {
$guard->handleUpload($_FILES['file'], '/path/to/destination/file.pdf');
} catch (Exception $e) {
echo '上传失败: ' . $e->getMessage();
}
数据库存储配额
<?php
class QuotaManager {
private $db;
private $userId;
public function __construct($userId) {
$this->db = new PDO('mysql:host=localhost;dbname=file_manager', 'user', 'pass');
$this->userId = $userId;
}
/**
* 获取用户配额
*/
public function getUserQuota() {
$stmt = $this->db->prepare("
SELECT quota_limit,
(SELECT COALESCE(SUM(file_size), 0) FROM files WHERE user_id = ?) as used
FROM users WHERE id = ?
");
$stmt->execute([$this->userId, $this->userId]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
/**
* 检查上传是否允许
*/
public function canUpload($fileSize) {
$quota = $this->getUserQuota();
$available = $quota['quota_limit'] - $quota['used'];
if ($fileSize > $available) {
throw new Exception("配额不足,剩余空间: " . $this->formatBytes($available));
}
return true;
}
/**
* 记录上传
*/
public function recordUpload($filePath, $fileSize) {
$stmt = $this->db->prepare("
INSERT INTO files (user_id, file_path, file_size, uploaded_at)
VALUES (?, ?, ?, NOW())
");
return $stmt->execute([$this->userId, $filePath, $fileSize]);
}
private function formatBytes($bytes) {
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$i = 0;
while ($bytes >= 1024 && $i < count($units) - 1) {
$bytes /= 1024;
$i++;
}
return round($bytes, 2) . ' ' . $units[$i];
}
}
高级方案:流式限制
使用流包装器限制写入
<?php
class LimitedWriteStream {
private $maxBytes;
private $written = 0;
private $stream;
public function __construct($stream, $maxBytes) {
$this->stream = $stream;
$this->maxBytes = $maxBytes;
}
public function write($data) {
$remaining = $this->maxBytes - $this->written;
if ($remaining <= 0) {
throw new Exception('写入限制已达到');
}
$writeLen = min(strlen($data), $remaining);
$written = fwrite($this->stream, substr($data, 0, $writeLen));
$this->written += $written;
return $written;
}
public function __destruct() {
fclose($this->stream);
}
}
// 使用示例
function uploadWithLimit($sourceFile, $destPath, $maxSize) {
$source = fopen($sourceFile, 'rb');
$dest = fopen($destPath, 'wb');
$limited = new LimitedWriteStream($dest, $maxSize);
while (!feof($source)) {
$data = fread($source, 8192);
$limited->write($data);
}
fclose($source);
}
监控与报警系统
实时磁盘监控
<?php
class DiskMonitor {
private $threshold = 90; // 90% 告警
public function checkDiskUsage($partition) {
$total = disk_total_space($partition);
$free = disk_free_space($partition);
$usedPercent = (($total - $free) / $total) * 100;
if ($usedPercent > $this->threshold) {
$this->sendAlert("磁盘使用率: {$usedPercent}%");
return false;
}
// 自动清理策略
if ($usedPercent > 80) {
$this->autoCleanup($partition);
}
return true;
}
private function autoCleanup($dir) {
$files = glob($dir . '/*.tmp'); // 清理临时文件
foreach ($files as $file) {
if (filemtime($file) < time() - 86400) { // 超过24小时
unlink($file);
}
}
}
private function sendAlert($message) {
// 发送邮件、短信或Webhook通知
mail('admin@example.com', '磁盘空间告警', $message);
}
}
前端拦截(辅助方案)
JavaScript预检查
function checkFileSize(file, maxSizeMB = 10) {
const maxSizeBytes = maxSizeMB * 1024 * 1024;
if (file.size > maxSizeBytes) {
alert(`文件大小不能超过 ${maxSizeMB}MB`);
return false;
}
// 可选:检查剩余空间(需要API支持)
checkServerStorage().then(available => {
if (file.size > available) {
alert('服务器存储空间不足');
return false;
}
});
return true;
}
// HTML表单
<form onsubmit="return checkFileSize(this.file.files[0], 50)">
<input type="file" name="file" required>
<button type="submit">上传</button>
</form>
推荐方案组合
- 初级项目:使用PHP配置 + 程序层检查
- 中级项目:数据库配额管理 + 磁盘监控
- 高级项目:流式限制 + 分布式存储 + 实时监控
关键点:
- 始终在服务端进行最终验证
- 实现多层防御(前端、应用层、系统层)
- 定期清理过期文件
- 设置合理的缓冲阈值(通常预留10-20%空间)
选择哪种方案取决于项目的具体需求和预算,对于大多数PHP项目,组合使用数据库配额管理和程序层检查就足够应对常见场景。