This commit is contained in:
walkor 2023-02-20 09:38:36 +08:00
parent 8df8c531e5
commit 0101e2437e
3 changed files with 122 additions and 289 deletions

View File

@ -5,10 +5,17 @@ namespace plugin\admin\app\controller;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
use plugin\admin\app\common\Util; use plugin\admin\app\common\Util;
use process\Monitor;
use support\exception\BusinessException; use support\exception\BusinessException;
use support\Log; use support\Log;
use support\Request; use support\Request;
use support\Response; 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 class PluginController extends Base
{ {
@ -20,11 +27,14 @@ class PluginController extends Base
/** /**
* @param Request $request * @param Request $request
* @return Response * @return string
* @throws GuzzleException
*/ */
public function index(Request $request): Response public function index(Request $request)
{ {
return view('plugin/index'); $client = $this->httpClient();
$response = $client->get("/webman-admin/apps");
return (string)$response->getBody();
} }
/** /**
@ -37,7 +47,7 @@ class PluginController extends Base
{ {
$installed = []; $installed = [];
clearstatcache(); clearstatcache();
$plugin_names = \array_diff(\scandir(base_path() . '/plugin/'), array('.', '..')) ?: []; $plugin_names = array_diff(scandir(base_path() . '/plugin/'), array('.', '..')) ?: [];
foreach ($plugin_names as $plugin_name) { foreach ($plugin_names as $plugin_name) {
if (is_dir(base_path() . "/plugin/$plugin_name") && $version = $this->getPluginVersion($plugin_name)) { if (is_dir(base_path() . "/plugin/$plugin_name") && $version = $this->getPluginVersion($plugin_name)) {
$installed[$plugin_name] = $version; $installed[$plugin_name] = $version;
@ -64,7 +74,7 @@ class PluginController extends Base
} }
$items = $data['result']['items']; $items = $data['result']['items'];
$count = $data['result']['total']; $count = $data['result']['total'];
return json(['code' =>0, 'msg' => 'ok', 'data' => $items, 'count' => $count]); return json(['code' => 0, 'msg' => 'ok', 'data' => $items, 'count' => $count]);
} }
/** /**
@ -77,7 +87,7 @@ class PluginController extends Base
{ {
$client = $this->httpClient(); $client = $this->httpClient();
$response = $client->get('/api/app/schema', ['query' => $request->get()]); $response = $client->get('/api/app/schema', ['query' => $request->get()]);
$data = json_decode($response->getBody()->getContents(), true); $data = json_decode((string)$response->getBody(), true);
$result = $data['result']; $result = $data['result'];
foreach ($result as &$item) { foreach ($result as &$item) {
$item['field'] = $item['field'] ?? $item['dataIndex']; $item['field'] = $item['field'] ?? $item['dataIndex'];
@ -90,14 +100,13 @@ class PluginController extends Base
* 安装 * 安装
* @param Request $request * @param Request $request
* @return Response * @return Response
* @throws GuzzleException * @throws GuzzleException|BusinessException
*/ */
public function install(Request $request): Response public function install(Request $request): Response
{ {
$name = $request->post('name'); $name = $request->post('name');
$version = $request->post('version'); $version = $request->post('version');
$installed_version = $this->getPluginVersion($name); $installed_version = $this->getPluginVersion($name);
$host = $request->host(true);
if (!$name || !$version) { if (!$name || !$version) {
return $this->json(1, '缺少参数'); return $this->json(1, '缺少参数');
} }
@ -111,21 +120,26 @@ class PluginController extends Base
} }
// 获取下载zip文件url // 获取下载zip文件url
$data = $this->getDownloadUrl($name, $user['uid'], $host, $version); $data = $this->getDownloadUrl($name, $version);
if ($data['code'] == -1) { if ($data['code'] == -1) {
return $this->json(0, '请登录', [ return $this->json(0, '请登录', [
'code' => 401, 'code' => 401,
'msg' => '请登录' 'msg' => '请登录'
]); ]);
} elseif ($data['code'] == -2) {
return $this->json(0, '未购买此插件', [
'code' => 402,
'msg' => '未购买此插件'
]);
} }
// 下载zip文件 // 下载zip文件
$base_path = base_path() . "/plugin/$name"; $base_path = base_path() . "/plugin/$name";
$zip_file = "$base_path.zip"; $zip_file = "$base_path.zip";
$extract_to = base_path() . '/plugin/'; $extract_to = base_path() . '/plugin/';
$this->downloadZipFile($data['result']['url'], $zip_file); $this->downloadZipFile($data['data']['url'], $zip_file);
$has_zip_archive = class_exists(\ZipArchive::class, false); $has_zip_archive = class_exists(ZipArchive::class, false);
if (!$has_zip_archive) { if (!$has_zip_archive) {
$cmd = $this->getUnzipCmd($zip_file, $extract_to); $cmd = $this->getUnzipCmd($zip_file, $extract_to);
if (!$cmd) { if (!$cmd) {
@ -136,10 +150,15 @@ class PluginController extends Base
} }
} }
$monitor_support_pause = method_exists(Monitor::class, 'pause');
if ($monitor_support_pause) {
Monitor::pause();
}
try {
// 解压zip到plugin目录 // 解压zip到plugin目录
if ($has_zip_archive) { if ($has_zip_archive) {
$zip = new \ZipArchive; $zip = new ZipArchive;
$zip->open($zip_file, \ZIPARCHIVE::CHECKCONS); $zip->open($zip_file, ZIPARCHIVE::CHECKCONS);
} }
$context = null; $context = null;
@ -171,6 +190,11 @@ class PluginController extends Base
call_user_func([$install_class, 'install'], $version); call_user_func([$install_class, 'install'], $version);
} }
} }
} finally {
if ($monitor_support_pause) {
Monitor::resume();
}
}
Util::reloadWebman(); Util::reloadWebman();
@ -206,7 +230,17 @@ class PluginController extends Base
// 删除目录 // 删除目录
clearstatcache(); clearstatcache();
if (is_dir($path)) { if (is_dir($path)) {
$monitor_support_pause = method_exists(Monitor::class, 'pause');
if ($monitor_support_pause) {
Monitor::pause();
}
try {
$this->rmDir($path); $this->rmDir($path);
} finally {
if ($monitor_support_pause) {
Monitor::resume();
}
}
} }
clearstatcache(); clearstatcache();
@ -215,6 +249,27 @@ class PluginController extends Base
return $this->json(0); 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 * @param Request $request
@ -226,7 +281,7 @@ class PluginController extends Base
$client = $this->httpClient(); $client = $this->httpClient();
$response = $client->get('/user/captcha?type=login'); $response = $client->get('/user/captcha?type=login');
$sid_str = $response->getHeaderLine('Set-Cookie'); $sid_str = $response->getHeaderLine('Set-Cookie');
if(preg_match('/PHPSID=([a-zA-z_0-9]+?);/', $sid_str, $match)) { if (preg_match('/PHPSID=([a-zA-Z_0-9]+?);/', $sid_str, $match)) {
$sid = $match[1]; $sid = $match[1];
session()->set('app-plugin-token', $sid); session()->set('app-plugin-token', $sid);
} }
@ -236,15 +291,17 @@ class PluginController extends Base
/** /**
* 登录官网 * 登录官网
* @param Request $request * @param Request $request
* @return Response * @return Response|string
* @throws GuzzleException * @throws GuzzleException
*/ */
public function login(Request $request): Response public function login(Request $request)
{ {
if ($request->method() === 'GET') {
return view('plugin/auth-login');
}
$client = $this->httpClient(); $client = $this->httpClient();
if ($request->method() === 'GET') {
$response = $client->get("/webman-admin/login");
return (string)$response->getBody();
}
$response = $client->post('/api/user/login', [ $response = $client->post('/api/user/login', [
'form_params' => [ 'form_params' => [
'email' => $request->post('username'), 'email' => $request->post('username'),
@ -272,37 +329,27 @@ class PluginController extends Base
/** /**
* 获取zip下载url * 获取zip下载url
* @param $name * @param $name
* @param $uid
* @param $host
* @param $version * @param $version
* @return mixed * @return mixed
* @throws BusinessException * @throws BusinessException
* @throws GuzzleException * @throws GuzzleException
*/ */
protected function getDownloadUrl($name, $uid, $host, $version) protected function getDownloadUrl($name, $version)
{ {
$client = $this->httpClient(); $client = $this->httpClient();
$response = $client->post('/api/app/download', [ $response = $client->get("/app/download/$name?version=$version");
'form_params' => [
'name' => $name,
'uid' => $uid,
'token' => session('app-plugin-token'),
'referer' => $host,
'version' => $version,
]
]);
$content = $response->getBody()->getContents(); $content = $response->getBody()->getContents();
$data = json_decode($content, true); $data = json_decode($content, true);
if (!$data) { if (!$data) {
$msg = "/api/app/download return $content"; $msg = "/api/app/download return $content";
Log::error($msg); Log::error($msg);
throw new BusinessException('访问官方接口失败'); throw new BusinessException('访问官方接口失败 ' . $response->getStatusCode() . ' ' . $response->getReasonPhrase());
} }
if ($data['code'] && $data['code'] != -1) { if ($data['code'] && $data['code'] != -1 && $data['code'] != -2) {
throw new BusinessException($data['msg']); throw new BusinessException($data['message']);
} }
if ($data['code'] == 0 && !isset($data['result']['url'])) { if ($data['code'] == 0 && !isset($data['data']['url'])) {
throw new BusinessException('官方接口返回数据错误'); throw new BusinessException('官方接口返回数据错误');
} }
return $data; return $data;
@ -344,7 +391,7 @@ class PluginController extends Base
$cmd = "$cmd -qq $zip_file -d $extract_to"; $cmd = "$cmd -qq $zip_file -d $extract_to";
} else if ($cmd = $this->findCmd('7z')) { } else if ($cmd = $this->findCmd('7z')) {
$cmd = "$cmd x -bb0 -y $zip_file -o$extract_to"; $cmd = "$cmd x -bb0 -y $zip_file -o$extract_to";
} else if ($cmd= $this->findCmd('7zz')) { } else if ($cmd = $this->findCmd('7zz')) {
$cmd = "$cmd x -bb0 -y $zip_file -o$extract_to"; $cmd = "$cmd x -bb0 -y $zip_file -o$extract_to";
} }
return $cmd; return $cmd;
@ -406,10 +453,10 @@ class PluginController extends Base
protected function rmDir($src) protected function rmDir($src)
{ {
$dir = opendir($src); $dir = opendir($src);
while(false !== ( $file = readdir($dir)) ) { while (false !== ($file = readdir($dir))) {
if (( $file != '.' ) && ( $file != '..' )) { if (($file != '.') && ($file != '..')) {
$full = $src . '/' . $file; $full = $src . '/' . $file;
if ( is_dir($full) ) { if (is_dir($full)) {
$this->rmDir($full); $this->rmDir($full);
} else { } else {
unlink($full); unlink($full);
@ -477,8 +524,8 @@ class PluginController extends Base
*/ */
protected function findCmd(string $name, string $default = null, array $extraDirs = []) protected function findCmd(string $name, string $default = null, array $extraDirs = [])
{ {
if (\ini_get('open_basedir')) { if (ini_get('open_basedir')) {
$searchPath = array_merge(explode(\PATH_SEPARATOR, \ini_get('open_basedir')), $extraDirs); $searchPath = array_merge(explode(PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs);
$dirs = []; $dirs = [];
foreach ($searchPath as $path) { foreach ($searchPath as $path) {
if (@is_dir($path)) { if (@is_dir($path)) {
@ -491,19 +538,19 @@ class PluginController extends Base
} }
} else { } else {
$dirs = array_merge( $dirs = array_merge(
explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
$extraDirs $extraDirs
); );
} }
$suffixes = ['']; $suffixes = [''];
if ('\\' === \DIRECTORY_SEPARATOR) { if ('\\' === DIRECTORY_SEPARATOR) {
$pathExt = getenv('PATHEXT'); $pathExt = getenv('PATHEXT');
$suffixes = array_merge($pathExt ? explode(\PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes); $suffixes = array_merge($pathExt ? explode(PATH_SEPARATOR, $pathExt) : ['.exe', '.bat', '.cmd', '.com'], $suffixes);
} }
foreach ($suffixes as $suffix) { foreach ($suffixes as $suffix) {
foreach ($dirs as $dir) { foreach ($dirs as $dir) {
if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) { if (@is_file($file = $dir . DIRECTORY_SEPARATOR . $name . $suffix) && ('\\' === DIRECTORY_SEPARATOR || @is_executable($file))) {
return $file; return $file;
} }
} }

View File

@ -1,69 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>登录 workerman.net 官网</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/pages/login.css" />
</head>
<body style="background: #fff !important;">
<form class="layui-form" style="margin-top:36px !important;">
<div class="layui-form-item pear-border" style="background:#ebf2f8;border:1px solid #ccddf6;padding:10px;border-radius: 4px;">
<div class="pear-text">注意此处登录 <a class="pear-text" href="https://www.workerman.net" target="_blank"><b>workerman.net</b></a> 官网账号</div>
</div>
<div class="layui-form-item">
<input lay-verify="required" hover class="layui-input" type="text" name="username" value="" />
</div>
<div class="layui-form-item">
<input lay-verify="required" hover class="layui-input" type="password" name="password" value="" />
</div>
<div class="layui-form-item">
<input hover lay-verify="required" class="code layui-input layui-input-inline" name="captcha" />
<img class="codeImage" width="120px"/>
</div>
<div class="layui-form-item">
<button type="submit" class="pear-btn pear-btn-primary login" lay-submit lay-filter="login">
登录
</button>
</div>
</form>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
layui.use(["form", "button", "popup", "layer"], function() {
var $ = layui.$, layer = layui.layer, form = layui.form;
function switchCaptcha() {
$(".codeImage").attr("src", "/app/admin/plugin/captcha?v=" + new Date().getTime());
}
switchCaptcha();
form.on("submit(login)", function (data) {
layer.load();
$.ajax({
url: "/app/admin/plugin/login",
type: "POST",
data: data.field,
success: function (res) {
layer.closeAll("loading");
if (!res.code) {
layui.popup.success("登录成功", function () {
parent.layer.close(parent.layer.getFrameIndex(window.name));
})
} else {
layui.popup.failure(res.msg, function () {
switchCaptcha();
})
}
}
});
return false;
});
$(".codeImage").on("click", function () {
switchCaptcha();
});
})
</script>
</body>
</html>

View File

@ -1,145 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>应用插件管理</title>
<link rel="stylesheet" href="/app/admin/component/pear/css/pear.css" />
<link rel="stylesheet" href="/app/admin/admin/css/reset.css" />
</head>
<body class="pear-container">
<div class="layui-card">
<div class="layui-card-body">
<table id="data-table" lay-filter="data-table"></table>
</div>
</div>
<script type="text/html" id="install">
{{# if(!d.installed){ }}
<button class="pear-btn pear-btn-xs pear-btn-primary" onclick="install('{{ d.name }}','{{ d.version }}')">
安装
</button>
{{# }else{ }}
{{# if(d.installed !== d.version){ }}
<button class="pear-btn pear-btn-xs pear-btn-success" onclick="install('{{ d.name }}','{{ d.version }}')">
升级
</button>
{{# } }}
{{# if(d.name !== "admin"){ }}
<button class="pear-btn pear-btn-xs pear-btn-danger" onclick="uninstall('{{ d.name }}','{{ d.installed }}')">
卸载
</button>
{{# } }}
{{# } }}
</script>
<script src="/app/admin/component/layui/layui.js"></script>
<script src="/app/admin/component/pear/pear.js"></script>
<script src="/app/admin/admin/js/permission.js"></script>
<script>
const SELECT_API = "/app/admin/plugin/list";
const AUTH_URL = "/app/admin/plugin/login";
const SCHEMA_API = "/app/admin/plugin/schema";
layui.use(["table", "form","common", "popup"], function() {
let table = layui.table;
let form = layui.form;
let $ = layui.$;
let common = layui.common;
$.ajax({
url: SCHEMA_API,
dataType: "json",
success: function (res) {
let cols = res.data;
layui.each(cols, function (k, v) {
if (v.field === "installed") {
cols[k].templet = "#install";
}
})
function render()
{
table.render({
elem: "#data-table",
url: SELECT_API,
page: true,
cols: [cols],
skin: "line",
size: "lg",
autoSort: false,
defaultToolbar: [{
title: "刷新",
layEvent: "refresh",
icon: "layui-icon-refresh",
}, "filter", "print", "exports"]
});
}
render();
}
});
window.install = function(name, version) {
let loading = layer.load();
$.ajax({
url: "/app/admin/plugin/install",
type: "POST",
dataType: "json",
data: {name, version},
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
// 需要登录官网
if (res.data && res.data.code === 401) {
layer.open({
type: 2,
title: "登录 workerman.net 官网",
shade: 0.1,
area: [common.isModile()?"100%":"500px", common.isModile()?"100%":"450px"],
content: AUTH_URL
});
return;
}
return layui.popup.success("安装成功", function () {
parent.location.reload();
});
},
complete: function () {
layer.close(loading);
}
});
}
window.uninstall = function(name, version) {
layer.confirm("确定卸载?", {
icon: 3,
title: "提示"
}, function(index) {
layer.close(index);
let loading = layer.load();
$.ajax({
url: "/app/admin/plugin/uninstall",
type: "POST",
dataType: "json",
data: {name, version},
success: function (res) {
if (res.code) {
return layui.popup.failure(res.msg);
}
return layui.popup.success("卸载成功", function () {
parent.location.reload();
});
},
complete: function () {
layer.close(loading);
}
});
});
}
})
</script>
</body>
</html>