├── 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 | [title]
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 |
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 |
--------------------------------------------------------------------------------