17 |
18 |
19 |
20 | listHeader) {
21 | echo "
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/public/lib/layui/lay/modules/laypage.js:
--------------------------------------------------------------------------------
1 | /** layui-v2.4.5 MIT License By https://www.layui.com */
2 | ;layui.define(function(e){"use strict";var a=document,t="getElementById",n="getElementsByTagName",i="laypage",r="layui-disabled",u=function(e){var a=this;a.config=e||{},a.config.index=++s.index,a.render(!0)};u.prototype.type=function(){var e=this.config;if("object"==typeof e.elem)return void 0===e.elem.length?2:3},u.prototype.view=function(){var e=this,a=e.config,t=a.groups="groups"in a?0|a.groups:5;a.layout="object"==typeof a.layout?a.layout:["prev","page","next"],a.count=0|a.count,a.curr=0|a.curr||1,a.limits="object"==typeof a.limits?a.limits:[10,20,30,40,50],a.limit=0|a.limit||10,a.pages=Math.ceil(a.count/a.limit)||1,a.curr>a.pages&&(a.curr=a.pages),t<0?t=1:t>a.pages&&(t=a.pages),a.prev="prev"in a?a.prev:"上一页",a.next="next"in a?a.next:"下一页";var n=a.pages>t?Math.ceil((a.curr+(t>1?1:0))/(t>0?t:1)):1,i={prev:function(){return a.prev?'
'+a.prev+"":""}(),page:function(){var e=[];if(a.count<1)return"";n>1&&a.first!==!1&&0!==t&&e.push('
'+(a.first||1)+"");var i=Math.floor((t-1)/2),r=n>1?a.curr-i:1,u=n>1?function(){var e=a.curr+(t-i-1);return e>a.pages?a.pages:e}():t;for(u-r
2&&e.push('…');r<=u;r++)r===a.curr?e.push('"+r+""):e.push(''+r+"");return a.pages>t&&a.pages>u&&a.last!==!1&&(u+1…'),0!==t&&e.push(''+(a.last||a.pages)+"")),e.join("")}(),next:function(){return a.next?''+a.next+"":""}(),count:'共 '+a.count+" 条",limit:function(){var e=['"}(),refresh:['','',""].join(""),skip:function(){return['到第','','页',""].join("")}()};return['',function(){var e=[];return layui.each(a.layout,function(a,t){i[t]&&e.push(i[t])}),e.join("")}(),"
"].join("")},u.prototype.jump=function(e,a){if(e){var t=this,i=t.config,r=e.children,u=e[n]("button")[0],l=e[n]("input")[0],p=e[n]("select")[0],c=function(){var e=0|l.value.replace(/\s|\D/g,"");e&&(i.curr=e,t.render())};if(a)return c();for(var o=0,y=r.length;oi.pages||(i.curr=e,t.render())});p&&s.on(p,"change",function(){var e=this.value;i.curr*e>i.count&&(i.curr=Math.ceil(i.count/e)),i.limit=e,t.render()}),u&&s.on(u,"click",function(){c()})}},u.prototype.skip=function(e){if(e){var a=this,t=e[n]("input")[0];t&&s.on(t,"keyup",function(t){var n=this.value,i=t.keyCode;/^(37|38|39|40)$/.test(i)||(/\D/.test(n)&&(this.value=n.replace(/\D/,"")),13===i&&a.jump(e,!0))})}},u.prototype.render=function(e){var n=this,i=n.config,r=n.type(),u=n.view();2===r?i.elem&&(i.elem.innerHTML=u):3===r?i.elem.html(u):a[t](i.elem)&&(a[t](i.elem).innerHTML=u),i.jump&&i.jump(i,e);var s=a[t]("layui-laypage-"+i.index);n.jump(s),i.hash&&!e&&(location.hash="!"+i.hash+"="+i.curr),n.skip(s)};var s={render:function(e){var a=new u(e);return a.index},index:layui.laypage?layui.laypage.index+1e4:0,on:function(e,a,t){return e.attachEvent?e.attachEvent("on"+a,function(a){a.target=a.srcElement,t.call(e,a)}):e.addEventListener(a,t,!1),this}};e(i,s)});
--------------------------------------------------------------------------------
/exec/Application.php:
--------------------------------------------------------------------------------
1 | loader = $loader;
25 |
26 | $controlNamespace = "SwoftAdmin\\Exec\\Controller";
27 | $dir = new Directory();
28 | foreach ($this->getDir($controlNamespace) as $scanDir => $namespace) {
29 | $dir->setScanDirectory($scanDir, $namespace);
30 | }
31 |
32 | foreach ($dir->scanClass() as $className) {
33 | // Annotation reader
34 | $reflectionClass = new \ReflectionClass($className);
35 |
36 | // Fix ignore abstract
37 | if ($reflectionClass->isAbstract()) {
38 | return;
39 | }
40 | $arr = explode('\\',$className);
41 | $handel = end($arr);
42 |
43 | $cmd = new $className();
44 | // Annotation reader
45 | $reader = new AnnotationReader();
46 | $reflectionMethods = $reflectionClass->getMethods();
47 | foreach ($reflectionMethods as $reflectionMethod) {
48 | $methodAnnotations = $reader->getMethodAnnotations($reflectionMethod);
49 | $this->addRoute($handel."@".$reflectionMethod->getName(),[$cmd,$reflectionMethod->getName()]);
50 | if (!empty($methodAnnotations)) {
51 | foreach ($methodAnnotations as $annotation) {
52 | if ($annotation instanceof CommandMapping) {
53 | $this->addRoute($annotation->getName(),[$cmd,$reflectionMethod->getName()]);
54 | }
55 | }
56 | }
57 | }
58 | }
59 |
60 | self::$myself = $this;
61 | }
62 |
63 | /**
64 | * @param string $name
65 | * @param callable $fun
66 | */
67 | public function addRoute(string $name, $fun)
68 | {
69 | $this->routes[$name] = $fun;
70 | }
71 |
72 | /**
73 | * 运行命令
74 | * @param array $argv
75 | */
76 | public function run(array $argv)
77 | {
78 | if (isset($argv[1]) && isset($this->routes[$argv[1]])) {
79 | $route = $argv[1];
80 | $call = $this->routes[$route];
81 |
82 | $class = $call[0];
83 | $class = new $class();
84 | $action = $call[1];
85 |
86 | $params = [];
87 | foreach ($argv as $key => $item) {
88 | $key > 1 and $params[] = $item;
89 | }
90 |
91 | $data = call_user_func_array([$class, $action], $params);
92 |
93 | echo serialize($data);
94 | return;
95 | }
96 | }
97 |
98 | /**
99 | * 获取目录
100 | * @param $namespace
101 | * @return array
102 | */
103 | public function getDir($namespace): array
104 | {
105 | $psr = $this->loader->getPrefixesPsr4();
106 | $arr = explode("\\", $namespace);
107 | foreach ($arr as $name) {
108 | $base = isset($base) ? $base.$name."\\" : $name."\\";
109 | if (isset($psr[$base])) {
110 | $temp = explode($base, $namespace);
111 | $find = end($temp);
112 | $find = str_replace("\\", "/", $find);
113 | break;
114 | }
115 | }
116 |
117 | if (!isset($find) || !isset($psr[$base])) {
118 | return [];
119 | }
120 | $data = [];
121 | foreach ($psr[$base] as $dir) {
122 | $dir = realpath($dir);
123 | $is = $dir."/".$find."/";
124 | if (is_dir($is)) {
125 | $data[$is] = $namespace."\\";
126 | }
127 | }
128 |
129 | return $data;
130 | }
131 |
132 | /**
133 | * 获取目录对象
134 | * @param $namespace
135 | * @return Directory
136 | */
137 | public static function getDirectory($namespace):Directory
138 | {
139 | $dirList = self::$myself->getDir($namespace);
140 |
141 | $dir = new Directory();
142 | foreach ($dirList as $scanDir=>$namespace){
143 | $dir->setScanDirectory($scanDir, $namespace);
144 | }
145 | return $dir;
146 | }
147 |
148 |
149 | /**
150 | * 获取目录对象
151 | * @param $namespace
152 | * @return array
153 | */
154 | public static function getDirs($namespace)
155 | {
156 | return self::$myself->getDir($namespace);
157 | }
158 |
159 | public static function getTemplate(string $name):string
160 | {
161 | return __DIR__."/template/".$name;
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/src/Http/Controller/ModelController.php:
--------------------------------------------------------------------------------
1 | dao();
35 |
36 | $view = new Table();
37 | $view->title = "Dao";
38 | $view->listTitle = [
39 | "title" => '标题',
40 | "path" => '类名',
41 | "bean" => '拥有Bean',
42 | ];
43 | $view->listHeader[] = new ReloadButton();
44 | $view->listHeader[] = new NewWindow('model/addClassShow','新增Dao');
45 |
46 | $view->listData = is_array($list) ? $list : [];
47 |
48 | $button = new NewWindowIcon('file/show', '文件内容');
49 | $button->mix = "true";
50 | $button->addField(['path'=>'path']);
51 | $view->addListButton($button);
52 |
53 | return $view->toString();
54 | }
55 |
56 | /**
57 | * 新增Dao
58 | * @RequestMapping("addClassShow")
59 | * @param Request $request
60 | * @return \Swoft\Http\Message\Response
61 | * @throws \Throwable
62 | */
63 | public function addDaoShow(Request $request)
64 | {
65 | $title = $request->get('title', "add Dao");
66 | $namespace = $request->get('namespace', "App/Model/Dao/");
67 | $suffix = $request->get('suffix', "Dao");
68 |
69 | $addView = new Form();
70 | $addView->title = $title;
71 | $addView->item[] = new Form\InputForm('title',"标题",'注释第一行说明');
72 | $addView->item[] = new Form\InputForm('className',"类名","自动加 {$suffix} 后缀");
73 | $addView->item[] = (new Form\InputForm('namespace','命名空间'))->setValue($namespace);
74 | $addView->item[] = (new Form\InputForm('suffix','后缀'))->setValue($suffix);
75 |
76 | $addView->action = 'model/addClass';
77 | return $addView->toString();
78 | }
79 |
80 | /**
81 | * Data目录
82 | * @RequestMapping("data")
83 | */
84 | public function data()
85 | {
86 | $list = Exec::bean(Model::class)->data();
87 |
88 | $view = new Table();
89 | $view->title = "Data目录";
90 | $view->listTitle = [
91 | "title" => '标题',
92 | "path" => '类名',
93 | "bean" => '拥有Bean',
94 | ];
95 | $view->listHeader[] = new ReloadButton();
96 | $view->listHeader[] = new NewWindow('model/addClassShow?title=Data&namespace=App/Model/Data/&suffix=Data','新增Data');
97 |
98 | $view->listData = is_array($list) ? $list : [];
99 |
100 | $button = new NewWindowIcon('file/show', '文件内容');
101 | $button->mix = "true";
102 | $button->addField(['path'=>'path']);
103 | $view->addListButton($button);
104 |
105 | return $view->toString();
106 | }
107 |
108 | /**
109 | * Logic目录
110 | * @RequestMapping("logic")
111 | */
112 | public function logic()
113 | {
114 | $list = Exec::bean(Model::class)->logic();
115 |
116 | $view = new Table();
117 | $view->title = "Logic目录";
118 | $view->listTitle = [
119 | "title" => '标题',
120 | "path" => '类名',
121 | "bean" => '拥有Bean',
122 | ];
123 | $view->listHeader[] = new ReloadButton();
124 | $view->listHeader[] = new NewWindow('model/addClassShow?title=Logic&namespace=App/Model/Logic/&suffix=Logic','新增Logic');
125 |
126 | $view->listData = is_array($list) ? $list : [];
127 |
128 | $button = new NewWindowIcon('file/show', '文件内容');
129 | $button->mix = "true";
130 | $button->addField(['path'=>'path']);
131 | $view->addListButton($button);
132 |
133 | return $view->toString();
134 | }
135 |
136 | /**
137 | * 新增类文件
138 | * @param Request $request
139 | * @RequestMapping("addClass")
140 | * @return array
141 | */
142 | public function addClass(Request $request)
143 | {
144 | $namespace = $request->post('namespace','');
145 | $name = $request->post('className','');
146 | $title = $request->post('title','');
147 | $suffix = $request->post("suffix",'');
148 |
149 | Exec::bean(Model::class)->addClass($namespace, $name, $title, $suffix);
150 | return Msg::success();
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/resource/home/welcome.php:
--------------------------------------------------------------------------------
1 | script("terminal/js/jquery-1.7.1.min.js");
6 | $data->script("terminal/js/jquery.mousewheel-min.js");
7 | $data->script("terminal/js/jquery.terminal.min.js");
8 | $data->script("https://unpkg.com/js-polyfills/keyboard.js");
9 | $data->link("terminal/css/jquery.terminal.min.css");
10 | ?>
11 |
12 |
13 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | system as $datum) { ?>
41 |
42 | |
43 | |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | ext as $datum) { ?>
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
128 |
--------------------------------------------------------------------------------
/exec/Controller/Postmen.php:
--------------------------------------------------------------------------------
1 | reader = new AnnotationReader();
25 |
26 | $postmen = [];
27 |
28 | foreach ($loader->scanClass() as $className) {
29 |
30 | // Annotation reader
31 | $reflectionClass = new \ReflectionClass($className);
32 |
33 | // Fix ignore abstract
34 | if ($reflectionClass->isAbstract()) {
35 | continue;
36 | }
37 | $postmen['info'] = $this->getInfo();
38 | $classAnnotations = $this->reader->getClassAnnotations($reflectionClass);
39 | // 类注解遍历
40 | foreach ($classAnnotations as $classAnnotation) {
41 | if ($classAnnotation instanceof Controller) {
42 | $postmen['item'][] = $this->getControllerItem($reflectionClass, $classAnnotation);
43 | }
44 | }
45 | }
46 |
47 | return $postmen;
48 | }
49 |
50 | protected function getInfo()
51 | {
52 | return [
53 | "_postman_id" => uniqid(),
54 | "name" => "swoft-admin",
55 | "schema" => "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
56 | ];
57 | }
58 |
59 | protected function getControllerItem(\ReflectionClass $reflectionClass, Controller $classAnnotation)
60 | {
61 | $itemList = [];
62 | $itemList['name'] = $this->getTitle($reflectionClass->getDocComment());
63 | $itemList['item'] = [];
64 |
65 |
66 | $contr = new ReflectionRoute();
67 |
68 | $reflectionMethods = $reflectionClass->getMethods();
69 | foreach ($reflectionMethods as $reflectionMethod) {
70 | $action = [];
71 | $action['name'] = $this->getTitle($reflectionMethod->getDocComment());
72 | $action['event'] = $this->getTestEvent();
73 |
74 | $prefix = $classAnnotation->getPrefix();
75 | $url = [];
76 | $methodAnnotations = $this->reader->getMethodAnnotations($reflectionMethod);
77 | if (!empty($methodAnnotations)) {
78 | foreach ($methodAnnotations as $annotation) {
79 | $url = [];
80 | $body = [];
81 | if ($annotation instanceof RequestMapping) {
82 | $path = $prefix ? $prefix."/".$annotation->getRoute() : $annotation->getRoute();
83 | $url['raw'] = "{{url}}".$path;
84 | $url['host'] = ["{{url}}"];
85 | $url['path'] = explode('/', $path);
86 | $methods = $annotation->getMethod();
87 | $method = "GET";
88 | if ($methods) {
89 | $method = reset($methods);
90 | }
91 | $routesInfo = $contr->getParameters($reflectionClass, $reflectionMethod);
92 |
93 | $body['mode'] = "formdata";
94 | foreach ($routesInfo['params'] ?? [] as $item) {
95 | $tem = [
96 | "key" => $item['key'],
97 | "value" => $item['default'],
98 | "description" => $item['title'],
99 | "type" => "text"
100 | ];
101 | $body['formdata'][] = $tem;
102 | }
103 | }
104 | }
105 |
106 | if ($url) {
107 | $action["request"]["url"] = $url;
108 | $action["request"]["body"] = $body;
109 | $action["request"]["method"] = $method;
110 | $itemList['item'][] = $action;
111 | }
112 | }
113 | }
114 | return $itemList;
115 | }
116 |
117 | /**
118 | * 是否生成自动测试
119 | * @return array
120 | */
121 | protected function getTestEvent()
122 | {
123 | return [
124 | [
125 | "listen" => "test",
126 | "script" => [
127 | "exec" => [
128 | "var data = JSON.parse(responseBody)",
129 | "if(data.code == '0'){",
130 | " tests[\"this code is 0\"] = true",
131 | "}else{",
132 | " tests[\"hhas error code \"] = false",
133 | "}"
134 | ],
135 | ],
136 | "type" => "text/javascript"
137 | ],
138 | ];
139 | }
140 |
141 | /**
142 | * 第一行注释
143 | * @param $doc
144 | * @return string
145 | */
146 | protected function getTitle($doc): string
147 | {
148 | $title = "";
149 |
150 | $arr = explode(PHP_EOL, $doc);
151 |
152 | foreach ($arr as $str) {
153 | $str = str_replace(["/", "*"], "", $str);
154 | $str = trim($str);
155 |
156 | if (strlen($str) > 0) {
157 | $title = $str;
158 | break;
159 | }
160 | }
161 | if ($title && $title{0} == "@") {
162 | return "";
163 | }
164 | return $title;
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/Http/Controller/LogsController.php:
--------------------------------------------------------------------------------
1 | title = "运行缓存目录";
37 | $view->listTitle = [
38 | "id" => 'ID',
39 | "path" => 'File',
40 | "size" => 'Size',
41 | ];
42 |
43 | $view->listData = $this->getFiles("@runtime/");
44 |
45 | $button = new NewWindowIcon('logs/runtimeFile', '文件内容');
46 | $button->mix = "true";
47 | $button->addField(['path' => 'path']);
48 | $view->addListButton($button);
49 |
50 | $button = new NewWindowIcon('logs/runtimeDown', '文件下载', '');
51 | $button->addField(['path' => 'path']);
52 | $view->addListButton($button);
53 |
54 | return $view->toString();
55 | }
56 |
57 | /**
58 | * 获取目录下文件列表
59 | * @param $path
60 | * @return array
61 | */
62 | private function getFiles($path)
63 | {
64 | $root = alias($path);
65 | $list = scandir($root);
66 | $arr = [];
67 | foreach ($list as $key => $item) {
68 | if (in_array($item, ['', '.', '..']) || is_dir($root.$item)) {
69 | continue;
70 | }
71 | $arr[] = [
72 | 'path' => $path.$item,
73 | 'size' => filesize($root.$item),
74 | ];
75 | }
76 | return $arr;
77 | }
78 |
79 | /**
80 | * 日记文件
81 | * @RequestMapping("logs")
82 | */
83 | public function logs()
84 | {
85 | $view = new Table();
86 | $view->title = "运行缓存目录";
87 | $view->listTitle = [
88 | "id" => 'ID',
89 | "path" => 'File',
90 | "size" => 'Size',
91 | ];
92 |
93 | $view->listData = $this->getFiles("@runtime/logs/");
94 |
95 | $button = new NewWindowIcon('logs/runtimeFile', "日志内容,倒序300行,如果多于300行,需要另行下载");
96 | $button->mix = "true";
97 | $button->addField(['path' => 'path']);
98 | $view->addListButton($button);
99 |
100 | $button = new NewWindowIcon('logs/runtimeDown', '文件下载', '');
101 | $button->addField(['path' => 'path']);
102 | $view->addListButton($button);
103 |
104 | return $view->toString();
105 | }
106 |
107 |
108 | /**
109 | * 查看文件
110 | * @RequestMapping("runtimeFile")
111 | * @param Request $request
112 | * @return FileContent
113 | */
114 | public function runtimeFile(Request $request)
115 | {
116 | $path = $request->get("path");
117 | $path = $this->getPath($path);
118 |
119 | $view = new Logs();
120 | $view->title = "显示控制器内容";
121 | $view->layContent = $this->getLastLines($path, 300);
122 | return $view->toString();
123 | }
124 |
125 | /**
126 | * 过滤不安全的路径
127 | * @param $path
128 | * @return string
129 | */
130 | private function getPath($path)
131 | {
132 | $path = str_replace('..', '', $path);
133 | if ( $path{0}!="@" ){
134 | Log::error("不能使用非 @ 开头路径");
135 | CLog::error("不能使用非 @ 开头路径");
136 | return "";
137 | }
138 | return alias($path);
139 | }
140 |
141 | /**
142 | * 下载日记文件
143 | * @RequestMapping("runtimeDown")
144 | * @param Request $request
145 | * @param Response $response
146 | * @return Response
147 | */
148 | public function runtimeDown(Request $request, Response $response)
149 | {
150 | $path = $request->get("path");
151 | $path = $this->getPath($path);
152 | $fileName = pathinfo($path,PATHINFO_BASENAME);
153 |
154 | $response->getCoResponse()->header('Content-Disposition',"attachment; filename={$fileName}");
155 |
156 | return $response->file($path, "application/octet-stream");
157 | }
158 |
159 | /**
160 | * 读取文件尾数行数
161 | * @param $file
162 | * @param int $line
163 | * @return string
164 | */
165 | public function getLastLines($file, $line = 1)
166 | {
167 | if (!$fp = fopen($file, 'r')) {
168 | Log::error("不能打开文件{$file}");
169 | CLog::error("不能打开文件{$file}");
170 | return "不能打开文件{$file}";
171 | }
172 | $pos = -2;
173 | $eof = "";
174 | $str = "";
175 | $head = false;
176 | while ($line > 0) {
177 | while ($eof != "\n") {
178 | if (!fseek($fp, $pos, SEEK_END)) {
179 | $eof = fgetc($fp);
180 | $pos--;
181 | } else {
182 | fseek($fp, 0, SEEK_SET);
183 | $head = true;
184 | break;
185 | }
186 | }
187 | $str .= fgets($fp);
188 | if ($head) {
189 | break;
190 | }
191 | $eof = "";
192 | $line--;
193 | }
194 | return $str;
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/exec/Controller/Controller.php:
--------------------------------------------------------------------------------
1 | getParameters($reflectionClass,$reflectionClass->getMethod($action));
37 | }
38 |
39 | /**
40 | * 获取所有路由信息
41 | * @return array
42 | * @throws \Doctrine\Common\Annotations\AnnotationException
43 | * @throws \ReflectionException
44 | * @CommandMapping(name="routes")
45 | */
46 | public function getRoutes()
47 | {
48 | $dir = Application::getDirectory('App\\Http\\Controller');
49 | return (new RoutesResource())->scanRoutes($dir);
50 | }
51 |
52 | /**
53 | * 获取控制器列表
54 | */
55 | public function getControllers()
56 | {
57 | $dir = Application::getDirectory('App\\Http\\Controller');
58 | return (new ControllerResource())->scanController($dir);
59 | }
60 |
61 | /**
62 | * 创建控制器
63 | * @param $name
64 | * @param $title
65 | * @param $mids
66 | */
67 | public function addControllers($name, $title = "无标题", ...$mids)
68 | {
69 | $data = Application::getDirs('App\\Http\\Controller');
70 | $dir = key($data);
71 |
72 | $className = $name."Controller";
73 | $path = $dir.$className.".php";
74 | if (file_exists($path)) {
75 | return;
76 | }
77 | $template = file_get_contents(Application::getTemplate("Controller"));
78 |
79 | $strMids = "";
80 | $use = "";
81 | if (is_array($mids) && $mids) {
82 | $use .= "\nuse Swoft\\Http\\Server\\Annotation\\Mapping\\Middlewares;";
83 | $use .= "\nuse Swoft\\Http\\Server\\Annotation\\Mapping\\Middleware;";
84 | foreach ($mids as $mid) {
85 | $mid = urldecode($mid);
86 | $arrT = explode("\\", $mid);
87 | $midName = end($arrT);
88 | $use .= "\nuse {$mid};";
89 | $strMids = $strMids." * @Middleware(".$midName."::class),\n";
90 | }
91 | }
92 | if ($strMids) {
93 | $strMids = "\n * @Middlewares({\n".$strMids." * })";
94 | }
95 |
96 | $template = str_replace(["{title}", "{name}", "{Middlewares}", "{use}"], [$title, $name, $strMids, $use],
97 | $template);
98 |
99 | file_put_contents($path, $template);
100 | return;
101 | }
102 |
103 | /**
104 | * 生成路由函数
105 | * @param string $con
106 | * @param string $route
107 | * @param string $function
108 | * @param string $title
109 | * @param string $method
110 | * @return bool|string
111 | * @throws \ReflectionException
112 | */
113 | public function addRoute($con, $route, $function, $title = "无注释", $method = "GET")
114 | {
115 | // 格式检查
116 | $con = urldecode($con);
117 |
118 | $reflector = new \ReflectionClass($con);
119 | $filePath = $reflector->getFileName();
120 | $content = file_get_contents($filePath);
121 | $content = trim($content);
122 | $strLen = strlen($content);
123 | $endStr = $content{$strLen - 1};
124 |
125 | if ($endStr != "}") {
126 | return "不是以 } 结束的类文件";
127 | }
128 |
129 | if (strlen($function) <= 2 || !preg_match('/^[_0-9a-z]{2,30}$/i', $function)) {
130 | return "函数名不合法";
131 | }
132 |
133 | foreach ($reflector->getMethods() as $reflectionMethod) {
134 | if ($reflectionMethod->getName() == $function) {
135 | return "函数名已存在";
136 | }
137 | }
138 |
139 | $firstStr = substr($content, 0, -1);
140 | $backups = getRootPath().'/runtime/admin/backups/';
141 | if (!is_dir($backups)) {
142 | if (!mkdir($backups, 0755, true)) {
143 | return false;
144 | }
145 | }
146 | copy($filePath, $backups.'/'.time().'.php');
147 | $newFunc = file_get_contents(__DIR__.'/../template/Route');
148 |
149 | /**
150 | * 补全命名空间引用
151 | * 必须有 use Swoft\Http\Server\Annotation\Mapping\Controller;
152 | */
153 | $needController = "use Swoft\Http\Server\Annotation\Mapping\Controller;";
154 | if (strpos($firstStr, $needController) === false) {
155 | return false;
156 | }
157 |
158 | $useArr = [
159 | "use Swoft\\Http\\Message\\Request;",
160 | "use Swoft\\Http\\Message\\Response;",
161 | "use Swoft\\Http\\Server\\Annotation\\Mapping\\RequestMapping;",
162 | "use Swoft\\Http\\Server\\Annotation\\Mapping\\RequestMethod;",
163 | ];
164 | foreach ($useArr as $use) {
165 | if (strpos($firstStr, $use) === false) {
166 | $firstStr = str_replace([$needController], [$needController."\n".$use], $firstStr);
167 | }
168 | }
169 |
170 | $newFunc = str_replace(["{title}", "{route}", "{function}", "{method}"], [$title, $route, $function, $method],
171 | $newFunc);
172 |
173 | file_put_contents($filePath, $firstStr.$newFunc);
174 | return true;
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/Command/AdminCommand.php:
--------------------------------------------------------------------------------
1 | createServer();
52 |
53 | // Check if it has started
54 | if ($server->isRunning()) {
55 | $masterPid = $server->getPid();
56 | output()->writeln("The HTTP server have been running!(PID: {$masterPid})");
57 | return;
58 | }
59 |
60 | // Startup settings
61 | $this->configStartOption($server);
62 |
63 | $settings = $server->getSetting();
64 | // Setting
65 | $workerNum = $settings['worker_num'];
66 |
67 | // Server startup parameters
68 | $mainHost = $server->getHost();
69 | $mainPort = $server->getPort();
70 | $modeName = $server->getModeName();
71 | $typeName = $server->getTypeName();
72 |
73 | // Http
74 | $panel = [
75 | 'HTTP' => [
76 | 'listen' => $mainHost . ':' . $mainPort,
77 | 'type' => $typeName,
78 | 'mode' => $modeName,
79 | 'worker' => $workerNum,
80 | ],
81 | ];
82 |
83 | // Port Listeners
84 | $panel = $this->appendPortsToPanel($server, $panel);
85 |
86 | Show::panel($panel);
87 |
88 | output()->writeln('HTTP server start success !');
89 |
90 | // Start the server
91 | $server->start();
92 | }
93 |
94 | /**
95 | * Reload worker processes
96 | *
97 | * @CommandMapping(usage="{fullCommand} [-t]")
98 | * @CommandOption("t", desc="Only to reload task processes, default to reload worker and task")
99 | *
100 | * @throws ReflectionException
101 | * @throws ContainerException
102 | */
103 | public function reload(): void
104 | {
105 | $server = $this->createServer();
106 | $script = input()->getScriptFile();
107 |
108 | // Check if it has started
109 | if (!$server->isRunning()) {
110 | output()->writeln('The HTTP server is not running! cannot reload');
111 | return;
112 | }
113 |
114 | output()->writef('Server %s is reloading', $script);
115 |
116 | if ($reloadTask = input()->hasOpt('t')) {
117 | Show::notice('Will only reload task worker');
118 | }
119 |
120 | if (!$server->reload($reloadTask)) {
121 | Show::error('The swoole server worker process reload fail!');
122 | return;
123 | }
124 |
125 | output()->writef('HTTP server %s reload success', $script);
126 | }
127 |
128 | /**
129 | * Stop the currently running server
130 | *
131 | * @CommandMapping()
132 | *
133 | * @throws ReflectionException
134 | * @throws ContainerException
135 | */
136 | public function stop(): void
137 | {
138 | $server = $this->createServer();
139 |
140 | // Check if it has started
141 | if (!$server->isRunning()) {
142 | output()->writeln('The HTTP server is not running! cannot stop.');
143 | return;
144 | }
145 |
146 | // Do stopping.
147 | $server->stop();
148 | }
149 |
150 | /**
151 | * Restart the http server
152 | *
153 | * @CommandMapping(usage="{fullCommand} [-d|--daemon]",)
154 | * @CommandOption("daemon", short="d", desc="Run server on the background")
155 | *
156 | * @throws ReflectionException
157 | * @throws ContainerException
158 | * @example
159 | * {fullCommand}
160 | * {fullCommand} -d
161 | */
162 | public function restart(): void
163 | {
164 | $server = $this->createServer();
165 |
166 | // Check if it has started
167 | if ($server->isRunning()) {
168 | $success = $server->stop();
169 |
170 | if (!$success) {
171 | output()->error('Stop the old server failed!');
172 | return;
173 | }
174 | }
175 |
176 | output()->writef('Server HTTP restart success !');
177 | $server->startWithDaemonize();
178 | }
179 |
180 | /**
181 | * @return Server
182 | * @throws ReflectionException
183 | * @throws ContainerException
184 | */
185 | private function createServer(): Server
186 | {
187 | $script = input()->getScriptFile();
188 | $command = $this->getFullCommand();
189 |
190 | /** @var Server $server */
191 | $server = bean('adminServer');
192 | $server->setScriptFile(Swoft::app()->getPath($script));
193 | $server->setFullCommand($command);
194 |
195 | return $server;
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/public/lib/layui/layui.js:
--------------------------------------------------------------------------------
1 | /** layui-v2.4.5 MIT License By https://www.layui.com */
2 | ;!function(e){"use strict";var t=document,o={modules:{},status:{},timeout:10,event:{}},n=function(){this.v="2.4.5"},r=function(){var e=t.currentScript?t.currentScript.src:function(){for(var e,o=t.scripts,n=o.length-1,r=n;r>0;r--)if("interactive"===o[r].readyState){e=o[r].src;break}return e||o[n].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),i=function(t){e.console&&console.error&&console.error("Layui hint: "+t)},a="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),u={layer:"modules/layer",laydate:"modules/laydate",laypage:"modules/laypage",laytpl:"modules/laytpl",layim:"modules/layim",layedit:"modules/layedit",form:"modules/form",upload:"modules/upload",tree:"modules/tree",table:"modules/table",element:"modules/element",rate:"modules/rate",colorpicker:"modules/colorpicker",slider:"modules/slider",carousel:"modules/carousel",flow:"modules/flow",util:"modules/util",code:"modules/code",jquery:"modules/jquery",mobile:"modules/mobile","layui.all":"../layui.all"};n.prototype.cache=o,n.prototype.define=function(e,t){var n=this,r="function"==typeof e,i=function(){var e=function(e,t){layui[e]=t,o.status[e]=!0};return"function"==typeof t&&t(function(n,r){e(n,r),o.callback[n]=function(){t(e)}}),this};return r&&(t=e,e=[]),layui["layui.all"]||!layui["layui.all"]&&layui["layui.mobile"]?i.call(n):(n.use(e,i),n)},n.prototype.use=function(e,n,l){function s(e,t){var n="PLaySTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/;("load"===e.type||n.test((e.currentTarget||e.srcElement).readyState))&&(o.modules[f]=t,d.removeChild(v),function r(){return++m>1e3*o.timeout/4?i(f+" is not a valid module"):void(o.status[f]?c():setTimeout(r,4))}())}function c(){l.push(layui[f]),e.length>1?y.use(e.slice(1),n,l):"function"==typeof n&&n.apply(layui,l)}var y=this,p=o.dir=o.dir?o.dir:r,d=t.getElementsByTagName("head")[0];e="string"==typeof e?[e]:e,window.jQuery&&jQuery.fn.on&&(y.each(e,function(t,o){"jquery"===o&&e.splice(t,1)}),layui.jquery=layui.$=jQuery);var f=e[0],m=0;if(l=l||[],o.host=o.host||(p.match(/\/\/([\s\S]+?)\//)||["//"+location.host+"/"])[0],0===e.length||layui["layui.all"]&&u[f]||!layui["layui.all"]&&layui["layui.mobile"]&&u[f])return c(),y;if(o.modules[f])!function g(){return++m>1e3*o.timeout/4?i(f+" is not a valid module"):void("string"==typeof o.modules[f]&&o.status[f]?c():setTimeout(g,4))}();else{var v=t.createElement("script"),h=(u[f]?p+"lay/":/^\{\/\}/.test(y.modules[f])?"":o.base||"")+(y.modules[f]||f)+".js";h=h.replace(/^\{\/\}/,""),v.async=!0,v.charset="utf-8",v.src=h+function(){var e=o.version===!0?o.v||(new Date).getTime():o.version||"";return e?"?v="+e:""}(),d.appendChild(v),!v.attachEvent||v.attachEvent.toString&&v.attachEvent.toString().indexOf("[native code")<0||a?v.addEventListener("load",function(e){s(e,h)},!1):v.attachEvent("onreadystatechange",function(e){s(e,h)}),o.modules[f]=h}return y},n.prototype.getStyle=function(t,o){var n=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return n[n.getPropertyValue?"getPropertyValue":"getAttribute"](o)},n.prototype.link=function(e,n,r){var a=this,u=t.createElement("link"),l=t.getElementsByTagName("head")[0];"string"==typeof n&&(r=n);var s=(r||e).replace(/\.|\//g,""),c=u.id="layuicss-"+s,y=0;return u.rel="stylesheet",u.href=e+(o.debug?"?v="+(new Date).getTime():""),u.media="all",t.getElementById(c)||l.appendChild(u),"function"!=typeof n?a:(function p(){return++y>1e3*o.timeout/100?i(e+" timeout"):void(1989===parseInt(a.getStyle(t.getElementById(c),"width"))?function(){n()}():setTimeout(p,100))}(),a)},o.callback={},n.prototype.factory=function(e){if(layui[e])return"function"==typeof o.callback[e]?o.callback[e]:null},n.prototype.addcss=function(e,t,n){return layui.link(o.dir+"css/"+e,t,n)},n.prototype.img=function(e,t,o){var n=new Image;return n.src=e,n.complete?t(n):(n.onload=function(){n.onload=null,"function"==typeof t&&t(n)},void(n.onerror=function(e){n.onerror=null,"function"==typeof o&&o(e)}))},n.prototype.config=function(e){e=e||{};for(var t in e)o[t]=e[t];return this},n.prototype.modules=function(){var e={};for(var t in u)e[t]=u[t];return e}(),n.prototype.extend=function(e){var t=this;e=e||{};for(var o in e)t[o]||t.modules[o]?i("模块名 "+o+" 已被占用"):t.modules[o]=e[o];return t},n.prototype.router=function(e){var t=this,e=e||location.hash,o={path:[],search:{},hash:(e.match(/[^#](#.*$)/)||[])[1]||""};return/^#\//.test(e)?(e=e.replace(/^#\//,""),o.href="/"+e,e=e.replace(/([^#])(#.*$)/,"$1").split("/")||[],t.each(e,function(e,t){/^\w+=/.test(t)?function(){t=t.split("="),o.search[t[0]]=t[1]}():o.path.push(t)}),o):o},n.prototype.data=function(t,o,n){if(t=t||"layui",n=n||localStorage,e.JSON&&e.JSON.parse){if(null===o)return delete n[t];o="object"==typeof o?o:{key:o};try{var r=JSON.parse(n[t])}catch(i){var r={}}return"value"in o&&(r[o.key]=o.value),o.remove&&delete r[o.key],n[t]=JSON.stringify(r),o.key?r[o.key]:r}},n.prototype.sessionData=function(e,t){return this.data(e,t,sessionStorage)},n.prototype.device=function(t){var o=navigator.userAgent.toLowerCase(),n=function(e){var t=new RegExp(e+"/([^\\s\\_\\-]+)");return e=(o.match(t)||[])[1],e||!1},r={os:function(){return/windows/.test(o)?"windows":/linux/.test(o)?"linux":/iphone|ipod|ipad|ios/.test(o)?"ios":/mac/.test(o)?"mac":void 0}(),ie:function(){return!!(e.ActiveXObject||"ActiveXObject"in e)&&((o.match(/msie\s(\d+)/)||[])[1]||"11")}(),weixin:n("micromessenger")};return t&&!r[t]&&(r[t]=n(t)),r.android=/android/.test(o),r.ios="ios"===r.os,r},n.prototype.hint=function(){return{error:i}},n.prototype.each=function(e,t){var o,n=this;if("function"!=typeof t)return n;if(e=e||[],e.constructor===Object){for(o in e)if(t.call(e[o],o,e[o]))break}else for(o=0;oi?1:r"].join("")),o=t.elem.next();(o.hasClass(u)||o.hasClass(c))&&o.remove(),a.ie&&a.ie<10&&t.elem.wrap(''),e.isFile()?(e.elemFile=t.elem,t.field=t.elem[0].name):t.elem.after(n),a.ie&&a.ie<10&&e.initIE()},p.prototype.initIE=function(){var e=this,t=e.config,n=i(''),a=i(['"].join(""));i("#"+f)[0]||i("body").append(n),t.elem.next().hasClass(c)||(e.elemFile.wrap(a),t.elem.next("."+c).append(function(){var e=[];return layui.each(t.data,function(i,t){t="function"==typeof t?t():t,e.push('')}),e.join("")}()))},p.prototype.msg=function(e){return t.msg(e,{icon:2,shift:6})},p.prototype.isFile=function(){var e=this.config.elem[0];if(e)return"input"===e.tagName.toLocaleLowerCase()&&"file"===e.type},p.prototype.preview=function(e){var i=this;window.FileReader&&layui.each(i.chooseFiles,function(i,t){var n=new FileReader;n.readAsDataURL(t),n.onload=function(){e&&e(i,t,this.result)}})},p.prototype.upload=function(e,t){var n,o=this,l=o.config,r=o.elemFile[0],u=function(){var t=0,n=0,a=e||o.files||o.chooseFiles||r.files,u=function(){l.multiple&&t+n===o.fileLength&&"function"==typeof l.allDone&&l.allDone({total:o.fileLength,successful:t,aborted:n})};layui.each(a,function(e,a){var r=new FormData;r.append(l.field,a),layui.each(l.data,function(e,i){i="function"==typeof i?i():i,r.append(e,i)}),i.ajax({url:l.url,type:"post",data:r,contentType:!1,processData:!1,dataType:"json",headers:l.headers||{},success:function(i){t++,d(e,i),u()},error:function(){n++,o.msg("请求上传接口出现异常"),m(e),u()}})})},c=function(){var e=i("#"+f);o.elemFile.parent().submit(),clearInterval(p.timer),p.timer=setInterval(function(){var i,t=e.contents().find("body");try{i=t.text()}catch(n){o.msg("获取上传后的响应信息出现异常"),clearInterval(p.timer),m()}i&&(clearInterval(p.timer),t.html(""),d(0,i))},30)},d=function(e,i){if(o.elemFile.next("."+s).remove(),r.value="","object"!=typeof i)try{i=JSON.parse(i)}catch(t){return i={},o.msg("请对上传接口返回有效JSON")}"function"==typeof l.done&&l.done(i,e||0,function(e){o.upload(e)})},m=function(e){l.auto&&(r.value=""),"function"==typeof l.error&&l.error(e||0,function(e){o.upload(e)})},h=l.exts,v=function(){var i=[];return layui.each(e||o.chooseFiles,function(e,t){i.push(t.name)}),i}(),g={preview:function(e){o.preview(e)},upload:function(e,i){var t={};t[e]=i,o.upload(t)},pushFile:function(){return o.files=o.files||{},layui.each(o.chooseFiles,function(e,i){o.files[e]=i}),o.files},resetFile:function(e,i,t){var n=new File([i],t);o.files=o.files||{},o.files[e]=n}},y=function(){if("choose"!==t&&!l.auto||(l.choose&&l.choose(g),"choose"!==t))return l.before&&l.before(g),a.ie?a.ie>9?u():c():void u()};if(v=0===v.length?r.value.match(/[^\/\\]+\..+/g)||[]||"":v,0!==v.length){switch(l.accept){case"file":if(h&&!RegExp("\\w\\.("+h+")$","i").test(escape(v)))return o.msg("选择的文件中包含不支持的格式"),r.value="";break;case"video":if(!RegExp("\\w\\.("+(h||"avi|mp4|wma|rmvb|rm|flash|3gp|flv")+")$","i").test(escape(v)))return o.msg("选择的视频中包含不支持的格式"),r.value="";break;case"audio":if(!RegExp("\\w\\.("+(h||"mp3|wav|mid")+")$","i").test(escape(v)))return o.msg("选择的音频中包含不支持的格式"),r.value="";break;default:if(layui.each(v,function(e,i){RegExp("\\w\\.("+(h||"jpg|png|gif|bmp|jpeg$")+")","i").test(escape(i))||(n=!0)}),n)return o.msg("选择的图片中包含不支持的格式"),r.value=""}if(o.fileLength=function(){var i=0,t=e||o.files||o.chooseFiles||r.files;return layui.each(t,function(){i++}),i}(),l.number&&o.fileLength>l.number)return o.msg("同时最多只能上传的数量为:"+l.number);if(l.size>0&&!(a.ie&&a.ie<10)){var F;if(layui.each(o.chooseFiles,function(e,i){if(i.size>1024*l.size){var t=l.size/1024;t=t>=1?t.toFixed(2)+"MB":l.size+"KB",r.value="",F=t}}),F)return o.msg("文件不能超过"+F)}y()}},p.prototype.events=function(){var e=this,t=e.config,o=function(i){e.chooseFiles={},layui.each(i,function(i,t){var n=(new Date).getTime();e.chooseFiles[n+"-"+i]=t})},l=function(i,n){var a=e.elemFile,o=i.length>1?i.length+"个文件":(i[0]||{}).name||a[0].value.match(/[^\/\\]+\..+/g)||[]||"";a.next().hasClass(s)&&a.next().remove(),e.upload(null,"choose"),e.isFile()||t.choose||a.after(''+o+"")};t.elem.off("upload.start").on("upload.start",function(){var a=i(this),o=a.attr("lay-data");if(o)try{o=new Function("return "+o)(),e.config=i.extend({},t,o)}catch(l){n.error("Upload element property lay-data configuration item has a syntax error: "+o)}e.config.item=a,e.elemFile[0].click()}),a.ie&&a.ie<10||t.elem.off("upload.over").on("upload.over",function(){var e=i(this);e.attr("lay-over","")}).off("upload.leave").on("upload.leave",function(){var e=i(this);e.removeAttr("lay-over")}).off("upload.drop").on("upload.drop",function(n,a){var r=i(this),u=a.originalEvent.dataTransfer.files||[];r.removeAttr("lay-over"),o(u),t.auto?e.upload(u):l(u)}),e.elemFile.off("upload.change").on("upload.change",function(){var i=this.files||[];o(i),t.auto?e.upload():l(i)}),t.bindAction.off("upload.action").on("upload.action",function(){e.upload()}),t.elem.data("haveEvents")||(e.elemFile.on("change",function(){i(this).trigger("upload.change")}),t.elem.on("click",function(){e.isFile()||i(this).trigger("upload.start")}),t.drag&&t.elem.on("dragover",function(e){e.preventDefault(),i(this).trigger("upload.over")}).on("dragleave",function(e){i(this).trigger("upload.leave")}).on("drop",function(e){e.preventDefault(),i(this).trigger("upload.drop",e)}),t.bindAction.on("click",function(){i(this).trigger("upload.action")}),t.elem.data("haveEvents",!0))},o.render=function(e){var i=new p(e);return l.call(i)},e(r,o)});
--------------------------------------------------------------------------------
/public/lib/layui/lay/modules/slider.js:
--------------------------------------------------------------------------------
1 | /** layui-v2.4.5 MIT License By https://www.layui.com */
2 | ;layui.define("jquery",function(e){"use strict";var i=layui.jquery,t={config:{},index:layui.slider?layui.slider.index+1e4:0,set:function(e){var t=this;return t.config=i.extend({},t.config,e),t},on:function(e,i){return layui.onevent.call(this,n,e,i)}},a=function(){var e=this,i=e.config;return{setValue:function(i,t){return e.slide("set",i,t||0)},config:i}},n="slider",l="layui-disabled",s="layui-slider",r="layui-slider-bar",o="layui-slider-wrap",u="layui-slider-wrap-btn",d="layui-slider-tips",v="layui-slider-input",c="layui-slider-input-txt",m="layui-slider-input-btn",p="layui-slider-hover",f=function(e){var a=this;a.index=++t.index,a.config=i.extend({},a.config,t.config,e),a.render()};f.prototype.config={type:"default",min:0,max:100,value:0,step:1,showstep:!1,tips:!0,input:!1,range:!1,height:200,disabled:!1,theme:"#009688"},f.prototype.render=function(){var e=this,t=e.config;if(t.step<1&&(t.step=1),t.maxt.min?a:t.min,t.value[1]=n>t.min?n:t.min,t.value[0]=t.value[0]>t.max?t.max:t.value[0],t.value[1]=t.value[1]>t.max?t.max:t.value[1];var r=Math.floor((t.value[0]-t.min)/(t.max-t.min)*100),v=Math.floor((t.value[1]-t.min)/(t.max-t.min)*100),m=v-r+"%";r+="%",v+="%"}else{"object"==typeof t.value&&(t.value=Math.min.apply(null,t.value)),t.valuet.max&&(t.value=t.max);var m=Math.floor((t.value-t.min)/(t.max-t.min)*100)+"%"}var p=t.disabled?"#c2c2c2":t.theme,f=''+(t.tips?'
':"")+'
'+(t.range?'
':"")+"
",h=i(t.elem),y=h.next("."+s);if(y[0]&&y.remove(),e.elemTemp=i(f),t.range?(e.elemTemp.find("."+o).eq(0).data("value",t.value[0]),e.elemTemp.find("."+o).eq(1).data("value",t.value[1])):e.elemTemp.find("."+o).data("value",t.value),h.html(e.elemTemp),"vertical"===t.type&&e.elemTemp.height(t.height+"px"),t.showstep){for(var g=(t.max-t.min)/t.step,b="",x=1;x ')}e.elemTemp.append(b)}if(t.input&&!t.range){var w=i('