Compare commits
2 Commits
6a0a2468f7
...
f553f7ebb3
Author | SHA1 | Date | |
---|---|---|---|
f553f7ebb3 | |||
|
53e3a79412 |
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
/.idea
|
||||
/.vscode
|
||||
/vendor
|
||||
*.log
|
||||
.env
|
||||
/tests/tmp
|
||||
/tests/.phpunit.result.cache
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 walkor<walkor@workerman.net> and contributors (see https://github.com/walkor/webman/contributors)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
16
README.md
16
README.md
@ -1,2 +1,16 @@
|
||||
# admin
|
||||
# webman
|
||||
|
||||
High performance HTTP Service Framework for PHP based on [Workerman](https://github.com/walkor/workerman).
|
||||
|
||||
# Manual
|
||||
|
||||
https://www.workerman.net/doc/webman
|
||||
|
||||
# Benchmarks
|
||||
|
||||
https://www.techempower.com/benchmarks/#section=test&runid=9716e3cd-9e53-433c-b6c5-d2c48c9593c1&hw=ph&test=db&l=zg24n3-1r&a=2
|
||||

|
||||
|
||||
## LICENSE
|
||||
|
||||
MIT
|
||||
|
24
app/controller/Index.php
Normal file
24
app/controller/Index.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace app\controller;
|
||||
|
||||
use support\Request;
|
||||
|
||||
class Index
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
return response('hello webman');
|
||||
}
|
||||
|
||||
public function view(Request $request)
|
||||
{
|
||||
return view('index/view', ['name' => 'webman']);
|
||||
}
|
||||
|
||||
public function json(Request $request)
|
||||
{
|
||||
return json(['code' => 0, 'msg' => 'ok']);
|
||||
}
|
||||
|
||||
}
|
4
app/functions.php
Normal file
4
app/functions.php
Normal file
@ -0,0 +1,4 @@
|
||||
<?php
|
||||
/**
|
||||
* Here is your custom functions.
|
||||
*/
|
42
app/middleware/StaticFile.php
Normal file
42
app/middleware/StaticFile.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace app\middleware;
|
||||
|
||||
use Webman\MiddlewareInterface;
|
||||
use Webman\Http\Response;
|
||||
use Webman\Http\Request;
|
||||
|
||||
/**
|
||||
* Class StaticFile
|
||||
* @package app\middleware
|
||||
*/
|
||||
class StaticFile implements MiddlewareInterface
|
||||
{
|
||||
public function process(Request $request, callable $next): Response
|
||||
{
|
||||
// Access to files beginning with. Is prohibited
|
||||
if (strpos($request->path(), '/.') !== false) {
|
||||
return response('<h1>403 forbidden</h1>', 403);
|
||||
}
|
||||
/** @var Response $response */
|
||||
$response = $next($request);
|
||||
// Add cross domain HTTP header
|
||||
/*$response->withHeaders([
|
||||
'Access-Control-Allow-Origin' => '*',
|
||||
'Access-Control-Allow-Credentials' => 'true',
|
||||
]);*/
|
||||
return $response;
|
||||
}
|
||||
}
|
29
app/model/Test.php
Normal file
29
app/model/Test.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace app\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
class Test extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'test';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = false;
|
||||
}
|
14
app/view/index/view.html
Normal file
14
app/view/index/view.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" href="/favicon.ico"/>
|
||||
<title>webman</title>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
hello <?=htmlspecialchars($name)?>
|
||||
</body>
|
||||
</html>
|
55
composer.json
Normal file
55
composer.json
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "workerman/webman",
|
||||
"type": "project",
|
||||
"keywords": [
|
||||
"high performance",
|
||||
"http service"
|
||||
],
|
||||
"homepage": "http://www.workerman.net",
|
||||
"license": "MIT",
|
||||
"description": "High performance HTTP Service Framework.",
|
||||
"authors": [
|
||||
{
|
||||
"name": "walkor",
|
||||
"email": "walkor@workerman.net",
|
||||
"homepage": "http://www.workerman.net",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"email": "walkor@workerman.net",
|
||||
"issues": "https://github.com/walkor/webman/issues",
|
||||
"forum": "http://wenda.workerman.net/",
|
||||
"wiki": "http://workerman.net/doc/webman",
|
||||
"source": "https://github.com/walkor/webman"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2",
|
||||
"workerman/webman-framework": "^1.4.3",
|
||||
"monolog/monolog": "^2.0",
|
||||
"webman/admin": "^0.2.5"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-event": "For better performance. "
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"": "./",
|
||||
"App\\": "./app"
|
||||
},
|
||||
"files": [
|
||||
"./support/helpers.php"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"post-package-install": [
|
||||
"support\\Plugin::install"
|
||||
],
|
||||
"post-package-update": [
|
||||
"support\\Plugin::install"
|
||||
],
|
||||
"pre-package-uninstall": [
|
||||
"support\\Plugin::uninstall"
|
||||
]
|
||||
}
|
||||
}
|
3058
composer.lock
generated
Normal file
3058
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
config/app.php
Normal file
26
config/app.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
use support\Request;
|
||||
|
||||
return [
|
||||
'debug' => true,
|
||||
'error_reporting' => E_ALL,
|
||||
'default_timezone' => 'Asia/Shanghai',
|
||||
'request_class' => Request::class,
|
||||
'public_path' => base_path() . DIRECTORY_SEPARATOR . 'public',
|
||||
'runtime_path' => base_path(false) . DIRECTORY_SEPARATOR . 'runtime',
|
||||
'controller_suffix' => '',
|
||||
'controller_reuse' => true,
|
||||
];
|
21
config/autoload.php
Normal file
21
config/autoload.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return [
|
||||
'files' => [
|
||||
base_path() . '/app/functions.php',
|
||||
base_path() . '/support/Request.php',
|
||||
base_path() . '/support/Response.php',
|
||||
]
|
||||
];
|
18
config/bootstrap.php
Normal file
18
config/bootstrap.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return [
|
||||
support\bootstrap\Session::class,
|
||||
support\bootstrap\LaravelDb::class,
|
||||
];
|
15
config/container.php
Normal file
15
config/container.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return new Webman\Container;
|
15
config/database.php
Normal file
15
config/database.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return [];
|
15
config/dependence.php
Normal file
15
config/dependence.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return [];
|
5
config/event.php
Normal file
5
config/event.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
];
|
17
config/exception.php
Normal file
17
config/exception.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return [
|
||||
'' => support\exception\Handler::class,
|
||||
];
|
32
config/log.php
Normal file
32
config/log.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return [
|
||||
'default' => [
|
||||
'handlers' => [
|
||||
[
|
||||
'class' => Monolog\Handler\RotatingFileHandler::class,
|
||||
'constructor' => [
|
||||
runtime_path() . '/logs/webman.log',
|
||||
7, //$maxFiles
|
||||
Monolog\Logger::DEBUG,
|
||||
],
|
||||
'formatter' => [
|
||||
'class' => Monolog\Formatter\LineFormatter::class,
|
||||
'constructor' => [null, 'Y-m-d H:i:s', true],
|
||||
],
|
||||
]
|
||||
],
|
||||
],
|
||||
];
|
15
config/middleware.php
Normal file
15
config/middleware.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return [];
|
4
config/plugin/webman/event/app.php
Normal file
4
config/plugin/webman/event/app.php
Normal file
@ -0,0 +1,4 @@
|
||||
<?php
|
||||
return [
|
||||
'enable' => true,
|
||||
];
|
17
config/plugin/webman/event/bootstrap.php
Normal file
17
config/plugin/webman/event/bootstrap.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return [
|
||||
Webman\Event\BootStrap::class,
|
||||
];
|
7
config/plugin/webman/event/command.php
Normal file
7
config/plugin/webman/event/command.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Webman\Event\EventListCommand;
|
||||
|
||||
return [
|
||||
EventListCommand::class
|
||||
];
|
37
config/process.php
Normal file
37
config/process.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
|
||||
return [
|
||||
// File update detection and automatic reload
|
||||
'monitor' => [
|
||||
'handler' => process\Monitor::class,
|
||||
'reloadable' => false,
|
||||
'constructor' => [
|
||||
// Monitor these directories
|
||||
'monitor_dir' => [
|
||||
app_path(),
|
||||
config_path(),
|
||||
base_path() . '/process',
|
||||
base_path() . '/support',
|
||||
base_path() . '/resource',
|
||||
base_path() . '/.env',
|
||||
],
|
||||
// Files with these suffixes will be monitored
|
||||
'monitor_extensions' => [
|
||||
'php', 'html', 'htm', 'env'
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
22
config/redis.php
Normal file
22
config/redis.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return [
|
||||
'default' => [
|
||||
'host' => '127.0.0.1',
|
||||
'password' => null,
|
||||
'port' => 6379,
|
||||
'database' => 0,
|
||||
],
|
||||
];
|
21
config/route.php
Normal file
21
config/route.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
use Webman\Route;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
31
config/server.php
Normal file
31
config/server.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return [
|
||||
'listen' => 'http://0.0.0.0:8787',
|
||||
'transport' => 'tcp',
|
||||
'context' => [],
|
||||
'name' => 'webman',
|
||||
'count' => cpu_count() * 2,
|
||||
'user' => '',
|
||||
'group' => '',
|
||||
'reusePort' => false,
|
||||
'event_loop' => '',
|
||||
'stop_timeout' => 2,
|
||||
'pid_file' => runtime_path() . '/webman.pid',
|
||||
'status_file' => runtime_path() . '/webman.status',
|
||||
'stdout_file' => runtime_path() . '/logs/stdout.log',
|
||||
'log_file' => runtime_path() . '/logs/workerman.log',
|
||||
'max_package_size' => 10 * 1024 * 1024
|
||||
];
|
65
config/session.php
Normal file
65
config/session.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
use Webman\Session\FileSessionHandler;
|
||||
use Webman\Session\RedisSessionHandler;
|
||||
use Webman\Session\RedisClusterSessionHandler;
|
||||
|
||||
return [
|
||||
|
||||
'type' => 'file', // or redis or redis_cluster
|
||||
|
||||
'handler' => FileSessionHandler::class,
|
||||
|
||||
'config' => [
|
||||
'file' => [
|
||||
'save_path' => runtime_path() . '/sessions',
|
||||
],
|
||||
'redis' => [
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 6379,
|
||||
'auth' => '',
|
||||
'timeout' => 2,
|
||||
'database' => '',
|
||||
'prefix' => 'redis_session_',
|
||||
],
|
||||
'redis_cluster' => [
|
||||
'host' => ['127.0.0.1:7000', '127.0.0.1:7001', '127.0.0.1:7001'],
|
||||
'timeout' => 2,
|
||||
'auth' => '',
|
||||
'prefix' => 'redis_session_',
|
||||
]
|
||||
],
|
||||
|
||||
'session_name' => 'PHPSID',
|
||||
|
||||
'auto_update_timestamp' => false,
|
||||
|
||||
'lifetime' => 7*24*60*60,
|
||||
|
||||
'cookie_lifetime' => 365*24*60*60,
|
||||
|
||||
'cookie_path' => '/',
|
||||
|
||||
'domain' => '',
|
||||
|
||||
'http_only' => true,
|
||||
|
||||
'secure' => false,
|
||||
|
||||
'same_site' => '',
|
||||
|
||||
'gc_probability' => [1, 1000],
|
||||
|
||||
];
|
23
config/static.php
Normal file
23
config/static.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
/**
|
||||
* Static file settings
|
||||
*/
|
||||
return [
|
||||
'enable' => true,
|
||||
'middleware' => [ // Static file Middleware
|
||||
//app\middleware\StaticFile::class,
|
||||
],
|
||||
];
|
25
config/translation.php
Normal file
25
config/translation.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
/**
|
||||
* Multilingual configuration
|
||||
*/
|
||||
return [
|
||||
// Default language
|
||||
'locale' => 'zh_CN',
|
||||
// Fallback language
|
||||
'fallback_locale' => ['zh_CN', 'en'],
|
||||
// Folder where language files are stored
|
||||
'path' => base_path() . '/resource/translations',
|
||||
];
|
22
config/view.php
Normal file
22
config/view.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
use support\view\Raw;
|
||||
use support\view\Twig;
|
||||
use support\view\Blade;
|
||||
use support\view\ThinkPHP;
|
||||
|
||||
return [
|
||||
'handler' => Raw::class
|
||||
];
|
105
plugin/admin/app/Admin.php
Normal file
105
plugin/admin/app/Admin.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
namespace plugin\admin\app;
|
||||
|
||||
use plugin\admin\app\model\AdminRole;
|
||||
use plugin\admin\app\model\AdminRule;
|
||||
use support\exception\BusinessException;
|
||||
|
||||
class Admin
|
||||
{
|
||||
/**
|
||||
* 判断权限
|
||||
* 如果没有权限则抛出异常
|
||||
*
|
||||
* @param string $controller
|
||||
* @param string $action
|
||||
* @return void
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public static function access(string $controller, string $action)
|
||||
{
|
||||
$code = 0;
|
||||
$msg = '';
|
||||
if (!static::canAccess($controller, $action, $code, $msg)) {
|
||||
throw new BusinessException($msg, $code);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否有权限
|
||||
*
|
||||
* @param string $controller
|
||||
* @param string $action
|
||||
* @param int $code
|
||||
* @param string $msg
|
||||
* @return bool
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public static function canAccess(string $controller, string $action, int &$code = 0, string &$msg = '')
|
||||
{
|
||||
// 获取控制器鉴权信息
|
||||
$class = new \ReflectionClass($controller);
|
||||
$properties = $class->getDefaultProperties();
|
||||
$noNeedLogin = $properties['noNeedLogin'] ?? [];
|
||||
$noNeedAuth = $properties['noNeedAuth'] ?? [];
|
||||
|
||||
// 不需要登录
|
||||
if (in_array($action, $noNeedLogin)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 获取登录信息
|
||||
$admin = admin();
|
||||
if (!$admin) {
|
||||
$msg = '请登录';
|
||||
// 401是未登录固定的返回码
|
||||
$code = 401;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 不需要鉴权
|
||||
if (in_array($action, $noNeedAuth)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 当前管理员无角色
|
||||
$roles = $admin['roles'];
|
||||
if (!$roles) {
|
||||
$msg = '无权限';
|
||||
$code = 2;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 角色没有规则
|
||||
$rules = AdminRole::whereIn('id', $roles)->pluck('rules');
|
||||
$rule_ids = [];
|
||||
foreach ($rules as $rule_string) {
|
||||
if (!$rule_string) {
|
||||
continue;
|
||||
}
|
||||
$rule_ids = array_merge($rule_ids, explode(',', $rule_string));
|
||||
}
|
||||
if (!$rule_ids) {
|
||||
$msg = '无权限';
|
||||
$code = 2;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 超级管理员
|
||||
if (in_array('*', $rule_ids)){
|
||||
return true;
|
||||
}
|
||||
|
||||
// 没有当前控制器的规则
|
||||
$rule = AdminRule::where(function ($query) use ($controller, $action) {
|
||||
$query->where('name', "$controller@$action")->orWhere('name', $controller);
|
||||
})->whereIn('id', $rule_ids)->first();
|
||||
if (!$rule) {
|
||||
$msg = '无权限';
|
||||
$code = 2;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
141
plugin/admin/app/Util.php
Normal file
141
plugin/admin/app/Util.php
Normal file
@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app;
|
||||
|
||||
use support\Db;
|
||||
use Support\Exception\BusinessException;
|
||||
|
||||
class Util
|
||||
{
|
||||
static public function passwordHash($password, $algo = PASSWORD_DEFAULT)
|
||||
{
|
||||
return password_hash($password, $algo);
|
||||
}
|
||||
|
||||
static function db()
|
||||
{
|
||||
return Db::connection('plugin.admin.mysql');
|
||||
}
|
||||
|
||||
static function schema()
|
||||
{
|
||||
return Db::schema('plugin.admin.mysql');
|
||||
}
|
||||
|
||||
static public function passwordVerify($password, $hash)
|
||||
{
|
||||
return password_verify($password, $hash);
|
||||
}
|
||||
|
||||
static public function checkTableName($table)
|
||||
{
|
||||
if (!preg_match('/^[a-zA-Z_0-9]+$/', $table)) {
|
||||
throw new BusinessException('表名不合法');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function camel($value)
|
||||
{
|
||||
static $cache = [];
|
||||
$key = $value;
|
||||
|
||||
if (isset($cache[$key])) {
|
||||
return $cache[$key];
|
||||
}
|
||||
|
||||
$value = ucwords(str_replace(['-', '_'], ' ', $value));
|
||||
|
||||
return $cache[$key] = str_replace(' ', '', $value);
|
||||
}
|
||||
|
||||
public static function smCamel($value)
|
||||
{
|
||||
return lcfirst(static::camel($value));
|
||||
}
|
||||
|
||||
public static function getCommentFirstLine($comment)
|
||||
{
|
||||
if ($comment === false) {
|
||||
return false;
|
||||
}
|
||||
foreach (explode("\n", $comment) as $str) {
|
||||
if ($s = trim($str, "*/\ \t\n\r\0\x0B")) {
|
||||
return $s;
|
||||
}
|
||||
}
|
||||
return $comment;
|
||||
}
|
||||
|
||||
public static function methodControlMap()
|
||||
{
|
||||
return [
|
||||
//method=>[控件]
|
||||
'integer' => ['InputNumber'],
|
||||
'string' => ['Input'],
|
||||
'text' => ['InputTextArea'],
|
||||
'date' => ['DatePicker'],
|
||||
'enum' => ['Select'],
|
||||
'float' => ['Input'],
|
||||
|
||||
'tinyInteger' => ['InputNumber'],
|
||||
'smallInteger' => ['InputNumber'],
|
||||
'mediumInteger' => ['InputNumber'],
|
||||
'bigInteger' => ['InputNumber'],
|
||||
|
||||
'unsignedInteger' => ['InputNumber'],
|
||||
'unsignedTinyInteger' => ['InputNumber'],
|
||||
'unsignedSmallInteger' => ['InputNumber'],
|
||||
'unsignedMediumInteger' => ['InputNumber'],
|
||||
'unsignedBigInteger' => ['InputNumber'],
|
||||
|
||||
'decimal' => ['Input'],
|
||||
'double' => ['Input'],
|
||||
|
||||
'mediumText' => ['InputTextArea'],
|
||||
'longText' => ['InputTextArea'],
|
||||
|
||||
'dateTime' => ['DatePicker'],
|
||||
|
||||
'time' => ['DatePicker'],
|
||||
'timestamp' => ['DatePicker'],
|
||||
|
||||
'char' => ['Input'],
|
||||
|
||||
'binary' => ['Input'],
|
||||
];
|
||||
}
|
||||
|
||||
public static function typeToControl($type)
|
||||
{
|
||||
if (stripos($type, 'int') !== false) {
|
||||
return 'InputNumber';
|
||||
}
|
||||
if (stripos($type, 'time') !== false || stripos($type, 'date') !== false) {
|
||||
return 'DatePicker';
|
||||
}
|
||||
if (stripos($type, 'text') !== false) {
|
||||
return 'InputTextArea';
|
||||
}
|
||||
if ($type === 'enum') {
|
||||
return 'Select';
|
||||
}
|
||||
return 'Input';
|
||||
}
|
||||
|
||||
public static function typeToMethod($type, $unsigned = false)
|
||||
{
|
||||
if (stripos($type, 'int') !== false) {
|
||||
$type = str_replace('int', 'Integer', $type);
|
||||
return $unsigned ? "unsigned" . ucfirst($type) : lcfirst($type);
|
||||
}
|
||||
$map = [
|
||||
'int' => 'integer',
|
||||
'varchar' => 'string',
|
||||
'mediumtext' => 'mediumText',
|
||||
'longtext' => 'longText',
|
||||
'datetime' => 'dateTime',
|
||||
];
|
||||
return $map[$type] ?? $type;
|
||||
}
|
||||
}
|
40
plugin/admin/app/controller/Base.php
Normal file
40
plugin/admin/app/controller/Base.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app\controller;
|
||||
|
||||
use plugin\admin\app\Util;
|
||||
use support\Db;
|
||||
use support\Request;
|
||||
|
||||
/**
|
||||
* 基础控制器
|
||||
*/
|
||||
class Base
|
||||
{
|
||||
|
||||
/**
|
||||
* 无需登录的方法及鉴权
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedLogin = [];
|
||||
|
||||
/**
|
||||
* 需要登录无需鉴权的方法
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedAuth = [];
|
||||
|
||||
/**
|
||||
* 返回格式化json数据
|
||||
*
|
||||
* @param int $code
|
||||
* @param string $msg
|
||||
* @param array $data
|
||||
* @return \support\Response
|
||||
*/
|
||||
protected function json(int $code, string $msg = 'ok', array $data = [])
|
||||
{
|
||||
return json(['code' => $code, 'result' => $data, 'message' => $msg, 'type' => $code ? 'error' : 'success']);
|
||||
}
|
||||
|
||||
}
|
367
plugin/admin/app/controller/Crud.php
Normal file
367
plugin/admin/app/controller/Crud.php
Normal file
@ -0,0 +1,367 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app\controller;
|
||||
|
||||
use plugin\admin\app\model\Option;
|
||||
use plugin\admin\app\Util;
|
||||
use support\Db;
|
||||
use support\Model;
|
||||
use support\Request;
|
||||
|
||||
trait Crud
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Model
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
|
||||
/**
|
||||
* 查询
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function select(Request $request)
|
||||
{
|
||||
[$where, $format, $page_size, $field, $order] = $this->selectInput($request);
|
||||
|
||||
$model = $this->model;
|
||||
foreach ($where as $column => $value) {
|
||||
if (is_array($value)) {
|
||||
if (in_array($value[0], ['>', '=', '<', '<>'])) {
|
||||
$model = $model->where($column, $value[0], $value[1]);
|
||||
} elseif ($value[0] == 'in') {
|
||||
$model = $model->whereIn($column, $value[1]);
|
||||
} else {
|
||||
$model = $model->whereBetween($column, $value);
|
||||
}
|
||||
} else {
|
||||
$model = $model->where($column, $value);
|
||||
}
|
||||
}
|
||||
$model = $model->orderBy($field, $order);
|
||||
if (in_array($format, ['select', 'tree', 'table_tree'])) {
|
||||
$items = $model->get();
|
||||
if ($format == 'select') {
|
||||
return $this->formatSelect($items);
|
||||
} elseif ($format == 'tree') {
|
||||
return $this->formatTree($items);
|
||||
}
|
||||
return $this->formatTableTree($items);
|
||||
}
|
||||
|
||||
$paginator = $model->paginate($page_size);
|
||||
return $this->json(0, 'ok', [
|
||||
'items' => $paginator->items(),
|
||||
'total' => $paginator->total()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function insert(Request $request)
|
||||
{
|
||||
$data = $request->post('data');
|
||||
$table = $this->model->getTable();
|
||||
$allow_column = Util::db()->select("desc `$table`");
|
||||
if (!$allow_column) {
|
||||
return $this->json(2, '表不存在');
|
||||
}
|
||||
$columns = array_column($allow_column, 'Field', 'Field');
|
||||
foreach ($data as $col => $item) {
|
||||
if (is_array($item)) {
|
||||
$data[$col] = implode(',', $item);
|
||||
continue;
|
||||
}
|
||||
if ($col === 'password') {
|
||||
$data[$col] = Util::passwordHash($item);
|
||||
}
|
||||
}
|
||||
$datetime = date('Y-m-d H:i:s');
|
||||
if (isset($columns['created_at']) && !isset($data['created_at'])) {
|
||||
$data['created_at'] = $datetime;
|
||||
}
|
||||
if (isset($columns['updated_at']) && !isset($data['updated_at'])) {
|
||||
$data['updated_at'] = $datetime;
|
||||
}
|
||||
$id = $this->model->insertGetId($data);
|
||||
return $this->json(0, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function update(Request $request)
|
||||
{
|
||||
$column = $request->post('column');
|
||||
$value = $request->post('value');
|
||||
$data = $request->post('data');
|
||||
$table = $this->model->getTable();
|
||||
$allow_column = Util::db()->select("desc `$table`");
|
||||
if (!$allow_column) {
|
||||
return $this->json(2, '表不存在');
|
||||
}
|
||||
foreach ($data as $col => $item) {
|
||||
if (is_array($item)) {
|
||||
$data[$col] = implode(',', $item);
|
||||
}
|
||||
if ($col === 'password') {
|
||||
// 密码为空,则不更新密码
|
||||
if ($item == '') {
|
||||
unset($data[$col]);
|
||||
continue;
|
||||
}
|
||||
$data[$col] = Util::passwordHash($item);
|
||||
}
|
||||
}
|
||||
$this->model->where($column, $value)->update($data);
|
||||
return $this->json(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
* @throws \Support\Exception\BusinessException
|
||||
*/
|
||||
public function delete(Request $request)
|
||||
{
|
||||
$column = $request->post('column');
|
||||
$value = $request->post('value');
|
||||
$this->model->where([$column => $value])->delete();
|
||||
return $this->json(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 摘要
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
* @throws \Support\Exception\BusinessException
|
||||
*/
|
||||
public function schema(Request $request)
|
||||
{
|
||||
$table = $this->model->getTable();
|
||||
Util::checkTableName($table);
|
||||
$schema = Option::where('name', "table_form_schema_$table")->value('value');
|
||||
$form_schema_map = $schema ? json_decode($schema, true) : [];
|
||||
|
||||
$data = $this->getSchema($table);
|
||||
foreach ($data['forms'] as $field => $item) {
|
||||
if (isset($form_schema_map[$field])) {
|
||||
$data['forms'][$field] = $form_schema_map[$field];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->json(0, 'ok', [
|
||||
'table' => $data['table'],
|
||||
'columns' => array_values($data['columns']),
|
||||
'forms' => array_values($data['forms']),
|
||||
'keys' => array_values($data['keys']),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按表获取摘要
|
||||
*
|
||||
* @param $table
|
||||
* @param $section
|
||||
* @return array|mixed
|
||||
*/
|
||||
protected function getSchema($table, $section = null)
|
||||
{
|
||||
$database = config('database.connections')['plugin.admin.mysql']['database'];
|
||||
$schema_raw = $section !== 'table' ? Util::db()->select("select * from information_schema.COLUMNS where TABLE_SCHEMA = '$database' and table_name = '$table'") : [];
|
||||
$forms = [];
|
||||
$columns = [];
|
||||
foreach ($schema_raw as $item) {
|
||||
$field = $item->COLUMN_NAME;
|
||||
$columns[$field] = [
|
||||
'field' => $field,
|
||||
'type' => Util::typeToMethod($item->DATA_TYPE, (bool)strpos($item->COLUMN_TYPE, 'unsigned')),
|
||||
'comment' => $item->COLUMN_COMMENT,
|
||||
'default' => $item->COLUMN_DEFAULT,
|
||||
'length' => $this->getLengthValue($item),
|
||||
'nullable' => $item->IS_NULLABLE !== 'NO',
|
||||
'primary_key' => $item->COLUMN_KEY === 'PRI',
|
||||
'auto_increment' => strpos($item->EXTRA, 'auto_increment') !== false
|
||||
];
|
||||
|
||||
$forms[$field] = [
|
||||
'field' => $field,
|
||||
'comment' => $item->COLUMN_COMMENT,
|
||||
'control' => Util::typeToControl($item->DATA_TYPE),
|
||||
'form_show' => $item->COLUMN_KEY !== 'PRI',
|
||||
'list_show' => true,
|
||||
'enable_sort' => false,
|
||||
'readonly' => $item->COLUMN_KEY === 'PRI',
|
||||
'searchable' => false,
|
||||
'search_type' => 'normal',
|
||||
'control_args' => '',
|
||||
];
|
||||
}
|
||||
$table_schema = $section == 'table' || !$section ? Util::db()->select("SELECT TABLE_COMMENT FROM information_schema.`TABLES` WHERE TABLE_SCHEMA='$database' and TABLE_NAME='$table'") : [];
|
||||
$indexes = $section == 'keys' || !$section ? Util::db()->select("SHOW INDEX FROM `$table`") : [];
|
||||
$keys = [];
|
||||
foreach ($indexes as $index) {
|
||||
$key_name = $index->Key_name;
|
||||
if ($key_name == 'PRIMARY') {
|
||||
continue;
|
||||
}
|
||||
if (!isset($keys[$key_name])) {
|
||||
$keys[$key_name] = [
|
||||
'name' => $key_name,
|
||||
'columns' => [],
|
||||
'type' => $index->Non_unique == 0 ? 'unique' : 'normal'
|
||||
];
|
||||
}
|
||||
$keys[$key_name]['columns'][] = $index->Column_name;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'table' => ['name' => $table, 'comment' => $table_schema[0]->TABLE_COMMENT ?? ''],
|
||||
'columns' => $columns,
|
||||
'forms' => $forms,
|
||||
'keys' => array_reverse($keys, true)
|
||||
];
|
||||
return $section ? $data[$section] : $data;
|
||||
}
|
||||
|
||||
protected function getLengthValue($schema)
|
||||
{
|
||||
$type = $schema->DATA_TYPE;
|
||||
if (in_array($type, ['float', 'decimal', 'double'])) {
|
||||
return "{$schema->NUMERIC_PRECISION},{$schema->NUMERIC_SCALE}";
|
||||
}
|
||||
if ($type === 'enum') {
|
||||
return implode(',', array_map(function($item){
|
||||
return trim($item, "'");
|
||||
}, explode(',', substr($schema->COLUMN_TYPE, 5, -1))));
|
||||
}
|
||||
if (in_array($type, ['varchar', 'text', 'char'])) {
|
||||
return $schema->CHARACTER_MAXIMUM_LENGTH;
|
||||
}
|
||||
if (in_array($type, ['time', 'datetime', 'timestamp'])) {
|
||||
return $schema->CHARACTER_MAXIMUM_LENGTH;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return array|\support\Response
|
||||
*/
|
||||
protected function selectInput(Request $request)
|
||||
{
|
||||
$field = $request->get('field');
|
||||
$order = $request->get('order', 'descend');
|
||||
$format = $request->get('format', 'normal');
|
||||
$page_size = $request->get('pageSize', $format === 'tree' ? 1000 : 10);
|
||||
$order = $order === 'ascend' ? 'asc' : 'desc';
|
||||
$where = $request->get();
|
||||
$table = $this->model->getTable();
|
||||
|
||||
$allow_column = Util::db()->select("desc `$table`");
|
||||
if (!$allow_column) {
|
||||
return $this->json(2, '表不存在');
|
||||
}
|
||||
$allow_column = array_column($allow_column, 'Field', 'Field');
|
||||
if (!in_array($field, $allow_column)) {
|
||||
$field = current($allow_column);
|
||||
}
|
||||
foreach ($where as $column => $value) {
|
||||
if (!$value || !isset($allow_column[$column]) ||
|
||||
(is_array($value) && ($value[0] == 'undefined' || $value[1] == 'undefined'))) {
|
||||
unset($where[$column]);
|
||||
}
|
||||
}
|
||||
|
||||
return [$where, $format, $page_size, $field, $order];
|
||||
}
|
||||
|
||||
/**
|
||||
* 树
|
||||
*
|
||||
* @param $items
|
||||
* @return \support\Response
|
||||
*/
|
||||
protected function formatTree($items)
|
||||
{
|
||||
$items_map = [];
|
||||
foreach ($items as $item) {
|
||||
$items_map[$item->id] = [
|
||||
'title' => $item->title ?? $item->name ?? $item->id,
|
||||
'value' => (string)$item->id,
|
||||
'key' => (string)$item->id,
|
||||
'pid' => $item->pid,
|
||||
];
|
||||
}
|
||||
$formatted_items = [];
|
||||
foreach ($items_map as $index => $item) {
|
||||
if ($item['pid'] && isset($items_map[$item['pid']])) {
|
||||
$items_map[$item['pid']]['children'][] = &$items_map[$index];
|
||||
}
|
||||
}
|
||||
foreach ($items_map as $item) {
|
||||
if (!$item['pid']) {
|
||||
$formatted_items[] = $item;
|
||||
}
|
||||
}
|
||||
return $this->json(0, 'ok', $formatted_items);
|
||||
}
|
||||
|
||||
/**
|
||||
* 表格树
|
||||
*
|
||||
* @param $items
|
||||
* @return \support\Response
|
||||
*/
|
||||
protected function formatTableTree($items)
|
||||
{
|
||||
$items_map = [];
|
||||
foreach ($items as $item) {
|
||||
$items_map[$item->id] = $item->toArray();
|
||||
}
|
||||
$formatted_items = [];
|
||||
foreach ($items_map as $item) {
|
||||
if ($item['pid'] && isset($items_map[$item['pid']])) {
|
||||
$items_map[$item['pid']]['children'][] = $item;
|
||||
}
|
||||
}
|
||||
foreach ($items_map as $item) {
|
||||
if (!$item['pid']) {
|
||||
$formatted_items[] = $item;
|
||||
}
|
||||
}
|
||||
return $this->json(0, 'ok', $formatted_items);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $items
|
||||
* @return \support\Response
|
||||
*/
|
||||
protected function formatSelect($items)
|
||||
{
|
||||
$formatted_items = [];
|
||||
foreach ($items as $item) {
|
||||
$formatted_items[] = [
|
||||
'title' => $item->title ?? $item->name ?? $item->id,
|
||||
'value' => $item->id,
|
||||
'key' => $item->id,
|
||||
];
|
||||
}
|
||||
return $this->json(0, 'ok', $formatted_items);
|
||||
}
|
||||
|
||||
protected function json(int $code, string $msg = 'ok', array $data = [])
|
||||
{
|
||||
return json(['code' => $code, 'result' => $data, 'message' => $msg, 'type' => $code ? 'error' : 'success']);
|
||||
}
|
||||
|
||||
}
|
37
plugin/admin/app/controller/IndexController.php
Normal file
37
plugin/admin/app/controller/IndexController.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app\controller;
|
||||
|
||||
use plugin\admin\app\Util;
|
||||
use support\Db;
|
||||
use support\Request;
|
||||
|
||||
class IndexController
|
||||
{
|
||||
|
||||
/**
|
||||
* 无需登录的方法
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedLogin = ['index'];
|
||||
|
||||
|
||||
/**
|
||||
* 后台主页
|
||||
*
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (!$request->queryString()) {
|
||||
// 检查是否安装了admin
|
||||
$database_config_file = base_path() . '/plugin/admin/config/database.php';
|
||||
clearstatcache();
|
||||
if (!is_file($database_config_file)) {
|
||||
return redirect('/app/admin?install#install');
|
||||
}
|
||||
}
|
||||
return response()->file(base_path() . '/plugin/admin/public/index.html');
|
||||
}
|
||||
|
||||
}
|
52
plugin/admin/app/controller/auth/AdminController.php
Normal file
52
plugin/admin/app/controller/auth/AdminController.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app\controller\auth;
|
||||
|
||||
use plugin\admin\app\controller\Base;
|
||||
use plugin\admin\app\controller\Crud;
|
||||
use plugin\admin\app\model\Admin;
|
||||
use support\Request;
|
||||
|
||||
/**
|
||||
* 管理员设置
|
||||
*/
|
||||
class AdminController extends Base
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Admin
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
/**
|
||||
* 增删改查
|
||||
*/
|
||||
use Crud;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->model = new Admin;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
* @throws \Support\Exception\BusinessException
|
||||
*/
|
||||
public function delete(Request $request)
|
||||
{
|
||||
$column = $request->post('column');
|
||||
$value = $request->post('value');
|
||||
if ($value == admin_id()) {
|
||||
return $this->json(1, '不能删除自己');
|
||||
}
|
||||
$this->model->where([$column => $value])->delete();
|
||||
return $this->json(0);
|
||||
}
|
||||
|
||||
}
|
102
plugin/admin/app/controller/auth/AdminRoleController.php
Normal file
102
plugin/admin/app/controller/auth/AdminRoleController.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app\controller\auth;
|
||||
|
||||
use plugin\admin\app\controller\Base;
|
||||
use plugin\admin\app\controller\Crud;
|
||||
use plugin\admin\app\model\AdminRole;
|
||||
use plugin\admin\app\model\AdminRule;
|
||||
use plugin\admin\app\Util;
|
||||
use support\Db;
|
||||
use support\Request;
|
||||
|
||||
/**
|
||||
* 管理员角色设置
|
||||
*/
|
||||
class AdminRoleController extends Base
|
||||
{
|
||||
/**
|
||||
* @var AdminRole
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
/**
|
||||
* 增删改查
|
||||
*/
|
||||
use Crud;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->model = new AdminRole;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function update(Request $request)
|
||||
{
|
||||
$column = $request->post('column');
|
||||
$value = $request->post('value');
|
||||
$data = $request->post('data');
|
||||
$table = $this->model->getTable();
|
||||
$allow_column = Util::db()->select("desc $table");
|
||||
if (!$allow_column) {
|
||||
return $this->json(2, '表不存在');
|
||||
}
|
||||
|
||||
$data['rules'] = array_filter(array_unique((array)$data['rules']));
|
||||
|
||||
$item = $this->model->where($column, $value)->first();
|
||||
if (!$item) {
|
||||
return $this->json(1, '记录不存在');
|
||||
}
|
||||
if ($item->id == 1) {
|
||||
$data['rules'] = '*';
|
||||
}
|
||||
|
||||
foreach ($data as $col => $item) {
|
||||
if (is_array($item)) {
|
||||
$data[$col] = implode(',', $item);
|
||||
}
|
||||
if ($col === 'password') {
|
||||
// 密码为空,则不更新密码
|
||||
if ($item == '') {
|
||||
unset($data[$col]);
|
||||
continue;
|
||||
}
|
||||
$data[$col] = Util::passwordHash($item);
|
||||
}
|
||||
}
|
||||
|
||||
$this->model->where($column, $value)->update($data);
|
||||
return $this->json(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
* @throws \Support\Exception\BusinessException
|
||||
*/
|
||||
public function delete(Request $request)
|
||||
{
|
||||
$column = $request->post('column');
|
||||
$value = $request->post('value');
|
||||
$item = $this->model->where($column, $value)->first();
|
||||
if (!$item) {
|
||||
return $this->json(0);
|
||||
}
|
||||
if ($item->id == 1) {
|
||||
return $this->json(1, '无法删除超级管理员角色');
|
||||
}
|
||||
$this->model->where('id', $item->id)->delete();
|
||||
return $this->json(0);
|
||||
}
|
||||
|
||||
}
|
474
plugin/admin/app/controller/auth/AdminRuleController.php
Normal file
474
plugin/admin/app/controller/auth/AdminRuleController.php
Normal file
@ -0,0 +1,474 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app\controller\auth;
|
||||
|
||||
use plugin\admin\app\controller\Base;
|
||||
use plugin\admin\app\controller\Crud;
|
||||
use plugin\admin\app\model\AdminRole;
|
||||
use plugin\admin\app\model\AdminRule;
|
||||
use plugin\admin\app\Util;
|
||||
use support\Db;
|
||||
use support\Request;
|
||||
|
||||
class AdminRuleController extends Base
|
||||
{
|
||||
/**
|
||||
* @var AdminRule
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
/**
|
||||
* 增删改查
|
||||
*/
|
||||
use Crud;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->model = new AdminRule;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限树
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function tree(Request $request)
|
||||
{
|
||||
$this->syncRules();
|
||||
$items = $this->model->get();
|
||||
return $this->formatTree($items);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据类同步规则到数据库
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function syncRules()
|
||||
{
|
||||
$items = $this->model->where('name', 'like', '%\\\\%')->get()->keyBy('name');
|
||||
$methods_in_db = [];
|
||||
$methods_in_files = [];
|
||||
foreach ($items as $item) {
|
||||
$class = $item->name;
|
||||
if (strpos($class, '@')) {
|
||||
$methods_in_db[$class] = $class;
|
||||
continue;
|
||||
}
|
||||
if (class_exists($class)) {
|
||||
$reflection = new \ReflectionClass($class);
|
||||
$properties = $reflection->getDefaultProperties();
|
||||
$no_need_auth = array_merge($properties['noNeedLogin'] ?? [], $properties['noNeedAuth'] ?? []);
|
||||
$class = $reflection->getName();
|
||||
$pid = $item->id;
|
||||
$methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);
|
||||
foreach ($methods as $method) {
|
||||
$method_name = $method->getName();
|
||||
if (strpos($method_name, '__') === 0 || in_array($method_name, $no_need_auth)) {
|
||||
continue;
|
||||
}
|
||||
$name = "$class@$method_name";
|
||||
$methods_in_files[$name] = $name;
|
||||
$title = Util::getCommentFirstLine($method->getDocComment()) ?? $method_name;
|
||||
$menu = $items[$name] ?? [];
|
||||
if ($menu) {
|
||||
if ($menu->title != $title) {
|
||||
AdminRule::where('name', $name)->update(['title' => $title]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$menu = new AdminRule;
|
||||
$menu->pid = $pid;
|
||||
$menu->name = $name;
|
||||
$menu->path = '';
|
||||
$menu->component = '';
|
||||
$menu->title = $title;
|
||||
$menu->is_menu = 0;
|
||||
$menu->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
// 从数据库中删除已经不存在的方法
|
||||
$menu_names_to_del = array_diff($methods_in_db, $methods_in_files);
|
||||
if ($menu_names_to_del) {
|
||||
AdminRule::whereIn('name', $menu_names_to_del)->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function select(Request $request)
|
||||
{
|
||||
[$where, $format, $page_size, $field, $order] = $this->selectInput($request);
|
||||
$where['is_menu'] = 1;
|
||||
$model = $this->model;
|
||||
foreach ($where as $column => $value) {
|
||||
if (is_array($value)) {
|
||||
if (in_array($value[0], ['>', '=', '<', '<>'])) {
|
||||
$model = $model->where($column, $value[0], $value[1]);
|
||||
} elseif ($value[0] == 'in') {
|
||||
$model = $model->whereIn($column, $value[1]);
|
||||
} else {
|
||||
$model = $model->whereBetween($column, $value);
|
||||
}
|
||||
} else {
|
||||
$model = $model->where($column, $value);
|
||||
}
|
||||
}
|
||||
$model = $model->orderBy($field, $order);
|
||||
if (in_array($format, ['select', 'tree', 'table_tree'])) {
|
||||
$items = $model->get();
|
||||
if ($format == 'select') {
|
||||
return $this->formatSelect($items);
|
||||
} elseif ($format == 'tree') {
|
||||
return $this->formatTree($items);
|
||||
}
|
||||
return $this->formatTableTree($items);
|
||||
}
|
||||
|
||||
$paginator = $model->paginate($page_size);
|
||||
return $this->json(0, 'ok', [
|
||||
'items' => $paginator->items(),
|
||||
'total' => $paginator->total()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function insert(Request $request)
|
||||
{
|
||||
$data = $request->post('data');
|
||||
$table = $this->model->getTable();
|
||||
$allow_column = Util::db()->select("desc $table");
|
||||
if (!$allow_column) {
|
||||
return $this->json(2, '表不存在');
|
||||
}
|
||||
$name = $data['name'];
|
||||
if ($this->model->where('name', $name)->first()) {
|
||||
return $this->json(1, "菜单key $name 已经存在");
|
||||
}
|
||||
$columns = array_column($allow_column, 'Field', 'Field');
|
||||
foreach ($data as $col => $item) {
|
||||
if (is_array($item)) {
|
||||
$data[$col] = implode(',', $item);
|
||||
}
|
||||
}
|
||||
$datetime = date('Y-m-d H:i:s');
|
||||
if (isset($columns['created_at']) && !isset($data['created_at'])) {
|
||||
$data['created_at'] = $datetime;
|
||||
}
|
||||
if (isset($columns['updated_at']) && !isset($data['updated_at'])) {
|
||||
$data['updated_at'] = $datetime;
|
||||
}
|
||||
$id = $this->model->insertGetId($data);
|
||||
return $this->json(0, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
* @throws \Support\Exception\BusinessException
|
||||
*/
|
||||
public function delete(Request $request)
|
||||
{
|
||||
$column = $request->post('column');
|
||||
$value = $request->post('value');
|
||||
$item = $this->model->where($column, $value)->first();
|
||||
if (!$item) {
|
||||
return $this->json(1, '记录不存在');
|
||||
}
|
||||
// 子规则一起删除
|
||||
$delete_ids = $children_ids = [$item['id']];
|
||||
while($children_ids) {
|
||||
$children_ids = $this->model->whereIn('pid', $children_ids)->pluck('id')->toArray();
|
||||
$delete_ids = array_merge($delete_ids, $children_ids);
|
||||
}
|
||||
$this->model->whereIn('id', $delete_ids)->delete();
|
||||
return $this->json(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 一键生成菜单
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
* @throws \Support\Exception\BusinessException
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
$table_name = $request->input('table');
|
||||
Util::checkTableName($table_name);
|
||||
$name = $request->post('name');
|
||||
$pid = $request->post('pid', 0);
|
||||
$icon = $request->post('icon', '');
|
||||
$path = '';
|
||||
$overwrite = $request->post('overwrite');
|
||||
|
||||
$pid = (int)$pid;
|
||||
if ($pid) {
|
||||
$parent_menu = AdminRule::find($pid);
|
||||
if (!$parent_menu) {
|
||||
return $this->json(1, '父菜单不存在');
|
||||
}
|
||||
$path = $parent_menu['path'];
|
||||
}
|
||||
|
||||
$table_basename = strpos($table_name, 'wa_') === 0 ? substr($table_name, 3) : $table_name;
|
||||
$model_class = Util::camel($table_basename);
|
||||
$suffix = substr($model_class, -2);
|
||||
if ($suffix != 'ss' && $suffix != 'es') {
|
||||
$model_class = rtrim($model_class, 's');
|
||||
}
|
||||
|
||||
$controller_class = $model_class . config('plugin.admin.app.controller_suffix');
|
||||
$path = trim($path, '/');
|
||||
$path_backslash = str_replace('/', '\\', $path);
|
||||
if ($path_backslash) {
|
||||
$controller_namespace = "plugin\\admin\\app\\controller\\$path_backslash";
|
||||
} else {
|
||||
$controller_namespace = "plugin\\admin\\app\\controller";
|
||||
}
|
||||
$controller_file = base_path() . '/' . str_replace('\\', '/', $controller_namespace) . "/$controller_class.php";
|
||||
|
||||
$model_file = base_path() . "/plugin/admin/app/model/$model_class.php";
|
||||
if (!$overwrite) {
|
||||
if (is_file($controller_file)) {
|
||||
return $this->json(1, substr($controller_file, strlen(base_path())) . '已经存在');
|
||||
}
|
||||
if (is_file($model_file)) {
|
||||
return $this->json(1, substr($model_file, strlen(base_path())) . '已经存在');
|
||||
}
|
||||
}
|
||||
|
||||
// 创建model
|
||||
$this->createModel($model_class, "plugin\\admin\\app\\model", $model_file, $table_name);
|
||||
|
||||
// 创建controller
|
||||
$this->createController($controller_class, $controller_namespace, $controller_file, $model_class, $name);
|
||||
|
||||
// 菜单相关参数
|
||||
$menu_path = str_replace('_', '', $table_basename);
|
||||
$suffix = substr($menu_path, -2);
|
||||
if ($suffix != 'ss' && $suffix != 'es') {
|
||||
$menu_path = rtrim($menu_path, 's');
|
||||
}
|
||||
$componet = '/database/table/View';
|
||||
$reflection = new \ReflectionClass("$controller_namespace\\$controller_class");
|
||||
$controller_class_with_nsp = $reflection->getName();
|
||||
|
||||
$menu = AdminRule::where('name', $controller_class_with_nsp)->first();
|
||||
if (!$menu) {
|
||||
$menu = new AdminRule;
|
||||
}
|
||||
$menu->pid = $pid;
|
||||
$menu->name = $controller_class_with_nsp;
|
||||
$menu->path = $pid ? $menu_path : "/$menu_path";
|
||||
$menu->component = $componet;
|
||||
$menu->title = $name;
|
||||
$menu->icon = $icon;
|
||||
$menu->save();
|
||||
|
||||
$roles = admin('roles');
|
||||
$rules = AdminRole::whereIn('id', $roles)->pluck('rules');
|
||||
$rule_ids = [];
|
||||
foreach ($rules as $rule_string) {
|
||||
if (!$rule_string) {
|
||||
continue;
|
||||
}
|
||||
$rule_ids = array_merge($rule_ids, explode(',', $rule_string));
|
||||
}
|
||||
|
||||
// 不是超级管理员,则需要给当前管理员这个菜单的权限
|
||||
if (!in_array('*', $rule_ids) && $roles){
|
||||
$role = AdminRole::find(current($roles));
|
||||
if ($role) {
|
||||
$role->rules .= ",{$menu->id}";
|
||||
}
|
||||
$role->save();
|
||||
}
|
||||
|
||||
return $this->json(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建model
|
||||
*
|
||||
* @param $class
|
||||
* @param $namespace
|
||||
* @param $file
|
||||
* @param $table
|
||||
* @return void
|
||||
*/
|
||||
protected function createModel($class, $namespace, $file, $table)
|
||||
{
|
||||
$this->mkdir($file);
|
||||
$table_val = "'$table'";
|
||||
$pk = 'id';
|
||||
$properties = '';
|
||||
$timestamps = '';
|
||||
$columns = [];
|
||||
try {
|
||||
$database = config('database.connections')['plugin.admin.mysql']['database'];
|
||||
//plugin.admin.mysql
|
||||
foreach (Util::db()->select("select COLUMN_NAME,DATA_TYPE,COLUMN_KEY,COLUMN_COMMENT from INFORMATION_SCHEMA.COLUMNS where table_name = '$table' and table_schema = '$database'") as $item) {
|
||||
if ($item->COLUMN_KEY === 'PRI') {
|
||||
$pk = $item->COLUMN_NAME;
|
||||
$item->COLUMN_COMMENT .= "(主键)";
|
||||
}
|
||||
$type = $this->getType($item->DATA_TYPE);
|
||||
$properties .= " * @property $type \${$item->COLUMN_NAME} {$item->COLUMN_COMMENT}\n";
|
||||
$columns[$item->COLUMN_NAME] = $item->COLUMN_NAME;
|
||||
}
|
||||
} catch (\Throwable $e) {}
|
||||
if (!isset($columns['created_at']) || !isset($columns['updated_at'])) {
|
||||
$timestamps = <<<EOF
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public \$timestamps = false;
|
||||
EOF;
|
||||
|
||||
}
|
||||
$properties = rtrim($properties) ?: ' *';
|
||||
$model_content = <<<EOF
|
||||
<?php
|
||||
|
||||
namespace $namespace;
|
||||
|
||||
/**
|
||||
$properties
|
||||
*/
|
||||
class $class extends Base
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected \$table = $table_val;
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected \$primaryKey = '$pk';
|
||||
|
||||
$timestamps
|
||||
|
||||
|
||||
}
|
||||
|
||||
EOF;
|
||||
file_put_contents($file, $model_content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建控制器
|
||||
*
|
||||
* @param $controller_class
|
||||
* @param $namespace
|
||||
* @param $file
|
||||
* @param $model_class
|
||||
* @param $name
|
||||
* @return void
|
||||
*/
|
||||
protected function createController($controller_class, $namespace, $file, $model_class, $name)
|
||||
{
|
||||
$this->mkdir($file);
|
||||
$controller_content = <<<EOF
|
||||
<?php
|
||||
|
||||
namespace $namespace;
|
||||
|
||||
use plugin\admin\app\controller\Base;
|
||||
use plugin\admin\app\controller\Crud;
|
||||
use plugin\\admin\\app\\model\\$model_class;
|
||||
use support\Request;
|
||||
|
||||
/**
|
||||
* $name
|
||||
*/
|
||||
class $controller_class extends Base
|
||||
{
|
||||
/**
|
||||
* 开启增删改查
|
||||
*/
|
||||
use Crud;
|
||||
|
||||
/**
|
||||
* @var $model_class
|
||||
*/
|
||||
protected \$model = null;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
\$this->model = new $model_class;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
EOF;
|
||||
file_put_contents($file, $controller_content);
|
||||
}
|
||||
|
||||
protected function mkdir($file)
|
||||
{
|
||||
$path = pathinfo($file, PATHINFO_DIRNAME);
|
||||
if (!is_dir($path)) {
|
||||
mkdir($path, 0777, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段类型到php类型映射
|
||||
*
|
||||
* @param string $type
|
||||
* @return string
|
||||
*/
|
||||
protected function getType(string $type)
|
||||
{
|
||||
if (strpos($type, 'int') !== false) {
|
||||
return 'integer';
|
||||
}
|
||||
switch ($type) {
|
||||
case 'varchar':
|
||||
case 'string':
|
||||
case 'text':
|
||||
case 'date':
|
||||
case 'time':
|
||||
case 'guid':
|
||||
case 'datetimetz':
|
||||
case 'datetime':
|
||||
case 'decimal':
|
||||
case 'enum':
|
||||
return 'string';
|
||||
case 'boolean':
|
||||
return 'integer';
|
||||
case 'float':
|
||||
return 'float';
|
||||
default:
|
||||
return 'mixed';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
245
plugin/admin/app/controller/common/AccountController.php
Normal file
245
plugin/admin/app/controller/common/AccountController.php
Normal file
@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app\controller\common;
|
||||
|
||||
use Gregwar\Captcha\CaptchaBuilder;
|
||||
use plugin\admin\app\controller\Base;
|
||||
use plugin\admin\app\model\Admin;
|
||||
use plugin\admin\app\Util;
|
||||
use support\Db;
|
||||
use support\exception\BusinessException;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
|
||||
/**
|
||||
* 管理员账户
|
||||
*/
|
||||
class AccountController extends Base
|
||||
{
|
||||
/**
|
||||
* 不需要登录的方法
|
||||
* @var string[]
|
||||
*/
|
||||
public $noNeedLogin = ['login', 'logout', 'captcha'];
|
||||
|
||||
/**
|
||||
* 不需要鉴权的方法
|
||||
* @var string[]
|
||||
*/
|
||||
public $noNeedAuth = ['info', 'getPermCode'];
|
||||
|
||||
/**
|
||||
* @var Admin
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->model = new Admin;
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function login(Request $request)
|
||||
{
|
||||
$captcha = $request->post('captcha');
|
||||
if (strtolower($captcha) !== session('captcha-login')) {
|
||||
return $this->json(1, '验证码错误');
|
||||
}
|
||||
$request->session()->forget('captcha-login');
|
||||
$username = $request->post('username', '');
|
||||
$password = $request->post('password', '');
|
||||
if (!$username) {
|
||||
return $this->json(1, '用户名不能为空');
|
||||
}
|
||||
$this->checkLoginLimit($username);
|
||||
$admin = Admin::where('username', $username)->first();
|
||||
if (!$admin || !Util::passwordVerify($password, $admin->password)) {
|
||||
return $this->json(1, '账户不存在或密码错误');
|
||||
}
|
||||
$this->removeLoginLimit($username);
|
||||
$admin = $admin->toArray();
|
||||
$session = $request->session();
|
||||
unset($admin['password']);
|
||||
$admin['roles'] = $admin['roles'] ? explode(',', $admin['roles']) : [];
|
||||
$session->set('admin', $admin);
|
||||
return $this->json(0, '登录成功', [
|
||||
'nickname' => $admin['nickname'],
|
||||
'token' => $request->sessionId(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function logout(Request $request)
|
||||
{
|
||||
$request->session()->delete('admin');
|
||||
return $this->json(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取登录信息
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function info(Request $request)
|
||||
{
|
||||
$admin = admin();
|
||||
if (!$admin) {
|
||||
return $this->json(1);
|
||||
}
|
||||
$info = [
|
||||
'nickname' => $admin['nickname'],
|
||||
'desc' => 'manager',
|
||||
'avatar' => $admin['avatar'],
|
||||
'token' => $request->sessionId(),
|
||||
'userId' => $admin['id'],
|
||||
'username' => $admin['username'],
|
||||
'email' => $admin['email'],
|
||||
'mobile' => $admin['mobile'],
|
||||
'roles' => []
|
||||
];
|
||||
return $this->json(0, 'ok', $info);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
* @param Request $request
|
||||
* @param $type
|
||||
* @return Response
|
||||
*/
|
||||
public function captcha(Request $request, $type = 'login')
|
||||
{
|
||||
$builder = new CaptchaBuilder;
|
||||
$builder->build();
|
||||
$request->session()->set("captcha-$type", strtolower($builder->getPhrase()));
|
||||
$img_content = $builder->get();
|
||||
return response($img_content, 200, ['Content-Type' => 'image/jpeg']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限码(目前没作用)
|
||||
* @return Response
|
||||
*/
|
||||
public function getPermCode()
|
||||
{
|
||||
return $this->json(0, 'ok', ['1000', '3000', '5000']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function update(Request $request)
|
||||
{
|
||||
$allow_column = [
|
||||
'nickname' => 'nickname',
|
||||
'avatar' => 'avatar',
|
||||
'email' => 'email',
|
||||
'mobile' => 'mobile',
|
||||
];
|
||||
|
||||
$data = $request->post();
|
||||
$update_data = [];
|
||||
foreach ($allow_column as $key => $column) {
|
||||
if (isset($data[$key])) {
|
||||
$update_data[$column] = $data[$key];
|
||||
}
|
||||
}
|
||||
if (isset($update_data['password'])) {
|
||||
$update_data['password'] = Util::passwordHash($update_data['password']);
|
||||
}
|
||||
Admin::where('id', admin_id())->update($update_data);
|
||||
return $this->json(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function password(Request $request)
|
||||
{
|
||||
$hash = admin('password');
|
||||
$password = $request->post('password');
|
||||
if (!$password) {
|
||||
return $this->json(2, '密码不能为空');
|
||||
}
|
||||
if (!Util::passwordVerify($request->post('old_password'), $hash)) {
|
||||
return $this->json(1, '原始密码不正确');
|
||||
}
|
||||
$update_data = [
|
||||
'password' => Util::passwordHash($password)
|
||||
];
|
||||
Admin::where('id', admin_id())->update($update_data);
|
||||
return $this->json(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查登录频率限制
|
||||
*
|
||||
* @param $username
|
||||
* @return void
|
||||
* @throws BusinessException
|
||||
*/
|
||||
protected function checkLoginLimit($username)
|
||||
{
|
||||
$limit_log_path = runtime_path() . '/login';
|
||||
if (!is_dir($limit_log_path)) {
|
||||
mkdir($limit_log_path, 0777, true);
|
||||
}
|
||||
$limit_file = $limit_log_path . '/' . md5($username) . '.limit';
|
||||
$time = date('YmdH') . ceil(date('i')/5);
|
||||
$limit_info = [];
|
||||
if (is_file($limit_file)) {
|
||||
$json_str = file_get_contents($limit_file);
|
||||
$limit_info = json_decode($json_str, true);
|
||||
}
|
||||
|
||||
if (!$limit_info || $limit_info['time'] != $time) {
|
||||
$limit_info = [
|
||||
'username' => $username,
|
||||
'count' => 0,
|
||||
'time' => $time
|
||||
];
|
||||
}
|
||||
$limit_info['count']++;
|
||||
file_put_contents($limit_file, json_encode($limit_info));
|
||||
if ($limit_info['count'] >= 5) {
|
||||
throw new BusinessException('登录失败次数过多,请5分钟后再试');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解除登录限制
|
||||
*
|
||||
* @param $username
|
||||
* @return void
|
||||
*/
|
||||
protected function removeLoginLimit($username)
|
||||
{
|
||||
$limit_log_path = runtime_path() . '/login';
|
||||
$limit_file = $limit_log_path . '/' . md5($username) . '.limit';
|
||||
if (is_file($limit_file)) {
|
||||
unlink($limit_file);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
226
plugin/admin/app/controller/common/InstallController.php
Normal file
226
plugin/admin/app/controller/common/InstallController.php
Normal file
@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app\controller\common;
|
||||
|
||||
use Gregwar\Captcha\CaptchaBuilder;
|
||||
use Illuminate\Database\Capsule\Manager;
|
||||
use plugin\admin\app\controller\Base;
|
||||
use plugin\admin\app\model\Admin;
|
||||
use plugin\admin\app\Util;
|
||||
use support\exception\BusinessException;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use support\Db;
|
||||
|
||||
/**
|
||||
* 安装
|
||||
*/
|
||||
class InstallController extends Base
|
||||
{
|
||||
/**
|
||||
* 不需要登录的方法
|
||||
* @var string[]
|
||||
*/
|
||||
public $noNeedLogin = ['step1', 'step2'];
|
||||
|
||||
/**
|
||||
* 设置数据库
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
* @throws BusinessException
|
||||
*/
|
||||
public function step1(Request $request)
|
||||
{
|
||||
$database_config_file = base_path() . '/plugin/admin/config/database.php';
|
||||
clearstatcache();
|
||||
if (is_file($database_config_file)) {
|
||||
return $this->json(1, '管理后台已经安装!如需重新安装,请删除该插件数据库配置文件并重启');
|
||||
}
|
||||
|
||||
if (!class_exists(CaptchaBuilder::class) || !class_exists(Manager::class)) {
|
||||
return $this->json(1, '请先restart重启webman后再进行此页面的设置');
|
||||
}
|
||||
|
||||
$user = $request->post('user');
|
||||
$password = $request->post('password');
|
||||
$database = $request->post('database');
|
||||
$host = $request->post('host');
|
||||
$port = $request->post('port');
|
||||
$overwrite = $request->post('overwrite');
|
||||
|
||||
$dsn = "mysql:dbname=$database;host=$host;port=$port;";
|
||||
try {
|
||||
$params = [
|
||||
\PDO::MYSQL_ATTR_INIT_COMMAND => "set names utf8mb4", //设置编码
|
||||
\PDO::ATTR_EMULATE_PREPARES => false,
|
||||
\PDO::ATTR_TIMEOUT => 5
|
||||
];
|
||||
$db = new \PDO($dsn, $user, $password, $params);
|
||||
$smt = $db->query("show tables");
|
||||
$tables = $smt->fetchAll();
|
||||
} catch (\Throwable $e) {
|
||||
if (stripos($e, 'Access denied for user')) {
|
||||
return $this->json(1, '数据库用户名或密码错误');
|
||||
}
|
||||
if (stripos($e, 'Connection refused')) {
|
||||
return $this->json(1, 'Connection refused. 请确认数据库IP端口是否正确,数据库已经启动');
|
||||
}
|
||||
if (stripos($e, 'timed out')) {
|
||||
return $this->json(1, '数据库连接超时,请确认数据库IP端口是否正确,安全组及防火墙已经放行端口');
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$tables_to_install = [
|
||||
'wa_admins',
|
||||
'wa_admin_roles',
|
||||
'wa_admin_rules',
|
||||
'wa_options',
|
||||
'wa_users',
|
||||
];
|
||||
|
||||
if (!$overwrite) {
|
||||
$tables_exist = [];
|
||||
foreach ($tables as $table) {
|
||||
$tables_exist[] = current($table);
|
||||
}
|
||||
$tables_conflict = array_intersect($tables_to_install, $tables_exist);
|
||||
if ($tables_conflict) {
|
||||
return $this->json(1, '以下表' . implode(',', $tables_conflict) . '已经存在,如需覆盖请选择强制覆盖');
|
||||
}
|
||||
}
|
||||
|
||||
$sql_file = base_path() . '/plugin/admin/webman-admin.sql';
|
||||
if (!is_file($sql_file)) {
|
||||
return $this->json(1, '数据库SQL文件不存在');
|
||||
}
|
||||
|
||||
$sql_query = file_get_contents($sql_file);
|
||||
$sql_query = $this->removeComments($sql_query);
|
||||
$sql_query = $this->splitSqlFile($sql_query, ';');
|
||||
foreach ($sql_query as $sql) {
|
||||
$db->exec($sql);
|
||||
}
|
||||
|
||||
$config_content = <<<EOF
|
||||
<?php
|
||||
return [
|
||||
'default' => 'mysql',
|
||||
'connections' => [
|
||||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'host' => '$host',
|
||||
'port' => '$port',
|
||||
'database' => '$database',
|
||||
'username' => '$user',
|
||||
'password' => '$password',
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_general_ci',
|
||||
'prefix' => '',
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
],
|
||||
],
|
||||
];
|
||||
EOF;
|
||||
|
||||
file_put_contents($database_config_file, $config_content);
|
||||
|
||||
// 尝试reload
|
||||
if (function_exists('posix_kill')) {
|
||||
set_error_handler(function () {});
|
||||
posix_kill(posix_getppid(), SIGUSR1);
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
return $this->json(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置管理员
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
* @throws BusinessException
|
||||
*/
|
||||
public function step2(Request $request)
|
||||
{
|
||||
$username = $request->post('username');
|
||||
$password = $request->post('password');
|
||||
$password2 = $request->post('password2');
|
||||
if ($password != $password2) {
|
||||
return $this->json(1, '两次密码不一致');
|
||||
}
|
||||
if (Admin::first()) {
|
||||
return $this->json(1, '后台已经安装完毕,无法通过此页面创建管理员');
|
||||
}
|
||||
$admin = new Admin;
|
||||
$admin->username = $username;
|
||||
$admin->password = Util::passwordHash($password);
|
||||
$admin->nickname = '超级管理员';
|
||||
$admin->roles = '1';
|
||||
$admin->save();
|
||||
return $this->json(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除sql文件中的注释
|
||||
*
|
||||
* @param $sql
|
||||
* @return string
|
||||
*/
|
||||
protected function removeComments($sql)
|
||||
{
|
||||
return preg_replace("/(\n--[^\n]*)/","", $sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sql
|
||||
* @param $delimiter
|
||||
* @return array
|
||||
*/
|
||||
function splitSqlFile($sql, $delimiter)
|
||||
{
|
||||
$tokens = explode($delimiter, $sql);
|
||||
$output = array();
|
||||
$matches = array();
|
||||
$token_count = count($tokens);
|
||||
for ($i = 0; $i < $token_count; $i++) {
|
||||
if (($i != ($token_count - 1)) || (strlen($tokens[$i] > 0))) {
|
||||
$total_quotes = preg_match_all("/'/", $tokens[$i], $matches);
|
||||
$escaped_quotes = preg_match_all("/(?<!\\\\)(\\\\\\\\)*\\\\'/", $tokens[$i], $matches);
|
||||
$unescaped_quotes = $total_quotes - $escaped_quotes;
|
||||
|
||||
if (($unescaped_quotes % 2) == 0) {
|
||||
$output[] = $tokens[$i];
|
||||
$tokens[$i] = "";
|
||||
} else {
|
||||
$temp = $tokens[$i] . $delimiter;
|
||||
$tokens[$i] = "";
|
||||
|
||||
$complete_stmt = false;
|
||||
for ($j = $i + 1; (!$complete_stmt && ($j < $token_count)); $j++) {
|
||||
$total_quotes = preg_match_all("/'/", $tokens[$j], $matches);
|
||||
$escaped_quotes = preg_match_all("/(?<!\\\\)(\\\\\\\\)*\\\\'/", $tokens[$j], $matches);
|
||||
$unescaped_quotes = $total_quotes - $escaped_quotes;
|
||||
if (($unescaped_quotes % 2) == 1) {
|
||||
$output[] = $temp . $tokens[$j];
|
||||
$tokens[$j] = "";
|
||||
$temp = "";
|
||||
$complete_stmt = true;
|
||||
$i = $j;
|
||||
} else {
|
||||
$temp .= $tokens[$j] . $delimiter;
|
||||
$tokens[$j] = "";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
}
|
235
plugin/admin/app/controller/common/MenuController.php
Normal file
235
plugin/admin/app/controller/common/MenuController.php
Normal file
@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app\controller\common;
|
||||
|
||||
use plugin\admin\app\controller\Base;
|
||||
use plugin\admin\app\model\AdminRole;
|
||||
use plugin\admin\app\model\AdminRule;
|
||||
use plugin\admin\app\Util;
|
||||
use function admin;
|
||||
|
||||
class MenuController extends Base
|
||||
{
|
||||
/**
|
||||
* 不需要权限的方法
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $noNeedAuth = ['get', 'tree'];
|
||||
|
||||
/**
|
||||
* @var AdminRule
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->model = new AdminRule;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单
|
||||
*
|
||||
* @return \support\Response
|
||||
*/
|
||||
function get()
|
||||
{
|
||||
[$rules, $items] = $this->getRulesAndItems();
|
||||
$items_map = [];
|
||||
foreach ($items as $item) {
|
||||
$items_map[$item['id']] = $item;
|
||||
}
|
||||
$formatted_items = [];
|
||||
foreach ($items_map as $index => $item) {
|
||||
foreach (['title', 'icon', 'hide_menu', 'frame_src'] as $name) {
|
||||
$value = $item[$name];
|
||||
unset($items_map[$index][$name]);
|
||||
if (!$value) {
|
||||
continue;
|
||||
}
|
||||
$items_map[$index]['meta'][Util::smCamel($name)] = $value;
|
||||
}
|
||||
if ($item['pid'] && isset($items_map[$item['pid']])) {
|
||||
$items_map[$item['pid']]['children'][] = &$items_map[$index];
|
||||
}
|
||||
}
|
||||
foreach ($items_map as $item) {
|
||||
if (!$item['pid']) {
|
||||
$formatted_items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
// 超级管理员权限为 *
|
||||
if (!in_array('*', $rules)) {
|
||||
$this->removeUncontain($formatted_items, 'id', $rules);
|
||||
}
|
||||
$this->removeUncontain($formatted_items, 'is_menu', [1]);
|
||||
$formatted_items = array_values($formatted_items);
|
||||
foreach ($formatted_items as &$item) {
|
||||
$this->arrayValues($item);
|
||||
}
|
||||
return $this->json(0, 'ok', $formatted_items);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单树
|
||||
*
|
||||
* @return \support\Response
|
||||
*/
|
||||
function tree()
|
||||
{
|
||||
[$rules, $items] = $this->getRulesAndItems();
|
||||
|
||||
$items_map = [];
|
||||
foreach ($items as $item) {
|
||||
if ($item['hide_menu']) {
|
||||
continue;
|
||||
}
|
||||
$items_map[$item['id']] = [
|
||||
'title' => $item['title'],
|
||||
'value' => (string)$item['id'],
|
||||
'key' => (string)$item['id'],
|
||||
'id' => $item['id'],
|
||||
'is_menu' => $item['is_menu'],
|
||||
'pid' => $item['pid'],
|
||||
];
|
||||
}
|
||||
$formatted_items = [];
|
||||
foreach ($items_map as $index => $item) {
|
||||
if ($item['pid'] && isset($items_map[$item['pid']])) {
|
||||
$items_map[$item['pid']]['children'][] = &$items_map[$index];
|
||||
}
|
||||
}
|
||||
foreach ($items_map as $item) {
|
||||
if (!$item['pid']) {
|
||||
$formatted_items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
// 超级管理员权限为 *
|
||||
if (!in_array('*', $rules)) {
|
||||
$this->removeUncontain($formatted_items, 'id', $rules);
|
||||
}
|
||||
$this->removeUncontain($formatted_items, 'is_menu', [1]);
|
||||
$this->recursiveRemove($formatted_items, ['id', 'pid', 'is_menu']);
|
||||
$formatted_items = array_values($formatted_items);
|
||||
foreach ($formatted_items as &$item) {
|
||||
$this->arrayValues($item);
|
||||
}
|
||||
|
||||
return $this->json(0, 'ok', $formatted_items);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除不包含某些数据的数组
|
||||
*
|
||||
* @param $array
|
||||
* @param $key
|
||||
* @param $values
|
||||
* @return void
|
||||
*/
|
||||
protected function removeUncontain(&$array, $key, $values)
|
||||
{
|
||||
foreach ($array as $k => &$item) {
|
||||
if (!is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
if (!$this->arrayContain($item, $key, $values)) {
|
||||
unset($array[$k]);
|
||||
} else {
|
||||
if (!isset($item['children'])) {
|
||||
continue;
|
||||
}
|
||||
$this->removeUncontain($item['children'], $key, $values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断数组是否包含某些数据
|
||||
*
|
||||
* @param $array
|
||||
* @param $key
|
||||
* @param $values
|
||||
* @return bool
|
||||
*/
|
||||
protected function arrayContain(&$array, $key, $values)
|
||||
{
|
||||
if (!is_array($array)) {
|
||||
return false;
|
||||
}
|
||||
if (isset($array[$key]) && in_array($array[$key], $values)) {
|
||||
return true;
|
||||
}
|
||||
if (!isset($array['children'])) {
|
||||
return false;
|
||||
}
|
||||
foreach ($array['children'] as $item) {
|
||||
if ($this->arrayContain($item, $key, $values)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归删除某些key
|
||||
*
|
||||
* @param $array
|
||||
* @param $keys
|
||||
* @return void
|
||||
*/
|
||||
protected function recursiveRemove(&$array, $keys)
|
||||
{
|
||||
if (!is_array($array)) {
|
||||
return;
|
||||
}
|
||||
foreach ($keys as $key) {
|
||||
unset($array[$key]);
|
||||
}
|
||||
foreach ($array as &$item) {
|
||||
$this->recursiveRemove($item, $keys);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限规则
|
||||
* @return array
|
||||
*/
|
||||
protected function getRulesAndItems()
|
||||
{
|
||||
$roles = admin('roles');
|
||||
$rules_strings = $roles ? AdminRole::whereIn('id', $roles)->pluck('rules') : [];
|
||||
$rules = [];
|
||||
foreach ($rules_strings as $rule_string) {
|
||||
if (!$rule_string) {
|
||||
continue;
|
||||
}
|
||||
$rules = array_merge($rules, explode(',', $rule_string));
|
||||
}
|
||||
|
||||
$items = AdminRule::get()->toArray();
|
||||
return [$rules, $items];
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归重建数组下标
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function arrayValues(&$array)
|
||||
{
|
||||
if (!is_array($array) || !isset($array['children'])) {
|
||||
return;
|
||||
}
|
||||
$array['children'] = array_values($array['children']);
|
||||
|
||||
foreach ($array['children'] as &$child) {
|
||||
$this->arrayValues($child);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
195
plugin/admin/app/controller/common/UploadController.php
Normal file
195
plugin/admin/app/controller/common/UploadController.php
Normal file
@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app\controller\common;
|
||||
|
||||
use Intervention\Image\ImageManagerStatic as Image;
|
||||
use plugin\admin\app\controller\Base;
|
||||
use support\Request;
|
||||
use function base_path;
|
||||
use function json;
|
||||
|
||||
/**
|
||||
* 上传
|
||||
*/
|
||||
class UploadController extends Base
|
||||
{
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function file(Request $request)
|
||||
{
|
||||
$file = current($request->file());
|
||||
if (!$file || !$file->isValid()) {
|
||||
return $this->json(1, '未找到文件');
|
||||
}
|
||||
$img_exts = [
|
||||
'jpg',
|
||||
'jpeg',
|
||||
'png',
|
||||
'gif'
|
||||
];
|
||||
if (in_array($file->getUploadExtension(), $img_exts)) {
|
||||
return $this->image($request);
|
||||
}
|
||||
$data = $this->base($request, '/upload/files/'.date('Ymd'));
|
||||
if ($data['code']) {
|
||||
return $this->json($data['code'], $data['message']);
|
||||
}
|
||||
return json(['code' => 0, 'message' => '上传成功', 'url' => $data['data']['src']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传头像
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function avatar(Request $request)
|
||||
{
|
||||
$file = current($request->file());
|
||||
if ($file && $file->isValid()) {
|
||||
$ext = strtolower($file->getUploadExtension());
|
||||
if (!in_array($ext, ['jpg', 'jpeg', 'gif', 'png'])) {
|
||||
return json(['code' => 2, 'msg' => '仅支持 jpg jpeg gif png格式']);
|
||||
}
|
||||
$image = Image::make($file);
|
||||
$width = $image->width();
|
||||
$height = $image->height();
|
||||
$size = $width > $height ? $height : $width;
|
||||
$relative_path = 'upload/avatar/' . date('Ym');
|
||||
$real_path = base_path() . "/plugin/admin/public/$relative_path";
|
||||
if (!is_dir($real_path)) {
|
||||
mkdir($real_path, 0777, true);
|
||||
}
|
||||
$name = bin2hex(pack('Nn',time(), random_int(1, 65535)));
|
||||
$ext = $file->getUploadExtension();
|
||||
|
||||
$image->crop($size, $size)->resize(300, 300);
|
||||
$path = base_path() . "/plugin/admin/public/$relative_path/$name.lg.$ext";
|
||||
$image->save($path);
|
||||
|
||||
$image->resize(120, 120);
|
||||
$path = base_path() . "/plugin/admin/public/$relative_path/$name.md.$ext";
|
||||
$image->save($path);
|
||||
|
||||
$image->resize(60, 60);
|
||||
$path = base_path() . "/plugin/admin/public/$relative_path/$name.$ext";
|
||||
$image->save($path);
|
||||
|
||||
$image->resize(30, 30);
|
||||
$path = base_path() . "/plugin/admin/public/$relative_path/$name.sm.$ext";
|
||||
$image->save($path);
|
||||
|
||||
return json([
|
||||
'code' => 0,
|
||||
'message' => '上传成功',
|
||||
'url' => "/app/admin/$relative_path/$name.$ext"
|
||||
]);
|
||||
}
|
||||
return json(['code' => 1, 'msg' => 'file not found']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传图片
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function image(Request $request)
|
||||
{
|
||||
$data = $this->base($request, '/upload/img/'.date('Ymd'));
|
||||
if ($data['code']) {
|
||||
return json(['code' => $data['code'], 'message' => $data['msg']]);
|
||||
}
|
||||
$realpath = $data['data']['realpath'];
|
||||
try {
|
||||
$img = Image::make($realpath);
|
||||
$max_height = 1170;
|
||||
$max_width = 1170;
|
||||
$width = $img->width();
|
||||
$height = $img->height();
|
||||
$ratio = 1;
|
||||
if ($height > $max_height || $width > $max_width) {
|
||||
$ratio = $width > $height ? $max_width / $width : $max_height / $height;
|
||||
}
|
||||
$img->resize($width*$ratio, $height*$ratio)->save($realpath);
|
||||
} catch (\Exception $e) {
|
||||
unlink($realpath);
|
||||
return json( [
|
||||
'code' => 500,
|
||||
'message' => '处理图片发生错误'
|
||||
]);
|
||||
}
|
||||
return json( [
|
||||
'code' => 0,
|
||||
'message' => '上传成功',
|
||||
'url' => $data['data']['src']
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上传数据
|
||||
*
|
||||
* @param Request $request
|
||||
* @param $relative_dir
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function base(Request $request, $relative_dir)
|
||||
{
|
||||
$relative_dir = ltrim($relative_dir, '/');
|
||||
$file = current($request->file());
|
||||
if (!$file || !$file->isValid()) {
|
||||
return ['code' => 400, 'message' => '未找到上传文件'];
|
||||
}
|
||||
|
||||
$base_dir = base_path() . '/plugin/admin/public/';
|
||||
$full_dir = $base_dir . $relative_dir;
|
||||
if (!is_dir($full_dir)) {
|
||||
mkdir($full_dir, 0777, true);
|
||||
}
|
||||
|
||||
$ext = strtolower($file->getUploadExtension());
|
||||
$ext_forbidden_map = ['php', 'php3', 'php5', 'css', 'js', 'html', 'htm', 'asp', 'jsp'];
|
||||
if (in_array($ext, $ext_forbidden_map)) {
|
||||
return ['code' => 400, 'message' => '不支持该格式的文件上传'];
|
||||
}
|
||||
|
||||
$relative_path = $relative_dir . '/' . bin2hex(pack('Nn',time(), random_int(1, 65535))) . ".$ext";
|
||||
$full_path = $base_dir . $relative_path;
|
||||
$file_size = $file->getSize();
|
||||
$file_name = $file->getUploadName();
|
||||
$file->move($full_path);
|
||||
return [
|
||||
'code' => 0,
|
||||
'msg' => '上传成功',
|
||||
'data' => [
|
||||
'src' => "/app/admin/$relative_path",
|
||||
'name' => $file_name,
|
||||
'realpath' => $full_path,
|
||||
'size' => $this->formatSize($file_size)
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
*
|
||||
* @param $file_size
|
||||
* @return string
|
||||
*/
|
||||
protected function formatSize($file_size) {
|
||||
$size = sprintf("%u", $file_size);
|
||||
if($size == 0) {
|
||||
return("0 Bytes");
|
||||
}
|
||||
$sizename = array(" Bytes", " KB", " MB", " GB", " TB", " PB", " EB", " ZB", " YB");
|
||||
return round($size/pow(1024, ($i = floor(log($size, 1024)))), 2) . $sizename[$i];
|
||||
}
|
||||
|
||||
}
|
711
plugin/admin/app/controller/database/TableController.php
Normal file
711
plugin/admin/app/controller/database/TableController.php
Normal file
@ -0,0 +1,711 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app\controller\database;
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use plugin\admin\app\controller\Base;
|
||||
use plugin\admin\app\model\Option;
|
||||
use plugin\admin\app\Util;
|
||||
use Support\Db;
|
||||
use Support\Exception\BusinessException;
|
||||
use Support\Request;
|
||||
|
||||
class TableController extends Base
|
||||
{
|
||||
/**
|
||||
* 不需要鉴权的方法
|
||||
* @var string[]
|
||||
*/
|
||||
public $noNeedAuth = ['types'];
|
||||
|
||||
/**
|
||||
* 查询表
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function show(Request $request)
|
||||
{
|
||||
$database = config('database.connections')['plugin.admin.mysql']['database'];
|
||||
$field = $request->get('field', 'TABLE_NAME');
|
||||
$order = $request->get('order', 'ascend');
|
||||
$allow_column = ['TABLE_NAME','TABLE_COMMENT','ENGINE','TABLE_ROWS','CREATE_TIME','UPDATE_TIME','TABLE_COLLATION'];
|
||||
if (!in_array($field, $allow_column)) {
|
||||
$field = 'TABLE_NAME';
|
||||
}
|
||||
$order = $order === 'ascend' ? 'asc' : 'desc';
|
||||
$tables = Util::db()->select("SELECT TABLE_NAME,TABLE_COMMENT,ENGINE,TABLE_ROWS,CREATE_TIME,UPDATE_TIME,TABLE_COLLATION FROM information_schema.`TABLES` WHERE TABLE_SCHEMA='$database' order by $field $order");
|
||||
|
||||
if ($tables) {
|
||||
$table_names = array_column($tables, 'TABLE_NAME');
|
||||
$table_rows_count = [];
|
||||
foreach ($table_names as $table_name) {
|
||||
$table_rows_count[$table_name] = Util::db()->table($table_name)->count();
|
||||
}
|
||||
foreach ($tables as $key => $table) {
|
||||
$tables[$key]->TABLE_ROWS = $table_rows_count[$table->TABLE_NAME] ?? $table->TABLE_ROWS;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->json(0, 'ok', $tables);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建表
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function create(Request $request)
|
||||
{
|
||||
$data = $request->post();
|
||||
$table_name = $data['table']['name'];
|
||||
$table_comment = $data['table']['comment'];
|
||||
$columns = $data['columns'];
|
||||
$keys = $data['keys'];
|
||||
Util::schema()->create($table_name, function (Blueprint $table) use ($columns) {
|
||||
$type_method_map = Util::methodControlMap();
|
||||
foreach ($columns as $column) {
|
||||
if (!isset($column['type'])) {
|
||||
throw new BusinessException("请为{$column['field']}选择类型");
|
||||
}
|
||||
if (!isset($type_method_map[$column['type']])) {
|
||||
throw new BusinessException("不支持的类型{$column['type']}");
|
||||
}
|
||||
$this->createColumn($column, $table);
|
||||
}
|
||||
$table->charset = 'utf8mb4';
|
||||
$table->collation = 'utf8mb4_general_ci';
|
||||
$table->engine = 'InnoDB';
|
||||
});
|
||||
// @todo 防注入
|
||||
Util::db()->statement("ALTER TABLE `$table_name` COMMENT '$table_comment'");
|
||||
|
||||
// 索引
|
||||
Util::schema()->table($table_name, function (Blueprint $table) use ($keys) {
|
||||
foreach ($keys as $key) {
|
||||
$name = $key['name'];
|
||||
$columns = $key['columns'];
|
||||
$type = $key['type'];
|
||||
if ($type == 'unique') {
|
||||
$table->unique($columns, $name);
|
||||
continue;
|
||||
}
|
||||
$table->index($columns, $name);
|
||||
}
|
||||
});
|
||||
|
||||
$form_schema = $request->post('forms', []);
|
||||
$form_schema_map = [];
|
||||
foreach ($form_schema as $item) {
|
||||
$form_schema_map[$item['field']] = $item;
|
||||
}
|
||||
$form_schema_map = json_encode($form_schema_map, JSON_UNESCAPED_UNICODE);
|
||||
$this->updateSchemaOption($table_name, $form_schema_map);
|
||||
return $this->json(0, 'ok');
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改表
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
* @throws BusinessException
|
||||
*/
|
||||
public function modify(Request $request)
|
||||
{
|
||||
$data = $request->post();
|
||||
$old_table_name = $data['table']['old_name'];
|
||||
$table_name = $data['table']['name'];
|
||||
$table_comment = $data['table']['comment'];
|
||||
$columns = $data['columns'];
|
||||
$keys = $data['keys'] ?? [];
|
||||
// 改表名
|
||||
if ($table_name != $old_table_name) {
|
||||
Util::checkTableName($table_name);
|
||||
Util::schema()->rename($old_table_name, $table_name);
|
||||
}
|
||||
|
||||
$old_columns = $this->getSchema($table_name, 'columns');
|
||||
$type_method_map = Util::methodControlMap();
|
||||
foreach ($columns as $column) {
|
||||
if (!isset($type_method_map[$column['type']])) {
|
||||
throw new BusinessException("不支持的类型{$column['type']}");
|
||||
}
|
||||
$field = $column['field'];
|
||||
|
||||
// 重命名的字段 mysql8才支持?
|
||||
if (isset($column['old_field']) && $column['old_field'] !== $field) {
|
||||
//Util::db()->statement("ALTER TABLE $table_name RENAME COLUMN {$column['old_field']} to $field");
|
||||
}
|
||||
|
||||
$old_column = $old_columns[$field] ?? [];
|
||||
// 类型更改
|
||||
foreach ($old_column as $key => $value) {
|
||||
if (isset($column[$key]) && $column[$key] != $value) {
|
||||
$this->modifyColumn($column, $table_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$table = $this->getSchema($table_name, 'table');
|
||||
// @todo $table_comment 防止SQL注入
|
||||
if ($table_comment !== $table['comment']) {
|
||||
Util::db()->statement("ALTER TABLE `$table_name` COMMENT '$table_comment'");
|
||||
}
|
||||
|
||||
$old_columns = $this->getSchema($table_name, 'columns');
|
||||
Util::schema()->table($table_name, function (Blueprint $table) use ($columns, $old_columns, $keys, $table_name) {
|
||||
foreach ($columns as $column) {
|
||||
$field = $column['field'];
|
||||
// 新字段
|
||||
if (!isset($old_columns[$field])) {
|
||||
$this->createColumn($column, $table);
|
||||
}
|
||||
}
|
||||
// 更新索引名字
|
||||
foreach ($keys as $key) {
|
||||
if (!empty($key['old_name']) && $key['old_name'] !== $key['name']) {
|
||||
$table->renameIndex($key['old_name'], $key['name']);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 找到删除的字段
|
||||
$old_columns = $this->getSchema($table_name, 'columns');
|
||||
$exists_column_names = array_column($columns, 'field', 'field');
|
||||
$old_columns_names = array_column($old_columns, 'field');
|
||||
$drop_column_names = array_diff($old_columns_names, $exists_column_names);
|
||||
foreach ($drop_column_names as $drop_column_name) {
|
||||
//$table->dropColumn($drop_column_name); 无法使用
|
||||
Util::db()->statement("ALTER TABLE $table_name DROP COLUMN $drop_column_name");
|
||||
}
|
||||
|
||||
$old_keys = $this->getSchema($table_name, 'keys');
|
||||
Util::schema()->table($table_name, function (Blueprint $table) use ($keys, $old_keys, $table_name) {
|
||||
foreach ($keys as $key) {
|
||||
$key_name = $key['name'];
|
||||
$old_key = $old_keys[$key_name] ?? [];
|
||||
// 如果索引有变动,则删除索引,重新建立索引
|
||||
if ($old_key && ($key['type'] != $old_key['type'] || $key['columns'] != $old_key['columns'])) {
|
||||
$old_key = [];
|
||||
unset($old_keys[$key_name]);
|
||||
$table->dropIndex($key_name);
|
||||
}
|
||||
// 重新建立索引
|
||||
if (!$old_key) {
|
||||
$name = $key['name'];
|
||||
$columns = $key['columns'];
|
||||
$type = $key['type'];
|
||||
if ($type == 'unique') {
|
||||
$table->unique($columns, $name);
|
||||
continue;
|
||||
}
|
||||
$table->index($columns, $name);
|
||||
}
|
||||
}
|
||||
|
||||
// 找到删除的索引
|
||||
$exists_key_names = array_column($keys, 'name', 'name');
|
||||
$old_keys_names = array_column($old_keys, 'name');
|
||||
$drop_keys_names = array_diff($old_keys_names, $exists_key_names);
|
||||
foreach ($drop_keys_names as $name) {
|
||||
$table->dropIndex($name);
|
||||
}
|
||||
});
|
||||
|
||||
$form_schema = $request->post('forms', []);
|
||||
$form_schema_map = [];
|
||||
foreach ($form_schema as $item) {
|
||||
$form_schema_map[$item['field']] = $item;
|
||||
}
|
||||
$form_schema_map = json_encode($form_schema_map, JSON_UNESCAPED_UNICODE);
|
||||
$option_name = $this->updateSchemaOption($table_name, $form_schema_map);
|
||||
|
||||
return $this->json(0,$option_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询记录
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function select(Request $request)
|
||||
{
|
||||
$page = $request->get('page', 1);
|
||||
$field = $request->get('field');
|
||||
$order = $request->get('order', 'descend');
|
||||
$table = $request->get('table');
|
||||
$format = $request->get('format', 'normal');
|
||||
$page_size = $request->get('pageSize', $format === 'tree' ? 1000 : 10);
|
||||
|
||||
if (!preg_match('/[a-zA-Z_0-9]+/', $table)) {
|
||||
return $this->json(1, '表不存在');
|
||||
}
|
||||
$allow_column = Util::db()->select("desc $table");
|
||||
if (!$allow_column) {
|
||||
return $this->json(2, '表不存在');
|
||||
}
|
||||
$allow_column = array_column($allow_column, 'Field', 'Field');
|
||||
if (!in_array($field, $allow_column)) {
|
||||
$field = current($allow_column);
|
||||
}
|
||||
$order = $order === 'ascend' ? 'asc' : 'desc';
|
||||
$paginator = Util::db()->table($table);
|
||||
foreach ($request->get() as $column => $value) {
|
||||
if (!$value) {
|
||||
continue;
|
||||
}
|
||||
if (isset($allow_column[$column])) {
|
||||
if (is_array($value)) {
|
||||
if ($value[0] == 'undefined' || $value[1] == 'undefined') {
|
||||
continue;
|
||||
}
|
||||
$paginator = $paginator->whereBetween($column, $value);
|
||||
} else {
|
||||
$paginator = $paginator->where($column, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
$paginator = $paginator->orderBy($field, $order)->paginate($page_size, '*', 'page', $page);
|
||||
|
||||
$items = $paginator->items();
|
||||
if ($format == 'tree') {
|
||||
$items_map = [];
|
||||
foreach ($items as $item) {
|
||||
$items_map[$item->id] = (array)$item;
|
||||
}
|
||||
$formatted_items = [];
|
||||
foreach ($items_map as $item) {
|
||||
if ($item['pid'] && isset($items_map[$item['pid']])) {
|
||||
$items_map[$item['pid']]['children'][] = $item;
|
||||
}
|
||||
}
|
||||
foreach ($items_map as $item) {
|
||||
if (!$item['pid']) {
|
||||
$formatted_items[] = $item;
|
||||
}
|
||||
}
|
||||
$items = $formatted_items;
|
||||
}
|
||||
|
||||
return $this->json(0, 'ok', [
|
||||
'items' => $items,
|
||||
'total' => $paginator->total()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入记录
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function insert(Request $request)
|
||||
{
|
||||
$table = $request->input('table');
|
||||
$data = $request->post('data');
|
||||
$columns = $this->getSchema($table, 'columns');
|
||||
foreach ($data as $col => $item) {
|
||||
if (is_array($item)) {
|
||||
$data[$col] = implode(',', $item);
|
||||
continue;
|
||||
}
|
||||
if ($col === 'password') {
|
||||
$data[$col] = Util::passwordHash($item);
|
||||
}
|
||||
}
|
||||
$datetime = date('Y-m-d H:i:s');
|
||||
if (isset($columns['created_at']) && !isset($data['created_at'])) {
|
||||
$data['created_at'] = $datetime;
|
||||
}
|
||||
if (isset($columns['updated_at']) && !isset($data['updated_at'])) {
|
||||
$data['updated_at'] = $datetime;
|
||||
}
|
||||
$id = Util::db()->table($table)->insertGetId($data);
|
||||
return $this->json(0, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新记录
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
* @throws BusinessException
|
||||
*/
|
||||
public function update(Request $request)
|
||||
{
|
||||
$table = $request->input('table');
|
||||
$column = $request->post('column');
|
||||
$value = $request->post('value');
|
||||
$data = $request->post('data');
|
||||
$columns = $this->getSchema($table, 'columns');
|
||||
foreach ($data as $col => $item) {
|
||||
if (is_array($item)) {
|
||||
$data[$col] = implode(',', $item);
|
||||
}
|
||||
if ($col === 'password') {
|
||||
// 密码为空,则不更新密码
|
||||
if ($item == '') {
|
||||
unset($data[$col]);
|
||||
continue;
|
||||
}
|
||||
$data[$col] = Util::passwordHash($item);
|
||||
}
|
||||
}
|
||||
$datetime = date('Y-m-d H:i:s');
|
||||
if (isset($columns['updated_at']) && !isset($data['updated_at'])) {
|
||||
$data['updated_at'] = $datetime;
|
||||
}
|
||||
var_export($data);
|
||||
Util::checkTableName($table);
|
||||
Util::db()->table($table)->where($column, $value)->update($data);
|
||||
return $this->json(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除记录
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
* @throws BusinessException
|
||||
*/
|
||||
public function delete(Request $request)
|
||||
{
|
||||
$table = $request->input('table');
|
||||
$column = $request->post('column');
|
||||
$value = $request->post('value');
|
||||
Util::checkTableName($table);
|
||||
Util::db()->table($table)->where([$column => $value])->delete();
|
||||
return $this->json(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 表摘要
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
* @throws BusinessException
|
||||
*/
|
||||
public function schema(Request $request)
|
||||
{
|
||||
$table = $request->get('table');
|
||||
Util::checkTableName($table);
|
||||
$schema = Option::where('name', "table_form_schema_$table")->value('value');
|
||||
$form_schema_map = $schema ? json_decode($schema, true) : [];
|
||||
|
||||
$data = $this->getSchema($table);
|
||||
foreach ($data['forms'] as $field => $item) {
|
||||
if (isset($form_schema_map[$field])) {
|
||||
$data['forms'][$field] = $form_schema_map[$field];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->json(0, 'ok', [
|
||||
'table' => $data['table'],
|
||||
'columns' => array_values($data['columns']),
|
||||
'forms' => array_values($data['forms']),
|
||||
'keys' => array_values($data['keys']),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取摘要
|
||||
*
|
||||
* @param $table
|
||||
* @param $section
|
||||
* @return array|mixed
|
||||
*/
|
||||
protected function getSchema($table, $section = null)
|
||||
{
|
||||
$database = config('database.connections')['plugin.admin.mysql']['database'];
|
||||
$schema_raw = $section !== 'table' ? Util::db()->select("select * from information_schema.COLUMNS where TABLE_SCHEMA = '$database' and table_name = '$table'") : [];
|
||||
$forms = [];
|
||||
$columns = [];
|
||||
foreach ($schema_raw as $item) {
|
||||
$field = $item->COLUMN_NAME;
|
||||
$columns[$field] = [
|
||||
'field' => $field,
|
||||
'type' => Util::typeToMethod($item->DATA_TYPE, (bool)strpos($item->COLUMN_TYPE, 'unsigned')),
|
||||
'comment' => $item->COLUMN_COMMENT,
|
||||
'default' => $item->COLUMN_DEFAULT,
|
||||
'length' => $this->getLengthValue($item),
|
||||
'nullable' => $item->IS_NULLABLE !== 'NO',
|
||||
'primary_key' => $item->COLUMN_KEY === 'PRI',
|
||||
'auto_increment' => strpos($item->EXTRA, 'auto_increment') !== false
|
||||
];
|
||||
|
||||
$forms[$field] = [
|
||||
'field' => $field,
|
||||
'comment' => $item->COLUMN_COMMENT,
|
||||
'control' => Util::typeToControl($item->DATA_TYPE),
|
||||
'form_show' => $item->COLUMN_KEY !== 'PRI',
|
||||
'list_show' => true,
|
||||
'enable_sort' => false,
|
||||
'readonly' => $item->COLUMN_KEY === 'PRI',
|
||||
'searchable' => false,
|
||||
'search_type' => 'normal',
|
||||
'control_args' => '',
|
||||
];
|
||||
}
|
||||
$table_schema = $section == 'table' || !$section ? Util::db()->select("SELECT TABLE_COMMENT FROM information_schema.`TABLES` WHERE TABLE_SCHEMA='$database' and TABLE_NAME='$table'") : [];
|
||||
$indexes = $section == 'keys' || !$section ? Util::db()->select("SHOW INDEX FROM $table") : [];
|
||||
$keys = [];
|
||||
foreach ($indexes as $index) {
|
||||
$key_name = $index->Key_name;
|
||||
if ($key_name == 'PRIMARY') {
|
||||
continue;
|
||||
}
|
||||
if (!isset($keys[$key_name])) {
|
||||
$keys[$key_name] = [
|
||||
'name' => $key_name,
|
||||
'columns' => [],
|
||||
'type' => $index->Non_unique == 0 ? 'unique' : 'normal'
|
||||
];
|
||||
}
|
||||
$keys[$key_name]['columns'][] = $index->Column_name;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'table' => ['name' => $table, 'comment' => $table_schema[0]->TABLE_COMMENT ?? ''],
|
||||
'columns' => $columns,
|
||||
'forms' => $forms,
|
||||
'keys' => array_reverse($keys, true)
|
||||
];
|
||||
return $section ? $data[$section] : $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段长度
|
||||
*
|
||||
* @param $schema
|
||||
* @return string
|
||||
*/
|
||||
protected function getLengthValue($schema)
|
||||
{
|
||||
$type = $schema->DATA_TYPE;
|
||||
if (in_array($type, ['float', 'decimal', 'double'])) {
|
||||
return "{$schema->NUMERIC_PRECISION},{$schema->NUMERIC_SCALE}";
|
||||
}
|
||||
if ($type === 'enum') {
|
||||
return implode(',', array_map(function($item){
|
||||
return trim($item, "'");
|
||||
}, explode(',', substr($schema->COLUMN_TYPE, 5, -1))));
|
||||
}
|
||||
if (in_array($type, ['varchar', 'text', 'char'])) {
|
||||
return $schema->CHARACTER_MAXIMUM_LENGTH;
|
||||
}
|
||||
if (in_array($type, ['time', 'datetime', 'timestamp'])) {
|
||||
return $schema->CHARACTER_MAXIMUM_LENGTH;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除表
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function drop(Request $request)
|
||||
{
|
||||
$table_name = $request->post('table');
|
||||
if (!$table_name) {
|
||||
return $this->json(0, 'not found');
|
||||
}
|
||||
$table_not_allow_drop = ['wa_admins', 'wa_users', 'wa_options', 'wa_admin_roles', 'wa_admin_rules'];
|
||||
if (in_array($table_name, $table_not_allow_drop)) {
|
||||
return $this->json(400, "$table_name 不允许删除");
|
||||
}
|
||||
Util::schema()->drop($table_name);
|
||||
// 删除schema
|
||||
Util::db()->table('wa_options')->where('name', "table_form_schema_$table_name")->delete();
|
||||
return $this->json(0, 'ok');
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建字段
|
||||
*
|
||||
* @param $column
|
||||
* @param Blueprint $table
|
||||
* @return mixed
|
||||
*/
|
||||
protected function createColumn($column, Blueprint $table)
|
||||
{
|
||||
$method = $column['type'];
|
||||
$args = [$column['field']];
|
||||
if (stripos($method, 'int') !== false) {
|
||||
// auto_increment 会自动成为主键
|
||||
if ($column['auto_increment']) {
|
||||
$column['nullable'] = false;
|
||||
$column['default'] = '';
|
||||
$args[] = true;
|
||||
}
|
||||
} elseif (in_array($method, ['string', 'char']) || stripos($method, 'time') !== false) {
|
||||
if ($column['length']) {
|
||||
$args[] = $column['length'];
|
||||
}
|
||||
} elseif ($method === 'enum') {
|
||||
$args[] = array_map('trim', explode(',', $column['length']));
|
||||
} elseif (in_array($method, ['float', 'decimal', 'double'])) {
|
||||
if ($column['length']) {
|
||||
$args = array_merge($args, array_map('trim', explode(',', $column['length'])));
|
||||
}
|
||||
} else {
|
||||
$column['auto_increment'] = false;
|
||||
}
|
||||
|
||||
$column_def = [$table, $method](...$args);
|
||||
if (!empty($column['comment'])) {
|
||||
$column_def = $column_def->comment($column['comment']);
|
||||
}
|
||||
|
||||
if (!$column['auto_increment'] && $column['primary_key']) {
|
||||
$column_def = $column_def->primary(true);
|
||||
}
|
||||
|
||||
if ($column['auto_increment'] && !$column['primary_key']) {
|
||||
$column_def = $column_def->primary(false);
|
||||
}
|
||||
$column_def = $column_def->nullable($column['nullable']);
|
||||
|
||||
if ($column['primary_key']) {
|
||||
$column_def = $column_def->nullable(false);
|
||||
}
|
||||
|
||||
if ($column['default'] && !in_array($method, ['text'])) {
|
||||
$column_def->default($column['default']);
|
||||
}
|
||||
return $column_def;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更改字段
|
||||
*
|
||||
* @param $column
|
||||
* @param Blueprint $table
|
||||
* @return mixed
|
||||
*/
|
||||
protected function modifyColumn($column, $table)
|
||||
{
|
||||
$method = $column['type'];
|
||||
$field = $column['field'];
|
||||
$nullable = $column['nullable'];
|
||||
$default = $column['default'];
|
||||
$comment = $column['comment'];
|
||||
$auto_increment = $column['auto_increment'];
|
||||
$length = (int)$column['length'];
|
||||
$primary_key = $column['primary_key'];
|
||||
// @todo 防止SQL注入
|
||||
if (isset($column['old_field']) && $column['old_field'] !== $field) {
|
||||
$sql = "ALTER TABLE $table CHANGE COLUMN {$column['old_field']} $field ";
|
||||
} else {
|
||||
$sql = "ALTER TABLE $table MODIFY $field ";
|
||||
}
|
||||
|
||||
if (stripos($method, 'integer') !== false) {
|
||||
$type = str_ireplace('integer', 'int', $method);
|
||||
if (stripos($method, 'unsigned') !== false) {
|
||||
$type = str_ireplace('unsigned', '', $type);
|
||||
$sql .= "$type ";
|
||||
$sql .= 'unsigned ';
|
||||
} else {
|
||||
$sql .= "$type ";
|
||||
}
|
||||
if ($auto_increment) {
|
||||
$column['nullable'] = false;
|
||||
$column['default'] = '';
|
||||
$sql .= 'AUTO_INCREMENT ';
|
||||
}
|
||||
} else {
|
||||
switch ($method) {
|
||||
case 'string':
|
||||
$length = $length ?: 255
|
||||
; $sql .= "varchar($length) ";
|
||||
break;
|
||||
case 'char':
|
||||
case 'time':
|
||||
$sql .= $length ? "$method($length) " : "$method ";
|
||||
break;
|
||||
case 'enum':
|
||||
// @todo 防止SQL注入
|
||||
$args = array_map('trim', explode(',', $column['length']));
|
||||
$sql .= "enum('" . implode("','", $args) . "') ";
|
||||
break;
|
||||
case 'double':
|
||||
case 'float':
|
||||
case 'decimal':
|
||||
if (trim($column['length'])) {
|
||||
$args = array_map('intval', explode(',', $column['length']));
|
||||
$args[1] = $args[1] ?? $args[0];
|
||||
$sql .= "$method({$args[0]}, {$args[1]}) ";
|
||||
break;
|
||||
}
|
||||
$sql .= "$method ";
|
||||
break;
|
||||
default :
|
||||
$sql .= "$method ";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!$nullable) {
|
||||
$sql .= "NOT NULL ";
|
||||
}
|
||||
|
||||
if ($default !== null && !in_array($method, ['text'])) {
|
||||
$sql .= "DEFAULT '$default' ";
|
||||
}
|
||||
|
||||
if ($comment !== null) {
|
||||
$sql .= "COMMENT '$comment' ";
|
||||
}
|
||||
|
||||
Util::db()->statement($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段类型列表
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \support\Response
|
||||
*/
|
||||
public function types(Request $request)
|
||||
{
|
||||
$types = Util::methodControlMap();
|
||||
return $this->json(0, 'ok', $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在options对用的name
|
||||
*
|
||||
* @param $table_name
|
||||
* @return string
|
||||
*/
|
||||
protected function getSchemaOptionName($table_name)
|
||||
{
|
||||
return "table_form_schema_$table_name";
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新表的form schema信息
|
||||
*
|
||||
* @param $table_name
|
||||
* @param $data
|
||||
* @return string
|
||||
*/
|
||||
protected function updateSchemaOption($table_name, $data)
|
||||
{
|
||||
$option_name = $this->getSchemaOptionName($table_name);
|
||||
$option = Option::where('name', $option_name)->first();
|
||||
if ($option) {
|
||||
Option::where('name', $option_name)->update(['value' => $data]);
|
||||
} else {
|
||||
Option::insert(['name' => $option_name, 'value' => $data]);
|
||||
}
|
||||
return $option_name;
|
||||
}
|
||||
|
||||
}
|
33
plugin/admin/app/controller/user/UserController.php
Normal file
33
plugin/admin/app/controller/user/UserController.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app\controller\user;
|
||||
|
||||
use plugin\admin\app\controller\Base;
|
||||
use plugin\admin\app\controller\Crud;
|
||||
use plugin\admin\app\model\User;
|
||||
use support\Request;
|
||||
|
||||
/**
|
||||
* 用户管理
|
||||
*/
|
||||
class UserController extends Base
|
||||
{
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
protected $model = null;
|
||||
|
||||
/**
|
||||
* 增删改查
|
||||
*/
|
||||
use Crud;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->model = new User;
|
||||
}
|
||||
|
||||
}
|
39
plugin/admin/app/exception/Handler.php
Normal file
39
plugin/admin/app/exception/Handler.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace plugin\admin\app\exception;
|
||||
|
||||
use Throwable;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
/**
|
||||
* Class Handler
|
||||
* @package Support\Exception
|
||||
*/
|
||||
class Handler extends \Support\Exception\Handler
|
||||
{
|
||||
public function render(Request $request, Throwable $exception): Response
|
||||
{
|
||||
$code = $exception->getCode();
|
||||
if ($request->expectsJson()) {
|
||||
$json = ['code' => $code ? $code : 500, 'message' => $this->_debug ? $exception->getMessage() : 'Server internal error', 'type' => 'failed'];
|
||||
$this->_debug && $json['traces'] = (string)$exception;
|
||||
return new Response(200, ['Content-Type' => 'application/json'],
|
||||
\json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
$error = $this->_debug ? \nl2br((string)$exception) : 'Server internal error';
|
||||
return new Response(500, [], $error);
|
||||
}
|
||||
}
|
72
plugin/admin/app/functions.php
Normal file
72
plugin/admin/app/functions.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* Here is your custom functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 当前登录管理员id
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
function admin_id()
|
||||
{
|
||||
return session('admin.id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前管理员
|
||||
*
|
||||
* @param null|array|string $fields
|
||||
* @return array|mixed|null
|
||||
*/
|
||||
function admin($fields = null)
|
||||
{
|
||||
if (!$admin = session('admin')) {
|
||||
return null;
|
||||
}
|
||||
if ($fields === null) {
|
||||
return $admin;
|
||||
}
|
||||
if (is_array($fields)) {
|
||||
$results = [];
|
||||
foreach ($fields as $field) {
|
||||
$results[$field] = $admin[$field] ?? null;
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
return $admin[$fields] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前登录用户id
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
function user_id()
|
||||
{
|
||||
return session('user.id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前登录用户
|
||||
*
|
||||
* @param null|array|string $fields
|
||||
* @return array|mixed|null
|
||||
*/
|
||||
function user($fields = null)
|
||||
{
|
||||
if (!$user = session('user')) {
|
||||
return null;
|
||||
}
|
||||
if ($fields === null) {
|
||||
return $user;
|
||||
}
|
||||
if (is_array($fields)) {
|
||||
$results = [];
|
||||
foreach ($fields as $field) {
|
||||
$results[$field] = $user[$field] ?? null;
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
return $user[$fields] ?? null;
|
||||
}
|
33
plugin/admin/app/middleware/AccessControl.php
Normal file
33
plugin/admin/app/middleware/AccessControl.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace plugin\admin\app\middleware;
|
||||
|
||||
use plugin\admin\app\Admin;
|
||||
use Webman\MiddlewareInterface;
|
||||
use Webman\Http\Response;
|
||||
use Webman\Http\Request;
|
||||
|
||||
class AccessControl implements MiddlewareInterface
|
||||
{
|
||||
public function process(Request $request, callable $handler): Response
|
||||
{
|
||||
$controller = $request->controller;
|
||||
$action = $request->action;
|
||||
|
||||
$code = 0;
|
||||
$msg = '';
|
||||
if (!Admin::canAccess($controller, $action, $code, $msg)) {
|
||||
$response = json(['code' => $code, 'message' => $msg, 'type' => 'error']);
|
||||
} else {
|
||||
$response = $request->method() == 'OPTIONS' ? response('') : $handler($request);
|
||||
}
|
||||
|
||||
return $response->withHeaders([
|
||||
'Access-Control-Allow-Credentials' => 'true',
|
||||
'Access-Control-Allow-Origin' => $request->header('Origin', '*'),
|
||||
'Access-Control-Allow-Methods' => '*',
|
||||
'Access-Control-Allow-Headers' => '*',
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
}
|
35
plugin/admin/app/model/Admin.php
Normal file
35
plugin/admin/app/model/Admin.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app\model;
|
||||
|
||||
|
||||
/**
|
||||
* @property integer $id ID(主键)
|
||||
* @property string $username 用户名
|
||||
* @property string $nickname 昵称
|
||||
* @property string $password 密码
|
||||
* @property string $avatar 头像
|
||||
* @property string $email 邮箱
|
||||
* @property string $mobile 手机
|
||||
* @property string $created_at 创建时间
|
||||
* @property string $updated_at 更新时间
|
||||
* @property string $roles 角色
|
||||
*/
|
||||
class Admin extends Base
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'wa_admins';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
|
||||
}
|
29
plugin/admin/app/model/AdminRole.php
Normal file
29
plugin/admin/app/model/AdminRole.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app\model;
|
||||
|
||||
/**
|
||||
* @property integer $id 主键(主键)
|
||||
* @property string $name 角色名
|
||||
* @property string $rules 规则
|
||||
* @property string $created_at 创建时间
|
||||
* @property string $updated_at 更新时间
|
||||
*/
|
||||
class AdminRole extends Base
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'wa_admin_roles';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
|
||||
}
|
36
plugin/admin/app/model/AdminRule.php
Normal file
36
plugin/admin/app/model/AdminRule.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app\model;
|
||||
|
||||
/**
|
||||
* @property integer $id 主键(主键)
|
||||
* @property string $title 标题
|
||||
* @property string $name 名字
|
||||
* @property integer $pid 上级id
|
||||
* @property string $component 组件
|
||||
* @property string $path 路径
|
||||
* @property string $icon 图标
|
||||
* @property string $created_at 创建时间
|
||||
* @property string $updated_at 更新时间
|
||||
* @property string $frame_src url
|
||||
* @property integer $hide_menu 隐藏菜单
|
||||
* @property integer $is_menu 是否菜单
|
||||
*/
|
||||
class AdminRule extends Base
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'wa_admin_rules';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
|
||||
}
|
26
plugin/admin/app/model/Base.php
Normal file
26
plugin/admin/app/model/Base.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app\model;
|
||||
|
||||
use DateTimeInterface;
|
||||
use support\Model;
|
||||
|
||||
|
||||
class Base extends Model
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $connection = 'plugin.admin.mysql';
|
||||
|
||||
/**
|
||||
* 格式化日期
|
||||
*
|
||||
* @param DateTimeInterface $date
|
||||
* @return string
|
||||
*/
|
||||
protected function serializeDate(DateTimeInterface $date)
|
||||
{
|
||||
return $date->format('Y-m-d H:i:s');
|
||||
}
|
||||
}
|
29
plugin/admin/app/model/Option.php
Normal file
29
plugin/admin/app/model/Option.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app\model;
|
||||
|
||||
|
||||
/**
|
||||
* @property integer $id (主键)
|
||||
* @property string $name 键
|
||||
* @property mixed $value 值
|
||||
* @property string $created_at 创建时间
|
||||
* @property string $updated_at 更新时间
|
||||
*/
|
||||
class Option extends Base
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'wa_options';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
}
|
35
plugin/admin/app/model/Role.php
Normal file
35
plugin/admin/app/model/Role.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app\model;
|
||||
|
||||
/**
|
||||
* @property integer $id 主键(主键)
|
||||
* @property string $name 名字
|
||||
* @property integer $pid 上级id
|
||||
* @property string $component 组件
|
||||
* @property string $path 路径
|
||||
* @property string $icon 图标
|
||||
* @property string $title 标题
|
||||
* @property string $created_at 创建时间
|
||||
* @property string $updated_at 更新时间
|
||||
* @property string $frame_src url
|
||||
* @property integer $hide_menu 隐藏菜单
|
||||
*/
|
||||
class Role extends Base
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'wa_admin_rules';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
|
||||
}
|
44
plugin/admin/app/model/User.php
Normal file
44
plugin/admin/app/model/User.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace plugin\admin\app\model;
|
||||
|
||||
/**
|
||||
* @property integer $id 主键(主键)
|
||||
* @property string $username 用户名
|
||||
* @property string $nickname 昵称
|
||||
* @property string $password 密码
|
||||
* @property string $sex 性别
|
||||
* @property string $avatar 头像
|
||||
* @property string $email 邮箱
|
||||
* @property string $mobile 手机
|
||||
* @property integer $level 等级
|
||||
* @property string $birthday 生日
|
||||
* @property integer $money 余额
|
||||
* @property integer $score 积分
|
||||
* @property string $last_time 上次登录时间
|
||||
* @property string $last_ip 上次登录ip
|
||||
* @property string $join_time 注册时间
|
||||
* @property string $join_ip 注册ip
|
||||
* @property string $token token
|
||||
* @property string $created_at 创建时间
|
||||
* @property string $updated_at 更新时间
|
||||
* @property string $roles 更新时间
|
||||
*/
|
||||
class User extends Base
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'wa_users';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
|
||||
}
|
18
plugin/admin/app/view/index/index.html
Normal file
18
plugin/admin/app/view/index/index.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="https://unpkg.byted-static.com/latest/byted/arco-config/assets/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Arco Design Pro - 开箱即用的中台前端/设计解决方案</title>
|
||||
<script type="module" crossorigin src="/assets/index.29d1f1fd.js"></script>
|
||||
<link rel="modulepreload" href="/assets/arco.f97752e2.js">
|
||||
<link rel="modulepreload" href="/assets/chart.1bb0aa8b.js">
|
||||
<link rel="modulepreload" href="/assets/vue.1b4fbe39.js">
|
||||
<link rel="stylesheet" href="/assets/index.efee5940.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
21
plugin/admin/config/app.php
Normal file
21
plugin/admin/config/app.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
use support\Request;
|
||||
|
||||
return [
|
||||
'debug' => true,
|
||||
'request_class' => Request::class,
|
||||
'controller_suffix' => 'Controller',
|
||||
];
|
19
plugin/admin/config/autoload.php
Normal file
19
plugin/admin/config/autoload.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return [
|
||||
'files' => [
|
||||
base_path() . '/plugin/admin/app/functions.php',
|
||||
]
|
||||
];
|
15
plugin/admin/config/bootstrap.php
Normal file
15
plugin/admin/config/bootstrap.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return [];
|
15
plugin/admin/config/container.php
Normal file
15
plugin/admin/config/container.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return new Webman\Container;
|
19
plugin/admin/config/database.php
Normal file
19
plugin/admin/config/database.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
return [
|
||||
'default' => 'mysql',
|
||||
'connections' => [
|
||||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '3306',
|
||||
'database' => 'admin',
|
||||
'username' => 'root',
|
||||
'password' => 'qwe904799077',
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_general_ci',
|
||||
'prefix' => '',
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
],
|
||||
],
|
||||
];
|
15
plugin/admin/config/dependence.php
Normal file
15
plugin/admin/config/dependence.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return [];
|
5
plugin/admin/config/event.php
Normal file
5
plugin/admin/config/event.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
];
|
19
plugin/admin/config/exception.php
Normal file
19
plugin/admin/config/exception.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
use plugin\admin\app\exception\Handler;
|
||||
|
||||
return [
|
||||
'' => Handler::class,
|
||||
];
|
32
plugin/admin/config/log.php
Normal file
32
plugin/admin/config/log.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return [
|
||||
'default' => [
|
||||
'handlers' => [
|
||||
[
|
||||
'class' => Monolog\Handler\RotatingFileHandler::class,
|
||||
'constructor' => [
|
||||
runtime_path() . '/logs/webman.log',
|
||||
7, //$maxFiles
|
||||
Monolog\Logger::DEBUG,
|
||||
],
|
||||
'formatter' => [
|
||||
'class' => Monolog\Formatter\LineFormatter::class,
|
||||
'constructor' => [null, 'Y-m-d H:i:s', true],
|
||||
],
|
||||
]
|
||||
],
|
||||
],
|
||||
];
|
21
plugin/admin/config/middleware.php
Normal file
21
plugin/admin/config/middleware.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
use plugin\admin\app\middleware\AccessControl;
|
||||
|
||||
return [
|
||||
'' => [
|
||||
AccessControl::class,
|
||||
]
|
||||
];
|
37
plugin/admin/config/process.php
Normal file
37
plugin/admin/config/process.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return [];
|
||||
return [
|
||||
// File update detection and automatic reload
|
||||
'monitor' => [
|
||||
'handler' => process\Monitor::class,
|
||||
'reloadable' => false,
|
||||
'constructor' => [
|
||||
// Monitor these directories
|
||||
'monitor_dir' => [
|
||||
app_path(),
|
||||
config_path(),
|
||||
base_path() . '/process',
|
||||
base_path() . '/support',
|
||||
base_path() . '/resource',
|
||||
base_path() . '/.env',
|
||||
],
|
||||
// Files with these suffixes will be monitored
|
||||
'monitor_extensions' => [
|
||||
'php', 'html', 'htm', 'env'
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
22
plugin/admin/config/redis.php
Normal file
22
plugin/admin/config/redis.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return [
|
||||
'default' => [
|
||||
'host' => '127.0.0.1',
|
||||
'password' => null,
|
||||
'port' => 6379,
|
||||
'database' => 0,
|
||||
],
|
||||
];
|
19
plugin/admin/config/route.php
Normal file
19
plugin/admin/config/route.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
use Webman\Route;
|
||||
use plugin\admin\app\controller\common\AccountController;
|
||||
|
||||
Route::any('/app/admin/common/account/captcha/{type}', [AccountController::class, 'captcha']);
|
||||
|
30
plugin/admin/config/server.php
Normal file
30
plugin/admin/config/server.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return [
|
||||
'listen' => 'http://0.0.0.0:8080',
|
||||
'transport' => 'tcp',
|
||||
'context' => [],
|
||||
'name' => 'webman',
|
||||
'count' => cpu_count() * 2,
|
||||
'user' => '',
|
||||
'group' => '',
|
||||
'reusePort' => false,
|
||||
'event_loop' => '',
|
||||
'pid_file' => runtime_path() . '/webman.pid',
|
||||
'status_file' => runtime_path() . '/webman.status',
|
||||
'stdout_file' => runtime_path() . '/logs/stdout.log',
|
||||
'log_file' => runtime_path() . '/logs/workerman.log',
|
||||
'max_package_size' => 10 * 1024 * 1024
|
||||
];
|
42
plugin/admin/config/session.php
Normal file
42
plugin/admin/config/session.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
return [
|
||||
|
||||
'type' => 'file', // or redis or redis_cluster
|
||||
|
||||
'handler' => Webman\FileSessionHandler::class,
|
||||
|
||||
'config' => [
|
||||
'file' => [
|
||||
'save_path' => runtime_path() . '/sessions',
|
||||
],
|
||||
'redis' => [
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 6379,
|
||||
'auth' => '',
|
||||
'timeout' => 2,
|
||||
'database' => '',
|
||||
'prefix' => 'redis_session_',
|
||||
],
|
||||
'redis_cluster' => [
|
||||
'host' => ['127.0.0.1:7000', '127.0.0.1:7001', '127.0.0.1:7001'],
|
||||
'timeout' => 2,
|
||||
'auth' => '',
|
||||
'prefix' => 'redis_session_',
|
||||
]
|
||||
],
|
||||
|
||||
'session_name' => 'PHPSID',
|
||||
];
|
23
plugin/admin/config/static.php
Normal file
23
plugin/admin/config/static.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
/**
|
||||
* Static file settings
|
||||
*/
|
||||
return [
|
||||
'enable' => true,
|
||||
'middleware' => [ // Static file Middleware
|
||||
//app\middleware\StaticFile::class,
|
||||
],
|
||||
];
|
25
plugin/admin/config/translation.php
Normal file
25
plugin/admin/config/translation.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
/**
|
||||
* Multilingual configuration
|
||||
*/
|
||||
return [
|
||||
// Default language
|
||||
'locale' => 'zh_CN',
|
||||
// Fallback language
|
||||
'fallback_locale' => ['zh_CN', 'en'],
|
||||
// Folder where language files are stored
|
||||
'path' => base_path() . '/resource/translations',
|
||||
];
|
22
plugin/admin/config/view.php
Normal file
22
plugin/admin/config/view.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
use support\view\Raw;
|
||||
use support\view\Twig;
|
||||
use support\view\Blade;
|
||||
use support\view\ThinkPHP;
|
||||
|
||||
return [
|
||||
'handler' => Raw::class
|
||||
];
|
BIN
plugin/admin/public/avatar.png
Normal file
BIN
plugin/admin/public/avatar.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
183
plugin/admin/webman-admin.sql
Normal file
183
plugin/admin/webman-admin.sql
Normal file
File diff suppressed because one or more lines are too long
195
process/Monitor.php
Normal file
195
process/Monitor.php
Normal file
@ -0,0 +1,195 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace process;
|
||||
|
||||
use Workerman\Timer;
|
||||
use Workerman\Worker;
|
||||
|
||||
/**
|
||||
* Class FileMonitor
|
||||
* @package process
|
||||
*/
|
||||
class Monitor
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $_paths = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $_extensions = [];
|
||||
|
||||
/**
|
||||
* FileMonitor constructor.
|
||||
* @param $monitor_dir
|
||||
* @param $monitor_extensions
|
||||
* @param $memory_limit
|
||||
*/
|
||||
public function __construct($monitor_dir, $monitor_extensions, $memory_limit = null)
|
||||
{
|
||||
$this->_paths = (array)$monitor_dir;
|
||||
$this->_extensions = $monitor_extensions;
|
||||
if (!Worker::getAllWorkers()) {
|
||||
return;
|
||||
}
|
||||
$disable_functions = explode(',', ini_get('disable_functions'));
|
||||
if (in_array('exec', $disable_functions, true)) {
|
||||
echo "\nMonitor file change turned off because exec() has been disabled by disable_functions setting in " . PHP_CONFIG_FILE_PATH . "/php.ini\n";
|
||||
} else {
|
||||
if (!Worker::$daemonize) {
|
||||
Timer::add(1, function () {
|
||||
$this->checkAllFilesChange();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$memory_limit = $this->getMemoryLimit($memory_limit);
|
||||
if ($memory_limit && DIRECTORY_SEPARATOR === '/') {
|
||||
Timer::add(60, [$this, 'checkMemory'], [$memory_limit]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $monitor_dir
|
||||
*/
|
||||
public function checkFilesChange($monitor_dir)
|
||||
{
|
||||
static $last_mtime, $too_many_files_check;
|
||||
if (!$last_mtime) {
|
||||
$last_mtime = time();
|
||||
}
|
||||
clearstatcache();
|
||||
if (!is_dir($monitor_dir)) {
|
||||
if (!is_file($monitor_dir)) {
|
||||
return;
|
||||
}
|
||||
$iterator = [new \SplFileInfo($monitor_dir)];
|
||||
} else {
|
||||
// recursive traversal directory
|
||||
$dir_iterator = new \RecursiveDirectoryIterator($monitor_dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS);
|
||||
$iterator = new \RecursiveIteratorIterator($dir_iterator);
|
||||
}
|
||||
$count = 0;
|
||||
foreach ($iterator as $file) {
|
||||
$count ++;
|
||||
/** var SplFileInfo $file */
|
||||
if (is_dir($file)) {
|
||||
continue;
|
||||
}
|
||||
// check mtime
|
||||
if ($last_mtime < $file->getMTime() && in_array($file->getExtension(), $this->_extensions, true)) {
|
||||
$var = 0;
|
||||
exec('"'.PHP_BINARY . '" -l ' . $file, $out, $var);
|
||||
if ($var) {
|
||||
$last_mtime = $file->getMTime();
|
||||
continue;
|
||||
}
|
||||
$last_mtime = $file->getMTime();
|
||||
echo $file . " update and reload\n";
|
||||
// send SIGUSR1 signal to master process for reload
|
||||
if (DIRECTORY_SEPARATOR === '/') {
|
||||
posix_kill(posix_getppid(), SIGUSR1);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$too_many_files_check && $count > 1000) {
|
||||
echo "Monitor: There are too many files ($count files) in $monitor_dir which makes file monitoring very slow\n";
|
||||
$too_many_files_check = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function checkAllFilesChange()
|
||||
{
|
||||
foreach ($this->_paths as $path) {
|
||||
if ($this->checkFilesChange($path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $memory_limit
|
||||
* @return void
|
||||
*/
|
||||
public function checkMemory($memory_limit)
|
||||
{
|
||||
$ppid = posix_getppid();
|
||||
$children_file = "/proc/$ppid/task/$ppid/children";
|
||||
if (!is_file($children_file) || !($children = file_get_contents($children_file))) {
|
||||
return;
|
||||
}
|
||||
foreach (explode(' ', $children) as $pid) {
|
||||
$pid = (int)$pid;
|
||||
$status_file = "/proc/$pid/status";
|
||||
if (!is_file($status_file) || !($status = file_get_contents($status_file))) {
|
||||
continue;
|
||||
}
|
||||
$mem = 0;
|
||||
if (preg_match('/VmRSS\s*?:\s*?(\d+?)\s*?kB/', $status, $match)) {
|
||||
$mem = $match[1];
|
||||
}
|
||||
$mem = (int)($mem / 1024);
|
||||
if ($mem >= $memory_limit) {
|
||||
posix_kill($pid, SIGINT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get memory limit
|
||||
* @return float
|
||||
*/
|
||||
protected function getMemoryLimit($memory_limit)
|
||||
{
|
||||
if ($memory_limit === 0) {
|
||||
return 0;
|
||||
}
|
||||
$use_php_ini = false;
|
||||
if (!$memory_limit) {
|
||||
$memory_limit = ini_get('memory_limit');
|
||||
$use_php_ini = true;
|
||||
}
|
||||
|
||||
if ($memory_limit == -1) {
|
||||
return 0;
|
||||
}
|
||||
$unit = $memory_limit[strlen($memory_limit) - 1];
|
||||
if ($unit == 'G') {
|
||||
$memory_limit = 1024 * (int)$memory_limit;
|
||||
} else if ($unit == 'M') {
|
||||
$memory_limit = (int)$memory_limit;
|
||||
} else if ($unit == 'K') {
|
||||
$memory_limit = (int)($memory_limit / 1024);
|
||||
} else {
|
||||
$memory_limit = (int)($memory_limit / (1024 * 1024));
|
||||
}
|
||||
if ($memory_limit < 30) {
|
||||
$memory_limit = 30;
|
||||
}
|
||||
if ($use_php_ini) {
|
||||
$memory_limit = (int)(0.8 * $memory_limit);
|
||||
}
|
||||
return $memory_limit;
|
||||
}
|
||||
}
|
12
public/404.html
Normal file
12
public/404.html
Normal file
@ -0,0 +1,12 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>404 Not Found - webman</title>
|
||||
</head>
|
||||
<body>
|
||||
<center>
|
||||
<h1>404 Not Found</h1>
|
||||
</center>
|
||||
<hr>
|
||||
<center><a href="https://www.workerman.net">webman</a></center>
|
||||
</body>
|
||||
</html>
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
4
runtime/.gitignore
vendored
Normal file
4
runtime/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
*
|
||||
!logs
|
||||
!views
|
||||
!.gitignore
|
2
runtime/logs/.gitignore
vendored
Normal file
2
runtime/logs/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
2
runtime/views/.gitignore
vendored
Normal file
2
runtime/views/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
4
start.php
Executable file
4
start.php
Executable file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
support\App::run();
|
55
support/Plugin.php
Normal file
55
support/Plugin.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace support;
|
||||
|
||||
class Plugin
|
||||
{
|
||||
public static function install($event)
|
||||
{
|
||||
static::findHepler();
|
||||
$operation = $event->getOperation();
|
||||
$autoload = method_exists($operation, 'getPackage') ? $operation->getPackage()->getAutoload() : $operation->getTargetPackage()->getAutoload();
|
||||
if (!isset($autoload['psr-4'])) {
|
||||
return;
|
||||
}
|
||||
$namespace = key($autoload['psr-4']);
|
||||
$install_function = "\\{$namespace}Install::install";
|
||||
$plugin_const = "\\{$namespace}Install::WEBMAN_PLUGIN";
|
||||
if (defined($plugin_const) && is_callable($install_function)) {
|
||||
$install_function();
|
||||
}
|
||||
}
|
||||
|
||||
public static function update($event)
|
||||
{
|
||||
static::install($event);
|
||||
}
|
||||
|
||||
public static function uninstall($event)
|
||||
{
|
||||
static::findHepler();
|
||||
$autoload = $event->getOperation()->getPackage()->getAutoload();
|
||||
if (!isset($autoload['psr-4'])) {
|
||||
return;
|
||||
}
|
||||
$namespace = key($autoload['psr-4']);
|
||||
$uninstall_function = "\\{$namespace}Install::uninstall";
|
||||
$plugin_const = "\\{$namespace}Install::WEBMAN_PLUGIN";
|
||||
if (defined($plugin_const) && is_callable($uninstall_function)) {
|
||||
$uninstall_function();
|
||||
}
|
||||
}
|
||||
|
||||
protected static function findHepler()
|
||||
{
|
||||
// Plugin.php in vendor
|
||||
$file = __DIR__ . '/../../../../../support/helpers.php';
|
||||
if (is_file($file)) {
|
||||
require_once $file;
|
||||
return;
|
||||
}
|
||||
// Plugin.php in webman
|
||||
require_once __DIR__ . '/helpers.php';
|
||||
}
|
||||
|
||||
}
|
24
support/Request.php
Normal file
24
support/Request.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace support;
|
||||
|
||||
/**
|
||||
* Class Request
|
||||
* @package support
|
||||
*/
|
||||
class Request extends \Webman\Http\Request
|
||||
{
|
||||
|
||||
}
|
24
support/Response.php
Normal file
24
support/Response.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
namespace support;
|
||||
|
||||
/**
|
||||
* Class Response
|
||||
* @package support
|
||||
*/
|
||||
class Response extends \Webman\Http\Response
|
||||
{
|
||||
|
||||
}
|
132
support/bootstrap.php
Normal file
132
support/bootstrap.php
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
use support\Log;
|
||||
use Webman\Bootstrap;
|
||||
use Webman\Config;
|
||||
use Webman\Route;
|
||||
use Webman\Middleware;
|
||||
use Webman\Util;
|
||||
|
||||
$worker = $worker ?? null;
|
||||
|
||||
if ($timezone = config('app.default_timezone')) {
|
||||
date_default_timezone_set($timezone);
|
||||
}
|
||||
|
||||
set_error_handler(function ($level, $message, $file = '', $line = 0) {
|
||||
if (error_reporting() & $level) {
|
||||
throw new ErrorException($message, 0, $level, $file, $line);
|
||||
}
|
||||
});
|
||||
|
||||
if ($worker) {
|
||||
register_shutdown_function(function ($start_time) {
|
||||
if (time() - $start_time <= 1) {
|
||||
sleep(1);
|
||||
}
|
||||
}, time());
|
||||
}
|
||||
|
||||
if (class_exists('Dotenv\Dotenv') && file_exists(base_path() . '/.env')) {
|
||||
if (method_exists('Dotenv\Dotenv', 'createUnsafeImmutable')) {
|
||||
Dotenv::createUnsafeImmutable(base_path())->load();
|
||||
} else {
|
||||
Dotenv::createMutable(base_path())->load();
|
||||
}
|
||||
}
|
||||
|
||||
support\App::loadAllConfig(['route']);
|
||||
|
||||
foreach (config('autoload.files', []) as $file) {
|
||||
include_once $file;
|
||||
}
|
||||
foreach (config('plugin', []) as $firm => $projects) {
|
||||
foreach ($projects as $name => $project) {
|
||||
if (!is_array($project)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($project['autoload']['files'] ?? [] as $file) {
|
||||
include_once $file;
|
||||
}
|
||||
}
|
||||
foreach ($projects['autoload']['files'] ?? [] as $file) {
|
||||
include_once $file;
|
||||
}
|
||||
}
|
||||
|
||||
Middleware::load(config('middleware', []), '');
|
||||
foreach (config('plugin', []) as $firm => $projects) {
|
||||
foreach ($projects as $name => $project) {
|
||||
if (!is_array($project) || $name === 'static') {
|
||||
continue;
|
||||
}
|
||||
Middleware::load($project['middleware'] ?? [], '');
|
||||
}
|
||||
Middleware::load($projects['middleware'] ?? [], $firm);
|
||||
if ($static_middlewares = config("plugin.$firm.static.middleware")) {
|
||||
Middleware::load(['__static__' => $static_middlewares], $firm);
|
||||
}
|
||||
}
|
||||
Middleware::load(['__static__' => config('static.middleware', [])], '');
|
||||
|
||||
foreach (config('bootstrap', []) as $class_name) {
|
||||
if (!class_exists($class_name)) {
|
||||
$log = "Warning: Class $class_name setting in config/bootstrap.php not found\r\n";
|
||||
echo $log;
|
||||
Log::error($log);
|
||||
continue;
|
||||
}
|
||||
/** @var Bootstrap $class_name */
|
||||
$class_name::start($worker);
|
||||
}
|
||||
|
||||
foreach (config('plugin', []) as $firm => $projects) {
|
||||
foreach ($projects as $name => $project) {
|
||||
if (!is_array($project)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($project['bootstrap'] ?? [] as $class_name) {
|
||||
if (!class_exists($class_name)) {
|
||||
$log = "Warning: Class $class_name setting in config/plugin/$firm/$name/bootstrap.php not found\r\n";
|
||||
echo $log;
|
||||
Log::error($log);
|
||||
continue;
|
||||
}
|
||||
/** @var Bootstrap $class_name */
|
||||
$class_name::start($worker);
|
||||
}
|
||||
}
|
||||
foreach ($projects['bootstrap'] ?? [] as $class_name) {
|
||||
if (!class_exists($class_name)) {
|
||||
$log = "Warning: Class $class_name setting in plugin/$firm/config/bootstrap.php not found\r\n";
|
||||
echo $log;
|
||||
Log::error($log);
|
||||
continue;
|
||||
}
|
||||
/** @var Bootstrap $class_name */
|
||||
$class_name::start($worker);
|
||||
}
|
||||
}
|
||||
|
||||
$directory = base_path() . '/plugin';
|
||||
$paths = [config_path()];
|
||||
foreach (Util::scanDir($directory) as $path) {
|
||||
if (is_dir($path = "$path/config")) {
|
||||
$paths[] = $path;
|
||||
}
|
||||
}
|
||||
Route::load($paths);
|
||||
|
482
support/helpers.php
Normal file
482
support/helpers.php
Normal file
@ -0,0 +1,482 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of webman.
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @author walkor<walkor@workerman.net>
|
||||
* @copyright walkor<walkor@workerman.net>
|
||||
* @link http://www.workerman.net/
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use support\Translation;
|
||||
use support\Container;
|
||||
use support\view\Raw;
|
||||
use support\view\Blade;
|
||||
use support\view\ThinkPHP;
|
||||
use support\view\Twig;
|
||||
use Workerman\Worker;
|
||||
use Webman\App;
|
||||
use Webman\Config;
|
||||
use Webman\Route;
|
||||
|
||||
// Phar support.
|
||||
if (\is_phar()) {
|
||||
\define('BASE_PATH', dirname(__DIR__));
|
||||
} else {
|
||||
\define('BASE_PATH', realpath(__DIR__ . '/../'));
|
||||
}
|
||||
\define('WEBMAN_VERSION', '1.4');
|
||||
|
||||
/**
|
||||
* @param $return_phar
|
||||
* @return false|string
|
||||
*/
|
||||
function base_path(bool $return_phar = true)
|
||||
{
|
||||
static $real_path = '';
|
||||
if (!$real_path) {
|
||||
$real_path = \is_phar() ? \dirname(Phar::running(false)) : BASE_PATH;
|
||||
}
|
||||
return $return_phar ? BASE_PATH : $real_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function app_path()
|
||||
{
|
||||
return BASE_PATH . DIRECTORY_SEPARATOR . 'app';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function public_path()
|
||||
{
|
||||
static $path = '';
|
||||
if (!$path) {
|
||||
$path = \config('app.public_path', BASE_PATH . DIRECTORY_SEPARATOR . 'public');
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function config_path()
|
||||
{
|
||||
return BASE_PATH . DIRECTORY_SEPARATOR . 'config';
|
||||
}
|
||||
|
||||
/**
|
||||
* Phar support.
|
||||
* Compatible with the 'realpath' function in the phar file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function runtime_path()
|
||||
{
|
||||
static $path = '';
|
||||
if (!$path) {
|
||||
$path = \config('app.runtime_path', BASE_PATH . DIRECTORY_SEPARATOR . 'runtime');
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $status
|
||||
* @param array $headers
|
||||
* @param string $body
|
||||
* @return Response
|
||||
*/
|
||||
function response($body = '', $status = 200, $headers = [])
|
||||
{
|
||||
return new Response($status, $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
* @param int $options
|
||||
* @return Response
|
||||
*/
|
||||
function json($data, $options = JSON_UNESCAPED_UNICODE)
|
||||
{
|
||||
return new Response(200, ['Content-Type' => 'application/json'], \json_encode($data, $options));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $xml
|
||||
* @return Response
|
||||
*/
|
||||
function xml($xml)
|
||||
{
|
||||
if ($xml instanceof SimpleXMLElement) {
|
||||
$xml = $xml->asXML();
|
||||
}
|
||||
return new Response(200, ['Content-Type' => 'text/xml'], $xml);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
* @param string $callback_name
|
||||
* @return Response
|
||||
*/
|
||||
function jsonp($data, $callback_name = 'callback')
|
||||
{
|
||||
if (!\is_scalar($data) && null !== $data) {
|
||||
$data = \json_encode($data);
|
||||
}
|
||||
return new Response(200, [], "$callback_name($data)");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $location
|
||||
* @param int $status
|
||||
* @param array $headers
|
||||
* @return Response
|
||||
*/
|
||||
function redirect(string $location, int $status = 302, array $headers = [])
|
||||
{
|
||||
$response = new Response($status, ['Location' => $location]);
|
||||
if (!empty($headers)) {
|
||||
$response->withHeaders($headers);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $template
|
||||
* @param array $vars
|
||||
* @param null $app
|
||||
* @return Response
|
||||
*/
|
||||
function view(string $template, array $vars = [], string $app = null)
|
||||
{
|
||||
$request = \request();
|
||||
$plugin = $request->plugin ?? '';
|
||||
$handler = \config($plugin ? "plugin.$plugin.view.handler" : 'view.handler');
|
||||
return new Response(200, [], $handler::render($template, $vars, $app));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $template
|
||||
* @param array $vars
|
||||
* @param string|null $app
|
||||
* @return Response
|
||||
* @throws Throwable
|
||||
*/
|
||||
function raw_view(string $template, array $vars = [], string $app = null)
|
||||
{
|
||||
return new Response(200, [], Raw::render($template, $vars, $app));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $template
|
||||
* @param array $vars
|
||||
* @param string|null $app
|
||||
* @return Response
|
||||
*/
|
||||
function blade_view(string $template, array $vars = [], string $app = null)
|
||||
{
|
||||
return new Response(200, [], Blade::render($template, $vars, $app));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $template
|
||||
* @param array $vars
|
||||
* @param string|null $app
|
||||
* @return Response
|
||||
*/
|
||||
function think_view(string $template, array $vars = [], string $app = null)
|
||||
{
|
||||
return new Response(200, [], ThinkPHP::render($template, $vars, $app));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $template
|
||||
* @param array $vars
|
||||
* @param string|null $app
|
||||
* @return Response
|
||||
*/
|
||||
function twig_view(string $template, array $vars = [], string $app = null)
|
||||
{
|
||||
return new Response(200, [], Twig::render($template, $vars, $app));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Webman\Http\Request|null
|
||||
*/
|
||||
function request()
|
||||
{
|
||||
return App::request();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $key
|
||||
* @param $default
|
||||
* @return array|mixed|null
|
||||
*/
|
||||
function config(string $key = null, $default = null)
|
||||
{
|
||||
return Config::get($key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param ...$parameters
|
||||
* @return string
|
||||
*/
|
||||
function route(string $name, ...$parameters)
|
||||
{
|
||||
$route = Route::getByName($name);
|
||||
if (!$route) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!$parameters) {
|
||||
return $route->url();
|
||||
}
|
||||
|
||||
if (\is_array(\current($parameters))) {
|
||||
$parameters = \current($parameters);
|
||||
}
|
||||
|
||||
return $route->url($parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $key
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
function session($key = null, $default = null)
|
||||
{
|
||||
$session = \request()->session();
|
||||
if (null === $key) {
|
||||
return $session;
|
||||
}
|
||||
if (\is_array($key)) {
|
||||
$session->put($key);
|
||||
return null;
|
||||
}
|
||||
if (\strpos($key, '.')) {
|
||||
$key_array = \explode('.', $key);
|
||||
$value = $session->all();
|
||||
foreach ($key_array as $index) {
|
||||
if (!isset($value[$index])) {
|
||||
return $default;
|
||||
}
|
||||
$value = $value[$index];
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
return $session->get($key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param array $parameters
|
||||
* @param string|null $domain
|
||||
* @param string|null $locale
|
||||
* @return string
|
||||
*/
|
||||
function trans(string $id, array $parameters = [], string $domain = null, string $locale = null)
|
||||
{
|
||||
$res = Translation::trans($id, $parameters, $domain, $locale);
|
||||
return $res === '' ? $id : $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|string $locale
|
||||
* @return string
|
||||
*/
|
||||
function locale(string $locale = null)
|
||||
{
|
||||
if (!$locale) {
|
||||
return Translation::getLocale();
|
||||
}
|
||||
Translation::setLocale($locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* 404 not found
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
function not_found()
|
||||
{
|
||||
return new Response(404, [], \file_get_contents(public_path() . '/404.html'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy dir.
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $dest
|
||||
* @param bool $overwrite
|
||||
* @return void
|
||||
*/
|
||||
function copy_dir(string $source, string $dest, bool $overwrite = false)
|
||||
{
|
||||
if (\is_dir($source)) {
|
||||
if (!is_dir($dest)) {
|
||||
\mkdir($dest);
|
||||
}
|
||||
$files = \scandir($source);
|
||||
foreach ($files as $file) {
|
||||
if ($file !== "." && $file !== "..") {
|
||||
\copy_dir("$source/$file", "$dest/$file");
|
||||
}
|
||||
}
|
||||
} else if (\file_exists($source) && ($overwrite || !\file_exists($dest))) {
|
||||
\copy($source, $dest);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove dir.
|
||||
*
|
||||
* @param string $dir
|
||||
* @return bool
|
||||
*/
|
||||
function remove_dir(string $dir)
|
||||
{
|
||||
if (\is_link($dir) || \is_file($dir)) {
|
||||
return \unlink($dir);
|
||||
}
|
||||
$files = \array_diff(\scandir($dir), array('.', '..'));
|
||||
foreach ($files as $file) {
|
||||
(\is_dir("$dir/$file") && !\is_link($dir)) ? \remove_dir("$dir/$file") : \unlink("$dir/$file");
|
||||
}
|
||||
return \rmdir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $worker
|
||||
* @param $class
|
||||
*/
|
||||
function worker_bind($worker, $class)
|
||||
{
|
||||
$callback_map = [
|
||||
'onConnect',
|
||||
'onMessage',
|
||||
'onClose',
|
||||
'onError',
|
||||
'onBufferFull',
|
||||
'onBufferDrain',
|
||||
'onWorkerStop',
|
||||
'onWebSocketConnect'
|
||||
];
|
||||
foreach ($callback_map as $name) {
|
||||
if (\method_exists($class, $name)) {
|
||||
$worker->$name = [$class, $name];
|
||||
}
|
||||
}
|
||||
if (\method_exists($class, 'onWorkerStart')) {
|
||||
\call_user_func([$class, 'onWorkerStart'], $worker);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $process_name
|
||||
* @param $config
|
||||
* @return void
|
||||
*/
|
||||
function worker_start($process_name, $config)
|
||||
{
|
||||
$worker = new Worker($config['listen'] ?? null, $config['context'] ?? []);
|
||||
$property_map = [
|
||||
'count',
|
||||
'user',
|
||||
'group',
|
||||
'reloadable',
|
||||
'reusePort',
|
||||
'transport',
|
||||
'protocol',
|
||||
];
|
||||
$worker->name = $process_name;
|
||||
foreach ($property_map as $property) {
|
||||
if (isset($config[$property])) {
|
||||
$worker->$property = $config[$property];
|
||||
}
|
||||
}
|
||||
|
||||
$worker->onWorkerStart = function ($worker) use ($config) {
|
||||
require_once \base_path() . '/support/bootstrap.php';
|
||||
|
||||
foreach ($config['services'] ?? [] as $server) {
|
||||
if (!\class_exists($server['handler'])) {
|
||||
echo "process error: class {$server['handler']} not exists\r\n";
|
||||
continue;
|
||||
}
|
||||
$listen = new Worker($server['listen'] ?? null, $server['context'] ?? []);
|
||||
if (isset($server['listen'])) {
|
||||
echo "listen: {$server['listen']}\n";
|
||||
}
|
||||
$instance = Container::make($server['handler'], $server['constructor'] ?? []);
|
||||
\worker_bind($listen, $instance);
|
||||
$listen->listen();
|
||||
}
|
||||
|
||||
if (isset($config['handler'])) {
|
||||
if (!\class_exists($config['handler'])) {
|
||||
echo "process error: class {$config['handler']} not exists\r\n";
|
||||
return;
|
||||
}
|
||||
|
||||
$instance = Container::make($config['handler'], $config['constructor'] ?? []);
|
||||
\worker_bind($worker, $instance);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Phar support.
|
||||
* Compatible with the 'realpath' function in the phar file.
|
||||
*
|
||||
* @param string $file_path
|
||||
* @return string
|
||||
*/
|
||||
function get_realpath(string $file_path): string
|
||||
{
|
||||
if (\strpos($file_path, 'phar://') === 0) {
|
||||
return $file_path;
|
||||
} else {
|
||||
return \realpath($file_path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
function is_phar()
|
||||
{
|
||||
return \class_exists(\Phar::class, false) && Phar::running();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
function cpu_count()
|
||||
{
|
||||
// Windows does not support the number of processes setting.
|
||||
if (\DIRECTORY_SEPARATOR === '\\') {
|
||||
return 1;
|
||||
}
|
||||
$count = 4;
|
||||
if (\is_callable('shell_exec')) {
|
||||
if (\strtolower(PHP_OS) === 'darwin') {
|
||||
$count = (int)\shell_exec('sysctl -n machdep.cpu.core_count');
|
||||
} else {
|
||||
$count = (int)\shell_exec('nproc');
|
||||
}
|
||||
}
|
||||
return $count > 0 ? $count : 4;
|
||||
}
|
3
windows.bat
Normal file
3
windows.bat
Normal file
@ -0,0 +1,3 @@
|
||||
CHCP 65001
|
||||
php windows.php
|
||||
pause
|
116
windows.php
Normal file
116
windows.php
Normal file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/**
|
||||
* Start file for windows
|
||||
*/
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
use process\Monitor;
|
||||
use support\App;
|
||||
use Dotenv\Dotenv;
|
||||
use Workerman\Worker;
|
||||
|
||||
ini_set('display_errors', 'on');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
if (class_exists('Dotenv\Dotenv') && file_exists(base_path() . '/.env')) {
|
||||
if (method_exists('Dotenv\Dotenv', 'createUnsafeImmutable')) {
|
||||
Dotenv::createUnsafeImmutable(base_path())->load();
|
||||
} else {
|
||||
Dotenv::createMutable(base_path())->load();
|
||||
}
|
||||
}
|
||||
|
||||
App::loadAllConfig(['route']);
|
||||
|
||||
$error_reporting = config('app.error_reporting');
|
||||
if (isset($error_reporting)) {
|
||||
error_reporting($error_reporting);
|
||||
}
|
||||
|
||||
$runtime_process_path = runtime_path() . DIRECTORY_SEPARATOR . '/windows';
|
||||
if (!is_dir($runtime_process_path)) {
|
||||
mkdir($runtime_process_path);
|
||||
}
|
||||
$process_files = [
|
||||
__DIR__ . DIRECTORY_SEPARATOR . 'start.php'
|
||||
];
|
||||
foreach (config('process', []) as $process_name => $config) {
|
||||
$process_files[] = write_process_file($runtime_process_path, $process_name, '');
|
||||
}
|
||||
|
||||
foreach (config('plugin', []) as $firm => $projects) {
|
||||
foreach ($projects as $name => $project) {
|
||||
if (!is_array($project)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($project['process'] ?? [] as $process_name => $config) {
|
||||
$process_files[] = write_process_file($runtime_process_path, $process_name, "$firm.$name");
|
||||
}
|
||||
}
|
||||
foreach ($projects['process'] ?? [] as $process_name => $config) {
|
||||
$process_files[] = write_process_file($runtime_process_path, $process_name, $firm);
|
||||
}
|
||||
}
|
||||
|
||||
function write_process_file($runtime_process_path, $process_name, $firm)
|
||||
{
|
||||
$process_param = $firm ? "plugin.$firm.$process_name" : $process_name;
|
||||
$config_param = $firm ? "config('plugin.$firm.process')['$process_name']" : "config('process')['$process_name']";
|
||||
$file_content = <<<EOF
|
||||
<?php
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
use Workerman\Worker;
|
||||
use Webman\Config;
|
||||
use support\App;
|
||||
|
||||
ini_set('display_errors', 'on');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
if (is_callable('opcache_reset')) {
|
||||
opcache_reset();
|
||||
}
|
||||
|
||||
App::loadAllConfig(['route']);
|
||||
|
||||
worker_start('$process_param', $config_param);
|
||||
|
||||
if (DIRECTORY_SEPARATOR != "/") {
|
||||
Worker::\$logFile = config('server')['log_file'] ?? Worker::\$logFile;
|
||||
}
|
||||
|
||||
Worker::runAll();
|
||||
|
||||
EOF;
|
||||
$process_file = $runtime_process_path . DIRECTORY_SEPARATOR . "start_$process_param.php";
|
||||
file_put_contents($process_file, $file_content);
|
||||
return $process_file;
|
||||
}
|
||||
|
||||
if ($monitor_config = config('process.monitor.constructor')) {
|
||||
$monitor = new Monitor(...array_values($monitor_config));
|
||||
}
|
||||
|
||||
function popen_processes($process_files)
|
||||
{
|
||||
$cmd = "php " . implode(' ', $process_files);
|
||||
$descriptorspec = [STDIN, STDOUT, STDOUT];
|
||||
$resource = proc_open($cmd, $descriptorspec, $pipes);
|
||||
if (!$resource) {
|
||||
exit("Can not execute $cmd\r\n");
|
||||
}
|
||||
return $resource;
|
||||
}
|
||||
|
||||
$resource = popen_processes($process_files);
|
||||
echo "\r\n";
|
||||
while (1) {
|
||||
sleep(1);
|
||||
if (!empty($monitor) && $monitor->checkAllFilesChange()) {
|
||||
$status = proc_get_status($resource);
|
||||
$pid = $status['pid'];
|
||||
shell_exec("taskkill /F /T /PID $pid");
|
||||
proc_close($resource);
|
||||
$resource = popen_processes($process_files);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user