diff --git a/src/plugin/admin/app/controller/plugin/AppController.php b/src/plugin/admin/app/controller/plugin/AppController.php index 6d4abbc..4ebb0ed 100644 --- a/src/plugin/admin/app/controller/plugin/AppController.php +++ b/src/plugin/admin/app/controller/plugin/AppController.php @@ -6,6 +6,7 @@ use GuzzleHttp\Client; use plugin\admin\api\Auth; use plugin\admin\app\controller\Base; use plugin\admin\app\Util; +use support\exception\BusinessException; use support\Log; use support\Request; use function base_path; @@ -89,53 +90,38 @@ class AppController extends Base 'message' => '请登录' ]); } - $client = $this->httpClient(); - $response = $client->post('/api/app/download', [ - 'form_params' => [ - 'name' => $name, - 'uid' => $user['uid'], - 'token' => session('app-plugin-token'), - 'referer' => $host, - 'version' => $version, - ] - ]); - $content = $response->getBody()->getContents(); - $data = json_decode($content, true); - if (!$data) { - $msg = "/api/app/download return $content"; - echo "msg\r\n"; - Log::error($msg); - } - if ($data['code']) { - if ($data['code'] == -1) { - return $this->json(0, '请登录', [ - 'code' => 401, - 'message' => '请登录' - ]); - } - return $this->json($data['code'], $data['msg']); + // 获取下载zip文件url + $data = $this->getDownloadUrl($name, $user['uid'], $host, $version); + if ($data['code'] == -1) { + return $this->json(0, '请登录', [ + 'code' => 401, + 'message' => '请登录' + ]); } - $url = $data['result']['url']; - $client = $this->downloadClient(); - $response = $client->get($url); - $body = $response->getBody(); - $status = $response->getStatusCode(); - if ($status == 404) { - return $this->json(1, '安装包不存在'); - } - $zip_content = $body->getContents(); - if (empty($zip_content)) { - return $this->json(1, '安装包不存在'); - } + // 下载zip文件 $base_path = base_path() . "/plugin/$name"; $zip_file = "$base_path.zip"; - file_put_contents($zip_file, $zip_content); + $extract_to = base_path() . '/plugin/'; + $this->downloadZipFile($data['result']['url'], $zip_file); + + $has_zip_archive = class_exists(\ZipArchive::class, false); + if (!$has_zip_archive) { + $cmd = $this->getUnzipCmd($zip_file, $extract_to); + if (!$cmd) { + throw new BusinessException('请给php安装zip模块或者给系统安装unzip命令'); + } + if (!function_exists('proc_open')) { + throw new BusinessException('请解除proc_open函数的禁用或者给php安装zip模块'); + } + } // 解压zip到plugin目录 - $zip = new \ZipArchive; - $zip->open($zip_file, \ZIPARCHIVE::CHECKCONS); + if ($has_zip_archive) { + $zip = new \ZipArchive; + $zip->open($zip_file, \ZIPARCHIVE::CHECKCONS); + } $context = null; $install_class = "\\plugin\\$name\\api\\Install"; @@ -146,7 +132,13 @@ class AppController extends Base } } - $zip->extractTo(base_path() . '/plugin/'); + if (!empty($zip)) { + $zip->extractTo(base_path() . '/plugin/'); + unset($zip); + } else { + $this->unzipWithCmd($cmd); + } + unlink($zip_file); if ($installed_version) { @@ -258,11 +250,137 @@ class AppController extends Base return $this->json(0); } - protected function getPluginVersion($name) + /** + * 获取zip下载url + * + * @param $name + * @param $uid + * @param $host + * @param $version + * @return mixed + * @throws BusinessException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function getDownloadUrl($name, $uid, $host, $version) { - return config("plugin.$name.app.version"); + $client = $this->httpClient(); + $response = $client->post('/api/app/download', [ + 'form_params' => [ + 'name' => $name, + 'uid' => $uid, + 'token' => session('app-plugin-token'), + 'referer' => $host, + 'version' => $version, + ] + ]); + + $content = $response->getBody()->getContents(); + $data = json_decode($content, true); + if (!$data) { + $msg = "/api/app/download return $content"; + Log::error($msg); + throw new BusinessException('访问官方接口失败'); + } + if ($data['code'] && $data['code'] != -1) { + throw new BusinessException($data['msg']); + } + if ($data['code'] == 0 && !isset($data['result']['url'])) { + throw new BusinessException('官方接口返回数据错误'); + } + return $data; } + /** + * 下载zip + * + * @param $url + * @param $file + * @return void + * @throws BusinessException + * @throws \GuzzleHttp\Exception\GuzzleException + */ + protected function downloadZipFile($url, $file) + { + $client = $this->downloadClient(); + $response = $client->get($url); + $body = $response->getBody(); + $status = $response->getStatusCode(); + if ($status == 404) { + throw new BusinessException('安装包不存在'); + } + $zip_content = $body->getContents(); + if (empty($zip_content)) { + throw new BusinessException('安装包不存在'); + } + file_put_contents($file, $zip_content); + } + + /** + * 获取系统支持的解压命令 + * + * @param $zip_file + * @param $extract_to + * @return mixed|string|null + */ + protected function getUnzipCmd($zip_file, $extract_to) + { + if ($cmd = $this->findCmd('unzip')) { + $cmd = "$cmd -qq $zip_file -d $extract_to"; + } else if ($cmd = $this->findCmd('7z')) { + $cmd = "$cmd x -bb0 -y $zip_file -o$extract_to"; + } else if ($cmd= $this->findCmd('7zz')) { + $cmd = "$cmd x -bb0 -y $zip_file -o$extract_to"; + } + return $cmd; + } + + /** + * 使用解压命令解压 + * + * @param $cmd + * @return void + * @throws BusinessException + */ + protected function unzipWithCmd($cmd) + { + $desc = [ + 0 => STDIN, + 1 => STDOUT, + 2 => ["pipe", "w"], + ]; + $handler = proc_open($cmd, $desc, $pipes); + if (!is_resource($handler)) { + throw new BusinessException("解压zip时出错:proc_open调用失败"); + } + $err = fread($pipes[2], 1024); + fclose($pipes[2]); + proc_close($handler); + if ($err) { + throw new BusinessException("解压zip时出错:$err"); + } + } + + /** + * 获取本地插件版本 + * + * @param $name + * @return array|mixed|null + */ + protected function getPluginVersion($name) + { + if (!is_file($file = base_path() . "/plugin/$name/config/app.php")) { + return null; + } + $config = include $file; + return $config['version'] ?? null; + } + + /** + * 删除目录 + * + * @param $src + * @return void + */ protected function rmDir($src) { $dir = opendir($src); @@ -280,6 +398,11 @@ class AppController extends Base rmdir($src); } + /** + * 获取httpclient + * + * @return Client + */ protected function httpClient() { // 下载zip @@ -301,6 +424,11 @@ class AppController extends Base return new Client($options); } + /** + * 获取下载httpclient + * + * @return Client + */ protected function downloadClient() { // 下载zip @@ -320,4 +448,49 @@ class AppController extends Base return new Client($options); } + /** + * 查找系统命令 + * + * @param string $name + * @param string|null $default + * @param array $extraDirs + * @return mixed|string|null + */ + function findCmd(string $name, string $default = null, array $extraDirs = []) + { + if (\ini_get('open_basedir')) { + $searchPath = array_merge(explode(\PATH_SEPARATOR, \ini_get('open_basedir')), $extraDirs); + $dirs = []; + foreach ($searchPath as $path) { + if (@is_dir($path)) { + $dirs[] = $path; + } else { + if (basename($path) == $name && @is_executable($path)) { + return $path; + } + } + } + } else { + $dirs = array_merge( + explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), + $extraDirs + ); + } + + $suffixes = ['']; + if ('\\' === \DIRECTORY_SEPARATOR) { + $pathExt = getenv('PATHEXT'); + $suffixes = array_merge($pathExt ? explode(\PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes); + } + foreach ($suffixes as $suffix) { + foreach ($dirs as $dir) { + if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) { + return $file; + } + } + } + + return $default; + } + }