ftp_mdtm() 函数获取FTP服务器上指定文件的最后修改时间。
ftp_mdtm(resource $ftp_stream, string $remote_file): int
| 参数 | 描述 |
|---|---|
$ftp_stream |
必需。FTP连接的资源标识符,由ftp_connect()或ftp_ssl_connect()函数返回。 |
$remote_file |
必需。远程文件的路径。 |
-1(如果文件不存在、服务器不支持或发生错误)获取远程文件的最后修改时间:
<?php
// FTP服务器信息
$ftp_server = "ftp.example.com";
$ftp_user = "username";
$ftp_pass = "password";
// 建立FTP连接
$conn_id = ftp_connect($ftp_server);
if ($conn_id === false) {
die("无法连接到FTP服务器");
}
// 登录FTP服务器
if (!ftp_login($conn_id, $ftp_user, $ftp_pass)) {
ftp_close($conn_id);
die("FTP登录失败");
}
// 开启被动模式
ftp_pasv($conn_id, true);
// 远程文件
$remote_file = "public_html/index.php";
// 获取文件修改时间
$timestamp = ftp_mdtm($conn_id, $remote_file);
if ($timestamp != -1) {
echo "文件: $remote_file\n";
echo "最后修改时间 (Unix时间戳): $timestamp\n";
echo "格式化时间: " . date('Y-m-d H:i:s', $timestamp) . "\n";
echo "相对时间: " . timeAgo($timestamp) . "\n";
} else {
echo "无法获取文件修改时间\n";
echo "可能原因:\n";
echo "1. 文件不存在\n";
echo "2. 服务器不支持MDTM命令\n";
echo "3. 权限不足\n";
}
// 关闭连接
ftp_close($conn_id);
// 辅助函数:将时间戳转换为相对时间
function timeAgo($timestamp) {
$current_time = time();
$time_diff = $current_time - $timestamp;
if ($time_diff < 60) {
return "刚刚";
} elseif ($time_diff < 3600) {
$minutes = floor($time_diff / 60);
return "{$minutes}分钟前";
} elseif ($time_diff < 86400) {
$hours = floor($time_diff / 3600);
return "{$hours}小时前";
} elseif ($time_diff < 2592000) {
$days = floor($time_diff / 86400);
return "{$days}天前";
} elseif ($time_diff < 31536000) {
$months = floor($time_diff / 2592000);
return "{$months}个月前";
} else {
$years = floor($time_diff / 31536000);
return "{$years}年前";
}
}
?>
<?php
/**
* 检查远程文件是否比本地文件新
*/
class FileSyncChecker {
private $conn;
public function __construct($host, $username, $password) {
$this->conn = ftp_connect($host);
if (!$this->conn) {
throw new Exception("无法连接到FTP服务器");
}
if (!ftp_login($this->conn, $username, $password)) {
ftp_close($this->conn);
throw new Exception("FTP登录失败");
}
ftp_pasv($this->conn, true);
}
/**
* 检查文件是否需要更新
*/
public function checkFileForUpdate($remote_file, $local_file) {
// 获取远程文件修改时间
$remote_mtime = ftp_mdtm($this->conn, $remote_file);
if ($remote_mtime == -1) {
throw new Exception("无法获取远程文件修改时间: $remote_file");
}
// 检查本地文件是否存在
if (file_exists($local_file)) {
$local_mtime = filemtime($local_file);
echo "文件比较: " . basename($remote_file) . "\n";
echo "远程修改时间: " . date('Y-m-d H:i:s', $remote_mtime) . "\n";
echo "本地修改时间: " . date('Y-m-d H:i:s', $local_mtime) . "\n";
if ($remote_mtime > $local_mtime) {
$time_diff = $remote_mtime - $local_mtime;
echo "远程文件较新 (" . formatTimeDiff($time_diff) . ")\n";
return true;
} elseif ($remote_mtime < $local_mtime) {
$time_diff = $local_mtime - $remote_mtime;
echo "本地文件较新 (" . formatTimeDiff($time_diff) . ")\n";
return false;
} else {
echo "文件时间相同\n";
return false;
}
} else {
echo "本地文件不存在,需要下载\n";
return true;
}
}
/**
* 批量检查多个文件
*/
public function batchCheck($file_pairs) {
$results = [
'need_update' => [],
'up_to_date' => [],
'errors' => []
];
foreach ($file_pairs as $pair) {
$remote_file = $pair['remote'];
$local_file = $pair['local'];
try {
if ($this->checkFileForUpdate($remote_file, $local_file)) {
$results['need_update'][] = [
'remote' => $remote_file,
'local' => $local_file
];
} else {
$results['up_to_date'][] = [
'remote' => $remote_file,
'local' => $local_file
];
}
} catch (Exception $e) {
$results['errors'][] = [
'file' => $remote_file,
'error' => $e->getMessage()
];
}
}
return $results;
}
/**
* 获取远程目录中所有文件的修改时间
*/
public function getRemoteFileTimes($directory = '.') {
$file_times = [];
$files = ftp_nlist($this->conn, $directory);
if ($files === false) {
throw new Exception("无法获取目录列表: $directory");
}
foreach ($files as $file) {
if ($file == '.' || $file == '..') {
continue;
}
$remote_path = $directory . '/' . basename($file);
// 检查是否为目录
$is_dir = (ftp_size($this->conn, $remote_path) == -1);
if (!$is_dir) {
$mtime = ftp_mdtm($this->conn, $remote_path);
if ($mtime != -1) {
$file_times[] = [
'filename' => basename($file),
'path' => $remote_path,
'mtime' => $mtime,
'formatted_time' => date('Y-m-d H:i:s', $mtime),
'size' => ftp_size($this->conn, $remote_path)
];
}
}
}
// 按修改时间排序
usort($file_times, function($a, $b) {
return $b['mtime'] - $a['mtime'];
});
return $file_times;
}
public function close() {
if (is_resource($this->conn)) {
ftp_close($this->conn);
}
}
public function __destruct() {
$this->close();
}
}
// 辅助函数:格式化时间差
function formatTimeDiff($seconds) {
if ($seconds < 60) {
return "{$seconds}秒";
} elseif ($seconds < 3600) {
$minutes = floor($seconds / 60);
return "{$minutes}分钟";
} elseif ($seconds < 86400) {
$hours = floor($seconds / 3600);
return "{$hours}小时";
} else {
$days = floor($seconds / 86400);
return "{$days}天";
}
}
// 使用示例
try {
$checker = new FileSyncChecker("ftp.example.com", "username", "password");
echo "=== 检查单个文件 ===\n";
$need_update = $checker->checkFileForUpdate(
"public_html/index.php",
"./local/index.php"
);
echo "需要更新: " . ($need_update ? "是" : "否") . "\n";
echo "\n=== 获取远程文件列表 ===\n";
$files = $checker->getRemoteFileTimes("public_html");
echo "找到 " . count($files) . " 个文件:\n";
foreach ($files as $file) {
echo "- {$file['filename']} ({$file['formatted_time']}, " .
formatBytes($file['size']) . ")\n";
}
echo "\n=== 批量检查 ===\n";
$pairs = [
['remote' => 'public_html/style.css', 'local' => './local/style.css'],
['remote' => 'public_html/script.js', 'local' => './local/script.js'],
['remote' => 'public_html/images/logo.png', 'local' => './local/images/logo.png']
];
$results = $checker->batchCheck($pairs);
echo "需要更新: " . count($results['need_update']) . " 个文件\n";
echo "已是最新: " . count($results['up_to_date']) . " 个文件\n";
echo "错误: " . count($results['errors']) . " 个\n";
$checker->close();
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}
// 辅助函数:格式化文件大小
function formatBytes($bytes, $precision = 2) {
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
return round($bytes, $precision) . ' ' . $units[$pow];
}
?>
<?php
// Web界面文件时间查看器
session_start();
// FTP配置
$ftp_config = [
'server' => 'ftp.example.com',
'username' => 'webuser',
'password' => 'password',
'root_dir' => '/public_html/'
];
// 处理目录浏览请求
$current_dir = $_GET['dir'] ?? $ftp_config['root_dir'];
$current_dir = rtrim($current_dir, '/') . '/';
// 连接到FTP服务器
$file_list = [];
$error = null;
$conn = ftp_connect($ftp_config['server']);
if ($conn && ftp_login($conn, $ftp_config['username'], $ftp_config['password'])) {
ftp_pasv($conn, true);
// 尝试切换到指定目录
if (@ftp_chdir($conn, $current_dir)) {
$files = ftp_nlist($conn, ".");
if ($files !== false) {
foreach ($files as $file) {
$filename = basename($file);
if ($filename == '.' || $filename == '..') {
continue;
}
$file_path = $current_dir . $filename;
// 检查是否为目录
$is_dir = (ftp_size($conn, $file_path) == -1);
if ($is_dir) {
// 目录
$file_list[] = [
'name' => $filename,
'type' => 'directory',
'path' => $file_path,
'icon' => 'fa-folder',
'color' => '#ffc107'
];
} else {
// 文件 - 获取详细信息
$mtime = ftp_mdtm($conn, $file_path);
$size = ftp_size($conn, $file_path);
$file_list[] = [
'name' => $filename,
'type' => 'file',
'path' => $file_path,
'size' => $size,
'formatted_size' => formatFileSize($size),
'mtime' => $mtime,
'formatted_time' => $mtime != -1 ? date('Y-m-d H:i:s', $mtime) : '未知',
'relative_time' => $mtime != -1 ? getRelativeTime($mtime) : '未知',
'icon' => getFileIcon($filename),
'color' => getFileColor($filename)
];
}
}
}
} else {
$error = "无法访问目录: $current_dir";
}
ftp_close($conn);
} else {
$error = "FTP连接失败";
}
// 按类型和名称排序
usort($file_list, function($a, $b) {
if ($a['type'] != $b['type']) {
return $a['type'] == 'directory' ? -1 : 1;
}
return strcasecmp($a['name'], $b['name']);
});
// 辅助函数
function getFileIcon($filename) {
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$icons = [
'pdf' => 'fa-file-pdf',
'txt' => 'fa-file-alt',
'html' => 'fa-file-code',
'htm' => 'fa-file-code',
'php' => 'fa-file-code',
'css' => 'fa-file-code',
'js' => 'fa-file-code',
'jpg' => 'fa-file-image',
'jpeg' => 'fa-file-image',
'png' => 'fa-file-image',
'gif' => 'fa-file-image',
'zip' => 'fa-file-archive',
'rar' => 'fa-file-archive',
'doc' => 'fa-file-word',
'docx' => 'fa-file-word',
'xls' => 'fa-file-excel',
'xlsx' => 'fa-file-excel'
];
return isset($icons[$extension]) ? $icons[$extension] : 'fa-file';
}
function getFileColor($filename) {
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$colors = [
'pdf' => '#e74c3c',
'txt' => '#95a5a6',
'html' => '#e67e22',
'htm' => '#e67e22',
'php' => '#3498db',
'css' => '#9b59b6',
'js' => '#f1c40f',
'jpg' => '#2ecc71',
'jpeg' => '#2ecc71',
'png' => '#2ecc71',
'gif' => '#2ecc71',
'zip' => '#f39c12',
'rar' => '#f39c12',
'doc' => '#2980b9',
'docx' => '#2980b9',
'xls' => '#27ae60',
'xlsx' => '#27ae60'
];
return isset($colors[$extension]) ? $colors[$extension] : '#7f8c8d';
}
function formatFileSize($bytes) {
if ($bytes >= 1073741824) {
return number_format($bytes / 1073741824, 2) . ' GB';
} elseif ($bytes >= 1048576) {
return number_format($bytes / 1048576, 2) . ' MB';
} elseif ($bytes >= 1024) {
return number_format($bytes / 1024, 2) . ' KB';
} else {
return $bytes . ' 字节';
}
}
function getRelativeTime($timestamp) {
$current_time = time();
$time_diff = $current_time - $timestamp;
if ($time_diff < 60) {
return '刚刚';
} elseif ($time_diff < 3600) {
$minutes = floor($time_diff / 60);
return $minutes . '分钟前';
} elseif ($time_diff < 86400) {
$hours = floor($time_diff / 3600);
return $hours . '小时前';
} elseif ($time_diff < 2592000) {
$days = floor($time_diff / 86400);
return $days . '天前';
} else {
return date('Y-m-d', $timestamp);
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FTP文件时间查看器</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
.file-item {
border-radius: 8px;
transition: all 0.2s;
border: 1px solid #dee2e6;
}
.file-item:hover {
background-color: #f8f9fa;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.file-icon {
font-size: 1.5em;
margin-right: 10px;
}
.file-name {
font-weight: 500;
word-break: break-all;
}
.file-meta {
font-size: 0.85em;
color: #6c757d;
}
.breadcrumb {
background-color: #e9ecef;
border-radius: 5px;
padding: 10px 15px;
}
.time-badge {
font-size: 0.75em;
}
.directory-link {
text-decoration: none;
color: inherit;
}
.directory-link:hover {
color: #0d6efd;
}
</style>
</head>
<body>
<div class="container mt-4">
<div class="row mb-4">
<div class="col">
<h1><i class="fas fa-clock me-2"></i>FTP文件时间查看器</h1>
<p class="text-muted">查看FTP服务器上文件的修改时间</p>
</div>
</div>
<?php if ($error): ?>
<div class="alert alert-danger">
<i class="fas fa-exclamation-triangle me-2"></i><?php echo htmlspecialchars($error); ?>
</div>
<?php endif; ?>
<!-- 面包屑导航 -->
<div class="row mb-3">
<div class="col">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<?php
$path_parts = explode('/', trim($current_dir, '/'));
$current_path = '';
echo '<li class="breadcrumb-item"><a href="?dir=' . urlencode($ftp_config['root_dir']) . '">根目录</a></li>';
foreach ($path_parts as $part) {
if (!empty($part)) {
$current_path .= '/' . $part;
echo '<li class="breadcrumb-item"><a href="?dir=' . urlencode($current_path . '/') . '">' .
htmlspecialchars($part) . '</a></li>';
}
}
?>
</ol>
</nav>
</div>
</div>
<!-- 文件列表 -->
<div class="row">
<div class="col">
<div class="card">
<div class="card-header">
<h5 class="mb-0">文件列表 - <?php echo htmlspecialchars($current_dir); ?></h5>
</div>
<div class="card-body">
<?php if (empty($file_list)): ?>
<div class="text-center py-5">
<i class="fas fa-folder-open fa-3x text-muted mb-3"></i>
<p class="text-muted">目录为空</p>
</div>
<?php else: ?>
<div class="row row-cols-1 row-cols-md-2 g-4">
<?php foreach ($file_list as $file): ?>
<div class="col">
<div class="file-item p-3 h-100">
<div class="d-flex align-items-start">
<i class="fas <?php echo $file['icon']; ?> file-icon"
style="color: <?php echo $file['color']; ?>"></i>
<div class="flex-grow-1">
<?php if ($file['type'] == 'directory'): ?>
<a href="?dir=<?php echo urlencode($file['path']); ?>"
class="directory-link">
<div class="file-name">
<i class="fas fa-folder me-1"></i>
<?php echo htmlspecialchars($file['name']); ?>
</div>
</a>
<?php else: ?>
<div class="file-name">
<?php echo htmlspecialchars($file['name']); ?>
</div>
<div class="file-meta mt-2">
<span class="badge bg-secondary me-2">
<i class="fas fa-hdd me-1"></i>
<?php echo $file['formatted_size']; ?>
</span>
<span class="badge bg-info time-badge">
<i class="fas fa-clock me-1"></i>
<?php echo $file['relative_time']; ?>
</span>
</div>
<div class="file-meta mt-1 small">
<i class="fas fa-calendar me-1"></i>
<?php echo $file['formatted_time']; ?>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<div class="card-footer text-muted">
<small>
<i class="fas fa-info-circle me-1"></i>
共 <?php echo count($file_list); ?> 个项目
<?php
$file_count = 0;
$dir_count = 0;
foreach ($file_list as $file) {
if ($file['type'] == 'file') {
$file_count++;
} else {
$dir_count++;
}
}
echo "($dir_count 个目录, $file_count 个文件)";
?>
</small>
</div>
</div>
</div>
</div>
<!-- 统计信息 -->
<div class="row mt-4">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h6 class="mb-0"><i class="fas fa-chart-bar me-2"></i>文件类型统计</h6>
</div>
<div class="card-body">
<?php
$file_types = [];
foreach ($file_list as $file) {
if ($file['type'] == 'file') {
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$file_types[$ext] = isset($file_types[$ext]) ? $file_types[$ext] + 1 : 1;
}
}
arsort($file_types);
?>
<ul class="list-unstyled mb-0">
<?php foreach ($file_types as $ext => $count): ?>
<li>
<i class="fas fa-file me-2"></i>
.<?php echo htmlspecialchars($ext ?: '无扩展名'); ?>
<span class="badge bg-primary float-end"><?php echo $count; ?></span>
</li>
<?php endforeach; ?>
</ul>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h6 class="mb-0"><i class="fas fa-calendar-alt me-2"></i>最近修改的文件</h6>
</div>
<div class="card-body">
<?php
$recent_files = [];
foreach ($file_list as $file) {
if ($file['type'] == 'file' && $file['mtime'] != -1) {
$recent_files[] = $file;
}
}
usort($recent_files, function($a, $b) {
return $b['mtime'] - $a['mtime'];
});
$recent_files = array_slice($recent_files, 0, 5);
?>
<ul class="list-unstyled mb-0">
<?php foreach ($recent_files as $file): ?>
<li class="mb-2">
<div class="d-flex justify-content-between">
<span class="text-truncate" title="<?php echo htmlspecialchars($file['name']); ?>">
<?php echo htmlspecialchars($file['name']); ?>
</span>
<span class="badge bg-secondary">
<?php echo $file['relative_time']; ?>
</span>
</div>
</li>
<?php endforeach; ?>
</ul>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 为文件项添加悬停效果
document.querySelectorAll('.file-item').forEach(function(item) {
item.addEventListener('mouseenter', function() {
this.style.cursor = 'pointer';
});
item.addEventListener('click', function() {
const link = this.querySelector('a.directory-link');
if (link) {
link.click();
}
});
});
// 更新页面标题显示当前目录
const currentDir = "<?php echo htmlspecialchars(basename(rtrim($current_dir, '/')) ?: '根目录'); ?>";
document.title = currentDir + ' - FTP文件时间查看器';
});
</script>
</body>
</html>
<?php
/**
* 基于文件修改时间的同步器
*/
class TimeBasedFileSyncer {
private $conn;
private $sync_log = [];
public function __construct($host, $username, $password) {
$this->conn = ftp_connect($host);
if (!$this->conn) {
throw new Exception("无法连接到FTP服务器");
}
if (!ftp_login($this->conn, $username, $password)) {
ftp_close($this->conn);
throw new Exception("FTP登录失败");
}
ftp_pasv($this->conn, true);
}
/**
* 同步单个文件
*/
public function syncFile($remote_file, $local_file, $direction = 'both') {
$result = [
'remote_file' => $remote_file,
'local_file' => $local_file,
'direction' => $direction,
'action' => 'none',
'reason' => '',
'timestamp' => time()
];
// 获取远程文件信息
$remote_exists = false;
$remote_mtime = -1;
$remote_size = -1;
if (ftp_size($this->conn, $remote_file) != -1) {
$remote_exists = true;
$remote_mtime = ftp_mdtm($this->conn, $remote_file);
$remote_size = ftp_size($this->conn, $remote_file);
}
// 获取本地文件信息
$local_exists = file_exists($local_file);
$local_mtime = $local_exists ? filemtime($local_file) : -1;
$local_size = $local_exists ? filesize($local_file) : -1;
// 同步逻辑
if (!$remote_exists && !$local_exists) {
$result['action'] = 'skip';
$result['reason'] = '远程和本地文件都不存在';
} elseif (!$remote_exists && $local_exists) {
if ($direction == 'upload' || $direction == 'both') {
// 上传本地文件到远程
$this->uploadFile($local_file, $remote_file);
$result['action'] = 'upload';
$result['reason'] = '远程文件不存在,上传本地文件';
} else {
$result['action'] = 'skip';
$result['reason'] = '远程文件不存在,但同步方向不允许上传';
}
} elseif ($remote_exists && !$local_exists) {
if ($direction == 'download' || $direction == 'both') {
// 下载远程文件到本地
$this->downloadFile($remote_file, $local_file);
$result['action'] = 'download';
$result['reason'] = '本地文件不存在,下载远程文件';
} else {
$result['action'] = 'skip';
$result['reason'] = '本地文件不存在,但同步方向不允许下载';
}
} else {
// 两个文件都存在,比较修改时间
if ($remote_mtime == -1 || $local_mtime == -1) {
$result['action'] = 'skip';
$result['reason'] = '无法获取文件修改时间';
} elseif ($remote_mtime > $local_mtime) {
if ($direction == 'download' || $direction == 'both') {
// 远程文件较新,下载
$this->downloadFile($remote_file, $local_file);
$result['action'] = 'download';
$result['reason'] = '远程文件较新';
} else {
$result['action'] = 'skip';
$result['reason'] = '远程文件较新,但同步方向不允许下载';
}
} elseif ($local_mtime > $remote_mtime) {
if ($direction == 'upload' || $direction == 'both') {
// 本地文件较新,上传
$this->uploadFile($local_file, $remote_file);
$result['action'] = 'upload';
$result['reason'] = '本地文件较新';
} else {
$result['action'] = 'skip';
$result['reason'] = '本地文件较新,但同步方向不允许上传';
}
} else {
$result['action'] = 'skip';
$result['reason'] = '文件时间相同';
}
}
// 记录同步结果
$this->sync_log[] = $result;
return $result;
}
/**
* 同步整个目录
*/
public function syncDirectory($remote_dir, $local_dir, $direction = 'both', $recursive = false) {
$results = [];
// 确保本地目录存在
if (!is_dir($local_dir)) {
mkdir($local_dir, 0755, true);
}
// 获取远程文件列表
$remote_items = ftp_nlist($this->conn, $remote_dir);
if ($remote_items === false) {
throw new Exception("无法获取远程目录列表: $remote_dir");
}
foreach ($remote_items as $item) {
$item_name = basename($item);
if ($item_name == '.' || $item_name == '..') {
continue;
}
$remote_path = $remote_dir . '/' . $item_name;
$local_path = $local_dir . '/' . $item_name;
// 检查是否为目录
$is_dir = (ftp_size($this->conn, $remote_path) == -1);
if ($is_dir && $recursive) {
// 递归同步子目录
$sub_results = $this->syncDirectory($remote_path, $local_path, $direction, true);
$results = array_merge($results, $sub_results);
} elseif (!$is_dir) {
// 同步文件
$result = $this->syncFile($remote_path, $local_path, $direction);
$results[] = $result;
}
}
return $results;
}
/**
* 下载文件
*/
private function downloadFile($remote_file, $local_file) {
$mode = $this->getTransferMode($remote_file);
return ftp_get($this->conn, $local_file, $remote_file, $mode);
}
/**
* 上传文件
*/
private function uploadFile($local_file, $remote_file) {
$mode = $this->getTransferMode($local_file);
return ftp_put($this->conn, $remote_file, $local_file, $mode);
}
/**
* 获取传输模式
*/
private function getTransferMode($filename) {
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$text_extensions = ['txt', 'html', 'htm', 'php', 'css', 'js', 'json', 'xml', 'csv'];
return in_array($extension, $text_extensions) ? FTP_ASCII : FTP_BINARY;
}
/**
* 获取同步日志
*/
public function getSyncLog() {
return $this->sync_log;
}
/**
* 生成同步报告
*/
public function generateSyncReport() {
$total = count($this->sync_log);
$actions = [
'download' => 0,
'upload' => 0,
'skip' => 0,
'error' => 0
];
foreach ($this->sync_log as $log) {
if (isset($actions[$log['action']])) {
$actions[$log['action']]++;
}
}
return [
'total_files' => $total,
'actions' => $actions,
'log' => $this->sync_log
];
}
public function close() {
if (is_resource($this->conn)) {
ftp_close($this->conn);
}
}
}
// 使用示例
try {
$syncer = new TimeBasedFileSyncer("ftp.example.com", "username", "password");
echo "=== 同步单个文件 ===\n";
$result = $syncer->syncFile(
"public_html/index.php",
"./sync/index.php",
"both"
);
echo "操作: {$result['action']}\n";
echo "原因: {$result['reason']}\n";
echo "\n=== 同步整个目录 ===\n";
$results = $syncer->syncDirectory(
"public_html",
"./sync/public_html",
"both",
true
);
$report = $syncer->generateSyncReport();
echo "同步完成\n";
echo "总文件数: {$report['total_files']}\n";
echo "下载: {$report['actions']['download']}\n";
echo "上传: {$report['actions']['upload']}\n";
echo "跳过: {$report['actions']['skip']}\n";
// 显示详细日志
echo "\n详细日志:\n";
foreach ($report['log'] as $log) {
echo "- {$log['remote_file']}: {$log['action']} ({$log['reason']})\n";
}
$syncer->close();
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}
?>
<?php
/**
* FTP文件变化监控器
*/
class FTPFileWatcher {
private $conn;
private $config;
private $running = false;
private $file_cache = [];
public function __construct($config) {
$this->config = array_merge([
'host' => '',
'username' => '',
'password' => '',
'watch_dir' => '/',
'check_interval' => 60, // 秒
'log_file' => null,
'callback' => null
], $config);
}
/**
* 启动监控
*/
public function start() {
echo "启动FTP文件监控器...\n";
echo "监控目录: {$this->config['watch_dir']}\n";
echo "检查间隔: {$this->config['check_interval']}秒\n\n";
$this->running = true;
$this->connect();
// 首次扫描
$this->scanFiles();
// 主循环
while ($this->running) {
$this->checkForChanges();
sleep($this->config['check_interval']);
}
}
/**
* 停止监控
*/
public function stop() {
echo "\n停止监控...\n";
$this->running = false;
$this->disconnect();
}
/**
* 连接FTP服务器
*/
private function connect() {
$this->conn = ftp_connect($this->config['host']);
if (!$this->conn) {
throw new Exception("无法连接到FTP服务器");
}
if (!ftp_login($this->conn, $this->config['username'], $this->config['password'])) {
ftp_close($this->conn);
throw new Exception("FTP登录失败");
}
ftp_pasv($this->conn, true);
}
/**
* 断开连接
*/
private function disconnect() {
if (is_resource($this->conn)) {
ftp_close($this->conn);
}
}
/**
* 扫描文件并建立缓存
*/
private function scanFiles() {
$this->file_cache = $this->getFileList($this->config['watch_dir']);
echo "初始扫描完成,发现 " . count($this->file_cache) . " 个文件\n";
}
/**
* 获取文件列表
*/
private function getFileList($directory, &$all_files = []) {
$files = ftp_nlist($this->conn, $directory);
if ($files === false) {
return [];
}
foreach ($files as $file) {
$filename = basename($file);
if ($filename == '.' || $filename == '..') {
continue;
}
$full_path = $directory . '/' . $filename;
// 检查是否为目录
$is_dir = (ftp_size($this->conn, $full_path) == -1);
if ($is_dir) {
// 递归扫描子目录
$this->getFileList($full_path, $all_files);
} else {
// 获取文件信息
$mtime = ftp_mdtm($this->conn, $full_path);
$size = ftp_size($this->conn, $full_path);
if ($mtime != -1) {
$all_files[$full_path] = [
'mtime' => $mtime,
'size' => $size,
'last_check' => time()
];
}
}
}
return $all_files;
}
/**
* 检查文件变化
*/
private function checkForChanges() {
echo "\n[" . date('Y-m-d H:i:s') . "] 检查文件变化...\n";
$current_files = $this->getFileList($this->config['watch_dir']);
$changes = [];
// 检查文件修改
foreach ($current_files as $path => $current_info) {
if (isset($this->file_cache[$path])) {
$cached_info = $this->file_cache[$path];
if ($current_info['mtime'] != $cached_info['mtime']) {
$changes[] = [
'type' => 'modified',
'file' => $path,
'old_mtime' => $cached_info['mtime'],
'new_mtime' => $current_info['mtime'],
'old_size' => $cached_info['size'],
'new_size' => $current_info['size']
];
}
} else {
// 新文件
$changes[] = [
'type' => 'created',
'file' => $path,
'mtime' => $current_info['mtime'],
'size' => $current_info['size']
];
}
}
// 检查文件删除
foreach ($this->file_cache as $path => $cached_info) {
if (!isset($current_files[$path])) {
$changes[] = [
'type' => 'deleted',
'file' => $path,
'mtime' => $cached_info['mtime'],
'size' => $cached_info['size']
];
}
}
// 处理变化
if (!empty($changes)) {
echo "发现 " . count($changes) . " 个变化:\n";
foreach ($changes as $change) {
$this->handleChange($change);
}
// 更新缓存
$this->file_cache = $current_files;
} else {
echo "没有发现变化\n";
}
}
/**
* 处理文件变化
*/
private function handleChange($change) {
$message = '';
switch ($change['type']) {
case 'created':
$message = "新文件: {$change['file']} (" .
formatBytes($change['size']) . ", " .
date('Y-m-d H:i:s', $change['mtime']) . ")";
break;
case 'modified':
$time_diff = $change['new_mtime'] - $change['old_mtime'];
$size_diff = $change['new_size'] - $change['old_size'];
$message = "文件修改: {$change['file']} (" .
"时间: " . date('Y-m-d H:i:s', $change['old_mtime']) . " → " .
date('Y-m-d H:i:s', $change['new_mtime']) . ", " .
"大小: " . formatBytes($change['old_size']) . " → " .
formatBytes($change['new_size']) . ")";
break;
case 'deleted':
$message = "文件删除: {$change['file']}";
break;
}
echo " {$message}\n";
// 记录到日志文件
if ($this->config['log_file']) {
$log_entry = "[" . date('Y-m-d H:i:s') . "] {$message}\n";
file_put_contents($this->config['log_file'], $log_entry, FILE_APPEND);
}
// 调用回调函数
if (is_callable($this->config['callback'])) {
call_user_func($this->config['callback'], $change);
}
}
}
// 辅助函数
function formatBytes($bytes) {
if ($bytes >= 1073741824) {
return number_format($bytes / 1073741824, 2) . ' GB';
} elseif ($bytes >= 1048576) {
return number_format($bytes / 1048576, 2) . ' MB';
} elseif ($bytes >= 1024) {
return number_format($bytes / 1024, 2) . ' KB';
} else {
return $bytes . ' 字节';
}
}
// 使用示例
try {
// 配置监控器
$watcher = new FTPFileWatcher([
'host' => 'ftp.example.com',
'username' => 'username',
'password' => 'password',
'watch_dir' => '/public_html',
'check_interval' => 30, // 每30秒检查一次
'log_file' => './ftp_changes.log',
'callback' => function($change) {
// 自定义处理逻辑
// 例如:发送邮件通知、触发同步等
}
]);
// 设置信号处理器(用于优雅退出)
pcntl_signal(SIGINT, function() use ($watcher) {
echo "\n接收到中断信号\n";
$watcher->stop();
exit(0);
});
pcntl_signal(SIGTERM, function() use ($watcher) {
echo "\n接收到终止信号\n";
$watcher->stop();
exit(0);
});
// 启动监控(在实际使用中可能需要在后台运行)
echo "按 Ctrl+C 停止监控\n";
$watcher->start();
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
exit(1);
}
?>
<?php
function checkMDTMSupport($ftp_server, $ftp_user, $ftp_pass) {
$conn = ftp_connect($ftp_server);
if (!$conn) {
return false;
}
if (!ftp_login($conn, $ftp_user, $ftp_pass)) {
ftp_close($conn);
return false;
}
ftp_pasv($conn, true);
// 尝试获取已知文件的修改时间
$test_files = ['index.html', 'index.php', 'readme.txt', '.htaccess'];
$supported = false;
foreach ($test_files as $test_file) {
$mtime = ftp_mdtm($conn, $test_file);
if ($mtime != -1) {
$supported = true;
echo "服务器支持MDTM命令 (测试文件: $test_file)\n";
break;
}
}
if (!$supported) {
// 尝试获取当前目录的修改时间(通常目录不支持)
$current_dir = ftp_pwd($conn);
$mtime = ftp_mdtm($conn, $current_dir);
if ($mtime != -1) {
echo "警告: 服务器可能不支持标准的MDTM命令\n";
} else {
echo "服务器不支持MDTM命令\n";
}
}
ftp_close($conn);
return $supported;
}
// 使用示例
checkMDTMSupport("ftp.example.com", "username", "password");
?>
<?php
function getFileTimeWithTimezone($ftp_conn, $remote_file, $timezone = 'UTC') {
$timestamp = ftp_mdtm($ftp_conn, $remote_file);
if ($timestamp == -1) {
return false;
}
// 创建DateTime对象
$date = new DateTime("@$timestamp");
$date->setTimezone(new DateTimeZone($timezone));
return [
'timestamp' => $timestamp,
'formatted' => $date->format('Y-m-d H:i:s'),
'timezone' => $timezone,
'iso8601' => $date->format('c'),
'rfc2822' => $date->format('r')
];
}
// 使用示例
$conn = ftp_connect("ftp.example.com");
if ($conn && ftp_login($conn, "user", "pass")) {
$times = getFileTimeWithTimezone($conn, "public_html/index.php", "Asia/Shanghai");
if ($times) {
echo "原始时间戳: {$times['timestamp']}\n";
echo "上海时区: {$times['formatted']}\n";
echo "时区: {$times['timezone']}\n";
echo "ISO8601格式: {$times['iso8601']}\n";
echo "RFC2822格式: {$times['rfc2822']}\n";
}
ftp_close($conn);
}
?>
| 函数 | 描述 |
|---|---|
filemtime() |
获取本地文件的修改时间 |
ftp_size() |
获取远程文件的大小 |
ftp_nlist() |
获取远程目录中的文件列表 |
ftp_rawlist() |
获取远程目录的详细列表 |
time() |
获取当前Unix时间戳 |
date() |
格式化日期/时间 |
ftp_mdtm()前,先用ftp_size()检查文件是否存在ftp_rawlist()解析时间)