PHP 原生 ZipArchive 处理大文件/深目录易超时、内存溢出。调用系统 tar 命令打包配合递归删除函数,是高性能文件清理的实用方案。本文提供生产可用的完整实现。
<?php
$dir = './temp'; // 待打包目录
$dest = 'backup_' . date('Ymd_His') . '.tar.gz'; // 输出文件
// 安全转义参数,防命令注入
$dirEsc = escapeshellarg($dir);
$destEsc = escapeshellarg($dest);
// 执行打包:-c 创建 -z gzip压缩 -v 显示过程 -f 文件名
$cmd = "tar czvf {$destEsc} {$dirEsc}";
$output = [];
$returnCode = 0;
exec($cmd, $output, $returnCode);
if ($returnCode === 0) {
echo "✅ 打包成功: {$dest} (" . filesize($dest) . " bytes)\n";
// 可选:打包后删除源目录
// recursiveDelete($dir);
} else {
echo "❌ 打包失败 (code: {$returnCode}):\n";
print_r($output);
}
// 解压命令参考
// exec("tar zxvf " . escapeshellarg($dest) . " -C " . escapeshellarg('./restore'));
?>
<?php
/**
* 递归删除目录及所有内容
* @param string $dirPath 目录路径
* @param bool $deleteSelf 是否删除目录本身 (false 仅清空)
* @return bool 成功返回 true
*/
function recursiveDelete(string $dirPath, bool $deleteSelf = true): bool {
// 规范化路径
$dirPath = rtrim($dirPath, '/\\');
if (!is_dir($dirPath)) {
trigger_error("目录不存在: $dirPath", E_USER_WARNING);
return false;
}
// 权限修正:确保可读可写可执行
@chmod($dirPath, 0755);
$items = scandir($dirPath);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$path = $dirPath . DIRECTORY_SEPARATOR . $item;
// 处理符号链接:仅删除链接,不跟随
if (is_link($path)) {
@unlink($path);
continue;
}
if (is_dir($path)) {
// 递归删除子目录
recursiveDelete($path, true);
} else {
// 删除文件:先尝试修改权限
@chmod($path, 0644);
@unlink($path);
}
}
if ($deleteSelf) {
return @rmdir($dirPath);
}
return true;
}
// 使用示例
if (recursiveDelete("./temp")) {
echo "✅ 目录清理成功\n";
} else {
echo "❌ 清理失败,请检查权限\n";
}
?>
<?php
class FileCleaner {
private $basePath;
private $dryRun = false;
private $errors = [];
private $deleted = 0;
private $freedBytes = 0;
public function __construct(string $basePath) {
$this->basePath = rtrim(realpath($basePath), '/\\');
}
public function setDryRun(bool $dryRun): self {
$this->dryRun = $dryRun;
return $this;
}
// 按模式删除 (glob 支持)
public function deleteByPattern(string $pattern): int {
$files = glob($this->basePath . DIRECTORY_SEPARATOR . $pattern);
foreach ($files as $file) {
$this->deletePath($file);
}
return $this->deleted;
}
// 按修改时间删除 (早于 N 天)
public function deleteOlderThan(int $days): int {
$cutoff = time() - $days * 86400;
$this->scanAndDelete($this->basePath, function($path) use ($cutoff) {
return filemtime($path) < $cutoff;
});
return $this->deleted;
}
// 按大小删除 (大于 N 字节)
public function deleteLargerThan(int $bytes): int {
$this->scanAndDelete($this->basePath, function($path) use ($bytes) {
return filesize($path) > $bytes;
});
return $this->deleted;
}
// 清空目录 (保留目录)
public function emptyDirectory(string $subPath = ''): bool {
$target = $this->basePath . ($subPath ? DIRECTORY_SEPARATOR . $subPath : '');
return $this->deletePath($target, false);
}
private function scanAndDelete(string $dir, callable $filter) {
$items = @scandir($dir);
if (!$items) return;
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$path = $dir . DIRECTORY_SEPARATOR . $item;
if (is_dir($path) && !is_link($path)) {
$this->scanAndDelete($path, $filter);
// 子目录清空后尝试删除
if ($filter($path)) $this->deletePath($path);
} elseif ($filter($path)) {
$this->deletePath($path);
}
}
}
private function deletePath(string $path, bool $deleteDir = true) {
if (!file_exists($path) && !is_link($path)) return;
$size = is_file($path) ? filesize($path) : 0;
if ($this->dryRun) {
echo "[DRY-RUN] Would delete: $path\n";
$this->deleted++;
$this->freedBytes += $size;
return;
}
if (is_dir($path) && !is_link($path)) {
if ($deleteDir) {
$success = recursiveDelete($path);
} else {
$success = recursiveDelete($path, false);
}
} else {
@chmod($path, 0644);
$success = @unlink($path);
}
if ($success) {
$this->deleted++;
$this->freedBytes += $size;
} else {
$this->errors[] = "删除失败: $path";
}
}
public function getReport(): array {
return [
'deleted_count' => $this->deleted,
'freed_bytes' => $this->freedBytes,
'freed_human' => $this->formatBytes($this->freedBytes),
'errors' => $this->errors,
];
}
private function formatBytes(int $bytes): string {
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$i = 0;
while ($bytes >= 1024 && $i < count($units) - 1) {
$bytes /= 1024;
$i++;
}
return round($bytes, 2) . ' ' . $units[$i];
}
}
// 使用示例
$cleaner = new FileCleaner('/var/www/storage/temp')
->setDryRun(false); // 先 true 测试,确认后改 false
$cleaner->deleteOlderThan(7); // 删除 7 天前文件
$cleaner->deleteByPattern('*.log'); // 删除所有 .log
$cleaner->deleteLargerThan(100 * 1024 * 1024); // 删除 >100MB 文件
$report = $cleaner->getReport();
echo "清理报告: 删除了 {$report['deleted_count']} 项,释放 {$report['freed_human']}\n";
if ($report['errors']) print_r($report['errors']);
?>
escapeshellarg()realpath() 规范化,确保在允许根目录下is_link() 识别,仅删除链接不跟随dryRun=true 预览# 每天凌晨 3 点清理 7 天前临时文件
0 3 * * * /usr/bin/php /opt/scripts/clean_temp.php >> /var/log/clean_temp.log 2>&1
# clean_temp.php 内容
<?php
require 'FileCleaner.php';
$cleaner = new FileCleaner('/var/www/temp')->setDryRun(false);
$cleaner->deleteOlderThan(7);
$report = $cleaner->getReport();
// 可选:邮件/钉钉推送报告
?>