Files
ulthon_admin/app/common/service/stack/StackModeService.php
augushong db057aa90e
All checks were successful
build-and-deploy / 直传代码并部署到 Host15 (push) Successful in 1m0s
feat(stack): 切换模式时自动删除多余的管理文件
- 重写 getModePlan: 跳过目标模式和 default 都不存在的文件 (不再抛异常)
- 重写 applyMode: 切换模式后自动清理孤立的管理文件, 删除前备份以支持 rollback
- 修复 HOSTPORT 为 3306 (容器内部端口, 非宿主机映射端口)
- 增加 backup_id 空值保护: 无备份时不执行删除
2026-04-29 23:35:56 +08:00

117 lines
3.7 KiB
PHP

<?php
namespace app\common\service\stack;
use base\common\service\stack\StackModeServiceBase;
use RuntimeException;
class StackModeService extends StackModeServiceBase
{
public function getModePlan(string $mode): array
{
$mode = trim($mode);
if ($mode === '') {
throw new RuntimeException('模式不能为空');
}
$config = $this->loadConfig();
$modes = $config['modes'];
if (!isset($modes[$mode])) {
throw new RuntimeException("模式不存在:{$mode}");
}
$defaultMode = $config['default_mode'];
$managedFiles = $config['managed_files'];
$plan = [];
$skippedFiles = [];
foreach ($managedFiles as $relativePath) {
$modeSource = $this->resolveModeFile($mode, $relativePath);
if ($modeSource !== null) {
$plan[] = [
'file' => $relativePath,
'source_mode' => $mode,
'source_path' => $modeSource,
];
continue;
}
$defaultSource = $this->resolveModeFile($defaultMode, $relativePath);
if ($defaultSource !== null) {
$plan[] = [
'file' => $relativePath,
'source_mode' => $defaultMode,
'source_path' => $defaultSource,
];
continue;
}
// File not found in mode OR default - skip it (don't throw)
$skippedFiles[] = $relativePath;
}
return [
'mode' => $mode,
'default_mode' => $defaultMode,
'managed_files' => $managedFiles,
'plan' => $plan,
'skipped_files' => $skippedFiles,
];
}
public function applyMode(string $mode, string $operator = 'system'): array
{
// Use our overridden getModePlan (skips missing files instead of throwing)
$result = parent::applyMode($mode, $operator);
$appliedFiles = $result['applied_files'] ?? [];
$backupId = $result['backup_id'] ?? '';
$config = $this->loadConfig();
$managedFiles = $config['managed_files'];
$deletedFiles = [];
foreach ($managedFiles as $relativePath) {
// Skip files already handled by parent
if (in_array($relativePath, $appliedFiles)) {
continue;
}
$targetPath = $this->toRootPath($relativePath);
if (!is_file($targetPath)) {
continue;
}
// This file was skipped by getModePlan (not in mode or default)
// Back it up before deleting
if ($backupId !== '') {
$backupDir = $this->joinPath($this->backupRoot, $backupId);
$backupFilesDir = $this->joinPath($backupDir, 'files');
$backupFilePath = $this->joinPath($backupFilesDir, $this->toSystemPath($relativePath));
$this->ensureDir(dirname($backupFilePath));
copy($targetPath, $backupFilePath);
// Update backup meta
$metaPath = $this->joinPath($backupDir, 'meta.json');
if (is_file($metaPath)) {
$meta = $this->readJsonFile($metaPath);
$meta['files'][] = [
'file' => $relativePath,
'existed' => true,
];
$this->writeJsonFile($metaPath, $meta);
}
// Only delete if we successfully backed up
@unlink($targetPath);
$deletedFiles[] = $relativePath;
}
}
$result['deleted_files'] = $deletedFiles;
return $result;
}
}