fix: 解决访问没有埋点的问题

This commit is contained in:
2025-11-10 17:28:52 +08:00
parent e440631275
commit 57dea2ca87
38 changed files with 4172 additions and 1624 deletions

679
scripts/enhanced_deploy.php Normal file
View File

@@ -0,0 +1,679 @@
<?php
// enhanced_deploy.php
#!/usr/bin/env php
/**
* 增强版自动部署脚本
* 支持根据补丁ZIP文件删除不需要的文件
* PHP 7.4 兼容版本
*/
declare(strict_types=1);
class EnhancedAutoDeployer
{
private string $zipPath;
private string $targetDir;
private array $config;
private array $log = [];
private bool $dryRun;
private bool $verbose;
private array $deleteList = [];
public function __construct(string $zipPath, string $targetDir, array $config = [])
{
$this->zipPath = $zipPath;
$this->targetDir = rtrim($targetDir, DIRECTORY_SEPARATOR);
$this->config = array_merge([
'dry_run' => false,
'verbose' => false,
'backup_enabled' => true,
'backup_dir' => $this->targetDir . '/backups/deploy',
'delete_enabled' => true,
'delete_list_file' => '_DELETE_LIST.txt',
'delete_manifest_file' => '_DELETE_MANIFEST.json',
'safe_delete' => true,
'max_backups' => 5,
'confirmation_required' => false
], $config);
$this->dryRun = $this->config['dry_run'];
$this->verbose = $this->config['verbose'];
$this->validatePaths();
}
/**
* 获取相对路径
*/
private function getRelativePath(string $path, string $basePath): string
{
$basePath = rtrim($basePath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
return str_replace($basePath, '', $path);
}
/**
* 执行部署(包含删除功能)
*/
public function deploy(): array
{
$this->logMessage('INFO', '开始增强版自动部署流程');
$this->logMessage('INFO', "ZIP文件: {$this->zipPath}");
$this->logMessage('INFO', "目标目录: {$this->targetDir}");
try {
// 打开ZIP文件
$zip = new ZipArchive();
if ($zip->open($this->zipPath) !== true) {
throw new RuntimeException("无法打开ZIP文件: {$this->zipPath}");
}
$this->logMessage('INFO', 'ZIP文件打开成功');
// 步骤1读取删除清单
$this->loadDeleteList($zip);
// 步骤2分析ZIP文件结构
$zipFiles = $this->analyzeZipStructure($zip);
$this->logMessage('INFO', "ZIP包中包含 {$zipFiles['file_count']} 个文件");
// 步骤3分析目标目录结构
$targetFiles = $this->analyzeTargetDirectory();
$this->logMessage('INFO', "目标目录中包含 {$targetFiles['file_count']} 个文件");
// 步骤4确认删除操作如果需要
if ($this->config['confirmation_required'] && !empty($this->deleteList)) {
if (!$this->confirmDeletion()) {
$zip->close();
throw new RuntimeException("用户取消部署操作");
}
}
// 步骤5执行删除操作
$deleteResult = $this->executeDeletion();
// 步骤6部署新文件和更新文件
$deployResult = $this->deployFiles($zip, $zipFiles, $targetFiles);
// 关闭ZIP文件
$zip->close();
// 合并结果
$result = array_merge($deployResult, [
'deletion' => $deleteResult
]);
$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 loadDeleteList(ZipArchive $zip): void
{
$this->deleteList = [];
// 尝试从多个可能的文件中读取删除清单
$possibleFiles = [
$this->config['delete_list_file'],
'DELETE_LIST.txt',
'delete.list',
'manifest.json',
$this->config['delete_manifest_file']
];
foreach ($possibleFiles as $listFile) {
$content = $zip->getFromName($listFile);
if ($content !== false) {
$this->parseDeleteList($content, $listFile);
break;
}
}
if (empty($this->deleteList)) {
$this->logMessage('INFO', '未找到删除清单文件,跳过删除操作');
} else {
$this->logMessage('INFO', "找到删除清单,包含 " . count($this->deleteList) . " 个待删除文件");
}
}
/**
* 解析删除清单
*/
private function parseDeleteList(string $content, string $sourceFile): void
{
$this->logMessage('DEBUG', "从文件 {$sourceFile} 解析删除清单");
// 尝试解析JSON格式
if (strpos($sourceFile, '.json') !== false) {
$data = json_decode($content, true);
if ($data && isset($data['files_to_delete'])) {
$this->deleteList = $data['files_to_delete'];
return;
}
}
// 解析文本格式(每行一个文件路径)
$lines = explode("\n", $content);
foreach ($lines as $line) {
$line = trim($line);
// 跳过空行和注释
if (empty($line) || $line[0] === '#' || $line[0] === ';') {
continue;
}
$this->deleteList[] = $line;
}
// 去重
$this->deleteList = array_unique($this->deleteList);
}
/**
* 执行删除操作
*/
private function executeDeletion(): array
{
if (!$this->config['delete_enabled'] || empty($this->deleteList)) {
return [
'total' => 0,
'deleted' => 0,
'skipped' => 0,
'errors' => 0,
'details' => []
];
}
$result = [
'total' => count($this->deleteList),
'deleted' => 0,
'skipped' => 0,
'errors' => 0,
'details' => []
];
$this->logMessage('INFO', "开始执行删除操作,共 {$result['total']} 个文件");
foreach ($this->deleteList as $fileToDelete) {
try {
$fullPath = $this->targetDir . DIRECTORY_SEPARATOR . $fileToDelete;
if (!$this->shouldDeleteFile($fullPath, $fileToDelete)) {
$result['skipped']++;
$result['details'][] = [
'file' => $fileToDelete,
'action' => 'skipped',
'reason' => '安全检查未通过',
'success' => true
];
continue;
}
$deleteResult = $this->deleteFile($fullPath, $fileToDelete);
if ($deleteResult) {
$result['deleted']++;
$result['details'][] = [
'file' => $fileToDelete,
'action' => 'deleted',
'success' => true
];
} else {
$result['skipped']++;
$result['details'][] = [
'file' => $fileToDelete,
'action' => 'skipped',
'reason' => '文件不存在或无法删除',
'success' => true
];
}
} catch (Exception $e) {
$result['errors']++;
$result['details'][] = [
'file' => $fileToDelete,
'action' => 'error',
'error' => $e->getMessage(),
'success' => false
];
$this->logMessage('ERROR', "删除文件失败 {$fileToDelete}: " . $e->getMessage());
}
}
$this->logMessage('SUCCESS', "删除操作完成: 成功 {$result['deleted']} 个, 跳过 {$result['skipped']} 个, 错误 {$result['errors']}");
return $result;
}
/**
* 检查是否应该删除文件
*/
private function shouldDeleteFile(string $fullPath, string $relativePath): bool
{
// 安全检查:确保路径在目标目录内
$realTarget = realpath($this->targetDir);
$realFile = realpath($fullPath);
if ($realFile === false || strpos($realFile, $realTarget) !== 0) {
$this->logMessage('WARNING', "安全警告: 跳过删除外部文件 {$relativePath}");
return false;
}
// 检查重要文件保护
$protectedPatterns = [
'/\.env$/',
'/config\.php$/',
'/database\.php$/',
'/\.htaccess$/',
'/web\.config$/',
'/index\.php$/',
'/composer\.json$/',
'/composer\.lock$/',
'/package\.json$/',
'/yarn\.lock$/',
'/\.gitignore$/'
];
foreach ($protectedPatterns as $pattern) {
if (preg_match($pattern, $relativePath)) {
$this->logMessage('WARNING', "保护文件: 跳过删除 {$relativePath}");
return false;
}
}
// 检查文件是否存在
if (!file_exists($fullPath)) {
$this->logMessage('DEBUG', "文件不存在,跳过删除: {$relativePath}");
return false;
}
// 检查是否为目录
if (is_dir($fullPath)) {
$this->logMessage('WARNING', "跳过删除目录: {$relativePath}");
return false;
}
return true;
}
/**
* 删除单个文件
*/
private function deleteFile(string $fullPath, string $relativePath): bool
{
if ($this->dryRun) {
$this->logMessage('DRY_RUN', "将删除文件: {$relativePath}");
return true;
}
// 备份文件(如果启用)
if ($this->config['backup_enabled']) {
$this->backupFile($fullPath, $relativePath);
}
// 删除文件
if (unlink($fullPath)) {
$this->logMessage('INFO', "文件已删除: {$relativePath}");
// 尝试删除空目录
$this->cleanupEmptyDirectories(dirname($fullPath));
return true;
} else {
$this->logMessage('ERROR', "无法删除文件: {$relativePath}");
return false;
}
}
/**
* 备份文件
*/
private function backupFile(string $filePath, string $relativePath): void
{
if (!file_exists($filePath)) {
return;
}
$backupDir = $this->config['backup_dir'];
if (!is_dir($backupDir)) {
mkdir($backupDir, 0755, true);
}
$backupPath = $backupDir . DIRECTORY_SEPARATOR .
date('Ymd_His') . '_' .
str_replace('/', '_', $relativePath);
$backupDir = dirname($backupPath);
if (!is_dir($backupDir)) {
mkdir($backupDir, 0755, true);
}
if (copy($filePath, $backupPath)) {
$this->logMessage('INFO', "文件已备份: {$backupPath}");
} else {
$this->logMessage('WARNING', "文件备份失败: {$relativePath}");
}
}
/**
* 清理空目录
*/
private function cleanupEmptyDirectories(string $directory): void
{
$currentDir = $directory;
$baseDir = realpath($this->targetDir);
while ($currentDir !== $baseDir && is_dir($currentDir)) {
// 检查目录是否为空
$files = scandir($currentDir);
if (count($files) === 2) { // 只有 . 和 ..
if ($this->dryRun) {
$this->logMessage('DRY_RUN', "将删除空目录: {$currentDir}");
} else {
if (rmdir($currentDir)) {
$this->logMessage('INFO', "空目录已删除: {$currentDir}");
}
}
$currentDir = dirname($currentDir);
} else {
break;
}
}
}
/**
* 确认删除操作
*/
private function confirmDeletion(): bool
{
echo "\n⚠️ 警告: 以下文件将被删除:\n";
echo "========================================\n";
foreach ($this->deleteList as $index => $file) {
echo sprintf("%3d. %s\n", $index + 1, $file);
}
echo "========================================\n";
echo "总共 " . count($this->deleteList) . " 个文件将被删除\n\n";
echo "是否继续?(y/N): ";
$handle = fopen('php://stdin', 'r');
$input = trim(fgets($handle));
fclose($handle);
return strtolower($input) === 'y';
}
/**
* 部署文件(从父类继承或实现)
*/
private function deployFiles(ZipArchive $zip, array $zipFiles, array $targetFiles): array
{
// 这里实现文件部署逻辑与之前的AutoDeployer类似
// 包括添加新文件和更新现有文件
$result = [
'added' => 0,
'updated' => 0,
'skipped' => 0,
'errors' => 0,
'details' => []
];
// 实现文件部署逻辑...
return $result;
}
/**
* 记录日志
*/
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', 'WARNING'])) {
echo $logEntry . PHP_EOL;
}
}
/**
* 验证路径
*/
private function validatePaths(): void
{
if (!file_exists($this->zipPath)) {
throw new InvalidArgumentException("ZIP文件不存在: {$this->zipPath}");
}
if (!is_dir($this->targetDir)) {
throw new InvalidArgumentException("目标目录不存在: {$this->targetDir}");
}
}
/**
* 获取日志
*/
public function getLog(): array
{
return $this->log;
}
}
// 命令行接口
class EnhancedDeployerCLI
{
public function run(array $argv): void
{
try {
$options = $this->parseOptions($argv);
if ($options['help']) {
$this->showHelp();
exit(0);
}
if (!$options['zip'] || !$options['target']) {
echo "错误: 必须指定 --zip 和 --target 参数\n";
$this->showHelp();
exit(1);
}
$deployer = new EnhancedAutoDeployer($options['zip'], $options['target'], [
'dry_run' => $options['dry-run'],
'verbose' => $options['verbose'],
'confirmation_required' => $options['confirm']
]);
$result = $deployer->deploy();
$this->showResults($result);
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . PHP_EOL;
exit(1);
}
}
private function parseOptions(array $argv): array
{
$options = [
'help' => false,
'dry-run' => false,
'verbose' => false,
'confirm' => false,
'zip' => null,
'target' => null
];
for ($i = 1; $i < count($argv); $i++) {
switch ($argv[$i]) {
case '--help':
case '-h':
$options['help'] = true;
break;
case '--dry-run':
case '-d':
$options['dry-run'] = true;
break;
case '--verbose':
case '-v':
$options['verbose'] = true;
break;
case '--confirm':
case '-c':
$options['confirm'] = true;
break;
case '--zip':
$options['zip'] = $argv[++$i] ?? null;
break;
case '--target':
$options['target'] = $argv[++$i] ?? null;
break;
}
}
return $options;
}
private function showHelp(): void
{
echo "增强版自动部署工具\n\n";
echo "用法: php enhanced_deploy.php [选项]\n\n";
echo "选项:\n";
echo " --zip <path> ZIP文件路径 (必需)\n";
echo " --target <path> 目标目录路径 (必需)\n";
echo " --dry-run, -d 试运行,不实际修改文件\n";
echo " --verbose, -v 显示详细输出\n";
echo " --confirm, -c 确认删除操作\n";
echo " --help, -h 显示此帮助信息\n\n";
echo "示例:\n";
echo " php enhanced_deploy.php --zip update.zip --target /var/www/html\n";
echo " php enhanced_deploy.php --zip patch.zip --target ./app --dry-run\n";
echo " php enhanced_deploy.php --zip deploy.zip --target /var/www --confirm\n";
}
private function showResults(array $result): void
{
echo "\n部署结果:\n";
echo "========================================\n";
echo "文件部署:\n";
echo " 新增: {$result['added']} 个文件\n";
echo " 更新: {$result['updated']} 个文件\n";
echo " 跳过: {$result['skipped']} 个文件\n";
echo " 错误: {$result['errors']} 个文件\n\n";
echo "文件删除:\n";
echo " 总计: {$result['deletion']['total']} 个文件\n";
echo " 删除: {$result['deletion']['deleted']} 个文件\n";
echo " 跳过: {$result['deletion']['skipped']} 个文件\n";
echo " 错误: {$result['deletion']['errors']} 个文件\n";
echo "========================================\n";
}
}
// 主程序入口
if (PHP_SAPI === 'cli') {
$cli = new EnhancedDeployerCLI();
$cli->run($argv);
} else {
echo "此脚本只能在命令行模式下运行\n";
exit(1);
}
?>