123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584 |
- <?php
- // +----------------------------------------------------------------------
- // | Niucloud-admin 企业快速开发的saas管理平台
- // +----------------------------------------------------------------------
- // | 官方网址:https://www.niucloud.com
- // +----------------------------------------------------------------------
- // | niucloud团队 版权所有 开源版本可自由商用
- // +----------------------------------------------------------------------
- // | Author: Niucloud Team
- // +----------------------------------------------------------------------
- namespace app\service\admin\upgrade;
- use app\dict\addon\AddonDict;
- use app\model\addon\Addon;
- 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\CoreModuleService;
- use app\service\core\schedule\CoreScheduleInstallService;
- use core\base\BaseAdminService;
- use core\exception\CommonException;
- use core\util\niucloud\BaseNiucloudClient;
- use think\facade\Cache;
- use think\facade\Db;
- /**
- * 框架及插件升级
- * @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' => '安装计划任务'],
- '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);
- }
- /**
- * 升级前环境检测
- * @param string $addon
- * @return void
- */
- public function upgradePreCheck(string $addon = '') {
- $niucloud_dir = $this->root_path . 'niucloud' . 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;
- try {
- 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');
- } catch (\Exception $e) {
- throw new CommonException($e->getMessage());
- }
- $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(), '', $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(), '', $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) ];
- $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 = '') {
- if ($this->upgrade_task) throw new CommonException('UPGRADE_TASK_EXIST');
- $upgrade = [
- 'product_key' => BaseNiucloudClient::PRODUCT,
- 'framework_version' => config('version.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');
- }
- $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);
- }
- $upgrade_tsak = [
- 'key' => $key,
- 'upgrade' => $upgrade,
- 'steps' => $this->steps,
- 'step' => 'requestUpgrade',
- 'executed' => ['requestUpgrade'],
- 'log' => [ $this->steps['requestUpgrade']['title'] ],
- 'params' => ['token' => $response['token'] ],
- 'upgrade_content' => $this->getUpgradeContent($addon)
- ];
- Cache::set($this->cache_key, $upgrade_tsak);
- return $upgrade_tsak;
- } 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);
- $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;
- $this->upgrade_task['log'][] = $this->steps[$step]['title'];
- }
- Cache::set($this->cache_key, $this->upgrade_task);
- } catch (\Exception $e) {
- $this->upgrade_task['step'] = $step;
- $this->upgrade_task['error'][] = '升级失败,失败原因:' . $e->getMessage().$e->getFile().$e->getLine();
- Cache::set($this->cache_key, $this->upgrade_task);
- $this->upgradeErrorHandle();
- }
- return true;
- } else {
- return true;
- }
- }
- /**
- * 下载升级文件
- * @param string $token
- * @param string $dir
- * @param int $index
- * @param $step
- * @return true|null
- */
- public function downloadFile(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;
- dir_mkdir($dir);
- }
- $res = (new CoreAddonCloudService())->downloadUpgradeFile($token, $dir, $index, $step, $length);
- return $res;
- }
- /**
- * 备份源码
- * @return true
- */
- public function backupCode() {
- (new BackupService())->backupCode();
- return true;
- }
- /**
- * 备份数据库
- * @return true
- */
- public function backupSql() {
- (new BackupService())->backupSql();
- return true;
- }
- /**
- * 覆盖更新升级的代码
- * @return void
- */
- public function coverCode($index = 0) {
- $this->upgrade_task['is_cover'] = 1;
- $addon = $this->upgrade_task['upgrade']['app_key'];
- $version_list = array_reverse($this->upgrade_task['upgrade_content']['version_list']);
- $code_dir = $this->upgrade_dir .$this->upgrade_task['key'] . DIRECTORY_SEPARATOR . 'download' . 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');
- } else {
- 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_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);
- }
- }
- /**
- * 处理手机端
- * @param string $verson_no
- * @return true
- */
- public function handleUniapp() {
- $code_dir = $this->upgrade_dir .$this->upgrade_task['key'] . DIRECTORY_SEPARATOR . 'download' . 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 void
- */
- public function refreshMenu() {
- if ($this->upgrade_task['upgrade']['app_key'] == AddonDict::FRAMEWORK_KEY) {
- (new InstallSystemService())->installMenu();
- } else {
- (new CoreMenuService())->refreshAddonMenu($this->upgrade_task['upgrade']['app_key']);
- }
- return true;
- }
- /**
- * 安装计划任务
- * @return true
- */
- public function installSchedule() {
- if ($this->upgrade_task['upgrade']['app_key'] == AddonDict::FRAMEWORK_KEY) {
- (new CoreScheduleInstallService())->installSystemSchedule();
- } else {
- (new CoreScheduleInstallService())->installAddonSchedule($this->upgrade_task['upgrade']['app_key']);
- }
- return true;
- }
- /**
- * 更新完成
- * @return void
- */
- public function upgradeComplete() {
- $addon = $this->upgrade_task['upgrade']['app_key'];
- 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);
- }
- $this->clearUpgradeTask(5);
- return true;
- }
- /**
- * 升级出错之后的处理
- * @return true|void
- */
- public function upgradeErrorHandle() {
- $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;
- Cache::set($this->cache_key, $this->upgrade_task);
- }
- /**
- * 恢复源码
- * @return void
- */
- public function restoreCode() {
- try {
- (new RestoreService())->restoreCode();
- return true;
- } catch (\Exception $e) {
- $this->upgrade_task['error'][] = '源码备份恢复失败稍后请手动恢复,失败原因:' . $e->getMessage().$e->getFile().$e->getLine();
- Cache::set($this->cache_key, $this->upgrade_task);
- return true;
- }
- }
- /**
- * 恢复数据库
- * @return void
- */
- public function restoreSql() {
- try {
- (new RestoreService())->restoreSql();
- return true;
- } catch (\Exception $e) {
- $this->upgrade_task['error'][] = '数据库备份恢复失败稍后请手动恢复,失败原因:' . $e->getMessage().$e->getFile().$e->getLine();
- Cache::set($this->cache_key, $this->upgrade_task);
- return true;
- }
- }
- public function restoreComplete() {
- $this->clearUpgradeTask(5);
- return true;
- }
- /**
- * 获取升级内容
- * @param string $addon
- * @return array|\core\util\niucloud\Response|object|\Psr\Http\Message\ResponseInterface
- * @throws \GuzzleHttp\Exception\GuzzleException
- */
- public function getUpgradeContent(string $addon = '') {
- $upgrade = [
- 'product_key' => BaseNiucloudClient::PRODUCT
- ];
- 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');
- }
- return (new CoreModuleService())->getUpgradeContent($upgrade)['data'] ?? [];
- }
- /**
- * 获取正在进行的升级任务
- * @return mixed|null
- */
- public function getUpgradeTask() {
- return $this->upgrade_task;
- }
- /**
- * 清除升级任务
- * @return true
- */
- public function clearUpgradeTask(int $delayed = 0) {
- if ($delayed) {
- Cache::set($this->cache_key, $this->upgrade_task, $delayed);
- } else {
- Cache::set($this->cache_key, null);
- }
- 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;
- }
- }
|