This commit is contained in:
root 2022-09-07 10:24:59 +08:00
parent 6a0a2468f7
commit 53e3a79412
90 changed files with 8720 additions and 1 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
/.idea
/.vscode
/vendor
*.log
.env
/tests/tmp
/tests/.phpunit.result.cache

21
LICENSE Normal file
View 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.

View File

@ -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
![image](https://user-images.githubusercontent.com/6073368/96447814-120fc980-1245-11eb-938d-6ea408716c72.png)
## LICENSE
MIT

24
app/controller/Index.php Normal file
View 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
View File

@ -0,0 +1,4 @@
<?php
/**
* Here is your custom functions.
*/

View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

26
config/app.php Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,5 @@
<?php
return [
];

17
config/exception.php Normal file
View 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
View 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
View 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 [];

View File

@ -0,0 +1,4 @@
<?php
return [
'enable' => true,
];

View 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,
];

View File

@ -0,0 +1,7 @@
<?php
use Webman\Event\EventListCommand;
return [
EventListCommand::class
];

37
config/process.php Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
}
}

View 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']);
}
}

View 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']);
}
}

View 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');
}
}

View 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);
}
}

View 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);
}
}

View 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';
}
}
}

View 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);
}
}
}

View 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;
}
}

View 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);
}
}
}

View 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];
}
}

View 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;
}
}

View 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;
}
}

View 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);
}
}

View 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;
}

View 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' => '*',
]);
}
}

View 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';
}

View 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';
}

View 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';
}

View 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');
}
}

View 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';
}

View 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';
}

View 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';
}

View 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>

View 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',
];

View 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',
]
];

View 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 [];

View 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;

View 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,
],
],
];

View 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 [];

View File

@ -0,0 +1,5 @@
<?php
return [
];

View 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,
];

View 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],
],
]
],
],
];

View 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,
]
];

View 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'
]
]
]
];

View 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,
],
];

View 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']);

View 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
];

View 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',
];

View 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,
],
];

View 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',
];

View 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
];

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

195
process/Monitor.php Normal file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

4
runtime/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*
!logs
!views
!.gitignore

2
runtime/logs/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

2
runtime/views/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

4
start.php Executable file
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,3 @@
CHCP 65001
php windows.php
pause

116
windows.php Normal file
View 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);
}
}