Files
YwxAppThink/ywxapp/command/Upgrade.php
T

132 lines
5.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
namespace ywxapp\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\facade\App;
use ywxapp\service\FrameworkService;
use ywxapp\service\AddonService;
/**
* 一键离线升级命令(手动执行升级)
*
* 用于绕过在线下载,直接对本地打包产物执行升级:
* - 框架核心包 / 框架整站包 → 复用 FrameworkService::apply(解压、备份、跑 upgrade.sql、写版本号)
* - 插件包 → 复用 AddonService::local(解压覆盖、导入 install.sql、重导菜单)
*
* 用法:
* # 框架核心补丁包(剥离 ywxapp/ 前缀,解压到 ywxapp/
* php think ywxapp:upgrade --file=runtime/framework/ywxapp-1.0.9.zip --ver=1.0.9 --type=framework --patch
* # 框架整站包(不解前缀,解压到项目根)
* php think ywxapp:upgrade --file=runtime/framework/ywxapp-1.0.9.zip --ver=1.0.9 --type=framework
* # 插件升级(zip 根已是插件内容)
* php think ywxapp:upgrade --file=runtime/market/forum-1.0.1.zip --type=addon
*
* 说明:
* - 框架 --ver 必填(写入 config/ywxapp.php);插件版本从 zip 内 info.php 自动读取。
* - 命令执行前会自动备份(FrameworkService 备份到 runtime/framework/backupAddonService 备份到 runtime/addon)。
*/
class Upgrade extends Command
{
protected function configure()
{
$this->setName('ywxapp:upgrade')
->addOption('file', 'f', Option::VALUE_REQUIRED, '升级包本地路径(zip')
->addOption('type', 't', Option::VALUE_REQUIRED, '类型: framework | addon')
->addOption('ver', 'r', Option::VALUE_OPTIONAL, '目标版本号(framework 必填;addon 自动读取)')
->addOption('patch', 'p', Option::VALUE_NONE, '框架补丁包(核心包,剥离 ywxapp/ 前缀;整站包勿加)')
->setDescription('本地离线升级:框架核心/整站包或插件包一键手动执行');
}
protected function execute(Input $input, Output $output): int
{
$file = trim((string) $input->getOption('file'));
$type = strtolower(trim((string) $input->getOption('type')));
$version = trim((string) $input->getOption('ver'));
$isPatch = (bool) $input->getOption('patch');
if ($file === '' || !is_file($file)) {
$output->writeln('<error>请通过 --file 指定存在的 zip 路径</error>');
return 1;
}
if (!in_array($type, ['framework', 'addon'], true)) {
$output->writeln('<error>--type 必须是 framework 或 addon</error>');
return 1;
}
try {
if ($type === 'framework') {
if ($version === '') {
$output->writeln('<error>framework 升级需通过 --version 指定目标版本号</error>');
return 1;
}
$output->writeln("<info>开始升级框架到 {$version}" . ($isPatch ? '补丁包' : '整站包') . "...</info>");
$svc = App::getInstance()->make(FrameworkService::class);
$res = $svc->apply($file, $version, $isPatch ? 1 : 0);
$output->writeln('<info>框架升级完成:' . ($res['msg'] ?? 'ok') . '</info>');
} else {
$output->writeln("<info>开始升级插件({$file}...</info>");
// 从 zip 内 info.php 解析插件名,用于实例化 AddonService(需先确定 addonDir
$addonName = $this->readAddonNameFromZip($file);
if ($addonName === '') {
$output->writeln('<error>无法从升级包内 info.php 解析插件名</error>');
return 1;
}
// 构造 think\File 对象(real 路径,足够 validate/upload 使用)
$fileObj = new \think\File($file);
$svc = new AddonService($addonName);
$res = $svc->localUpgrade($fileObj);
$output->writeln('<info>插件升级完成:' . $addonName
. ' ' . ($res['from'] ?? '') . ' -> ' . ($res['to'] ?? '') . '</info>');
}
} catch (\Throwable $e) {
$output->writeln('<error>升级失败:' . $e->getMessage() . '</error>');
return 1;
}
$output->writeln('<comment>建议随后清理缓存:删除 runtime/cache、runtime/temp</comment>');
return 0;
}
/**
* 从升级包 zip 内 info.php 解析插件名
*
* @param string $zipFile
* @return string
*/
private function readAddonNameFromZip(string $zipFile): string
{
$zip = new \ZipArchive();
if ($zip->open($zipFile) !== true) {
return '';
}
try {
// 寻找包内 info.php(可能位于根目录或 <addon>/info.php
$name = '';
for ($i = 0; $i < $zip->numFiles; $i++) {
$entry = $zip->getNameIndex($i);
if (basename($entry) === 'info.php' && substr($entry, -strlen('info.php')) === 'info.php') {
$content = $zip->getFromIndex($i);
if ($content !== false && preg_match("/'name'\s*=>\s*'([^']+)'/", $content, $m)) {
$name = $m[1];
break;
}
}
}
return $name;
} finally {
$zip->close();
}
}
}