├── tests ├── .gitkeep ├── bootstrap.php └── helpSystemTest.php ├── .env.example ├── Dockerfile ├── public └── .gitingore ├── storage ├── log │ └── .gitignore ├── secret │ └── .gitignore ├── cache │ └── view │ │ └── .gitignore └── db.sql ├── app ├── nginx │ ├── view │ │ └── home.html │ ├── Controller.php │ └── nginx.php ├── panel │ ├── view │ │ ├── home.html │ │ └── layout.html │ ├── static │ │ ├── js │ │ │ └── home.js │ │ └── css │ │ │ └── home.css │ ├── readme.md │ ├── WebSocketAction.php │ ├── Controller.php │ └── panel.php └── login │ ├── view │ └── login.php │ ├── LoginAuth.php │ ├── init.php │ └── Login.php ├── .gitignore ├── config ├── setting.php └── plugin.php ├── bootstrap.php ├── core ├── App │ ├── WSActionInterface.php │ ├── Controller.php │ ├── PluginInterface.php │ └── Hook.php ├── Component │ ├── Route │ │ ├── Middleware.php │ │ ├── Group.php │ │ └── Route.php │ └── Db │ │ └── sqlite.php ├── Helpers │ ├── Helper.php │ ├── Plugin.php │ └── System.php ├── Facade │ ├── Db.php │ ├── Route.php │ └── Facade.php ├── functions.php └── Application.php └── composer.json /tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | debug=false -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php 2 | 3 | -------------------------------------------------------------------------------- /public/.gitingore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitingore 3 | -------------------------------------------------------------------------------- /storage/log/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /storage/secret/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /storage/cache/view/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 2 |

nginx,6666

3 | -------------------------------------------------------------------------------- /app/panel/view/home.html: -------------------------------------------------------------------------------- 1 |
2 |

我现在也不知道首页放什么

