132 lines
5.9 KiB
PHP
132 lines
5.9 KiB
PHP
<?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/backup;AddonService 备份到 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();
|
||
}
|
||
}
|
||
}
|