mirror of
https://gitee.com/niucloud-team/niucloud.git
synced 2025-12-10 17:22:47 +00:00
965 lines
38 KiB
PHP
965 lines
38 KiB
PHP
<?php
|
||
// +----------------------------------------------------------------------
|
||
// | Niucloud-admin 企业快速开发的saas管理平台
|
||
// +----------------------------------------------------------------------
|
||
// | 官方网址:https://www.niucloud.com
|
||
// +----------------------------------------------------------------------
|
||
// | niucloud团队 版权所有 开源版本可自由商用
|
||
// +----------------------------------------------------------------------
|
||
// | Author: Niucloud Team
|
||
// +----------------------------------------------------------------------
|
||
|
||
namespace app\service\admin\upgrade;
|
||
|
||
use app\dict\addon\AddonDict;
|
||
use app\dict\sys\AppTypeDict;
|
||
use app\dict\sys\BackupDict;
|
||
use app\dict\sys\UpgradeDict;
|
||
use app\model\addon\Addon;
|
||
use app\model\site\Site;
|
||
use app\model\sys\SysBackupRecords;
|
||
use app\service\admin\install\InstallSystemService;
|
||
use app\service\admin\sys\ConfigService;
|
||
use app\service\core\addon\CoreAddonCloudService;
|
||
use app\service\core\addon\CoreAddonInstallService;
|
||
use app\service\core\addon\CoreAddonService;
|
||
use app\service\core\addon\CoreDependService;
|
||
use app\service\core\addon\WapTrait;
|
||
use app\service\core\channel\CoreH5Service;
|
||
use app\service\core\menu\CoreMenuService;
|
||
use app\service\core\niucloud\CoreCloudBuildService;
|
||
use app\service\core\niucloud\CoreModuleService;
|
||
use app\service\core\schedule\CoreScheduleInstallService;
|
||
use core\base\BaseAdminService;
|
||
use core\exception\CloudBuildException;
|
||
use core\exception\CommonException;
|
||
use core\util\DbBackup;
|
||
use core\util\niucloud\BaseNiucloudClient;
|
||
use think\facade\Cache;
|
||
use think\facade\Db;
|
||
use think\facade\Log;
|
||
|
||
/**
|
||
* 框架及插件升级
|
||
* @package app\service\core\upgrade
|
||
*/
|
||
class UpgradeService extends BaseAdminService
|
||
{
|
||
use WapTrait;
|
||
use ExecuteSqlTrait;
|
||
|
||
protected $upgrade_dir;
|
||
|
||
protected $root_path;
|
||
|
||
protected $cache_key = 'upgrade';
|
||
|
||
protected $upgrade_task = null;
|
||
|
||
protected $addon = '';
|
||
|
||
private $steps = [
|
||
'requestUpgrade' => [ 'step' => 'requestUpgrade', 'title' => '请求升级' ],
|
||
'downloadFile' => [ 'step' => 'downloadFile', 'title' => '' ],
|
||
'backupCode' => [ 'step' => 'backupCode', 'title' => '备份源码' ],
|
||
'backupSql' => [ 'step' => 'backupSql', 'title' => '' ],
|
||
'coverCode' => [ 'step' => 'coverCode', 'title' => '合并更新文件' ],
|
||
'handleUniapp' => [ 'step' => 'handleUniapp', 'title' => '处理uniapp' ],
|
||
'refreshMenu' => [ 'step' => 'refreshMenu', 'title' => '刷新菜单' ],
|
||
'installSchedule' => [ 'step' => 'installSchedule', 'title' => '安装计划任务' ],
|
||
'cloudBuild' => [ 'step' => 'cloudBuild', 'title' => '开始云编译' ],
|
||
'gteCloudBuildLog' => [ 'step' => 'gteCloudBuildLog', 'title' => '' ],
|
||
'upgradeComplete' => [ 'step' => 'upgradeComplete', 'title' => '升级完成' ]
|
||
];
|
||
|
||
public function __construct()
|
||
{
|
||
parent::__construct();
|
||
|
||
$this->root_path = dirname(root_path()) . DIRECTORY_SEPARATOR;
|
||
$this->upgrade_dir = $this->root_path . 'upgrade' . DIRECTORY_SEPARATOR;
|
||
$this->upgrade_task = Cache::get($this->cache_key);
|
||
|
||
if (!empty($this->upgrade_task) && !isset($this->upgrade_task[ 'upgrade_apps' ])) {
|
||
$this->upgrade_task[ 'upgrade_apps' ] = [ AddonDict::FRAMEWORK_KEY ];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 升级前环境检测
|
||
* @param string $addon
|
||
* @return array|array[]
|
||
*/
|
||
public function upgradePreCheck(string $addon = '')
|
||
{
|
||
$niucloud_dir = $this->root_path . 'niucloud' . DIRECTORY_SEPARATOR;
|
||
$upgrade_dir = $this->root_path . 'upgrade' . DIRECTORY_SEPARATOR;
|
||
$admin_dir = $this->root_path . 'admin' . DIRECTORY_SEPARATOR;
|
||
$web_dir = $this->root_path . 'web' . DIRECTORY_SEPARATOR;
|
||
$wap_dir = $this->root_path . 'uni-app' . DIRECTORY_SEPARATOR;
|
||
|
||
if (!is_dir($admin_dir)) throw new CommonException('ADMIN_DIR_NOT_EXIST');
|
||
if (!is_dir($web_dir)) throw new CommonException('WEB_DIR_NOT_EXIST');
|
||
if (!is_dir($wap_dir)) throw new CommonException('UNIAPP_DIR_NOT_EXIST');
|
||
|
||
$data = [
|
||
// 目录检测
|
||
'dir' => [
|
||
// 要求可读权限
|
||
'is_readable' => [],
|
||
// 要求可写权限
|
||
'is_write' => []
|
||
]
|
||
];
|
||
|
||
$data[ 'dir' ][ 'is_readable' ][] = [ 'dir' => str_replace(project_path(), '', $niucloud_dir), 'status' => is_readable($niucloud_dir) ];
|
||
$data[ 'dir' ][ 'is_readable' ][] = [ 'dir' => str_replace(project_path(), '', $upgrade_dir), 'status' => is_readable($upgrade_dir) ];
|
||
|
||
$data[ 'dir' ][ 'is_readable' ][] = [ 'dir' => str_replace(project_path(), '', $admin_dir), 'status' => is_readable($admin_dir) ];
|
||
$data[ 'dir' ][ 'is_readable' ][] = [ 'dir' => str_replace(project_path(), '', $web_dir), 'status' => is_readable($web_dir) ];
|
||
$data[ 'dir' ][ 'is_readable' ][] = [ 'dir' => str_replace(project_path(), '', $wap_dir), 'status' => is_readable($wap_dir) ];
|
||
|
||
$data[ 'dir' ][ 'is_write' ][] = [ 'dir' => str_replace(project_path(), '', $niucloud_dir), 'status' => is_write($niucloud_dir) ];
|
||
$data[ 'dir' ][ 'is_write' ][] = [ 'dir' => str_replace(project_path(), '', $upgrade_dir), 'status' => is_write($upgrade_dir) ];
|
||
$data[ 'dir' ][ 'is_write' ][] = [ 'dir' => str_replace(project_path(), '', $admin_dir), 'status' => is_write($admin_dir) ];
|
||
$data[ 'dir' ][ 'is_write' ][] = [ 'dir' => str_replace(project_path(), '', $web_dir), 'status' => is_write($web_dir) ];
|
||
$data[ 'dir' ][ 'is_write' ][] = [ 'dir' => str_replace(project_path(), '', $wap_dir), 'status' => is_write($wap_dir) ];
|
||
|
||
// 检测全部目录及文件是否可读可写,忽略指定目录
|
||
|
||
// 忽略指定目录,admin
|
||
$exclude_admin_dir = [ 'dist', 'node_modules', '.git' ];
|
||
$check_res = checkDirPermissions(project_path() . 'admin', [], $exclude_admin_dir);
|
||
|
||
// 忽略指定目录,uni-app
|
||
$exclude_uniapp_dir = [ 'dist', 'node_modules', '.git' ];
|
||
$check_res = array_merge2($check_res, checkDirPermissions(project_path() . 'uni-app', [], $exclude_uniapp_dir));
|
||
|
||
// 忽略指定目录,web
|
||
$exclude_web_dir = [ '.nuxt', '.output', 'dist', 'node_modules', '.git' ];
|
||
$check_res = array_merge2($check_res, checkDirPermissions(project_path() . 'web', [], $exclude_web_dir));
|
||
|
||
// 忽略指定目录,niucloud
|
||
$exclude_niucloud_dir = [
|
||
'public' . DIRECTORY_SEPARATOR . 'admin',
|
||
'public' . DIRECTORY_SEPARATOR . 'wap',
|
||
'public' . DIRECTORY_SEPARATOR . 'web',
|
||
'public' . DIRECTORY_SEPARATOR . 'upload',
|
||
'public' . DIRECTORY_SEPARATOR . 'file',
|
||
'runtime',
|
||
'vendor',
|
||
'.user.ini',
|
||
'.git'
|
||
];
|
||
$check_res = array_merge2($check_res, checkDirPermissions(project_path() . 'niucloud', [], $exclude_niucloud_dir));
|
||
|
||
if (!empty($check_res[ 'unreadable' ])) {
|
||
foreach ($check_res[ 'unreadable' ] as $item) {
|
||
$data[ 'dir' ][ 'is_readable' ][] = [ 'dir' => str_replace(project_path(), '', $item), 'status' => false ];
|
||
}
|
||
}
|
||
if (!empty($check_res[ 'not_writable' ])) {
|
||
foreach ($check_res[ 'not_writable' ] as $item) {
|
||
$data[ 'dir' ][ 'is_write' ][] = [ 'dir' => str_replace(project_path(), '', $item), 'status' => false ];
|
||
}
|
||
}
|
||
|
||
$check_res = array_merge(
|
||
array_column($data[ 'dir' ][ 'is_readable' ], 'status'),
|
||
array_column($data[ 'dir' ][ 'is_write' ], 'status')
|
||
);
|
||
|
||
// 是否通过校验
|
||
$data[ 'is_pass' ] = !in_array(false, $check_res);
|
||
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 升级
|
||
* @param $addon
|
||
* @return array
|
||
*/
|
||
public function upgrade(string $addon = '', $data = [])
|
||
{
|
||
if ($this->upgrade_task) throw new CommonException('UPGRADE_TASK_EXIST');
|
||
|
||
$upgrade_content = $this->getUpgradeContent($addon);
|
||
if (empty($upgrade_content['content'])) throw new CommonException("NOT_EXIST_UPGRADE_CONTENT");
|
||
|
||
// 过滤掉没有更新版本的插件
|
||
$upgrade_content['content'] = array_values(array_filter(array_map(function ($item){
|
||
if (!empty($item['version_list'])) return $item;
|
||
}, $upgrade_content['content'])));
|
||
if (empty($upgrade_content['content'])) throw new CommonException("NOT_EXIST_UPGRADE_CONTENT");
|
||
|
||
$upgrade_content['upgrade_apps'] = [];
|
||
foreach ($upgrade_content['content'] as $item) {
|
||
$upgrade_content['upgrade_apps'][] = $item['app']['app_key'];
|
||
}
|
||
|
||
$upgrade_title = '框架';
|
||
|
||
$upgrade = [
|
||
'product_key' => BaseNiucloudClient::PRODUCT,
|
||
'framework_version' => config('version.version')
|
||
];
|
||
|
||
$upgrade[ 'app_key' ] = $upgrade_content['content'][0]['app']['app_key'];
|
||
$upgrade[ 'version' ] = $upgrade_content['content'][0]['version'];
|
||
|
||
// if (!$addon) {
|
||
// $upgrade[ 'app_key' ] = AddonDict::FRAMEWORK_KEY;
|
||
// $upgrade[ 'version' ] = config('version.version');
|
||
// } else {
|
||
// $upgrade[ 'app_key' ] = $addon;
|
||
// $upgrade[ 'version' ] = ( new Addon() )->where([ [ 'key', '=', $addon ] ])->value('version');
|
||
// $upgrade_title = ( new Addon() )->where([ [ 'key', '=', $addon ] ])->value('title');
|
||
//
|
||
// // 判断框架版本是否低于插件支持版本
|
||
// $last_version = $upgrade_content[ 'version_list' ][ count($upgrade_content[ 'version_list' ]) - 1 ];
|
||
// if (str_replace('.', '', config('version.version')) < str_replace('.', '', $last_version[ 'niucloud_version' ][ 'version_no' ])) {
|
||
// throw new CommonException('BEFORE_UPGRADING_NEED_UPGRADE_FRAMEWORK');
|
||
// }
|
||
// }
|
||
|
||
$response = ( new CoreAddonCloudService() )->upgradeAddon($upgrade);
|
||
if (isset($response[ 'code' ]) && $response[ 'code' ] == 0) throw new CommonException($response[ 'msg' ]);
|
||
|
||
try {
|
||
$key = uniqid();
|
||
$upgrade_dir = $this->upgrade_dir . $key . DIRECTORY_SEPARATOR;
|
||
|
||
if (!is_dir($upgrade_dir)) {
|
||
dir_mkdir($upgrade_dir);
|
||
}
|
||
|
||
// 是否需要备份
|
||
$is_need_backup = $data['is_need_backup'] ?? true;
|
||
if (!$is_need_backup) {
|
||
unset($this->steps['backupCode']);
|
||
unset($this->steps['backupSql']);
|
||
}
|
||
|
||
// 是否需要云编译
|
||
$is_need_cloudbuild = $data['is_need_cloudbuild'] ?? true;
|
||
if (!$is_need_cloudbuild) {
|
||
unset($this->steps['cloudBuild']);
|
||
unset($this->steps['gteCloudBuildLog']);
|
||
}
|
||
|
||
$upgrade_task = [
|
||
'key' => $key,
|
||
'upgrade' => $upgrade,
|
||
'steps' => $this->steps,
|
||
'step' => 'requestUpgrade',
|
||
'executed' => [ 'requestUpgrade' ],
|
||
'log' => [ $this->steps[ 'requestUpgrade' ][ 'title' ] ],
|
||
'params' => [ 'app_key' => $upgrade[ 'app_key' ], 'token' => $response[ 'token' ] ],
|
||
'upgrade_content' => $upgrade_content,
|
||
'upgrade_apps' => $upgrade_content['upgrade_apps'],
|
||
'is_need_backup' => $is_need_backup,
|
||
];
|
||
|
||
foreach ($upgrade_content[ 'content' ] as $k => $v) {
|
||
unset($upgrade_content[ 'content' ][ $k ][ 'version_list' ]);
|
||
}
|
||
|
||
// 记录升级日志
|
||
( new UpgradeRecordsService() )->add([
|
||
'upgrade_key' => $upgrade_task[ 'key' ],
|
||
'app_key' => $upgrade[ 'app_key' ],
|
||
'name' => $upgrade_title,
|
||
'content' => json_encode($upgrade_content),
|
||
'prev_version' => "",
|
||
'current_version' => "",
|
||
'status' => UpgradeDict::STATUS_READY,
|
||
'is_need_backup' => $is_need_backup
|
||
]);
|
||
|
||
Cache::set($this->cache_key, $upgrade_task);
|
||
return $upgrade_task;
|
||
} catch (\Exception $e) {
|
||
throw new CommonException($e->getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 执行升级
|
||
* @return true
|
||
*/
|
||
public function execute()
|
||
{
|
||
if (!$this->upgrade_task) return true;
|
||
|
||
$steps = isset($this->upgrade_task[ 'steps' ]) ? array_keys($this->upgrade_task[ 'steps' ]) : array_keys($this->steps);
|
||
if (isset($this->upgrade_task[ 'steps' ])) $this->steps = $this->upgrade_task[ 'steps' ];
|
||
$index = array_search($this->upgrade_task[ 'step' ], $steps);
|
||
$step = $steps[ $index + 1 ] ?? '';
|
||
$params = $this->upgrade_task[ 'params' ] ?? [];
|
||
|
||
if ($step) {
|
||
try {
|
||
$res = $this->$step(...$params);
|
||
|
||
if (is_array($res)) {
|
||
$this->upgrade_task[ 'params' ] = $res;
|
||
} else {
|
||
$this->upgrade_task[ 'step' ] = $step;
|
||
$this->upgrade_task[ 'params' ] = [];
|
||
$this->upgrade_task[ 'executed' ][] = $step;
|
||
if (!empty($this->steps[ $step ][ 'title' ])) {
|
||
$this->upgrade_task[ 'log' ][] = $this->steps[ $step ][ 'title' ];
|
||
}
|
||
}
|
||
if (!in_array($step, [ 'upgradeComplete', 'restoreComplete' ])) {
|
||
Cache::set($this->cache_key, $this->upgrade_task);
|
||
} else {
|
||
$this->clearUpgradeTask(2);
|
||
}
|
||
} catch (CloudBuildException $e) {
|
||
if (strpos($e->getMessage(), '队列') !== false) {
|
||
throw new CloudBuildException($e->getMessage());
|
||
}
|
||
$this->upgrade_task[ 'step' ] = $step;
|
||
$this->upgrade_task[ 'error' ][] = '升级失败,失败原因:' . $e->getMessage() . $e->getFile() . $e->getLine();
|
||
|
||
$fail_reason = [
|
||
'Message' => '失败原因:' . $e->getMessage(),
|
||
'File' => '文件:' . $e->getFile(),
|
||
'Line' => '代码行号:' . $e->getLine(),
|
||
'Trace' => $e->getTrace()
|
||
];
|
||
$this->upgradeErrorHandle($fail_reason);
|
||
} catch (\Exception $e) {
|
||
$this->upgrade_task[ 'step' ] = $step;
|
||
$this->upgrade_task[ 'error' ][] = '升级失败,失败原因:' . $e->getMessage() . $e->getFile() . $e->getLine();
|
||
$fail_reason = [
|
||
'Message' => '失败原因:' . $e->getMessage(),
|
||
'File' => '文件:' . $e->getFile(),
|
||
'Line' => '代码行号:' . $e->getLine(),
|
||
'Trace' => $e->getTrace()
|
||
];
|
||
$this->upgradeErrorHandle($fail_reason);
|
||
}
|
||
return true;
|
||
} else {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 下载升级文件
|
||
* @param string $token
|
||
* @param string $dir
|
||
* @param int $index
|
||
* @param $step
|
||
* @return true|null
|
||
*/
|
||
public function downloadFile(string $app_key, string $token, string $dir = '', int $index = -1, $step = 0, $length = 0)
|
||
{
|
||
if (!$dir) {
|
||
$dir = $this->upgrade_dir . $this->upgrade_task[ 'key' ] . DIRECTORY_SEPARATOR . 'download' . DIRECTORY_SEPARATOR . $app_key . DIRECTORY_SEPARATOR;
|
||
dir_mkdir($dir);
|
||
}
|
||
$res = ( new CoreAddonCloudService() )->downloadUpgradeFile($app_key, $token, $dir, $index, $step, $length);
|
||
|
||
if ($res === true) {
|
||
$index = array_search($app_key, $this->upgrade_task['upgrade_apps']);
|
||
if ($app_key == AddonDict::FRAMEWORK_KEY) {
|
||
$this->upgrade_task['log'][] = "下载更新文件";
|
||
} else {
|
||
$this->upgrade_task['log'][] = "下载". $this->upgrade_task['upgrade_content']['content'][$index]['app']['app_name'] ."更新文件";
|
||
}
|
||
$index++;
|
||
if (isset($this->upgrade_task['upgrade_apps'][$index])) {
|
||
$upgrade = [
|
||
'product_key' => BaseNiucloudClient::PRODUCT,
|
||
'framework_version' => config('version.version'),
|
||
'app_key' => $this->upgrade_task['upgrade_content']['content'][$index]['app']['app_key'],
|
||
'version' => $this->upgrade_task['upgrade_content']['content'][$index]['version']
|
||
];
|
||
$response = ( new CoreAddonCloudService() )->upgradeAddon($upgrade);
|
||
if (isset($response[ 'code' ]) && $response[ 'code' ] == 0) throw new CommonException($response[ 'msg' ]);
|
||
|
||
return ['app_key' => $upgrade[ 'app_key' ], 'token' => $response[ 'token' ]];
|
||
}
|
||
}
|
||
return $res;
|
||
}
|
||
|
||
/**
|
||
* 备份源码
|
||
* @return true
|
||
*/
|
||
public function backupCode()
|
||
{
|
||
( new BackupService() )->backupCode();
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 备份数据库
|
||
* @return true
|
||
*/
|
||
public function backupSql($index = 0)
|
||
{
|
||
$backup_dir = $this->upgrade_dir . $this->upgrade_task[ 'key' ] . DIRECTORY_SEPARATOR . 'backup' . DIRECTORY_SEPARATOR . 'sql' . DIRECTORY_SEPARATOR;
|
||
// 创建目录
|
||
dir_mkdir($backup_dir);
|
||
|
||
$db = new DbBackup($backup_dir, 1024 * 1024 * 2, key: $this->upgrade_task[ 'key' ]);
|
||
|
||
$prefix = config('database.connections.' . config('database.default'))[ 'prefix' ];
|
||
|
||
// 不需要备份的表
|
||
$not_need_backup = [
|
||
"{$prefix}sys_schedule_log",
|
||
"{$prefix}sys_user_log",
|
||
"{$prefix}jobs",
|
||
"{$prefix}jobs_failed",
|
||
"{$prefix}sys_upgrade_records",
|
||
"{$prefix}sys_backup_records"
|
||
];
|
||
|
||
$result = $db->setExcludeTables($not_need_backup)->backupDatabaseSegment();
|
||
if ($db->getBackupProgress() == 100) {
|
||
$this->upgrade_task[ 'log' ][] = "数据库备份完成";
|
||
} else {
|
||
$this->upgrade_task[ 'log' ][] = $db->getBackupProgress() == 0 ? '数据库开始备份' : '数据库备份已备份'. $db->getBackupProgress() . '%';
|
||
}
|
||
if ($result === true) return true;
|
||
return ['index' => $result];
|
||
}
|
||
|
||
/**
|
||
* 覆盖更新升级的代码
|
||
* @return array|true
|
||
*/
|
||
public function coverCode($index = 0, $addon = "")
|
||
{
|
||
$this->upgrade_task[ 'is_cover' ] = 1;
|
||
if (empty($addon)) $addon = $this->upgrade_task['upgrade_apps'][0];
|
||
|
||
$app_index = array_search($addon, $this->upgrade_task['upgrade_apps']);
|
||
|
||
$version_list = array_reverse($this->upgrade_task[ 'upgrade_content' ]['content'][$app_index][ 'version_list' ]);
|
||
$code_dir = $this->upgrade_dir . $this->upgrade_task[ 'key' ] . DIRECTORY_SEPARATOR . 'download' . DIRECTORY_SEPARATOR . $addon . DIRECTORY_SEPARATOR . 'code' . DIRECTORY_SEPARATOR;
|
||
|
||
$version_item = $version_list[ $index ];
|
||
$version_no = $version_item[ 'version_no' ];
|
||
|
||
$to_dir = $addon == AddonDict::FRAMEWORK_KEY ? rtrim($this->root_path, DIRECTORY_SEPARATOR) : $this->root_path . 'niucloud' . DIRECTORY_SEPARATOR . 'addon' . DIRECTORY_SEPARATOR . $addon;
|
||
|
||
// 获取文件变更记录
|
||
if (file_exists($code_dir . $version_no . '.txt')) {
|
||
$change = array_filter(explode("\n", file_get_contents($code_dir . $version_no . '.txt')));
|
||
foreach ($change as &$item) {
|
||
list($operation, $md5, $file) = $item = explode(' ', $item);
|
||
if ($operation == '-') {
|
||
@unlink($to_dir . $file);
|
||
}
|
||
}
|
||
// 合并依赖
|
||
$this->installDepend($code_dir . $version_no, array_column($change, 2));
|
||
}
|
||
|
||
// 覆盖文件
|
||
if (is_dir($code_dir . $version_no)) {
|
||
// 忽略环境变量文件
|
||
$exclude_files = [ '.env.development', '.env.production', '.env', '.env.dev', '.env.product' ];
|
||
dir_copy($code_dir . $version_no, $to_dir, exclude_files: $exclude_files);
|
||
if ($addon != AddonDict::FRAMEWORK_KEY) {
|
||
( new CoreAddonInstallService($addon) )->installDir();
|
||
}
|
||
}
|
||
|
||
$upgrade_file_dir = 'v' . str_replace('.', '', $version_no);
|
||
if ($addon == AddonDict::FRAMEWORK_KEY) {
|
||
$class_path = "\\app\\upgrade\\{$upgrade_file_dir}\\Upgrade";
|
||
$sql_file = root_path() . 'app' . DIRECTORY_SEPARATOR . 'upgrade' . DIRECTORY_SEPARATOR . $upgrade_file_dir . DIRECTORY_SEPARATOR . 'upgrade.sql';
|
||
} else {
|
||
$class_path = "\\addon\\{$addon}\\app\\upgrade\\{$upgrade_file_dir}\\Upgrade";
|
||
$sql_file = root_path() . 'addon' . DIRECTORY_SEPARATOR . $addon . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'upgrade' . DIRECTORY_SEPARATOR . $upgrade_file_dir . DIRECTORY_SEPARATOR . 'upgrade.sql';
|
||
}
|
||
|
||
// 执行升级sql
|
||
if (file_exists($sql_file)) {
|
||
$this->executeSql($sql_file);
|
||
}
|
||
|
||
// 执行升级方法
|
||
if (class_exists($class_path)) {
|
||
( new $class_path() )->handle();
|
||
}
|
||
|
||
$index++;
|
||
if ($index < count($version_list)) {
|
||
return compact('index', 'addon');
|
||
} else {
|
||
$app_index++;
|
||
if (isset($this->upgrade_task['upgrade_apps'][$app_index])) {
|
||
return ['index' => 0, 'addon' => $this->upgrade_task['upgrade_apps'][$app_index]];
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 合并依赖
|
||
* @param string $version_no
|
||
* @return void
|
||
*/
|
||
public function installDepend(string $dir, array $change_files)
|
||
{
|
||
$addon = $this->upgrade_task[ 'upgrade' ][ 'app_key' ];
|
||
$depend_service = new CoreDependService();
|
||
|
||
if ($addon == AddonDict::FRAMEWORK_KEY) {
|
||
$composer = '/niucloud/composer.json';
|
||
$admin_package = '/admin/package.json';
|
||
$web_package = '/web/package.json';
|
||
$uniapp_package = '/uni-app/package.json';
|
||
} else {
|
||
$composer = "/niucloud/addon/{$addon}/package/composer.json";
|
||
$admin_package = "/niucloud/addon/{$addon}/package/admin-package.json";
|
||
$web_package = "/niucloud/addon/{$addon}/package/web-package.json";
|
||
$uniapp_package = "/niucloud/addon/{$addon}/package/uni-app-package.json";
|
||
}
|
||
|
||
if (in_array($composer, $change_files)) {
|
||
$original = $depend_service->getComposerContent();
|
||
$new = $depend_service->jsonFileToArray($dir . $composer);
|
||
foreach ($new as $name => $value) {
|
||
$original[ $name ] = isset($original[ $name ]) && is_array($original[ $name ]) ? array_map('unserialize', array_unique(array_map('serialize', array_merge($original[ $name ], $new[ $name ])))) : $new[ $name ];
|
||
}
|
||
$depend_service->writeArrayToJsonFile($original, $dir . $composer);
|
||
}
|
||
if (in_array($admin_package, $change_files)) {
|
||
$original = $depend_service->getNpmContent('admin');
|
||
$new = $depend_service->jsonFileToArray($dir . $admin_package);
|
||
|
||
foreach ($new as $name => $value) {
|
||
$original[ $name ] = isset($original[ $name ]) && is_array($original[ $name ]) ? array_merge($original[ $name ], $new[ $name ]) : $new[ $name ];
|
||
}
|
||
$depend_service->writeArrayToJsonFile($original, $dir . $admin_package);
|
||
}
|
||
if (in_array($web_package, $change_files)) {
|
||
$original = $depend_service->getNpmContent('web');
|
||
$new = $depend_service->jsonFileToArray($dir . $web_package);
|
||
|
||
foreach ($new as $name => $value) {
|
||
$original[ $name ] = isset($original[ $name ]) && is_array($original[ $name ]) ? array_merge($original[ $name ], $new[ $name ]) : $new[ $name ];
|
||
}
|
||
$depend_service->writeArrayToJsonFile($original, $dir . $web_package);
|
||
}
|
||
if (in_array($uniapp_package, $change_files)) {
|
||
$original = $depend_service->getNpmContent('uni-app');
|
||
$new = $depend_service->jsonFileToArray($dir . $uniapp_package);
|
||
|
||
foreach ($new as $name => $value) {
|
||
$original[ $name ] = isset($original[ $name ]) && is_array($original[ $name ]) ? array_merge($original[ $name ], $new[ $name ]) : $new[ $name ];
|
||
}
|
||
$depend_service->writeArrayToJsonFile($original, $dir . $uniapp_package);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理手机端
|
||
* @return true
|
||
*/
|
||
public function handleUniapp()
|
||
{
|
||
$key = end($this->upgrade_task['upgrade_apps']);
|
||
|
||
$code_dir = $this->upgrade_dir . $this->upgrade_task[ 'key' ] . DIRECTORY_SEPARATOR . 'download' . DIRECTORY_SEPARATOR . $key . DIRECTORY_SEPARATOR . 'code' . DIRECTORY_SEPARATOR;
|
||
$exclude_files = [ '.env.development', '.env.production', 'manifest.json' ];
|
||
dir_copy($code_dir . 'uni-app', $this->root_path . 'uni-app', exclude_files: $exclude_files);
|
||
|
||
$addon_list = ( new CoreAddonService() )->getInstallAddonList();
|
||
$depend_service = new CoreDependService();
|
||
|
||
if (!empty($addon_list)) {
|
||
|
||
foreach ($addon_list as $addon => $item) {
|
||
$this->addon = $addon;
|
||
|
||
// 编译 diy-group 自定义组件代码文件
|
||
$this->compileDiyComponentsCode($this->root_path . 'uni-app' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR, $addon);
|
||
|
||
// 编译 pages.json 页面路由代码文件
|
||
$this->installPageCode($this->root_path . 'uni-app' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR);
|
||
|
||
// 编译 加载插件标题语言包
|
||
$this->compileLocale($this->root_path . 'uni-app' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR, $addon);
|
||
|
||
// 合并插件依赖
|
||
$addon_uniapp_package = str_replace('/', DIRECTORY_SEPARATOR, project_path() . "niucloud/addon/{$addon}/package/uni-app-package.json");
|
||
|
||
if (file_exists($addon_uniapp_package)) {
|
||
$original = $depend_service->getNpmContent('uni-app');
|
||
$new = $depend_service->jsonFileToArray($addon_uniapp_package);
|
||
|
||
foreach ($new as $name => $value) {
|
||
$original[ $name ] = isset($original[ $name ]) && is_array($original[ $name ]) ? array_merge($original[ $name ], $new[ $name ]) : $new[ $name ];
|
||
}
|
||
|
||
$uniapp_package = $this->root_path . 'uni-app' . DIRECTORY_SEPARATOR . 'package.json';
|
||
$depend_service->writeArrayToJsonFile($original, $uniapp_package);
|
||
}
|
||
}
|
||
}
|
||
|
||
$map = ( new ConfigService() )->getMap();
|
||
( new CoreH5Service() )->mapKeyChange($map[ 'key' ]);
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 执行升级sql
|
||
* @param string $sql_file
|
||
* @return true
|
||
*/
|
||
private function executeSql(string $sql_file)
|
||
{
|
||
$sql_content = file_get_contents($sql_file);
|
||
|
||
if (!empty($sql_content)) {
|
||
$prefix = config('database.connections.mysql.prefix');
|
||
$sql_data = array_filter($this->getSqlQuery($sql_content));
|
||
|
||
if (!empty($sql_data)) {
|
||
foreach ($sql_data as $sql) {
|
||
$sql = $prefix ? $this->handleSqlPrefix($sql, $prefix) : $sql;
|
||
Db::query($sql);
|
||
}
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 刷新菜单
|
||
* @return array|true
|
||
*/
|
||
public function refreshMenu($addon = "")
|
||
{
|
||
if (empty($addon)) $addon = $this->upgrade_task['upgrade_apps'][0];
|
||
$app_index = array_search($addon, $this->upgrade_task['upgrade_apps']);
|
||
|
||
if ($addon == AddonDict::FRAMEWORK_KEY) {
|
||
( new InstallSystemService() )->installMenu();
|
||
} else {
|
||
( new CoreMenuService() )->refreshAddonMenu($addon);
|
||
}
|
||
|
||
$app_index++;
|
||
if (isset($this->upgrade_task['upgrade_apps'][$app_index])) {
|
||
return ['addon' => $this->upgrade_task['upgrade_apps'][$app_index]];
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 安装计划任务
|
||
* @return true
|
||
*/
|
||
public function installSchedule($addon = "")
|
||
{
|
||
if (empty($addon)) $addon = $this->upgrade_task['upgrade_apps'][0];
|
||
$app_index = array_search($addon, $this->upgrade_task['upgrade_apps']);
|
||
|
||
if ($addon == AddonDict::FRAMEWORK_KEY) {
|
||
( new CoreScheduleInstallService() )->installSystemSchedule();
|
||
} else {
|
||
( new CoreScheduleInstallService() )->installAddonSchedule($addon);
|
||
}
|
||
|
||
$app_index++;
|
||
if (isset($this->upgrade_task['upgrade_apps'][$app_index])) {
|
||
return ['addon' => $this->upgrade_task['upgrade_apps'][$app_index]];
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 云编译
|
||
* @return void
|
||
*/
|
||
public function cloudBuild()
|
||
{
|
||
( new CoreCloudBuildService() )->cloudBuild();
|
||
}
|
||
|
||
/**
|
||
* 获取云编译日志
|
||
* @return array|true
|
||
*/
|
||
public function gteCloudBuildLog()
|
||
{
|
||
$log = ( new CoreCloudBuildService() )->getBuildLog();
|
||
if (empty($log)) return true;
|
||
|
||
foreach ($log[ 'data' ][ 0 ] as $item) {
|
||
if ($item[ 'code' ] == 0) {
|
||
$this->upgrade_task[ 'step' ] = 'gteCloudBuildLog';
|
||
$this->upgrade_task[ 'error' ][] = $item[ 'msg' ];
|
||
Cache::set($this->cache_key, $this->upgrade_task);
|
||
|
||
$fail_reason = [
|
||
'Message' => '失败原因 云编译错误:' . $item[ 'msg' ],
|
||
'File' => '',
|
||
'Line' => '',
|
||
'Trace' => ''
|
||
];
|
||
|
||
( new CoreCloudBuildService() )->clearTask();
|
||
|
||
$this->upgradeErrorHandle($fail_reason);
|
||
|
||
return true;
|
||
}
|
||
if (!in_array($item[ 'action' ], $this->upgrade_task[ 'log' ])) {
|
||
$this->upgrade_task[ 'log' ][] = $item[ 'action' ];
|
||
Cache::set($this->cache_key, $this->upgrade_task);
|
||
}
|
||
}
|
||
|
||
sleep(2);
|
||
return [];
|
||
}
|
||
|
||
/**
|
||
* 更新完成
|
||
* @return true
|
||
*/
|
||
public function upgradeComplete()
|
||
{
|
||
foreach ($this->upgrade_task['upgrade_apps'] as $addon) {
|
||
if ($addon != AddonDict::FRAMEWORK_KEY) {
|
||
$core_addon_service = new CoreAddonService();
|
||
$install_data = $core_addon_service->getAddonConfig($addon);
|
||
$install_data[ 'icon' ] = 'addon/' . $addon . '/icon.png';
|
||
$core_addon_service->set($install_data);
|
||
}
|
||
}
|
||
// 执行完成,更新升级记录状态,备份记录状态
|
||
( new UpgradeRecordsService() )->complete($this->upgrade_task[ 'key' ]);
|
||
$this->clearUpgradeTask(2);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 升级出错之后的处理
|
||
* @return void
|
||
*/
|
||
public function upgradeErrorHandle($fail_reason = [])
|
||
{
|
||
$steps = [];
|
||
$steps[ $this->upgrade_task[ 'step' ] ] = [];
|
||
|
||
if (isset($this->upgrade_task[ 'is_cover' ])) {
|
||
$steps[ 'restoreCode' ] = [ 'step' => 'restoreCode', 'title' => '恢复源码备份' ];
|
||
$steps[ 'restoreSql' ] = [ 'step' => 'restoreSql', 'title' => '恢复数据库备份' ];
|
||
}
|
||
$steps[ 'restoreComplete' ] = [ 'step' => 'restoreComplete', 'title' => '备份恢复完成' ];
|
||
$this->upgrade_task[ 'steps' ] = $steps;
|
||
$this->upgrade_task[ 'params' ] = [];
|
||
Cache::set($this->cache_key, $this->upgrade_task);
|
||
|
||
Log::write('升级出错之后的处理:'.json_encode($fail_reason));
|
||
}
|
||
|
||
/**
|
||
* 恢复源码
|
||
* @return true
|
||
*/
|
||
public function restoreCode()
|
||
{
|
||
if (!isset($this->upgrade_task['is_need_backup']) || $this->upgrade_task['is_need_backup']) {
|
||
$backup_dir = $this->upgrade_dir . $this->upgrade_task[ 'key' ] . DIRECTORY_SEPARATOR . 'backup' . DIRECTORY_SEPARATOR . 'code' . DIRECTORY_SEPARATOR;
|
||
} else {
|
||
$backup_dir = $this->upgrade_dir . $this->upgrade_task['upgrade_content']['last_backup'][ 'key' ] . DIRECTORY_SEPARATOR . 'backup' . DIRECTORY_SEPARATOR . 'code' . DIRECTORY_SEPARATOR;
|
||
}
|
||
try {
|
||
if (is_dir($backup_dir)) {
|
||
dir_copy($backup_dir, rtrim($this->root_path, DIRECTORY_SEPARATOR));
|
||
}
|
||
return true;
|
||
} catch (\Exception $e) {
|
||
$this->upgrade_task[ 'error' ][] = '源码备份恢复失败稍后请手动恢复,源码备份文件路径:' . $backup_dir . ',失败原因:' . $e->getMessage() . $e->getFile() . $e->getLine();
|
||
Cache::set($this->cache_key, $this->upgrade_task);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 恢复数据库
|
||
* @return array|true
|
||
*/
|
||
public function restoreSql($index = 0)
|
||
{
|
||
if (!isset($this->upgrade_task['is_need_backup']) || $this->upgrade_task['is_need_backup']) {
|
||
$backup_dir = $this->upgrade_dir . $this->upgrade_task[ 'key' ] . DIRECTORY_SEPARATOR . 'backup' . DIRECTORY_SEPARATOR . 'sql' . DIRECTORY_SEPARATOR;
|
||
} else {
|
||
$backup_dir = $this->upgrade_dir . $this->upgrade_task['upgrade_content']['last_backup'][ 'key' ] . DIRECTORY_SEPARATOR . 'backup' . DIRECTORY_SEPARATOR . 'sql' . DIRECTORY_SEPARATOR;
|
||
}
|
||
try {
|
||
if (is_dir($backup_dir)) {
|
||
$db = new DbBackup($backup_dir, key: $this->upgrade_task[ 'key' ]);
|
||
$result = $db->restoreDatabase();
|
||
if ($result !== true) return ['index' => $result];
|
||
|
||
$restore_progress = $db->getRestoreProgress();
|
||
if ($restore_progress % 5 == 0) {
|
||
$this->upgrade_task[ 'log' ][] = $restore_progress == 0 ? '数据库开始恢复' : '数据库恢复中已恢复'. $restore_progress . '%';
|
||
}
|
||
}
|
||
|
||
// 处理admin站点不为0的问题
|
||
$site_model = new Site();
|
||
$site_model->where([
|
||
[ 'site_id', '<>', 0 ],
|
||
[ 'app_type', '=', AppTypeDict::ADMIN ]
|
||
])->update([ 'site_id' => 0 ]);
|
||
|
||
return true;
|
||
} catch (\Exception $e) {
|
||
$this->upgrade_task[ 'error' ][] = '数据库备份恢复失败稍后请手动恢复,数据库备份文件路径:' . $backup_dir . ',失败原因:' . $e->getMessage() . $e->getFile() . $e->getLine();
|
||
Cache::set($this->cache_key, $this->upgrade_task);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
public function restoreComplete()
|
||
{
|
||
( new UpgradeRecordsService() )->failed($this->upgrade_task[ 'key' ], $this->upgrade_task['error']);
|
||
$this->clearUpgradeTask(2);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 获取升级内容
|
||
* @param string $addon
|
||
* @return array
|
||
* @throws \GuzzleHttp\Exception\GuzzleException
|
||
*/
|
||
public function getUpgradeContent(string $addon = '')
|
||
{
|
||
$content = [
|
||
'content' => [],
|
||
'upgrade_apps' => []
|
||
];
|
||
|
||
$upgrade = [
|
||
'product_key' => BaseNiucloudClient::PRODUCT
|
||
];
|
||
$apps = [];
|
||
if (!$addon) {
|
||
$upgrade[ 'app_key' ] = AddonDict::FRAMEWORK_KEY;
|
||
$upgrade[ 'version' ] = config('version.version');
|
||
array_push($apps, $upgrade);
|
||
} else {
|
||
$addons = array_filter(explode(',', $addon));
|
||
foreach ($addons as $key) {
|
||
if ($key != AddonDict::FRAMEWORK_KEY) {
|
||
$info = ( new Addon() )->where([ [ 'key', '=', $key ] ])->field('version,type')->find();
|
||
$upgrade[ 'app_key' ] = $key;
|
||
$upgrade[ 'version' ] = $info['version'];
|
||
if ($info['type'] == 'app') {
|
||
array_unshift($apps, $upgrade);
|
||
} else {
|
||
array_push($apps, $upgrade);
|
||
}
|
||
}
|
||
}
|
||
if (in_array(AddonDict::FRAMEWORK_KEY, $addons)) {
|
||
$upgrade[ 'app_key' ] = AddonDict::FRAMEWORK_KEY;
|
||
$upgrade[ 'version' ] = config('version.version');
|
||
array_unshift($apps, $upgrade);
|
||
}
|
||
}
|
||
|
||
foreach ($apps as $item) {
|
||
$upgrade_content = ( new CoreModuleService() )->getUpgradeContent($item)[ 'data' ] ?? [];
|
||
if (!empty($upgrade_content)) {
|
||
$content['content'][] = $upgrade_content;
|
||
$content['upgrade_apps'][] = $upgrade_content['app']['app_key'];
|
||
}
|
||
}
|
||
|
||
if (!empty($content['content'])) {
|
||
$content['last_backup'] = (new SysBackupRecords())->where([ ['status', '=', BackupDict::STATUS_COMPLETE ] ])->order('complete_time desc')->find();
|
||
}
|
||
|
||
return $content;
|
||
}
|
||
|
||
/**
|
||
* 获取正在进行的升级任务
|
||
* @return mixed|null
|
||
*/
|
||
public function getUpgradeTask()
|
||
{
|
||
return $this->upgrade_task;
|
||
}
|
||
|
||
/**
|
||
* 清除升级任务
|
||
* @return true
|
||
*/
|
||
public function clearUpgradeTask(int $delayed = 0, int $is_active = 0)
|
||
{
|
||
// 主动取消升级
|
||
if ($is_active && $this->upgrade_task && !empty($this->upgrade_task[ 'key' ])) {
|
||
( new UpgradeRecordsService() )->edit([ [ 'upgrade_key', '=', $this->upgrade_task[ 'key' ] ], ['status', '=', UpgradeDict::STATUS_READY ] ], ['status' => UpgradeDict::STATUS_CANCEL]);
|
||
}
|
||
|
||
if ($delayed) {
|
||
Cache::set($this->cache_key, $this->upgrade_task, $delayed);
|
||
} else {
|
||
Cache::set($this->cache_key, null);
|
||
}
|
||
// 清除云编译的任务缓存
|
||
( new CoreCloudBuildService() )->clearTask();
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 获取插件定义的package目录
|
||
* @param string $addon
|
||
* @return string
|
||
*/
|
||
public function geAddonPackagePath(string $addon)
|
||
{
|
||
return root_path() . 'addon' . DIRECTORY_SEPARATOR . $addon . DIRECTORY_SEPARATOR . 'package' . DIRECTORY_SEPARATOR;
|
||
}
|
||
|
||
/**
|
||
* 用户操作
|
||
* @param $operate
|
||
* @return void
|
||
*/
|
||
public function operate($operate)
|
||
{
|
||
if (!$this->upgrade_task) return true;
|
||
|
||
switch ($operate) {
|
||
case 'local':
|
||
$this->upgrade_task[ 'step' ] = 'gteCloudBuildLog';
|
||
Cache::set($this->cache_key, $this->upgrade_task);
|
||
break;
|
||
case 'rollback':
|
||
$fail_reason = [
|
||
'Message' => '失败原因:一键云编译队列任务过多',
|
||
'File' => '',
|
||
'Line' => '',
|
||
'Trace' => ''
|
||
];
|
||
$this->upgradeErrorHandle($fail_reason);
|
||
break;
|
||
}
|
||
}
|
||
}
|