3 |
-------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | composer.lock 4 | vendor 5 | *.db 6 | .env 7 | *.log 8 | 9 | -------------------------------------------------------------------------------- /config/setting.php: -------------------------------------------------------------------------------- 1 | env('debug'), 15 | ]; -------------------------------------------------------------------------------- /bootstrap.php: -------------------------------------------------------------------------------- 1 | run(); -------------------------------------------------------------------------------- /config/plugin.php: -------------------------------------------------------------------------------- 1 | view('view/home'); 20 | } 21 | } -------------------------------------------------------------------------------- /storage/db.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE config 2 | ( 3 | key varchar PRIMARY KEY NOT NULL, 4 | value varchar NOT NULL, 5 | time bigint NOT NULL 6 | ); 7 | CREATE TABLE plugin 8 | ( 9 | package varchar primary key not null, 10 | name varchar not null, 11 | path varchar(1024) not null, 12 | source integer not null, 13 | is_install integer default 0 not null, 14 | is_enable integer default 0 not null 15 | ); -------------------------------------------------------------------------------- /core/Helpers/Helper.php: -------------------------------------------------------------------------------- 1 | assertNotFalse(System::ssh_port()); 22 | } 23 | } -------------------------------------------------------------------------------- /app/panel/readme.md: -------------------------------------------------------------------------------- 1 | > 这里是本项目自带的面板,这里将说明面板提供的接口 2 | 3 | ### 嵌入点 4 | > 这里说明本面板中的一些嵌入点,替换规则为```[数组键名]```,如果未指明则为直接输出 5 | 6 | #### MB_HTML_HEAD 7 | 面板头部,需要引用的文件 8 | 9 | #### MB_LEFT_MENU 10 | 面板左边的导航栏 11 | ```html 12 | 13 | ``` 14 | 15 | #### MB_PLUGIN 16 | 插件子栏 17 | ```html 18 | [title] 19 | ``` 20 | 21 | #### MB_SETTING 22 | 设置子栏 23 | ````html 24 | [title] 25 | ```` 26 | -------------------------------------------------------------------------------- /app/panel/WebSocketAction.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 账号:
14 | 密码:
15 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /core/Facade/Db.php: -------------------------------------------------------------------------------- 1 | view('view/home'); 20 | } 21 | 22 | public function wsocket(\swoole_http_response $response) { 23 | $response->status(101); 24 | return WebSocketAction::class; 25 | } 26 | } -------------------------------------------------------------------------------- /app/panel/static/css/home.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow: hidden; 3 | } 4 | 5 | .left-nav { 6 | border-top: 1px solid #505050; 7 | width: 200px; 8 | display: inline-block; 9 | position: absolute; 10 | top: 56px; 11 | bottom: 0; 12 | } 13 | 14 | .left-nav .nav-link { 15 | color: #fff; 16 | } 17 | 18 | .left-nav .nav-item .nav-link:hover { 19 | background: #57575a; 20 | } 21 | 22 | .content { 23 | position: absolute; 24 | left: 200px; 25 | top: 56px; 26 | bottom: 0; 27 | right: 0; 28 | } 29 | 30 | .nav-sub { 31 | display: none; 32 | } 33 | 34 | .nav-sub .dropdown-item { 35 | color: #fff; 36 | } 37 | 38 | .nav-item { 39 | 40 | } -------------------------------------------------------------------------------- /app/login/LoginAuth.php: -------------------------------------------------------------------------------- 1 | cookie['MB_TOKEN'] ?? ''; 22 | if ($token && $token === read_config('MB_TOKEN', 3600)) { 23 | expire_config('MB_TOKEN'); 24 | return true; 25 | } 26 | $response->status(302); 27 | $response->header('Location', '/login'); 28 | return false; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /core/Facade/Route.php: -------------------------------------------------------------------------------- 1 | $name(...$arguments); 34 | } 35 | } -------------------------------------------------------------------------------- /core/App/Controller.php: -------------------------------------------------------------------------------- 1 | getClassPath() . '/' . $template . '.html', $this); 21 | if (app('debug') !== true) { 22 | $view->setCacheDir(app('root_path') . '/storage/cache/view'); 23 | } 24 | return $view; 25 | } 26 | 27 | protected function getClassPath() { 28 | $className = static::class; 29 | if (!isset(static::$classPath[$className])) { 30 | $ref = new \ReflectionClass($this); 31 | static::$classPath[$className] = path2dir($ref->getFileName()); 32 | } 33 | return static::$classPath[$className]; 34 | } 35 | } -------------------------------------------------------------------------------- /core/App/PluginInterface.php: -------------------------------------------------------------------------------- 1 | name = $name; 23 | if (func_num_args() === 1) { 24 | $this->quote_route = &static::$route; 25 | } else { 26 | $this->quote_route = &$quote_route; 27 | } 28 | } 29 | 30 | public function middleware(string $middleware) { 31 | $this->quote_route [$this->name]['param']['middleware'][] = $middleware; 32 | return $this; 33 | } 34 | 35 | public function namespace(string $namespace) { 36 | $this->quote_route [$this->name]['param']['namespace'] = $namespace; 37 | return $this; 38 | } 39 | 40 | public function addRoute(array $methods, string $url, $controller) { 41 | foreach ($methods as $value) { 42 | $value = strtoupper($value); 43 | if (in_array($value, static::$method)) { 44 | $this->quote_route [$this->name]['method'][$value][$url]['controller'] = $controller; 45 | } 46 | } 47 | return $this; 48 | } 49 | } -------------------------------------------------------------------------------- /app/login/init.php: -------------------------------------------------------------------------------- 1 | namespace('App\\login'); 27 | Route::group('auth')->middleware(LoginAuth::class); 28 | } 29 | 30 | public function route(Group $group) { 31 | // TODO: Implement route() method. 32 | 33 | } 34 | 35 | /** 36 | * 安装插件 37 | * @return mixed 38 | */ 39 | public static function install() { 40 | // TODO: Implement install() method. 41 | } 42 | 43 | /** 44 | * 卸载插件 45 | * @return mixed 46 | */ 47 | public static function uninstall() { 48 | // TODO: Implement uninstall() method. 49 | } 50 | 51 | /** 52 | * 开启插件 53 | * @return mixed 54 | */ 55 | public static function enable() { 56 | // TODO: Implement enable() method. 57 | } 58 | 59 | /** 60 | * 关闭插件 61 | * @return mixed 62 | */ 63 | public static function disable() { 64 | // TODO: Implement disable() method. 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/nginx/nginx.php: -------------------------------------------------------------------------------- 1 | ' nginx', 26 | 'href' => '/nginx' 27 | ]); 28 | } 29 | 30 | public function route(Group $group) { 31 | // TODO: Implement route() method. 32 | Route::get('/nginx', 'Controller@home'); 33 | Route::get('/nginx', 'Controller@home'); 34 | $group->namespace('App\\nginx'); 35 | } 36 | 37 | /** 38 | * 安装插件 39 | * @return mixed 40 | */ 41 | public static function install() { 42 | // TODO: Implement install() method. 43 | } 44 | 45 | /** 46 | * 卸载插件 47 | * @return mixed 48 | */ 49 | public static function uninstall() { 50 | // TODO: Implement uninstall() method. 51 | } 52 | 53 | /** 54 | * 开启插件 55 | * @return mixed 56 | */ 57 | public static function enable() { 58 | // TODO: Implement enable() method. 59 | } 60 | 61 | /** 62 | * 关闭插件 63 | * @return mixed 64 | */ 65 | public static function disable() { 66 | // TODO: Implement disable() method. 67 | } 68 | } -------------------------------------------------------------------------------- /core/App/Hook.php: -------------------------------------------------------------------------------- 1 | server['request_method']) === 'GET') { 27 | return $this->view("view/login"); 28 | } else { 29 | $error_num = read_config('MB_LOGIN_ERROE_NUM_' . $_request->server['remote_addr'], 1800); 30 | $error_num = $error_num ?: 0; 31 | if ($error_num >= 5 && read_config('MB_LOGIN_TIME_' . $_request->server['remote_addr'], 1800)) { 32 | return '登录失败超过5次,请半小时后再试'; 33 | } 34 | write_config('MB_LOGIN_TIME_' . $_request->server['remote_addr'], time()); 35 | write_config('MB_LOGIN_ERROE_NUM_' . $_request->server['remote_addr'], $error_num + 1); 36 | if (isset($_request->post['user']) && isset($_request->post['passwd'])) { 37 | $user = read_config('MB_USER'); 38 | $passwd = read_config('MB_PASSWD'); 39 | if ($user === $_request->post['user'] && 40 | $passwd === $this->encodePasswd($user, $_request->post['passwd'])) { 41 | write_config('MB_TOKEN', $token = str_rand(32)); 42 | $_response->cookie('MB_TOKEN', $token); 43 | if (($ret = Hook::add(MB_LOGIN, function ($ret) { 44 | if ($ret !== true) { 45 | return false; 46 | } 47 | return true; 48 | }, $_request, $_response)) !== true) { 49 | return $ret; 50 | } 51 | return '登录成功'; 52 | } else { 53 | return '账号或者密码错误'; 54 | } 55 | } else { 56 | return '账号或密码不能为空'; 57 | } 58 | } 59 | } 60 | 61 | protected function encodePasswd($u, $p) { 62 | return hash('sha256', $u . '|' . $p . 'MB_PWD'); 63 | } 64 | 65 | } 66 | 67 | -------------------------------------------------------------------------------- /app/panel/view/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {:view_embed(MB_HTML_HEAD)} 17 | Document 18 | 19 | 20 | 21 | 24 | 25 | 50 | 51 | {:$page_content} 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/panel/panel.php: -------------------------------------------------------------------------------- 1 | status(302); 34 | $_response->header('location', '/'); 35 | return '登录成功,进入面板'; 36 | }); 37 | View::setLayout(__DIR__ . '/view/layout.html'); 38 | } 39 | 40 | public function route(Group $group) { 41 | // TODO: Implement route() method. 42 | Route::get('/', 'Controller@home'); 43 | Route::get('/wsocket', 'Controller@wsocket'); 44 | $group->namespace('App\\panel'); 45 | } 46 | 47 | /** 48 | * active demo 暂时没有想去实现 49 | * @param array $param 50 | * @param string $html 51 | * @return string|string[]|null 52 | * @throws \HuanL\Container\InstantiationException 53 | */ 54 | public function isurl($param = [], $html = '') { 55 | $html = $html[0] ?? ''; 56 | $req = app(\Swoole\Http\Request::class); 57 | $info = $req->server['path_info']; 58 | if (substr($param['href'], 0, strlen($info)) == $info) { 59 | $html = preg_replace('#class="(.*?)"#', 'class="$1 active"', $html); 60 | } 61 | return $html; 62 | } 63 | 64 | /** 65 | * 安装插件 66 | * @return mixed 67 | */ 68 | public static function install() { 69 | // TODO: Implement install() method. 70 | var_dump(System::copy_dir(__DIR__ . '/static', app('root_path') . '/public')); 71 | } 72 | 73 | /** 74 | * 卸载插件 75 | * @return mixed 76 | */ 77 | public static function uninstall() { 78 | // TODO: Implement uninstall() method. 79 | } 80 | 81 | /** 82 | * 开启插件 83 | * @return mixed 84 | */ 85 | public static function enable() { 86 | // TODO: Implement enable() method. 87 | } 88 | 89 | /** 90 | * 关闭插件 91 | * @return mixed 92 | */ 93 | public static function disable() { 94 | // TODO: Implement disable() method. 95 | } 96 | } -------------------------------------------------------------------------------- /core/Helpers/Plugin.php: -------------------------------------------------------------------------------- 1 | get([ 30 | 'package' => $package 31 | ]); 32 | } 33 | 34 | /** 35 | * 插件是否安装 36 | * @param $package 37 | * @return array|bool 38 | */ 39 | public static function isinstall($package) { 40 | $result = self::getInfo($package); 41 | if ($result && $result['is_install']) { 42 | return $result; 43 | } 44 | return false; 45 | } 46 | 47 | /** 48 | * 插件是否开启 49 | * @param $package 50 | * @return bool|array 51 | */ 52 | public static function isenable($package) { 53 | $result = self::isinstall($package); 54 | if ($result && $result['is_enable'] == 1) { 55 | return $result; 56 | } 57 | return false; 58 | } 59 | 60 | public static function install($package, $source = 0) { 61 | if (self::isinstall($package)) { 62 | return -1; 63 | } 64 | switch ($source) { 65 | case self::SOURCE_CORE: 66 | { 67 | if (self::getInfo($package)) { 68 | Db::table('plugin')->update(['is_install' => 1, 'is_enable' => 1]); 69 | } else { 70 | Db::table('plugin')->insert([ 71 | 'package' => $package, 'name' => $package, 72 | 'path' => $package, 'source' => $source, 'is_install' => 1, 73 | 'is_enable' => 1 74 | ]); 75 | } 76 | /** @var PluginInterface $package */ 77 | $package::install(); 78 | $package::enable();//核心插件直接开启 79 | break; 80 | } 81 | } 82 | } 83 | 84 | public static function enable($package) { 85 | if (self::enable($package)) { 86 | return -1; 87 | } 88 | $info = self::getInfo($package); 89 | switch ($info['source']) { 90 | case self::SOURCE_CORE: 91 | { 92 | break; 93 | } 94 | } 95 | } 96 | 97 | public static function disable() { 98 | 99 | } 100 | 101 | 102 | } -------------------------------------------------------------------------------- /core/Helpers/System.php: -------------------------------------------------------------------------------- 1 | 0) { 92 | $port = $match[1]; 93 | } 94 | self::cacheSet('ssh_port', $port); 95 | return $port; 96 | } 97 | 98 | public static function exec($command) { 99 | /** @var command $ssh */ 100 | $ssh = app('command'); 101 | return $ssh->exec($command); 102 | } 103 | 104 | protected static function cacheGet($key) { 105 | if (isset(self::$cache[$key])) { 106 | return self::$cache[$key]; 107 | } 108 | return false; 109 | } 110 | 111 | protected static function cacheSet($key, $val) { 112 | self::$cache[$key] = $val; 113 | } 114 | 115 | } -------------------------------------------------------------------------------- /core/functions.php: -------------------------------------------------------------------------------- 1 | make($abstract, $parameter); 37 | } 38 | } 39 | 40 | if (!function_exists('read_config')) { 41 | function read_config(string $key, $expire = 0) { 42 | $result = \WNPanel\Core\Facade\Db::table('config')->get([ 43 | 'key' => $key 44 | ]); 45 | if ($result === false) { 46 | return false; 47 | } 48 | if ($expire && $result['time'] + $expire < time()) { 49 | return false; 50 | } 51 | return $result['value']; 52 | } 53 | } 54 | 55 | if (!function_exists('write_config')) { 56 | function write_config(string $key, string $value) { 57 | if (read_config($key) !== false) { 58 | return \WNPanel\Core\Facade\Db::table('config')->where(['key' => $key])->update([ 59 | 'value' => $value, 60 | 'time' => time() 61 | ]); 62 | } else { 63 | return \WNPanel\Core\Facade\Db::table('config')->insert([ 64 | 'key' => $key, 65 | 'value' => $value, 66 | 'time' => time() 67 | ]); 68 | } 69 | } 70 | } 71 | 72 | if (!function_exists('expire_config')) { 73 | function expire_config(string $key) { 74 | return \WNPanel\Core\Facade\Db::table('config')->where(['key' => $key])->update([ 75 | 'time' => time() 76 | ]); 77 | } 78 | } 79 | 80 | if (!function_exists('env')) { 81 | function env($key) { 82 | static $env_array = []; 83 | if (empty($env_array)) { 84 | $tmp = fopen(app('root_path') . '/.env', 'r+'); 85 | if ($tmp) { 86 | while ($str = fgets($tmp)) { 87 | $pos = strpos($str, '='); 88 | $value = trim(substr($str, $pos + 1)); 89 | if ($value === 'true') { 90 | $value = true; 91 | } else if ($value === 'false') { 92 | $value = false; 93 | } 94 | $env_array[substr($str, 0, $pos)] = $value; 95 | } 96 | } 97 | } 98 | return $env_array[$key] ?? ''; 99 | } 100 | } 101 | 102 | if (!function_exists('wnm_log')) { 103 | function wnm_log($content) { 104 | echo '[' . date('Y/m/d H:i:s') . ']' . $content . "\n"; 105 | } 106 | } 107 | 108 | if (!function_exists('path2dir')) { 109 | function path2dir($path) { 110 | return substr($path, 0, strrpos($path, '/')); 111 | } 112 | } 113 | 114 | if (!function_exists('view_embed')) { 115 | function view_embed(string $name, $format = null, ...$param) { 116 | return \WNPanel\Core\App\Hook::embed_point($name, function ($array) use ($format, $param) { 117 | if (empty($format)) { 118 | return $array; 119 | } 120 | if (is_string($format)) { 121 | return preg_replace_callback('/\[(.*?)\]/', function ($match) use ($array) { 122 | return $array[$match[1]] ?? ''; 123 | }, $format); 124 | } else if (is_callable($format)) { 125 | return preg_replace_callback('/\[(.*?)\]/', function ($match) use ($array) { 126 | return $array[$match[1]] ?? ''; 127 | }, call_user_func_array($format, [$array, $param])); 128 | } 129 | }); 130 | } 131 | } -------------------------------------------------------------------------------- /core/Component/Db/sqlite.php: -------------------------------------------------------------------------------- 1 | = 1) { 38 | try { 39 | static::$db = new SQLite3($path, SQLITE3_OPEN_READWRITE); 40 | } catch (\Exception $exception) { 41 | if (strpos($exception->getMessage(), 'unable to open database file')) { 42 | static::$db = new SQLite3($path); 43 | call_user_func($create, $this); 44 | } else { 45 | throw $exception; 46 | } 47 | } 48 | } 49 | } 50 | 51 | public function table($table) { 52 | $this->table = $table; 53 | $this->where = ''; 54 | $this->bindValues = []; 55 | return $this; 56 | } 57 | 58 | public function exec(string $sql) { 59 | return static::$db->exec($sql); 60 | } 61 | 62 | /** 63 | * @param array $where 64 | * @return bool|array 65 | */ 66 | public function get(array $where) { 67 | $this->sql = 'select * from ' . $this->table . ' where '; 68 | $values = []; 69 | foreach ($where as $key => $value) { 70 | $values[] = $value; 71 | $this->sql .= "$key=? and"; 72 | } 73 | $this->sql = substr($this->sql, 0, strlen($this->sql) - 3); 74 | $this->sql .= ' limit 1'; 75 | if ($stmt = $this->prepare($this->sql, $values)) { 76 | if ($result = $stmt->execute()) { 77 | return $result->fetchArray(); 78 | } 79 | return false; 80 | } 81 | return false; 82 | } 83 | 84 | public function insert(array $values) { 85 | $this->_insert($values, $param); 86 | if ($stmt = $this->prepare($this->sql, $param)) { 87 | if ($stmt->execute()->finalize()) { 88 | return static::$db->changes(); 89 | } 90 | return false; 91 | } 92 | return false; 93 | } 94 | 95 | protected function _insert(array $values, &$param) { 96 | $this->sql = 'insert into ' . $this->table . '(`' . implode('`,`', array_keys($values)) . '`) values('; 97 | foreach ($values as $value) { 98 | $this->sql .= '?,'; 99 | $param[] = $value; 100 | } 101 | $this->sql = substr($this->sql, 0, strlen($this->sql) - 1); 102 | $this->sql .= ')'; 103 | } 104 | 105 | public function where(array $where) { 106 | foreach ($where as $key => $value) { 107 | $this->where .= "and $key=? "; 108 | $this->bindValues[] = $value; 109 | } 110 | return $this; 111 | } 112 | 113 | public function update(array $set) { 114 | $data = ''; 115 | foreach ($set as $key => $value) { 116 | $data .= ",`{$key}`=?"; 117 | $param[] = $value; 118 | } 119 | $this->bindValues = array_merge($param, $this->bindValues); 120 | $data = substr($data, 1); 121 | $this->where = substr($this->where, 3); 122 | $this->sql = "update {$this->table} set $data where " . $this->where; 123 | if ($stmt = $this->prepare($this->sql, $this->bindValues)) { 124 | if ($stmt->execute()->finalize()) { 125 | return static::$db->changes(); 126 | } 127 | return false; 128 | } 129 | return false; 130 | } 131 | 132 | public function delete() { 133 | $this->where = substr($this->where, 3); 134 | $this->sql = 'delete from ' . $this->table . ' where ' . $this->where; 135 | if ($stmt = $this->prepare($this->sql, $this->bindValues)) { 136 | if ($stmt->execute()->finalize()) { 137 | return static::$db->changes(); 138 | } 139 | return false; 140 | } 141 | return false; 142 | } 143 | 144 | public function prepare($sql, $bindValues) { 145 | if ($stmt = static::$db->prepare($sql)) { 146 | for ($i = 0; $i < count($bindValues); $i++) { 147 | $stmt->bindValue($i + 1, $bindValues[$i]); 148 | } 149 | return $stmt; 150 | } 151 | return false; 152 | } 153 | 154 | } -------------------------------------------------------------------------------- /core/Component/Route/Route.php: -------------------------------------------------------------------------------- 1 | quote_route); 44 | return static::$group; 45 | } 46 | static $n = 0; 47 | $n++; 48 | $tmp_group = static::$group; 49 | $tmp_groupName = static::$groupName; 50 | 51 | static::$groupName = 'group_' . $n; 52 | $group = new Group(static::$groupName, static::$group->quote_route[$tmp_groupName]['group']); 53 | 54 | static::$group = $group; 55 | call_user_func($func, $group); 56 | static::$groupName = $tmp_groupName; 57 | static::$group = $tmp_group; 58 | 59 | return $group; 60 | } 61 | 62 | public function get(string $url, $controller) { 63 | return $this->addRoute(['GET'], $url, $controller); 64 | } 65 | 66 | public function post(string $url, $controller) { 67 | return $this->addRoute(['POST'], $url, $controller); 68 | } 69 | 70 | public function any(string $url, $controller) { 71 | return $this->addRoute(static::$method, $url, $controller); 72 | } 73 | 74 | protected function addRoute(array $methods, string $url, $controller) { 75 | static::$group->addRoute($methods, $url, $controller); 76 | return $this; 77 | } 78 | 79 | public static function resolve(\swoole_http_request $request, \swoole_http_response $response) { 80 | $method = strtoupper($request->server['request_method']); 81 | foreach (static::$route as $value) { 82 | $param = $value['param'] ?? []; 83 | if (($param = static::recursive_resolve($value, $method, $request->server['path_info'], $param)) !== false) { 84 | return static::deal_controller($request, $response, $param); 85 | } 86 | } 87 | $response->status(404); 88 | return false; 89 | } 90 | 91 | private static function deal_controller(\swoole_http_request $request, \swoole_http_response $response, $param) { 92 | //加载中间件和处理命名空间 93 | $ret = null; 94 | if (isset($param['group_param']['middleware'])) { 95 | foreach ($param['group_param']['middleware'] as $value) { 96 | /** @var Middleware $group */ 97 | $middleware = new $value; 98 | if (($ret = $middleware->handle($request, $response)) !== true) { 99 | break; 100 | } 101 | } 102 | } 103 | if (is_null($ret) || $ret === true) { 104 | $controller = $param['param']['controller']; 105 | if (!($controller instanceof \Closure)) { 106 | if (isset($param['group_param']['namespace'])) { 107 | $controller = $param['group_param']['namespace'] . '\\' . $controller; 108 | } 109 | } 110 | // $param['uri_param']['_request'] = $request; 111 | // $param['uri_param']['_response'] = $response; 112 | app()->instance('route_uri', $param['uri']); 113 | app()->instance(\Swoole\Http\Request::class, $request); 114 | app()->instance(\Swoole\Http\Response::class, $response); 115 | $ret = app()->call($controller, $param['uri_param']); 116 | } 117 | return $ret; 118 | } 119 | 120 | private static function merge_param($param1, $param2) { 121 | if (isset($param2['namespace'])) { 122 | if (isset($param1['namespace'])) { 123 | $param1['namespace'] .= '\\' . $param2['namespace']; 124 | } else { 125 | $param1['namespace'] = $param2['namespace']; 126 | } 127 | } 128 | if (isset($param2['middleware'])) { 129 | if (isset($param1['middleware'])) { 130 | array_merge($param1['middleware'], $param2['middleware']); 131 | } else { 132 | $param1['middleware'] = $param2['middleware']; 133 | } 134 | } 135 | return $param1; 136 | } 137 | 138 | private static function recursive_resolve(array $route, string $method, string $pathinfo, array &$param = []) { 139 | foreach ($route['method'][$method] ?? [] as $key => $value) { 140 | if (($uri_param = static::resolve_uri($pathinfo, $key)) !== false) { 141 | $param = static::merge_param($param, $route['param'] ?? []); 142 | return [ 143 | 'uri' => $key, 144 | 'param' => $route['method'][$method][$key], 145 | 'uri_param' => $uri_param, 146 | 'group_param' => $param 147 | ]; 148 | } 149 | } 150 | if (empty($route['group'])) { 151 | return false; 152 | } 153 | foreach ($route['group'] as $value) { 154 | if ($ret = static::recursive_resolve($value, $method, $pathinfo, $param)) { 155 | return $ret; 156 | } 157 | } 158 | return false; 159 | } 160 | 161 | private static function resolve_uri($pathinfo, $uri) { 162 | $uri = substr($uri, 0, 1) === '/' ? $uri : ('/' . $uri); 163 | $uri_param = []; 164 | //匹配参数,转换为正则表达式 165 | $regex = preg_replace_callback([ 166 | '|{(\w+?)}|', 167 | '|{(\w+?)\?}|' 168 | ], function ($match) use (&$uri_param) { 169 | $uri_param[] = $match[1]; 170 | if (substr($match[0], strlen($match[0]) - 2, 1) == '?') { 171 | return '(\w*)'; 172 | } 173 | return '(\w+)'; 174 | }, $uri); 175 | $regex = '^' . $regex . '[/]?$'; 176 | if (!preg_match("|$regex|", $pathinfo, $matches)) { 177 | return false; 178 | } 179 | if (sizeof($matches) - 1 !== sizeof($uri_param)) { 180 | return false; 181 | } 182 | for ($i = 1; $i < sizeof($matches); $i++) { 183 | $uri_param[$uri_param[$i - 1]] = $matches[$i]; 184 | unset($uri_param[$i - 1]); 185 | } 186 | return $uri_param; 187 | } 188 | 189 | } -------------------------------------------------------------------------------- /core/Application.php: -------------------------------------------------------------------------------- 1 | rootPath = $rootPath; 43 | $this->initialization(); 44 | $this->loadBaseComponent(); 45 | $this->loadDatabase(); 46 | // $this->initBasePlugin(); 47 | } 48 | 49 | public function initialization() { 50 | $this->instance('root_path', $this->rootPath); 51 | $this->setting = require $this->rootPath . '/config/setting.php'; 52 | $this->pluginList = require $this->rootPath . '/config/plugin.php'; 53 | 54 | $this->instance('debug', $this->setting['debug']); 55 | 56 | } 57 | 58 | protected function enableDebug() { 59 | if (extension_loaded('inotify')) { 60 | $this->watchfile_hotrestart(); 61 | } 62 | } 63 | 64 | protected function watchfile_hotrestart() { 65 | $notify_id = inotify_init(); 66 | if (!$notify_id) { 67 | return false; 68 | } 69 | foreach ($this->pluginList as $item) { 70 | //TODO:目录遍历 71 | try { 72 | $reflex = new \ReflectionClass($item); 73 | } catch (\ReflectionException $e) { 74 | wnm_log('plugin:' . $item . ' ' . $e->getMessage()); 75 | return false; 76 | } 77 | $dir = path2dir($reflex->getFileName()); 78 | inotify_add_watch($notify_id, $dir, IN_CREATE | IN_DELETE | IN_MODIFY); 79 | } 80 | $this->setting['inotify_last_time'] = time(); 81 | swoole_event_add($notify_id, function () use ($notify_id) { 82 | $events = inotify_read($notify_id); 83 | if (!empty($events) && $this->setting['inotify_last_time'] <= time() - 2) { 84 | if ($events[0]['mask'] != IN_DELETE) { 85 | return; 86 | } 87 | //TODO: 暂时先用IN_DELETE判断最后修改 88 | wnm_log('notify file change reload'); 89 | $this->setting['inotify_last_time'] = time(); 90 | $this->server->reload(); 91 | } 92 | }); 93 | } 94 | 95 | public function run() { 96 | $this->startServer(); 97 | } 98 | 99 | protected function loadBaseComponent() { 100 | $this->singleton('route', Route::class); 101 | $this->initSSH(); 102 | } 103 | 104 | protected function initSSH() { 105 | 106 | $commond = new command(); 107 | if ($ssh = $commond->initSSH('localhost', System::ssh_port())) { 108 | //TODO:暂时直接用root权限,后面感觉需要用其他操作改一下 109 | if (!$ssh->login_pubkey('root', $this->rootPath . '/storage/secret/rsa.pub', $this->rootPath . '/storage/secret/rsa.pri')) { 110 | $commond->clearSSH(); 111 | } 112 | } 113 | $this->instance(command::class, $commond); 114 | $this->alias('command', command::class); 115 | } 116 | 117 | protected function loadDatabase() { 118 | //TODO:数据库需要一些密码等权限操作 119 | $db = new sqlite($this->rootPath . '/storage/wnm.db', function (sqlite $db) { 120 | //TODO: Create Table 121 | $dbcontent = explode(';', file_get_contents($this->rootPath . "/storage/db.sql")); 122 | foreach ($dbcontent as $sql) { 123 | try { 124 | $db->exec($sql); 125 | } catch (\Throwable $exception) { 126 | 127 | } 128 | } 129 | }); 130 | app()->instance('db', $db); 131 | } 132 | 133 | protected function bindWebSocket() { 134 | $shared_table = new \swoole_table(8); 135 | $shared_table->column('classname', \swoole_table::TYPE_STRING, 64); 136 | $shared_table->create(); 137 | $this->server->on('open', function (\swoole_websocket_server $server, \swoole_http_request $request) use ($shared_table) { 138 | $response = new class extends \swoole_http_response { 139 | public $code = 200; 140 | 141 | public function status($http_code, $reason = NULL) { 142 | $this->code = $http_code; 143 | } 144 | }; 145 | $ret = Route::resolve($request, $response); 146 | if ($response->code != 101) { 147 | $server->disconnect($request->fd, 1000, 'Permission error'); 148 | return false; 149 | } 150 | try { 151 | $reflectionClass = new \ReflectionClass($ret); 152 | if (in_array(WSActionInterface::class, $reflectionClass->getInterfaceNames())) { 153 | $shared_table->set($request->fd, ['classname' => $ret]); 154 | } 155 | } catch (\Throwable $exception) { 156 | 157 | } 158 | }); 159 | $this->server->on('message', function (\swoole_websocket_server $server, \swoole_websocket_frame $frame) use ($shared_table) { 160 | if ($shared_table->exist($frame->fd)) { 161 | $class = $shared_table->get($frame->fd, 'classname'); 162 | call_user_func_array([$class, 'action'], [$server, $frame]); 163 | } 164 | }); 165 | } 166 | 167 | protected function startServer() { 168 | $this->initBasePlugin(); 169 | $this->server = new \swoole_websocket_server("0.0.0.0", 8000); 170 | $this->bindWebSocket(); 171 | $this->server->set([ 172 | 'document_root' => $this->rootPath . '/public', 173 | 'enable_static_handler' => true, 174 | 'worker_num' => 4, 175 | 'log_file' => $this->rootPath . '/storage/log/wnm-' . date('Y-m-d') . '.log' 176 | ]); 177 | $this->server->on('workerstart', function (\swoole_server $server, $id) { 178 | wnm_log('worker start id:' . $id); 179 | $this->instance('worker_id', $id); 180 | $server->db = new sqlite($this->rootPath . '/storage/wnm.db'); 181 | app()->instance('db', $server->db); 182 | }); 183 | $this->server->on('request', function (\swoole_http_request $request, \swoole_http_response $response) { 184 | //TODO: deal route 185 | $ret = Route::resolve($request, $response); 186 | if (is_string($ret)) { 187 | $response->header("content-type", "text/html;charset=utf8"); 188 | $response->write($ret); 189 | } else if ($ret instanceof View) { 190 | $response->header("content-type", "text/html;charset=utf8"); 191 | $response->write($ret->execute()); 192 | } else if (method_exists($ret, '__toString')) { 193 | $response->write($ret); 194 | } 195 | $response->end(); 196 | }); 197 | $this->server->on('start', function () { 198 | if ($this->setting['debug']) { 199 | $this->enableDebug(); 200 | } 201 | }); 202 | 203 | $this->server->start(); 204 | } 205 | 206 | protected function initBasePlugin() { 207 | foreach ($this->pluginList as $value) { 208 | /** @var PluginInterface $instance */ 209 | $instance = new $value(); 210 | if ($instance instanceof PluginInterface) { 211 | if (!Plugin::isenable($value)) { 212 | Plugin::install($value, Plugin::SOURCE_CORE); 213 | } 214 | $instance->init(); 215 | \WNPanel\Core\Facade\Route::group(function ($group) use ($instance) { 216 | $instance->route($group); 217 | }); 218 | } else { 219 | throw new \Exception($value . ' is error'); 220 | } 221 | } 222 | } 223 | 224 | } 225 | 226 | --------------------------------------------------------------------------------