| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549 |
- <?php
- namespace plugin\admin\app\controller;
- use GuzzleHttp\Client;
- use GuzzleHttp\Exception\GuzzleException;
- use plugin\admin\app\common\Util;
- use plugin\admin\app\controller\Base;
- use process\Monitor;
- use support\exception\BusinessException;
- use support\Log;
- use support\Request;
- use support\Response;
- use ZIPARCHIVE;
- use function array_diff;
- use function ini_get;
- use function scandir;
- use const DIRECTORY_SEPARATOR;
- use const PATH_SEPARATOR;
- class PluginController extends Base
- {
- /**
- * 不需要鉴权的方法
- * @var string[]
- */
- protected $noNeedAuth = ['schema', 'captcha'];
- /**
- * @param Request $request
- * @return string
- * @throws GuzzleException
- */
- public function index(Request $request)
- {
- $client = $this->httpClient();
- $response = $client->get("/webman-admin/apps");
- return (string)$response->getBody();
- }
- /**
- * 列表
- * @param Request $request
- * @return Response
- * @throws GuzzleException
- */
- public function list(Request $request): Response
- {
- $installed = $this->getLocalPlugins();
- $client = $this->httpClient();
- $query = $request->get();
- $query['version'] = $this->getAdminVersion();
- $response = $client->get('/api/app/list', ['query' => $query]);
- $content = $response->getBody()->getContents();
- $data = json_decode($content, true);
- if (!$data) {
- $msg = "/api/app/list return $content";
- echo "msg\r\n";
- Log::error($msg);
- return $this->json(1, '获取数据出错');
- }
- $disabled = is_phar();
- foreach ($data['data']['items'] as $key => $item) {
- $name = $item['name'];
- $data['data']['items'][$key]['installed'] = $installed[$name] ?? 0;
- $data['data']['items'][$key]['disabled'] = $disabled;
- }
- $items = $data['data']['items'];
- $count = $data['data']['total'];
- return json(['code' => 0, 'msg' => 'ok', 'data' => $items, 'count' => $count]);
- }
- /**
- * 安装
- * @param Request $request
- * @return Response
- * @throws GuzzleException|BusinessException
- */
- public function install(Request $request): Response
- {
- $name = $request->post('name');
- $version = $request->post('version');
- $installed_version = $this->getPluginVersion($name);
- if (!$name || !$version) {
- return $this->json(1, '缺少参数');
- }
- $user = session('app-plugin-user');
- if (!$user) {
- return $this->json(-1, '请登录');
- }
- // 获取下载zip文件url
- $data = $this->getDownloadUrl($name, $version);
- if ($data['code'] != 0) {
- return $this->json($data['code'], $data['msg'], $data['data'] ?? []);
- }
- // 下载zip文件
- $base_path = base_path() . "/plugin/$name";
- $zip_file = "$base_path.zip";
- $extract_to = base_path() . '/plugin/';
- $this->downloadZipFile($data['data']['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模块');
- }
- }
- Util::pauseFileMonitor();
- try {
- // 解压zip到plugin目录
- if ($has_zip_archive) {
- $zip = new ZipArchive;
- $zip->open($zip_file, ZIPARCHIVE::CHECKCONS);
- }
- $context = null;
- $install_class = "\\plugin\\$name\\api\\Install";
- if ($installed_version) {
- // 执行beforeUpdate
- if (class_exists($install_class) && method_exists($install_class, 'beforeUpdate')) {
- $context = call_user_func([$install_class, 'beforeUpdate'], $installed_version, $version);
- }
- }
- if (!empty($zip)) {
- $zip->extractTo(base_path() . '/plugin/');
- unset($zip);
- } else {
- $this->unzipWithCmd($cmd);
- }
- unlink($zip_file);
- if ($installed_version) {
- // 执行update更新
- if (class_exists($install_class) && method_exists($install_class, 'update')) {
- call_user_func([$install_class, 'update'], $installed_version, $version, $context);
- }
- } else {
- // 执行install安装
- if (class_exists($install_class) && method_exists($install_class, 'install')) {
- call_user_func([$install_class, 'install'], $version);
- }
- }
- } finally {
- Util::resumeFileMonitor();
- }
- Util::reloadWebman();
- return $this->json(0);
- }
- /**
- * 卸载
- * @param Request $request
- * @return Response
- */
- public function uninstall(Request $request): Response
- {
- $name = $request->post('name');
- $version = $request->post('version');
- if (!$name || !preg_match('/^[a-zA-Z0-9_]+$/', $name)) {
- return $this->json(1, '参数错误');
- }
- // 获得插件路径
- clearstatcache();
- $path = get_realpath(base_path() . "/plugin/$name");
- if (!$path || !is_dir($path)) {
- return $this->json(1, '已经删除');
- }
- // 执行uninstall卸载
- $install_class = "\\plugin\\$name\\api\\Install";
- if (class_exists($install_class) && method_exists($install_class, 'uninstall')) {
- call_user_func([$install_class, 'uninstall'], $version);
- }
- // 删除目录
- clearstatcache();
- if (is_dir($path)) {
- $monitor_support_pause = method_exists(Monitor::class, 'pause');
- if ($monitor_support_pause) {
- Monitor::pause();
- }
- try {
- $this->rmDir($path);
- } finally {
- if ($monitor_support_pause) {
- Monitor::resume();
- }
- }
- }
- clearstatcache();
- Util::reloadWebman();
- return $this->json(0);
- }
- /**
- * 支付
- * @param Request $request
- * @return string|Response
- * @throws GuzzleException
- */
- public function pay(Request $request)
- {
- $app = $request->get('app');
- if (!$app) {
- return response('app not found');
- }
- $token = session('app-plugin-token');
- if (!$token) {
- return 'Please login workerman.net';
- }
- $client = $this->httpClient();
- $response = $client->get("/payment/app/$app/$token");
- return (string)$response->getBody();
- }
- /**
- * 登录验证码
- * @param Request $request
- * @return Response
- * @throws GuzzleException
- */
- public function captcha(Request $request): Response
- {
- $client = $this->httpClient();
- $response = $client->get('/user/captcha?type=login');
- $sid_str = $response->getHeaderLine('Set-Cookie');
- if (preg_match('/PHPSID=([a-zA-Z_0-9]+?);/', $sid_str, $match)) {
- $sid = $match[1];
- session()->set('app-plugin-token', $sid);
- }
- return response($response->getBody()->getContents())->withHeader('Content-Type', 'image/jpeg');
- }
- /**
- * 登录官网
- * @param Request $request
- * @return Response|string
- * @throws GuzzleException
- */
- public function login(Request $request)
- {
- $client = $this->httpClient();
- if ($request->method() === 'GET') {
- $response = $client->get("/webman-admin/login");
- return (string)$response->getBody();
- }
- $response = $client->post('/api/user/login', [
- 'form_params' => [
- 'email' => $request->post('username'),
- 'password' => $request->post('password'),
- 'captcha' => $request->post('captcha')
- ]
- ]);
- $content = $response->getBody()->getContents();
- $data = json_decode($content, true);
- if (!$data) {
- $msg = "/api/user/login return $content";
- echo "msg\r\n";
- Log::error($msg);
- return $this->json(1, '发生错误');
- }
- if ($data['code'] != 0) {
- return $this->json($data['code'], $data['msg']);
- }
- session()->set('app-plugin-user', [
- 'uid' => $data['data']['uid']
- ]);
- return $this->json(0);
- }
- /**
- * 获取zip下载url
- * @param $name
- * @param $version
- * @return mixed
- * @throws BusinessException
- * @throws GuzzleException
- */
- protected function getDownloadUrl($name, $version)
- {
- $client = $this->httpClient();
- $response = $client->get("/app/download/$name?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('访问官方接口失败 ' . $response->getStatusCode() . ' ' . $response->getReasonPhrase());
- }
- if ($data['code'] && $data['code'] != -1 && $data['code'] != -2) {
- throw new BusinessException($data['msg']);
- }
- if ($data['code'] == 0 && !isset($data['data']['url'])) {
- throw new BusinessException('官方接口返回数据错误');
- }
- return $data;
- }
- /**
- * 下载zip
- * @param $url
- * @param $file
- * @return void
- * @throws BusinessException
- * @throws 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 -o -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 => ["pipe", "r"],
- 1 => ["pipe", "w"],
- 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");
- }
- }
- /**
- * 获取已安装的插件列表
- * @return array
- */
- protected function getLocalPlugins(): array
- {
- clearstatcache();
- $installed = [];
- $plugin_names = array_diff(scandir(base_path() . '/plugin/'), array('.', '..')) ?: [];
- foreach ($plugin_names as $plugin_name) {
- if (is_dir(base_path() . "/plugin/$plugin_name") && $version = $this->getPluginVersion($plugin_name)) {
- $installed[$plugin_name] = $version;
- }
- }
- return $installed;
- }
- /**
- * 获取已安装的插件列表
- * @param Request $request
- * @return Response
- */
- public function getInstalledPlugins(Request $request): Response
- {
- return $this->json(0, 'ok', $this->getLocalPlugins());
- }
-
- /**
- * 获取本地插件版本
- * @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;
- }
- /**
- * 获取webman/admin版本
- * @return string
- */
- protected function getAdminVersion(): string
- {
- return config('plugin.admin.app.version', '');
- }
- /**
- * 删除目录
- * @param $src
- * @return void
- */
- protected function rmDir($src)
- {
- $dir = opendir($src);
- while (false !== ($file = readdir($dir))) {
- if (($file != '.') && ($file != '..')) {
- $full = $src . '/' . $file;
- if (is_dir($full)) {
- $this->rmDir($full);
- } else {
- unlink($full);
- }
- }
- }
- closedir($dir);
- rmdir($src);
- }
- /**
- * 获取httpclient
- * @return Client
- */
- protected function httpClient(): Client
- {
- // 下载zip
- $options = [
- 'base_uri' => config('plugin.admin.app.plugin_market_host'),
- 'timeout' => 60,
- 'connect_timeout' => 5,
- 'verify' => false,
- 'http_errors' => false,
- 'headers' => [
- 'Referer' => \request()->fullUrl(),
- 'User-Agent' => 'webman-app-plugin',
- 'Accept' => 'application/json;charset=UTF-8',
- ]
- ];
- if ($token = session('app-plugin-token')) {
- $options['headers']['Cookie'] = "PHPSID=$token;";
- }
- return new Client($options);
- }
- /**
- * 获取下载httpclient
- * @return Client
- */
- protected function downloadClient(): Client
- {
- // 下载zip
- $options = [
- 'timeout' => 59,
- 'connect_timeout' => 5,
- 'verify' => false,
- 'http_errors' => false,
- 'headers' => [
- 'Referer' => \request()->fullUrl(),
- 'User-Agent' => 'webman-app-plugin',
- ]
- ];
- if ($token = session('app-plugin-token')) {
- $options['headers']['Cookie'] = "PHPSID=$token;";
- }
- return new Client($options);
- }
- /**
- * 查找系统命令
- * @param string $name
- * @param string|null $default
- * @param array $extraDirs
- * @return mixed|string|null
- */
- protected 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) : ['.exe', '.bat', '.cmd', '.com'], $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;
- }
- }
|