本文目录导读:

在PHP项目中实现增量同步时,对比文件修改时间是最基础、最高效的策略之一,以下是几种常见的实现方法和完整的代码示例,涵盖了从简单到健壮的方案。
基础实现:比较 filemtime
这是最直接的方法,利用 PHP 的 filemtime() 函数获取文件最后修改时间。
代码示例
<?php
$sourceDir = '/path/to/source/';
$targetDir = '/path/to/target/';
function syncByMtime($source, $target, $exclude = ['.', '..', '.git', '.svn']) {
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS)
);
$synced = 0;
$skipped = 0;
foreach ($files as $file) {
$relativePath = $files->getSubPathname();
// 排除不需要同步的文件
$skip = false;
foreach ($exclude as $pattern) {
if (strpos($relativePath, $pattern) !== false) {
$skip = true;
break;
}
}
if ($skip) {
continue;
}
$sourceFile = $file->getPathname();
$targetFile = $target . $relativePath;
// 获取源文件修改时间
$sourceTime = filemtime($sourceFile);
// 检查目标文件是否存在
if (file_exists($targetFile)) {
$targetTime = filemtime($targetFile);
// 只同步源文件更新的情况
if ($sourceTime > $targetTime) {
copy($sourceFile, $targetFile);
touch($targetFile, $sourceTime); // 保留原始修改时间
echo "Updated: $relativePath\n";
$synced++;
} else {
$skipped++;
}
} else {
// 目标文件不存在,创建目录并复制
$dir = dirname($targetFile);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
copy($sourceFile, $targetFile);
touch($targetFile, $sourceTime);
echo "Created: $relativePath\n";
$synced++;
}
}
echo "Synced: $synced, Skipped: $skipped\n";
return ['synced' => $synced, 'skipped' => $skipped];
}
// 执行同步
syncByMtime($sourceDir, $targetDir);
?>
带缓存的高效实现
对于大型项目,每次扫描所有文件性能较差,可以维护一个修改时间缓存文件。
<?php
class IncrementalSync {
private $sourceDir;
private $targetDir;
private $cacheFile;
private $cache = [];
public function __construct($sourceDir, $targetDir, $cacheFile = null) {
$this->sourceDir = rtrim($sourceDir, '/') . '/';
$this->targetDir = rtrim($targetDir, '/') . '/';
$this->cacheFile = $cacheFile ?? __DIR__ . '/sync_cache.json';
}
public function sync() {
$this->loadCache();
$changes = 0;
$files = $this->scanFiles();
foreach ($files as $relativePath => $sourceMtime) {
$targetFile = $this->targetDir . $relativePath;
$cachedMtime = $this->cache[$relativePath] ?? null;
// 检查是否需要同步
if ($cachedMtime === null || $sourceMtime > $cachedMtime) {
$this->syncFile($relativePath, $sourceMtime);
$this->cache[$relativePath] = $sourceMtime;
$changes++;
}
}
// 清理:删除目标中多余的缓存项
$this->cleanupCache($files);
$this->saveCache();
return $changes;
}
private function scanFiles() {
$files = [];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->sourceDir, RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if ($file->isFile()) {
$relativePath = $iterator->getSubPathname();
$files[$relativePath] = filemtime($file->getPathname());
}
}
return $files;
}
private function syncFile($relativePath, $mtime) {
$sourceFile = $this->sourceDir . $relativePath;
$targetFile = $this->targetDir . $relativePath;
$dir = dirname($targetFile);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
copy($sourceFile, $targetFile);
touch($targetFile, $mtime);
echo "Synced: $relativePath\n";
}
private function cleanupCache($currentFiles) {
// 删除缓存中但源文件已不存在的记录
foreach ($this->cache as $path => $mtime) {
if (!isset($currentFiles[$path])) {
unset($this->cache[$path]);
}
}
}
private function loadCache() {
if (file_exists($this->cacheFile)) {
$this->cache = json_decode(file_get_contents($this->cacheFile), true) ?? [];
}
}
private function saveCache() {
file_put_contents(
$this->cacheFile,
json_encode($this->cache, JSON_PRETTY_PRINT)
);
}
}
// 使用示例
$sync = new IncrementalSync('/path/to/source', '/path/to/target');
$syncedCount = $sync->sync();
echo "Total synced files: $syncedCount\n";
?>
使用 md5_file 结合修改时间的混合方案
修改时间可能被篡改,结合文件内容哈希可以更准确。
<?php
function smartSync($sourceDir, $targetDir, $hashDbFile = 'sync_hash.db') {
// 加载已有哈希数据库
$hashDb = file_exists($hashDbFile) ? unserialize(file_get_contents($hashDbFile)) : [];
$changedFiles = 0;
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($sourceDir, RecursiveDirectoryIterator::SKIP_DOTS)
);
$currentHashes = [];
foreach ($iterator as $file) {
$relativePath = str_replace($sourceDir, '', $file->getPathname());
$sourceFile = $file->getPathname();
$targetFile = $targetDir . $relativePath;
// 方法1: 先比较修改时间(快速筛选)
$sourceMtime = filemtime($sourceFile);
$needCheck = false;
if (!file_exists($targetFile)) {
$needCheck = true;
} elseif ($sourceMtime > filemtime($targetFile)) {
$needCheck = true;
}
// 方法2: 再用哈希验证(防止时间戳欺骗)
if ($needCheck) {
$hash = md5_file($sourceFile);
$currentHashes[$relativePath] = $hash;
if (!isset($hashDb[$relativePath]) || $hashDb[$relativePath] !== $hash) {
// 文件已更改,进行同步
$targetDirPath = dirname($targetFile);
if (!is_dir($targetDirPath)) {
mkdir($targetDirPath, 0755, true);
}
if (copy($sourceFile, $targetFile)) {
$hashDb[$relativePath] = $hash;
touch($targetFile, $sourceMtime);
$changedFiles++;
echo "Changed: $relativePath\n";
}
} else {
// 哈希相同但时间戳不同,更新时间戳
touch($targetFile, $sourceMtime);
}
} else {
// 文件未变化,保留哈希值
$currentHashes[$relativePath] = $hashDb[$relativePath] ?? md5_file($sourceFile);
}
}
// 更新哈希数据库
file_put_contents($hashDbFile, serialize($currentHashes));
return $changedFiles;
}
?>
命令行使用 rsync(推荐生产环境)
PHP 可以调用系统命令来使用更专业的工具:
<?php
function rsyncSync($source, $target, $options = '-avz') {
$source = rtrim($source, '/') . '/';
$target = rtrim($target, '/') . '/';
// 构建 rsync 命令
$cmd = sprintf(
'rsync %s --delete --times "%s" "%s" 2>&1',
$options,
$source,
$target
);
$output = [];
$returnVar = 0;
exec($cmd, $output, $returnVar);
if ($returnVar === 0) {
echo "Rsync completed successfully\n";
return implode("\n", $output);
} else {
echo "Rsync error: " . implode("\n", $output) . "\n";
return false;
}
}
// 使用示例
rsyncSync('/path/to/source', '/path/to/target');
?>
性能优化建议
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 小型项目 (<1000文件) | 基础 filemtime | 简单直接,足够快 |
| 中型项目 (1K-10K文件) | 带缓存的实现 | 减少重复扫描 |
| 大型项目 (>10K文件) | rsync 或 inotify | 专有工具更高效 |
| 实时同步 | inotify (Linux) + PHP | 事件驱动,无需轮询 |
注意事项
- 时区问题:
filemtime()返回 Unix 时间戳,不受时区影响 - 权限问题:确保 PHP 有读取源文件和写入目标文件的权限
- 符号链接:默认
RecursiveDirectoryIterator会跟随符号链接,可使用SKIP_LINKS跳过 - 大文件处理:对于大文件,考虑使用
stream_copy_to_stream避免内存溢出 - 并发处理:多进程同步时要注意文件锁
选择哪种方案取决于你的具体需求,对于大多数 PHP 项目,带缓存的 filemtime 方案已经足够高效和可靠。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播