output; $input = $this->input; $this->cleanWorkpaceDir(); $current_version = Version::VERSION; $current_version_dir = App::getRuntimePath() . '/update/' . $current_version; $last_version_dir = App::getRuntimePath() . '/update/last'; $version_file_regx = "/\bconst VERSION\s*=\s*'[\d\.a-z]+'/"; $output->writeln('获取最新代码'); $last_version_git = new Git(); $last_version_repo = $last_version_git->cloneRepository(self::REPO, $last_version_dir); $tags = $last_version_repo->getTags(); $update_level = Env::get('adminsystem.update_level', 'production'); if ($update_level == 'production') { $tags = array_filter($tags, function ($value) { if (strpos($value, '-')) { return false; } return true; }); } usort($tags, function ($a, $b) { return version_compare($a, $b); }); $last_version = $tags[count($tags) - 1]; if ($last_version == $current_version) { $output->writeln('当前版本为最新版本'); if ($input->hasOption('reinstall')) { $output->writeln('重装代码'); } else { $this->cleanWorkpaceDir(); return; } } // 将最新代码切换到最新版本,因为最新的提交可能没有发布版本 $output->writeln('切换最新代码的最新版本'); $last_version_repo->checkout($last_version); $current_version_git = new Git(); $output->writeln('获取当前版本代码'); $current_version_repo = $current_version_git->cloneRepository(self::REPO, $current_version_dir); $output->writeln('切换版本' . $current_version); $current_version_repo->checkout($current_version); $output->writeln('开始比较版本差异'); $current_version_filesystem = new Filesystem(new LocalFilesystemAdapter($current_version_dir)); $last_version_filesystem = new Filesystem(new LocalFilesystemAdapter($last_version_dir)); // app、config下的所有文件和根目录下的几个文件 // 如果与当前版本一致(没有定制过),则将这种文件增加到完全跟踪列表,(新版覆盖、新版删除), // 否则不会覆盖和删除,但会将新文件追加到相应目录下 // 其余的所有代码都应当完全跟踪 // 完全跳过runtime、vendor、.git目录 $list_optional_update_files = []; $list_optional_update_files = array_merge( $list_optional_update_files, $current_version_filesystem->listContents('/app', true) ->map(fn (StorageAttributes $attributes) => $attributes->path()) ->toArray() ); $list_optional_update_files = array_merge( $list_optional_update_files, $current_version_filesystem->listContents('/config', true) ->map(fn (StorageAttributes $attributes) => $attributes->path()) ->toArray() ); $list_optional_update_files = array_merge( $list_optional_update_files, $current_version_filesystem->listContents('/') ->map(fn (StorageAttributes $attributes) => $attributes->path()) ->toArray() ); $ignore_prefix = [ 'runtime', 'vendor', '.git', ]; // 当前版本的所有文件 $current_version_files = $current_version_filesystem->listContents('/', true) ->filter(function (StorageAttributes $attributes) use ($ignore_prefix) { if ($attributes->isDir()) { return false; } foreach ($ignore_prefix as $prefix) { if (str_starts_with($attributes->path(), $prefix)) { return false; } } return true; }) ->map(fn (StorageAttributes $attributes) => $attributes->path()) ->toArray(); // 最新版本的所有文件 $last_version_files = $last_version_filesystem->listContents('/', true) ->filter(function (StorageAttributes $attributes) use ($ignore_prefix) { if ($attributes->isDir()) { return false; } foreach ($ignore_prefix as $prefix) { if (str_starts_with($attributes->path(), $prefix)) { return false; } } return true; }) ->map(fn (StorageAttributes $attributes) => $attributes->path()) ->toArray(); $changed_files = []; // 需要删除的文件 $need_delete_files = array_diff($current_version_files, $last_version_files); foreach ($need_delete_files as $file_path) { $changed_files[$file_path] = 'delete'; } // 需要增加的文件 $need_add_files = array_diff($last_version_files, $current_version_files); foreach ($need_add_files as $file_path) { $changed_files[$file_path] = 'add'; } // 需要更新的文件 $need_update_files = array_intersect($current_version_files, $last_version_files); foreach ($need_update_files as $file_path) { $changed_files[$file_path] = 'update'; } // 提示用户有一些完全跟踪的文件被修改了 $optional_update_waring_files = []; $force_update_waring_files = []; $now_dir = App::getRootPath(); $need_process_files = []; foreach ($changed_files as $file_path => $type) { if ($type == 'add') { $need_process_files[$file_path] = $type; continue; } $now_file_path = $now_dir . '/' . $file_path; $current_file_path = $current_version_dir . '/' . $file_path; $last_file_path = $last_version_dir . '/' . $file_path; // 如果新老版本文件一致,则不需要处理 if (PathTools::compareFiles($current_file_path, $last_file_path)) { continue; } if (PathTools::compareFiles($now_file_path, $current_file_path)) { // 如果当前代码 和 当前版本 一致 $need_process_files[$file_path] = $type; continue; } if (in_array($file_path, $list_optional_update_files)) { // 可选更新的文件发生了变化,提示用户手动维护上游信息 $optional_update_waring_files[$file_path] = $type; } else { // 强制更新的文件被定制了,需要提醒可能会产生错误 $force_update_waring_files[$file_path] = $type; } } if (!empty($optional_update_waring_files)) { foreach ($optional_update_waring_files as $file_path => $type) { $output->writeln($file_path . ' ' . $type); } $output->writeln('以上文件被您修改了,这些文件是默认的系统文件,并非您的主要业务代码,'); $output->writeln('您可能通过扩展机制修改了以上文件来定制系统代码的逻辑'); $output->writeln('您可能需要根据扩展规则查看系统逻辑是否发生了变化,如果发生了变化,您需要手动修改这些文件'); $is_udpate_optinal_files = $output->confirm($input, '确定要更新吗?(建议不更新)', false); if ($is_udpate_optinal_files) { $need_process_files = array_merge($need_process_files, $optional_update_waring_files); } } if (!empty($force_update_waring_files)) { foreach ($force_update_waring_files as $file_path => $type) { $output->writeln($file_path . ' ' . $type); } $output->writeln('以上文件被您定制了,您不应该修改这些文件,'); $output->writeln('但您出于某些原因修改了他们,如果继续更新,会覆盖至最新版本,'); $output->writeln('这些改动不应该发生,继续自动升级可能会导致错误,'); $output->writeln('建议您选择更新,然后将这些改动的逻辑通过扩展的机制重新实现'); $is_udpate_force_files = $output->confirm($input, '确定要更新吗?(建议更新)', false); if ($is_udpate_force_files) { $need_process_files = array_merge($need_process_files, $force_update_waring_files); } } if (empty($need_process_files)) { $output->writeln('没有需要更新的文件'); $this->cleanWorkpaceDir(); return; } // 处理需要更新的文件 foreach ($need_process_files as $file_path => $type) { $now_file_path = $now_dir . '/' . $file_path; $last_file_path = $last_version_dir . '/' . $file_path; if ($type == 'delete') { $output->writeln('删除文件' . $now_file_path); unlink($now_file_path); } elseif ($type == 'add') { $output->writeln('增加文件' . $now_file_path); copy($last_file_path, $now_file_path); } elseif ($type == 'update') { $output->writeln('更新文件' . $now_file_path); copy($last_file_path, $now_file_path); } } // 检测now的composer依赖和最新的composer依赖 $last_composer_json = file_get_contents($last_version_dir . '/composer.json'); $output->writeln($last_composer_json); $output->writeln('请参考以上最新composer文件调整您的依赖'); // 分析出最新需要的但now没有的 // 为用户整理出要手动调整的composer命令 $output->writeln('更新完成'); // 更新完成 $update_tips = include $last_version_dir . '/extend/base/admin/service/adminUpdateData/tips.php'; // 按照版本号排序 usort($update_tips, function ($a, $b) { return version_compare($a['version'], $b['version']); }); foreach ($update_tips as $tips_item) { if (version_compare($tips_item['version'], $current_version) <= 0) { continue; } $output->writeln('版本' . $tips_item['version'] . '更新说明:'); $output->writeln('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>'); foreach ($tips_item['desc'] as $desc) { $output->writeln($desc); } } $this->cleanWorkpaceDir(); } protected function cleanWorkpaceDir() { $dir = App::getRuntimePath() . '/update/'; $this->output->writeln('清理目录 ' . $dir); PathTools::removeDir($dir); } }