fix: 解决访问没有埋点的问题
This commit is contained in:
533
scripts/deploy.php
Normal file
533
scripts/deploy.php
Normal file
@@ -0,0 +1,533 @@
|
||||
<?php
|
||||
// deploy.php
|
||||
#!/usr/bin/env php
|
||||
/**
|
||||
* 文件自动部署脚本
|
||||
* 功能:比较ZIP包与目标目录的差异,自动部署文件
|
||||
* PHP 7.4 兼容版本
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class AutoDeployer
|
||||
{
|
||||
private string $zipPath;
|
||||
private string $targetDir;
|
||||
private array $log = [];
|
||||
private bool $dryRun = false;
|
||||
private bool $verbose = false;
|
||||
private bool $backup = false;
|
||||
|
||||
public function __construct(string $zipPath, string $targetDir, array $options = [])
|
||||
{
|
||||
$this->zipPath = $zipPath;
|
||||
$this->targetDir = rtrim($targetDir, DIRECTORY_SEPARATOR);
|
||||
$this->dryRun = $options['dryRun'] ?? false;
|
||||
$this->verbose = $options['verbose'] ?? false;
|
||||
$this->backup = $options['backup'] ?? false;
|
||||
|
||||
$this->validatePaths();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证路径有效性
|
||||
*/
|
||||
private function validatePaths(): void
|
||||
{
|
||||
if (!file_exists($this->zipPath)) {
|
||||
throw new InvalidArgumentException("ZIP文件不存在: {$this->zipPath}");
|
||||
}
|
||||
|
||||
if (!is_readable($this->zipPath)) {
|
||||
throw new InvalidArgumentException("ZIP文件不可读: {$this->zipPath}");
|
||||
}
|
||||
|
||||
if (!is_dir($this->targetDir)) {
|
||||
throw new InvalidArgumentException("目标目录不存在: {$this->targetDir}");
|
||||
}
|
||||
|
||||
if (!is_writable($this->targetDir)) {
|
||||
throw new InvalidArgumentException("目标目录不可写: {$this->targetDir}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行部署
|
||||
*/
|
||||
public function deploy(): array
|
||||
{
|
||||
$this->logMessage('INFO', '开始自动部署流程');
|
||||
$this->logMessage('INFO', "ZIP文件: {$this->zipPath}");
|
||||
$this->logMessage('INFO', "目标目录: {$this->targetDir}");
|
||||
|
||||
try {
|
||||
// 打开ZIP文件
|
||||
$zip = new ZipArchive();
|
||||
$openResult = $zip->open($this->zipPath);
|
||||
|
||||
if ($openResult !== true) {
|
||||
throw new RuntimeException("无法打开ZIP文件,错误代码: {$openResult}");
|
||||
}
|
||||
|
||||
$this->logMessage('INFO', 'ZIP文件打开成功');
|
||||
|
||||
// 分析ZIP文件结构
|
||||
$zipFiles = $this->analyzeZipStructure($zip);
|
||||
$this->logMessage('INFO', "ZIP包中包含 {$zipFiles['fileCount']} 个文件");
|
||||
|
||||
// 分析目标目录结构
|
||||
$targetFiles = $this->analyzeTargetDirectory();
|
||||
$this->logMessage('INFO', "目标目录中包含 {$targetFiles['fileCount']} 个文件");
|
||||
|
||||
// 比较差异并部署
|
||||
$result = $this->compareAndDeploy($zip, $zipFiles, $targetFiles);
|
||||
|
||||
// 关闭ZIP文件
|
||||
$zip->close();
|
||||
|
||||
$this->logMessage('SUCCESS', '部署完成');
|
||||
return $result;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->logMessage('ERROR', "部署失败: " . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析ZIP文件结构
|
||||
*/
|
||||
private function analyzeZipStructure(ZipArchive $zip): array
|
||||
{
|
||||
$files = [];
|
||||
$fileCount = 0;
|
||||
$totalSize = 0;
|
||||
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$fileInfo = $zip->statIndex($i);
|
||||
|
||||
if ($fileInfo === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fileName = $fileInfo['name'];
|
||||
|
||||
// 跳过目录条目
|
||||
if (substr($fileName, -1) === '/') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$files[$fileName] = [
|
||||
'size' => $fileInfo['size'],
|
||||
'compressed_size' => $fileInfo['comp_size'],
|
||||
'modified' => $fileInfo['mtime'],
|
||||
'crc32' => $fileInfo['crc']
|
||||
];
|
||||
|
||||
$fileCount++;
|
||||
$totalSize += $fileInfo['size'];
|
||||
}
|
||||
|
||||
return [
|
||||
'files' => $files,
|
||||
'fileCount' => $fileCount,
|
||||
'totalSize' => $totalSize
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析目标目录结构
|
||||
*/
|
||||
private function analyzeTargetDirectory(): array
|
||||
{
|
||||
$files = [];
|
||||
$fileCount = 0;
|
||||
$totalSize = 0;
|
||||
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator(
|
||||
$this->targetDir,
|
||||
RecursiveDirectoryIterator::SKIP_DOTS
|
||||
),
|
||||
RecursiveIteratorIterator::SELF_FIRST
|
||||
);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isDir()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$relativePath = $this->getRelativePath($file->getPathname(), $this->targetDir);
|
||||
$files[$relativePath] = [
|
||||
'size' => $file->getSize(),
|
||||
'modified' => $file->getMTime(),
|
||||
'permissions' => $file->getPerms()
|
||||
];
|
||||
|
||||
$fileCount++;
|
||||
$totalSize += $file->getSize();
|
||||
}
|
||||
|
||||
return [
|
||||
'files' => $files,
|
||||
'fileCount' => $fileCount,
|
||||
'totalSize' => $totalSize
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较差异并部署文件
|
||||
*/
|
||||
private function compareAndDeploy(ZipArchive $zip, array $zipFiles, array $targetFiles): array
|
||||
{
|
||||
$results = [
|
||||
'added' => 0,
|
||||
'updated' => 0,
|
||||
'skipped' => 0,
|
||||
'errors' => 0,
|
||||
'details' => []
|
||||
];
|
||||
|
||||
foreach ($zipFiles['files'] as $filePath => $zipFileInfo) {
|
||||
$targetFilePath = $this->targetDir . DIRECTORY_SEPARATOR . $filePath;
|
||||
$targetFileExists = isset($targetFiles['files'][$filePath]);
|
||||
|
||||
try {
|
||||
if (!$targetFileExists) {
|
||||
// 新文件 - 需要创建目录并复制文件
|
||||
$result = $this->deployNewFile($zip, $filePath, $targetFilePath);
|
||||
$results['added']++;
|
||||
$results['details'][] = [
|
||||
'file' => $filePath,
|
||||
'action' => 'added',
|
||||
'success' => true
|
||||
];
|
||||
} else {
|
||||
// 文件已存在 - 检查是否需要更新
|
||||
$needsUpdate = $this->needsUpdate($zip, $filePath, $targetFiles['files'][$filePath]);
|
||||
|
||||
if ($needsUpdate) {
|
||||
$result = $this->deployUpdatedFile($zip, $filePath, $targetFilePath);
|
||||
$results['updated']++;
|
||||
$results['details'][] = [
|
||||
'file' => $filePath,
|
||||
'action' => 'updated',
|
||||
'success' => true
|
||||
];
|
||||
} else {
|
||||
$results['skipped']++;
|
||||
$results['details'][] = [
|
||||
'file' => $filePath,
|
||||
'action' => 'skipped',
|
||||
'success' => true
|
||||
];
|
||||
$this->logMessage('INFO', "文件无需更新: {$filePath}");
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$results['errors']++;
|
||||
$results['details'][] = [
|
||||
'file' => $filePath,
|
||||
'action' => 'error',
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
$this->logMessage('ERROR', "处理文件失败 {$filePath}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 部署新文件
|
||||
*/
|
||||
private function deployNewFile(ZipArchive $zip, string $filePath, string $targetFilePath): bool
|
||||
{
|
||||
$this->logMessage('INFO', "添加新文件: {$filePath}");
|
||||
|
||||
if ($this->dryRun) {
|
||||
$this->logMessage('DRY_RUN', "将创建文件: {$targetFilePath}");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 创建目录(如果需要)
|
||||
$targetDir = dirname($targetFilePath);
|
||||
if (!is_dir($targetDir)) {
|
||||
if (!mkdir($targetDir, 0755, true)) {
|
||||
throw new RuntimeException("无法创建目录: {$targetDir}");
|
||||
}
|
||||
$this->logMessage('INFO', "创建目录: {$targetDir}");
|
||||
}
|
||||
|
||||
// 从ZIP中提取文件
|
||||
$fileContent = $zip->getFromName($filePath);
|
||||
if ($fileContent === false) {
|
||||
throw new RuntimeException("无法从ZIP中读取文件: {$filePath}");
|
||||
}
|
||||
|
||||
// 写入文件
|
||||
if (file_put_contents($targetFilePath, $fileContent) === false) {
|
||||
throw new RuntimeException("无法写入文件: {$targetFilePath}");
|
||||
}
|
||||
|
||||
$this->logMessage('SUCCESS', "文件创建成功: {$filePath}");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 部署更新文件
|
||||
*/
|
||||
private function deployUpdatedFile(ZipArchive $zip, string $filePath, string $targetFilePath): bool
|
||||
{
|
||||
$this->logMessage('INFO', "更新文件: {$filePath}");
|
||||
|
||||
if ($this->dryRun) {
|
||||
$this->logMessage('DRY_RUN', "将更新文件: {$targetFilePath}");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 备份原文件(可选)
|
||||
if ($this->backup && file_exists($targetFilePath)) {
|
||||
$backupPath = $targetFilePath . '.backup.' . date('YmdHis');
|
||||
if (copy($targetFilePath, $backupPath)) {
|
||||
$this->logMessage('INFO', "文件已备份: {$backupPath}");
|
||||
}
|
||||
}
|
||||
|
||||
// 从ZIP中提取文件内容
|
||||
$fileContent = $zip->getFromName($filePath);
|
||||
if ($fileContent === false) {
|
||||
throw new RuntimeException("无法从ZIP中读取文件: {$filePath}");
|
||||
}
|
||||
|
||||
// 写入文件
|
||||
if (file_put_contents($targetFilePath, $fileContent) === false) {
|
||||
throw new RuntimeException("无法写入文件: {$targetFilePath}");
|
||||
}
|
||||
|
||||
$this->logMessage('SUCCESS', "文件更新成功: {$filePath}");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否需要更新
|
||||
*/
|
||||
private function needsUpdate(ZipArchive $zip, string $filePath, array $targetFileInfo): bool
|
||||
{
|
||||
$zipFileInfo = $zip->statName($filePath);
|
||||
if ($zipFileInfo === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 比较文件大小
|
||||
if ($zipFileInfo['size'] != $targetFileInfo['size']) {
|
||||
$this->logMessage('DEBUG', "文件大小不同: {$filePath} (ZIP: {$zipFileInfo['size']}, 目标: {$targetFileInfo['size']})");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 比较修改时间(ZIP时间可能不准确,作为次要判断)
|
||||
if ($zipFileInfo['mtime'] > $targetFileInfo['modified']) {
|
||||
$this->logMessage('DEBUG', "ZIP文件较新: {$filePath}");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 比较CRC32校验(最可靠的方法)
|
||||
$targetCrc = hash_file('crc32b', $this->targetDir . DIRECTORY_SEPARATOR . $filePath);
|
||||
$zipCrc = sprintf('%08x', $zipFileInfo['crc']);
|
||||
|
||||
if (strtolower($targetCrc) !== strtolower($zipCrc)) {
|
||||
$this->logMessage('DEBUG', "文件内容不同: {$filePath}");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取相对路径
|
||||
*/
|
||||
private function getRelativePath(string $path, string $basePath): string
|
||||
{
|
||||
$basePath = rtrim($basePath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
||||
return str_replace($basePath, '', $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录日志
|
||||
*/
|
||||
private function logMessage(string $level, string $message): void
|
||||
{
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$logEntry = "[{$timestamp}] [{$level}] {$message}";
|
||||
|
||||
$this->log[] = $logEntry;
|
||||
|
||||
if ($this->verbose || in_array($level, ['ERROR', 'SUCCESS'])) {
|
||||
echo $logEntry . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部署日志
|
||||
*/
|
||||
public function getLog(): array
|
||||
{
|
||||
return $this->log;
|
||||
}
|
||||
}
|
||||
|
||||
// 命令行接口
|
||||
class CommandLineInterface
|
||||
{
|
||||
private array $options;
|
||||
|
||||
public function __construct(array $argv)
|
||||
{
|
||||
$this->options = $this->parseOptions($argv);
|
||||
}
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
try {
|
||||
$this->showBanner();
|
||||
|
||||
if ($this->options['help'] ?? false) {
|
||||
$this->showHelp();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// 验证参数
|
||||
$zipPath = $this->options['zip'] ?? null;
|
||||
$targetDir = $this->options['target'] ?? null;
|
||||
|
||||
if (!$zipPath || !$targetDir) {
|
||||
echo "错误: 必须指定 --zip 和 --target 参数\n";
|
||||
$this->showHelp();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// 创建部署器实例
|
||||
$deployer = new AutoDeployer($zipPath, $targetDir, [
|
||||
'dryRun' => $this->options['dry-run'] ?? false,
|
||||
'verbose' => $this->options['verbose'] ?? false,
|
||||
'backup' => $this->options['backup'] ?? false
|
||||
]);
|
||||
|
||||
// 执行部署
|
||||
$result = $deployer->deploy();
|
||||
|
||||
// 显示结果摘要
|
||||
$this->showSummary($result);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "错误: " . $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private function parseOptions(array $argv): array
|
||||
{
|
||||
$options = [
|
||||
'help' => false,
|
||||
'verbose' => false,
|
||||
'dry-run' => false
|
||||
];
|
||||
|
||||
for ($i = 1; $i < count($argv); $i++) {
|
||||
$arg = $argv[$i];
|
||||
|
||||
switch ($arg) {
|
||||
case '--help':
|
||||
case '-h':
|
||||
$options['help'] = true;
|
||||
break;
|
||||
|
||||
case '--verbose':
|
||||
case '-v':
|
||||
$options['verbose'] = true;
|
||||
break;
|
||||
|
||||
case '--dry-run':
|
||||
case '-d':
|
||||
$options['dry-run'] = true;
|
||||
break;
|
||||
|
||||
case '--backup':
|
||||
case '-b':
|
||||
$options['backup'] = true;
|
||||
break;
|
||||
|
||||
case '--zip':
|
||||
$options['zip'] = $argv[++$i] ?? null;
|
||||
break;
|
||||
|
||||
case '--target':
|
||||
$options['target'] = $argv[++$i] ?? null;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (substr($arg, 0, 1) === '-') {
|
||||
echo "警告: 未知选项 {$arg}\n";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
private function showBanner(): void
|
||||
{
|
||||
echo "========================================\n";
|
||||
echo " 文件自动部署工具 v1.0\n";
|
||||
echo " PHP 7.4 兼容版本\n";
|
||||
echo "========================================\n\n";
|
||||
}
|
||||
|
||||
private function showHelp(): void
|
||||
{
|
||||
echo "用法: php deploy.php [选项]\n\n";
|
||||
echo "选项:\n";
|
||||
echo " --zip <path> ZIP文件路径 (必需)\n";
|
||||
echo " --target <path> 目标目录路径 (必需)\n";
|
||||
echo " --dry-run, -d 试运行,不实际修改文件\n";
|
||||
echo " --backup, -b 部署前备份目标文件或目录\n";
|
||||
echo " --verbose, -v 显示详细输出\n";
|
||||
echo " --help, -h 显示此帮助信息\n\n";
|
||||
echo "示例:\n";
|
||||
echo " php deploy.php --zip update.zip --target /var/www/html\n";
|
||||
echo " php deploy.php --zip patch.zip --target ./app --dry-run\n";
|
||||
echo " php deploy.php --zip release.zip --target /var/www --verbose\n";
|
||||
}
|
||||
|
||||
private function showSummary(array $result): void
|
||||
{
|
||||
echo "\n部署摘要:\n";
|
||||
echo "========================================\n";
|
||||
echo "新增文件: {$result['added']}\n";
|
||||
echo "更新文件: {$result['updated']}\n";
|
||||
echo "跳过文件: {$result['skipped']}\n";
|
||||
echo "错误文件: {$result['errors']}\n";
|
||||
echo "========================================\n";
|
||||
|
||||
if ($result['errors'] > 0) {
|
||||
echo "\n错误详情:\n";
|
||||
foreach ($result['details'] as $detail) {
|
||||
if (!$detail['success']) {
|
||||
echo " - {$detail['file']}: {$detail['error']}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 主程序入口
|
||||
if (PHP_SAPI === 'cli') {
|
||||
$cli = new CommandLineInterface($argv);
|
||||
$cli->run();
|
||||
} else {
|
||||
echo "此脚本只能在命令行模式下运行\n";
|
||||
exit(1);
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user