diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..03cf94d --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/.idea +/.vscode +/vendor +*.log +.env +/tests/tmp +/tests/.phpunit.result.cache \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2c66292 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 walkor and contributors (see https://github.com/walkor/webman/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 9216b6b..10dbc00 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,16 @@ -# admin +# webman +High performance HTTP Service Framework for PHP based on [Workerman](https://github.com/walkor/workerman). + +# Manual + +https://www.workerman.net/doc/webman + +# Benchmarks + +https://www.techempower.com/benchmarks/#section=test&runid=9716e3cd-9e53-433c-b6c5-d2c48c9593c1&hw=ph&test=db&l=zg24n3-1r&a=2 +![image](https://user-images.githubusercontent.com/6073368/96447814-120fc980-1245-11eb-938d-6ea408716c72.png) + +## LICENSE + +MIT diff --git a/app/controller/Index.php b/app/controller/Index.php new file mode 100644 index 0000000..ed33c4f --- /dev/null +++ b/app/controller/Index.php @@ -0,0 +1,24 @@ + 'webman']); + } + + public function json(Request $request) + { + return json(['code' => 0, 'msg' => 'ok']); + } + +} diff --git a/app/functions.php b/app/functions.php new file mode 100644 index 0000000..5c9c58d --- /dev/null +++ b/app/functions.php @@ -0,0 +1,4 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace app\middleware; + +use Webman\MiddlewareInterface; +use Webman\Http\Response; +use Webman\Http\Request; + +/** + * Class StaticFile + * @package app\middleware + */ +class StaticFile implements MiddlewareInterface +{ + public function process(Request $request, callable $next): Response + { + // Access to files beginning with. Is prohibited + if (strpos($request->path(), '/.') !== false) { + return response('

403 forbidden

', 403); + } + /** @var Response $response */ + $response = $next($request); + // Add cross domain HTTP header + /*$response->withHeaders([ + 'Access-Control-Allow-Origin' => '*', + 'Access-Control-Allow-Credentials' => 'true', + ]);*/ + return $response; + } +} diff --git a/app/model/Test.php b/app/model/Test.php new file mode 100644 index 0000000..92d70e3 --- /dev/null +++ b/app/model/Test.php @@ -0,0 +1,29 @@ + + + + + + + + webman + + + +hello + + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..34352bd --- /dev/null +++ b/composer.json @@ -0,0 +1,55 @@ +{ + "name": "workerman/webman", + "type": "project", + "keywords": [ + "high performance", + "http service" + ], + "homepage": "http://www.workerman.net", + "license": "MIT", + "description": "High performance HTTP Service Framework.", + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "support": { + "email": "walkor@workerman.net", + "issues": "https://github.com/walkor/webman/issues", + "forum": "http://wenda.workerman.net/", + "wiki": "http://workerman.net/doc/webman", + "source": "https://github.com/walkor/webman" + }, + "require": { + "php": ">=7.2", + "workerman/webman-framework": "^1.4.3", + "monolog/monolog": "^2.0", + "webman/admin": "^0.2.5" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "autoload": { + "psr-4": { + "": "./", + "App\\": "./app" + }, + "files": [ + "./support/helpers.php" + ] + }, + "scripts": { + "post-package-install": [ + "support\\Plugin::install" + ], + "post-package-update": [ + "support\\Plugin::install" + ], + "pre-package-uninstall": [ + "support\\Plugin::uninstall" + ] + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..793029d --- /dev/null +++ b/composer.lock @@ -0,0 +1,3058 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "dbeb972084418cf1bdac7e8c41841e41", + "packages": [ + { + "name": "doctrine/inflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "reference": "8b7ff3e4b7de6b2c84da85637b59fd2880ecaa89", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "vimeo/psalm": "^4.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2021-10-22T20:16:43+00:00" + }, + { + "name": "gregwar/captcha", + "version": "v1.1.9", + "source": { + "type": "git", + "url": "https://github.com/Gregwar/Captcha.git", + "reference": "4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Gregwar/Captcha/zipball/4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5", + "reference": "4bb668e6b40e3205a020ca5ee4ca8cff8b8780c5", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-gd": "*", + "ext-mbstring": "*", + "php": ">=5.3.0", + "symfony/finder": "*" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "type": "captcha", + "autoload": { + "psr-4": { + "Gregwar\\": "src/Gregwar" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Passault", + "email": "g.passault@gmail.com", + "homepage": "http://www.gregwar.com/" + }, + { + "name": "Jeremy Livingston", + "email": "jeremy.j.livingston@gmail.com" + } + ], + "description": "Captcha generator", + "homepage": "https://github.com/Gregwar/Captcha", + "keywords": [ + "bot", + "captcha", + "spam" + ], + "support": { + "issues": "https://github.com/Gregwar/Captcha/issues", + "source": "https://github.com/Gregwar/Captcha/tree/master" + }, + "time": "2020-03-24T14:39:05+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.4.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/69568e4293f4fa993f3b0e51c9723e1e17c41379", + "reference": "69568e4293f4fa993f3b0e51c9723e1e17c41379", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.4.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2022-08-28T14:45:39+00:00" + }, + { + "name": "illuminate/bus", + "version": "v8.83.23", + "source": { + "type": "git", + "url": "https://github.com/illuminate/bus.git", + "reference": "d2a8ae4bfd881086e55455e470776358eab27eae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/bus/zipball/d2a8ae4bfd881086e55455e470776358eab27eae", + "reference": "d2a8ae4bfd881086e55455e470776358eab27eae", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/pipeline": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" + }, + "suggest": { + "illuminate/queue": "Required to use closures when chaining jobs (^7.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Bus\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Bus package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-03-07T15:02:42+00:00" + }, + { + "name": "illuminate/collections", + "version": "v8.83.23", + "source": { + "type": "git", + "url": "https://github.com/illuminate/collections.git", + "reference": "705a4e1ef93cd492c45b9b3e7911cccc990a07f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/collections/zipball/705a4e1ef93cd492c45b9b3e7911cccc990a07f4", + "reference": "705a4e1ef93cd492c45b9b3e7911cccc990a07f4", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "php": "^7.3|^8.0" + }, + "suggest": { + "symfony/var-dumper": "Required to use the dump method (^5.4)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Collections package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-06-23T15:29:49+00:00" + }, + { + "name": "illuminate/container", + "version": "v8.83.23", + "source": { + "type": "git", + "url": "https://github.com/illuminate/container.git", + "reference": "14062628d05f75047c5a1360b9350028427d568e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/container/zipball/14062628d05f75047c5a1360b9350028427d568e", + "reference": "14062628d05f75047c5a1360b9350028427d568e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "illuminate/contracts": "^8.0", + "php": "^7.3|^8.0", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Container\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Container package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-02-02T21:03:35+00:00" + }, + { + "name": "illuminate/contracts", + "version": "v8.83.23", + "source": { + "type": "git", + "url": "https://github.com/illuminate/contracts.git", + "reference": "5e0fd287a1b22a6b346a9f7cd484d8cf0234585d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/5e0fd287a1b22a6b346a9f7cd484d8cf0234585d", + "reference": "5e0fd287a1b22a6b346a9f7cd484d8cf0234585d", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.3|^8.0", + "psr/container": "^1.0", + "psr/simple-cache": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Contracts package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-01-13T14:47:47+00:00" + }, + { + "name": "illuminate/database", + "version": "v8.83.23", + "source": { + "type": "git", + "url": "https://github.com/illuminate/database.git", + "reference": "9c056fddf7c2a5cf30d3ffac3c7e50fcc5f8a322" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/database/zipball/9c056fddf7c2a5cf30d3ffac3c7e50fcc5f8a322", + "reference": "9c056fddf7c2a5cf30d3ffac3c7e50fcc5f8a322", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "illuminate/collections": "^8.0", + "illuminate/container": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0", + "symfony/console": "^5.4" + }, + "suggest": { + "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.13.3|^3.1.4).", + "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", + "illuminate/console": "Required to use the database commands (^8.0).", + "illuminate/events": "Required to use the observers with Eloquent (^8.0).", + "illuminate/filesystem": "Required to use the migrations (^8.0).", + "illuminate/pagination": "Required to paginate the result set (^8.0).", + "symfony/finder": "Required to use Eloquent model factories (^5.4)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Database\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Database package.", + "homepage": "https://laravel.com", + "keywords": [ + "database", + "laravel", + "orm", + "sql" + ], + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-07-14T14:03:49+00:00" + }, + { + "name": "illuminate/events", + "version": "v8.83.23", + "source": { + "type": "git", + "url": "https://github.com/illuminate/events.git", + "reference": "b7f06cafb6c09581617f2ca05d69e9b159e5a35d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/events/zipball/b7f06cafb6c09581617f2ca05d69e9b159e5a35d", + "reference": "b7f06cafb6c09581617f2ca05d69e9b159e5a35d", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "illuminate/bus": "^8.0", + "illuminate/collections": "^8.0", + "illuminate/container": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Illuminate\\Events\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Events package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2021-09-15T14:32:50+00:00" + }, + { + "name": "illuminate/macroable", + "version": "v8.83.23", + "source": { + "type": "git", + "url": "https://github.com/illuminate/macroable.git", + "reference": "aed81891a6e046fdee72edd497f822190f61c162" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/aed81891a6e046fdee72edd497f822190f61c162", + "reference": "aed81891a6e046fdee72edd497f822190f61c162", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.3|^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Macroable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2021-11-16T13:57:03+00:00" + }, + { + "name": "illuminate/pagination", + "version": "v8.83.23", + "source": { + "type": "git", + "url": "https://github.com/illuminate/pagination.git", + "reference": "16fe8dc35f9d18c58a3471469af656a02e9ab692" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/pagination/zipball/16fe8dc35f9d18c58a3471469af656a02e9ab692", + "reference": "16fe8dc35f9d18c58a3471469af656a02e9ab692", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Pagination\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Pagination package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-06-27T13:26:06+00:00" + }, + { + "name": "illuminate/pipeline", + "version": "v8.83.23", + "source": { + "type": "git", + "url": "https://github.com/illuminate/pipeline.git", + "reference": "23aeff5b26ae4aee3f370835c76bd0f4e93f71d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/pipeline/zipball/23aeff5b26ae4aee3f370835c76bd0f4e93f71d2", + "reference": "23aeff5b26ae4aee3f370835c76bd0f4e93f71d2", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "illuminate/contracts": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Pipeline\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Pipeline package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2021-03-26T18:39:16+00:00" + }, + { + "name": "illuminate/support", + "version": "v8.83.23", + "source": { + "type": "git", + "url": "https://github.com/illuminate/support.git", + "reference": "c3d643e77082786ae8a51502c757e9b1a3ee254e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/support/zipball/c3d643e77082786ae8a51502c757e9b1a3ee254e", + "reference": "c3d643e77082786ae8a51502c757e9b1a3ee254e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "doctrine/inflector": "^1.4|^2.0", + "ext-json": "*", + "ext-mbstring": "*", + "illuminate/collections": "^8.0", + "illuminate/contracts": "^8.0", + "illuminate/macroable": "^8.0", + "nesbot/carbon": "^2.53.1", + "php": "^7.3|^8.0", + "voku/portable-ascii": "^1.6.1" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "suggest": { + "illuminate/filesystem": "Required to use the composer class (^8.0).", + "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^1.3|^2.0.2).", + "ramsey/uuid": "Required to use Str::uuid() (^4.2.2).", + "symfony/process": "Required to use the composer class (^5.4).", + "symfony/var-dumper": "Required to use the dd function (^5.4).", + "vlucas/phpdotenv": "Required to use the Env class and env helper (^5.4.1)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Support package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-06-27T13:26:30+00:00" + }, + { + "name": "intervention/image", + "version": "2.7.2", + "source": { + "type": "git", + "url": "https://github.com/Intervention/image.git", + "reference": "04be355f8d6734c826045d02a1079ad658322dad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Intervention/image/zipball/04be355f8d6734c826045d02a1079ad658322dad", + "reference": "04be355f8d6734c826045d02a1079ad658322dad", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "guzzlehttp/psr7": "~1.1 || ^2.0", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "~0.9.2", + "phpunit/phpunit": "^4.8 || ^5.7 || ^7.5.15" + }, + "suggest": { + "ext-gd": "to use GD library based image processing.", + "ext-imagick": "to use Imagick based image processing.", + "intervention/imagecache": "Caching extension for the Intervention Image library" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + }, + "laravel": { + "providers": [ + "Intervention\\Image\\ImageServiceProvider" + ], + "aliases": { + "Image": "Intervention\\Image\\Facades\\Image" + } + } + }, + "autoload": { + "psr-4": { + "Intervention\\Image\\": "src/Intervention/Image" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oliver Vogel", + "email": "oliver@intervention.io", + "homepage": "https://intervention.io/" + } + ], + "description": "Image handling and manipulation library with support for Laravel integration", + "homepage": "http://image.intervention.io/", + "keywords": [ + "gd", + "image", + "imagick", + "laravel", + "thumbnail", + "watermark" + ], + "support": { + "issues": "https://github.com/Intervention/image/issues", + "source": "https://github.com/Intervention/image/tree/2.7.2" + }, + "funding": [ + { + "url": "https://paypal.me/interventionio", + "type": "custom" + }, + { + "url": "https://github.com/Intervention", + "type": "github" + } + ], + "time": "2022-05-21T17:30:32+00:00" + }, + { + "name": "monolog/monolog", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "720488632c590286b88b80e62aa3d3d551ad4a50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/720488632c590286b88b80e62aa3d3d551ad4a50", + "reference": "720488632c590286b88b80e62aa3d3d551ad4a50", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2", + "guzzlehttp/guzzle": "^7.4", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpspec/prophecy": "^1.15", + "phpstan/phpstan": "^0.12.91", + "phpunit/phpunit": "^8.5.14", + "predis/predis": "^1.1 || ^2.0", + "rollbar/rollbar": "^1.3 || ^2 || ^3", + "ruflin/elastica": "^7", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/2.8.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2022-07-24T11:55:47+00:00" + }, + { + "name": "nesbot/carbon", + "version": "2.62.1", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "01bc4cdefe98ef58d1f9cb31bdbbddddf2a88f7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/01bc4cdefe98ef58d1f9cb31bdbbddddf2a88f7a", + "reference": "01bc4cdefe98ef58d1f9cb31bdbbddddf2a88f7a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "doctrine/dbal": "^2.0 || ^3.0", + "doctrine/orm": "^2.7", + "friendsofphp/php-cs-fixer": "^3.0", + "kylekatarnls/multi-tester": "^2.0", + "ondrejmirtes/better-reflection": "*", + "phpmd/phpmd": "^2.9", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.99 || ^1.7.14", + "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6", + "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", + "squizlabs/php_codesniffer": "^3.4" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.x-dev", + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/briannesbitt/Carbon/issues", + "source": "https://github.com/briannesbitt/Carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2022-09-02T07:48:13+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "psr/container", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.1" + }, + "time": "2021-03-05T17:36:06+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "time": "2017-10-23T01:57:42+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "symfony/console", + "version": "v5.4.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/c072aa8f724c3af64e2c7a96b796a4863d24dba1", + "reference": "c072aa8f724c3af64e2c7a96b796a4863d24dba1", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" + }, + "conflict": { + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0" + }, + "require-dev": { + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v5.4.12" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-08-17T13:18:05+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:53:40+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.4.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/7872a66f57caffa2916a584db1aa7f12adc76f8c", + "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.4.11" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-29T07:37:50+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "433d05519ce6990bf3530fba6957499d327395c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", + "reference": "433d05519ce6990bf3530fba6957499d327395c2", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-php73", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-10T07:21:04+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-30T19:17:29+00:00" + }, + { + "name": "symfony/string", + "version": "v5.4.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "2fc515e512d721bf31ea76bd02fe23ada4640058" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/2fc515e512d721bf31ea76bd02fe23ada4640058", + "reference": "2fc515e512d721bf31ea76bd02fe23ada4640058", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" + }, + "conflict": { + "symfony/translation-contracts": ">=3.0" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/http-client": "^4.4|^5.0|^6.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v5.4.12" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-08-12T17:03:11+00:00" + }, + { + "name": "symfony/translation", + "version": "v5.4.12", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "42ecc77eb4f229ce2df702a648ec93b8478d76ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/42ecc77eb4f229ce2df702a648ec93b8478d76ae", + "reference": "42ecc77eb4f229ce2df702a648ec93b8478d76ae", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation-contracts": "^2.3" + }, + "conflict": { + "symfony/config": "<4.4", + "symfony/console": "<5.3", + "symfony/dependency-injection": "<5.0", + "symfony/http-kernel": "<5.0", + "symfony/twig-bundle": "<5.0", + "symfony/yaml": "<4.4" + }, + "provide": { + "symfony/translation-implementation": "2.3" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.0|^6.0", + "symfony/finder": "^4.4|^5.0|^6.0", + "symfony/http-client-contracts": "^1.1|^2.0|^3.0", + "symfony/http-kernel": "^5.0|^6.0", + "symfony/intl": "^4.4|^5.0|^6.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/service-contracts": "^1.1.2|^2|^3", + "symfony/yaml": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v5.4.12" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-08-02T15:52:22+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/136b19dd05cdf0709db6537d058bcab6dd6e2dbe", + "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-27T16:58:25+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "87337c91b9dfacee02452244ee14ab3c43bc485a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/87337c91b9dfacee02452244ee14ab3c43bc485a", + "reference": "87337c91b9dfacee02452244ee14ab3c43bc485a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "http://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/1.6.1" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2022-01-24T18:55:24+00:00" + }, + { + "name": "webman/admin", + "version": "v0.2.5", + "source": { + "type": "git", + "url": "https://github.com/webman-php/admin.git", + "reference": "462b8ade91dbf5c60d0fc567e1098b98a00cafab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/admin/zipball/462b8ade91dbf5c60d0fc567e1098b98a00cafab", + "reference": "462b8ade91dbf5c60d0fc567e1098b98a00cafab", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "gregwar/captcha": "^1.1.0", + "illuminate/database": ">=7.30", + "illuminate/events": ">=7.30", + "illuminate/pagination": ">=7.30", + "intervention/image": "^2.7", + "webman/event": "^1.0", + "workerman/webman-framework": "^1.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webman\\Admin\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Webman plugin webman/admin", + "support": { + "issues": "https://github.com/webman-php/admin/issues", + "source": "https://github.com/webman-php/admin/tree/v0.2.5" + }, + "time": "2022-09-02T07:57:01+00:00" + }, + { + "name": "webman/event", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/webman-php/event.git", + "reference": "db17d2fd6a5a8799f97f587b17a26b814901e01a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/event/zipball/db17d2fd6a5a8799f97f587b17a26b814901e01a", + "reference": "db17d2fd6a5a8799f97f587b17a26b814901e01a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "type": "library", + "autoload": { + "psr-4": { + "Webman\\Event\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Webman event plugin", + "support": { + "issues": "https://github.com/webman-php/event/issues", + "source": "https://github.com/webman-php/event/tree/v1.0.2" + }, + "time": "2022-06-16T06:25:20+00:00" + }, + { + "name": "workerman/webman-framework", + "version": "v1.4.5", + "source": { + "type": "git", + "url": "https://github.com/walkor/webman-framework.git", + "reference": "1860a6a4c670b5ac85a1b44d019466f2ea49742e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/webman-framework/zipball/1860a6a4c670b5ac85a1b44d019466f2ea49742e", + "reference": "1860a6a4c670b5ac85a1b44d019466f2ea49742e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "nikic/fast-route": "^1.3", + "php": ">=7.2", + "psr/container": ">=1.0", + "workerman/workerman": "^4.0.4" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "type": "library", + "autoload": { + "psr-4": { + "Webman\\": "./src", + "Support\\": "./src/support", + "support\\": "./src/support", + "Support\\View\\": "./src/support/view", + "Support\\Bootstrap\\": "./src/support/bootstrap", + "Support\\Exception\\": "./src/support/exception" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "https://www.workerman.net", + "role": "Developer" + } + ], + "description": "High performance HTTP Service Framework.", + "homepage": "https://www.workerman.net", + "keywords": [ + "High Performance", + "http service" + ], + "support": { + "email": "walkor@workerman.net", + "forum": "https://wenda.workerman.net/", + "issues": "https://github.com/walkor/webman/issues", + "source": "https://github.com/walkor/webman-framework", + "wiki": "https://doc.workerman.net/" + }, + "funding": [ + { + "url": "https://opencollective.com/walkor", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/walkor", + "type": "patreon" + } + ], + "time": "2022-09-02T12:46:49+00:00" + }, + { + "name": "workerman/workerman", + "version": "v4.0.42", + "source": { + "type": "git", + "url": "https://github.com/walkor/workerman.git", + "reference": "83f09b50eaf7412504604030daa9e1f9c767e6c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/workerman/zipball/83f09b50eaf7412504604030daa9e1f9c767e6c3", + "reference": "83f09b50eaf7412504604030daa9e1f9c767e6c3", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.4" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "type": "library", + "autoload": { + "psr-4": { + "Workerman\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", + "homepage": "http://www.workerman.net", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "email": "walkor@workerman.net", + "forum": "http://wenda.workerman.net/", + "issues": "https://github.com/walkor/workerman/issues", + "source": "https://github.com/walkor/workerman", + "wiki": "http://doc.workerman.net/" + }, + "funding": [ + { + "url": "https://opencollective.com/workerman", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/walkor", + "type": "patreon" + } + ], + "time": "2022-07-29T09:06:29+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.2" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/config/app.php b/config/app.php new file mode 100644 index 0000000..7e44e6a --- /dev/null +++ b/config/app.php @@ -0,0 +1,26 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\Request; + +return [ + 'debug' => true, + 'error_reporting' => E_ALL, + 'default_timezone' => 'Asia/Shanghai', + 'request_class' => Request::class, + 'public_path' => base_path() . DIRECTORY_SEPARATOR . 'public', + 'runtime_path' => base_path(false) . DIRECTORY_SEPARATOR . 'runtime', + 'controller_suffix' => '', + 'controller_reuse' => true, +]; diff --git a/config/autoload.php b/config/autoload.php new file mode 100644 index 0000000..69a8135 --- /dev/null +++ b/config/autoload.php @@ -0,0 +1,21 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'files' => [ + base_path() . '/app/functions.php', + base_path() . '/support/Request.php', + base_path() . '/support/Response.php', + ] +]; diff --git a/config/bootstrap.php b/config/bootstrap.php new file mode 100644 index 0000000..44054e0 --- /dev/null +++ b/config/bootstrap.php @@ -0,0 +1,18 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + support\bootstrap\Session::class, + support\bootstrap\LaravelDb::class, +]; diff --git a/config/container.php b/config/container.php new file mode 100644 index 0000000..106b7b4 --- /dev/null +++ b/config/container.php @@ -0,0 +1,15 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return new Webman\Container; \ No newline at end of file diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..7dc463a --- /dev/null +++ b/config/database.php @@ -0,0 +1,15 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return []; diff --git a/config/dependence.php b/config/dependence.php new file mode 100644 index 0000000..8e964ed --- /dev/null +++ b/config/dependence.php @@ -0,0 +1,15 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return []; \ No newline at end of file diff --git a/config/event.php b/config/event.php new file mode 100644 index 0000000..28a3b2c --- /dev/null +++ b/config/event.php @@ -0,0 +1,5 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + '' => support\exception\Handler::class, +]; \ No newline at end of file diff --git a/config/log.php b/config/log.php new file mode 100644 index 0000000..7f05de5 --- /dev/null +++ b/config/log.php @@ -0,0 +1,32 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'default' => [ + 'handlers' => [ + [ + 'class' => Monolog\Handler\RotatingFileHandler::class, + 'constructor' => [ + runtime_path() . '/logs/webman.log', + 7, //$maxFiles + Monolog\Logger::DEBUG, + ], + 'formatter' => [ + 'class' => Monolog\Formatter\LineFormatter::class, + 'constructor' => [null, 'Y-m-d H:i:s', true], + ], + ] + ], + ], +]; diff --git a/config/middleware.php b/config/middleware.php new file mode 100644 index 0000000..8e964ed --- /dev/null +++ b/config/middleware.php @@ -0,0 +1,15 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return []; \ No newline at end of file diff --git a/config/plugin/webman/event/app.php b/config/plugin/webman/event/app.php new file mode 100644 index 0000000..8f9c426 --- /dev/null +++ b/config/plugin/webman/event/app.php @@ -0,0 +1,4 @@ + true, +]; \ No newline at end of file diff --git a/config/plugin/webman/event/bootstrap.php b/config/plugin/webman/event/bootstrap.php new file mode 100644 index 0000000..e5b09ba --- /dev/null +++ b/config/plugin/webman/event/bootstrap.php @@ -0,0 +1,17 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + Webman\Event\BootStrap::class, +]; diff --git a/config/plugin/webman/event/command.php b/config/plugin/webman/event/command.php new file mode 100644 index 0000000..e860cf7 --- /dev/null +++ b/config/plugin/webman/event/command.php @@ -0,0 +1,7 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + + +return [ + // File update detection and automatic reload + 'monitor' => [ + 'handler' => process\Monitor::class, + 'reloadable' => false, + 'constructor' => [ + // Monitor these directories + 'monitor_dir' => [ + app_path(), + config_path(), + base_path() . '/process', + base_path() . '/support', + base_path() . '/resource', + base_path() . '/.env', + ], + // Files with these suffixes will be monitored + 'monitor_extensions' => [ + 'php', 'html', 'htm', 'env' + ] + ] + ] +]; diff --git a/config/redis.php b/config/redis.php new file mode 100644 index 0000000..2f9757a --- /dev/null +++ b/config/redis.php @@ -0,0 +1,22 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'default' => [ + 'host' => '127.0.0.1', + 'password' => null, + 'port' => 6379, + 'database' => 0, + ], +]; diff --git a/config/route.php b/config/route.php new file mode 100644 index 0000000..a5064fc --- /dev/null +++ b/config/route.php @@ -0,0 +1,21 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use Webman\Route; + + + + + + diff --git a/config/server.php b/config/server.php new file mode 100644 index 0000000..a65ac6a --- /dev/null +++ b/config/server.php @@ -0,0 +1,31 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'listen' => 'http://0.0.0.0:8787', + 'transport' => 'tcp', + 'context' => [], + 'name' => 'webman', + 'count' => cpu_count() * 2, + 'user' => '', + 'group' => '', + 'reusePort' => false, + 'event_loop' => '', + 'stop_timeout' => 2, + 'pid_file' => runtime_path() . '/webman.pid', + 'status_file' => runtime_path() . '/webman.status', + 'stdout_file' => runtime_path() . '/logs/stdout.log', + 'log_file' => runtime_path() . '/logs/workerman.log', + 'max_package_size' => 10 * 1024 * 1024 +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 0000000..043f8c4 --- /dev/null +++ b/config/session.php @@ -0,0 +1,65 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use Webman\Session\FileSessionHandler; +use Webman\Session\RedisSessionHandler; +use Webman\Session\RedisClusterSessionHandler; + +return [ + + 'type' => 'file', // or redis or redis_cluster + + 'handler' => FileSessionHandler::class, + + 'config' => [ + 'file' => [ + 'save_path' => runtime_path() . '/sessions', + ], + 'redis' => [ + 'host' => '127.0.0.1', + 'port' => 6379, + 'auth' => '', + 'timeout' => 2, + 'database' => '', + 'prefix' => 'redis_session_', + ], + 'redis_cluster' => [ + 'host' => ['127.0.0.1:7000', '127.0.0.1:7001', '127.0.0.1:7001'], + 'timeout' => 2, + 'auth' => '', + 'prefix' => 'redis_session_', + ] + ], + + 'session_name' => 'PHPSID', + + 'auto_update_timestamp' => false, + + 'lifetime' => 7*24*60*60, + + 'cookie_lifetime' => 365*24*60*60, + + 'cookie_path' => '/', + + 'domain' => '', + + 'http_only' => true, + + 'secure' => false, + + 'same_site' => '', + + 'gc_probability' => [1, 1000], + +]; diff --git a/config/static.php b/config/static.php new file mode 100644 index 0000000..2f76cf3 --- /dev/null +++ b/config/static.php @@ -0,0 +1,23 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Static file settings + */ +return [ + 'enable' => true, + 'middleware' => [ // Static file Middleware + //app\middleware\StaticFile::class, + ], +]; \ No newline at end of file diff --git a/config/translation.php b/config/translation.php new file mode 100644 index 0000000..96589b2 --- /dev/null +++ b/config/translation.php @@ -0,0 +1,25 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Multilingual configuration + */ +return [ + // Default language + 'locale' => 'zh_CN', + // Fallback language + 'fallback_locale' => ['zh_CN', 'en'], + // Folder where language files are stored + 'path' => base_path() . '/resource/translations', +]; \ No newline at end of file diff --git a/config/view.php b/config/view.php new file mode 100644 index 0000000..e3a7b85 --- /dev/null +++ b/config/view.php @@ -0,0 +1,22 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\view\Raw; +use support\view\Twig; +use support\view\Blade; +use support\view\ThinkPHP; + +return [ + 'handler' => Raw::class +]; diff --git a/plugin/admin/app/Admin.php b/plugin/admin/app/Admin.php new file mode 100644 index 0000000..8e8a116 --- /dev/null +++ b/plugin/admin/app/Admin.php @@ -0,0 +1,105 @@ +getDefaultProperties(); + $noNeedLogin = $properties['noNeedLogin'] ?? []; + $noNeedAuth = $properties['noNeedAuth'] ?? []; + + // 不需要登录 + if (in_array($action, $noNeedLogin)) { + return true; + } + + // 获取登录信息 + $admin = admin(); + if (!$admin) { + $msg = '请登录'; + // 401是未登录固定的返回码 + $code = 401; + return false; + } + + // 不需要鉴权 + if (in_array($action, $noNeedAuth)) { + return true; + } + + // 当前管理员无角色 + $roles = $admin['roles']; + if (!$roles) { + $msg = '无权限'; + $code = 2; + return false; + } + + // 角色没有规则 + $rules = AdminRole::whereIn('id', $roles)->pluck('rules'); + $rule_ids = []; + foreach ($rules as $rule_string) { + if (!$rule_string) { + continue; + } + $rule_ids = array_merge($rule_ids, explode(',', $rule_string)); + } + if (!$rule_ids) { + $msg = '无权限'; + $code = 2; + return false; + } + + // 超级管理员 + if (in_array('*', $rule_ids)){ + return true; + } + + // 没有当前控制器的规则 + $rule = AdminRule::where(function ($query) use ($controller, $action) { + $query->where('name', "$controller@$action")->orWhere('name', $controller); + })->whereIn('id', $rule_ids)->first(); + if (!$rule) { + $msg = '无权限'; + $code = 2; + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/plugin/admin/app/Util.php b/plugin/admin/app/Util.php new file mode 100644 index 0000000..be6109a --- /dev/null +++ b/plugin/admin/app/Util.php @@ -0,0 +1,141 @@ +[控件] + 'integer' => ['InputNumber'], + 'string' => ['Input'], + 'text' => ['InputTextArea'], + 'date' => ['DatePicker'], + 'enum' => ['Select'], + 'float' => ['Input'], + + 'tinyInteger' => ['InputNumber'], + 'smallInteger' => ['InputNumber'], + 'mediumInteger' => ['InputNumber'], + 'bigInteger' => ['InputNumber'], + + 'unsignedInteger' => ['InputNumber'], + 'unsignedTinyInteger' => ['InputNumber'], + 'unsignedSmallInteger' => ['InputNumber'], + 'unsignedMediumInteger' => ['InputNumber'], + 'unsignedBigInteger' => ['InputNumber'], + + 'decimal' => ['Input'], + 'double' => ['Input'], + + 'mediumText' => ['InputTextArea'], + 'longText' => ['InputTextArea'], + + 'dateTime' => ['DatePicker'], + + 'time' => ['DatePicker'], + 'timestamp' => ['DatePicker'], + + 'char' => ['Input'], + + 'binary' => ['Input'], + ]; + } + + public static function typeToControl($type) + { + if (stripos($type, 'int') !== false) { + return 'InputNumber'; + } + if (stripos($type, 'time') !== false || stripos($type, 'date') !== false) { + return 'DatePicker'; + } + if (stripos($type, 'text') !== false) { + return 'InputTextArea'; + } + if ($type === 'enum') { + return 'Select'; + } + return 'Input'; + } + + public static function typeToMethod($type, $unsigned = false) + { + if (stripos($type, 'int') !== false) { + $type = str_replace('int', 'Integer', $type); + return $unsigned ? "unsigned" . ucfirst($type) : lcfirst($type); + } + $map = [ + 'int' => 'integer', + 'varchar' => 'string', + 'mediumtext' => 'mediumText', + 'longtext' => 'longText', + 'datetime' => 'dateTime', + ]; + return $map[$type] ?? $type; + } +} \ No newline at end of file diff --git a/plugin/admin/app/controller/Base.php b/plugin/admin/app/controller/Base.php new file mode 100644 index 0000000..64cf775 --- /dev/null +++ b/plugin/admin/app/controller/Base.php @@ -0,0 +1,40 @@ + $code, 'result' => $data, 'message' => $msg, 'type' => $code ? 'error' : 'success']); + } + +} diff --git a/plugin/admin/app/controller/Crud.php b/plugin/admin/app/controller/Crud.php new file mode 100644 index 0000000..800e21d --- /dev/null +++ b/plugin/admin/app/controller/Crud.php @@ -0,0 +1,367 @@ +selectInput($request); + + $model = $this->model; + foreach ($where as $column => $value) { + if (is_array($value)) { + if (in_array($value[0], ['>', '=', '<', '<>'])) { + $model = $model->where($column, $value[0], $value[1]); + } elseif ($value[0] == 'in') { + $model = $model->whereIn($column, $value[1]); + } else { + $model = $model->whereBetween($column, $value); + } + } else { + $model = $model->where($column, $value); + } + } + $model = $model->orderBy($field, $order); + if (in_array($format, ['select', 'tree', 'table_tree'])) { + $items = $model->get(); + if ($format == 'select') { + return $this->formatSelect($items); + } elseif ($format == 'tree') { + return $this->formatTree($items); + } + return $this->formatTableTree($items); + } + + $paginator = $model->paginate($page_size); + return $this->json(0, 'ok', [ + 'items' => $paginator->items(), + 'total' => $paginator->total() + ]); + } + + /** + * 添加 + * @param Request $request + * @return \support\Response + */ + public function insert(Request $request) + { + $data = $request->post('data'); + $table = $this->model->getTable(); + $allow_column = Util::db()->select("desc `$table`"); + if (!$allow_column) { + return $this->json(2, '表不存在'); + } + $columns = array_column($allow_column, 'Field', 'Field'); + foreach ($data as $col => $item) { + if (is_array($item)) { + $data[$col] = implode(',', $item); + continue; + } + if ($col === 'password') { + $data[$col] = Util::passwordHash($item); + } + } + $datetime = date('Y-m-d H:i:s'); + if (isset($columns['created_at']) && !isset($data['created_at'])) { + $data['created_at'] = $datetime; + } + if (isset($columns['updated_at']) && !isset($data['updated_at'])) { + $data['updated_at'] = $datetime; + } + $id = $this->model->insertGetId($data); + return $this->json(0, $id); + } + + /** + * 更新 + * @param Request $request + * @return \support\Response + */ + public function update(Request $request) + { + $column = $request->post('column'); + $value = $request->post('value'); + $data = $request->post('data'); + $table = $this->model->getTable(); + $allow_column = Util::db()->select("desc `$table`"); + if (!$allow_column) { + return $this->json(2, '表不存在'); + } + foreach ($data as $col => $item) { + if (is_array($item)) { + $data[$col] = implode(',', $item); + } + if ($col === 'password') { + // 密码为空,则不更新密码 + if ($item == '') { + unset($data[$col]); + continue; + } + $data[$col] = Util::passwordHash($item); + } + } + $this->model->where($column, $value)->update($data); + return $this->json(0); + } + + /** + * 删除 + * @param Request $request + * @return \support\Response + * @throws \Support\Exception\BusinessException + */ + public function delete(Request $request) + { + $column = $request->post('column'); + $value = $request->post('value'); + $this->model->where([$column => $value])->delete(); + return $this->json(0); + } + + /** + * 摘要 + * @param Request $request + * @return \support\Response + * @throws \Support\Exception\BusinessException + */ + public function schema(Request $request) + { + $table = $this->model->getTable(); + Util::checkTableName($table); + $schema = Option::where('name', "table_form_schema_$table")->value('value'); + $form_schema_map = $schema ? json_decode($schema, true) : []; + + $data = $this->getSchema($table); + foreach ($data['forms'] as $field => $item) { + if (isset($form_schema_map[$field])) { + $data['forms'][$field] = $form_schema_map[$field]; + } + } + + return $this->json(0, 'ok', [ + 'table' => $data['table'], + 'columns' => array_values($data['columns']), + 'forms' => array_values($data['forms']), + 'keys' => array_values($data['keys']), + ]); + } + + /** + * 按表获取摘要 + * + * @param $table + * @param $section + * @return array|mixed + */ + protected function getSchema($table, $section = null) + { + $database = config('database.connections')['plugin.admin.mysql']['database']; + $schema_raw = $section !== 'table' ? Util::db()->select("select * from information_schema.COLUMNS where TABLE_SCHEMA = '$database' and table_name = '$table'") : []; + $forms = []; + $columns = []; + foreach ($schema_raw as $item) { + $field = $item->COLUMN_NAME; + $columns[$field] = [ + 'field' => $field, + 'type' => Util::typeToMethod($item->DATA_TYPE, (bool)strpos($item->COLUMN_TYPE, 'unsigned')), + 'comment' => $item->COLUMN_COMMENT, + 'default' => $item->COLUMN_DEFAULT, + 'length' => $this->getLengthValue($item), + 'nullable' => $item->IS_NULLABLE !== 'NO', + 'primary_key' => $item->COLUMN_KEY === 'PRI', + 'auto_increment' => strpos($item->EXTRA, 'auto_increment') !== false + ]; + + $forms[$field] = [ + 'field' => $field, + 'comment' => $item->COLUMN_COMMENT, + 'control' => Util::typeToControl($item->DATA_TYPE), + 'form_show' => $item->COLUMN_KEY !== 'PRI', + 'list_show' => true, + 'enable_sort' => false, + 'readonly' => $item->COLUMN_KEY === 'PRI', + 'searchable' => false, + 'search_type' => 'normal', + 'control_args' => '', + ]; + } + $table_schema = $section == 'table' || !$section ? Util::db()->select("SELECT TABLE_COMMENT FROM information_schema.`TABLES` WHERE TABLE_SCHEMA='$database' and TABLE_NAME='$table'") : []; + $indexes = $section == 'keys' || !$section ? Util::db()->select("SHOW INDEX FROM `$table`") : []; + $keys = []; + foreach ($indexes as $index) { + $key_name = $index->Key_name; + if ($key_name == 'PRIMARY') { + continue; + } + if (!isset($keys[$key_name])) { + $keys[$key_name] = [ + 'name' => $key_name, + 'columns' => [], + 'type' => $index->Non_unique == 0 ? 'unique' : 'normal' + ]; + } + $keys[$key_name]['columns'][] = $index->Column_name; + } + + $data = [ + 'table' => ['name' => $table, 'comment' => $table_schema[0]->TABLE_COMMENT ?? ''], + 'columns' => $columns, + 'forms' => $forms, + 'keys' => array_reverse($keys, true) + ]; + return $section ? $data[$section] : $data; + } + + protected function getLengthValue($schema) + { + $type = $schema->DATA_TYPE; + if (in_array($type, ['float', 'decimal', 'double'])) { + return "{$schema->NUMERIC_PRECISION},{$schema->NUMERIC_SCALE}"; + } + if ($type === 'enum') { + return implode(',', array_map(function($item){ + return trim($item, "'"); + }, explode(',', substr($schema->COLUMN_TYPE, 5, -1)))); + } + if (in_array($type, ['varchar', 'text', 'char'])) { + return $schema->CHARACTER_MAXIMUM_LENGTH; + } + if (in_array($type, ['time', 'datetime', 'timestamp'])) { + return $schema->CHARACTER_MAXIMUM_LENGTH; + } + return ''; + } + + /** + * @param Request $request + * @return array|\support\Response + */ + protected function selectInput(Request $request) + { + $field = $request->get('field'); + $order = $request->get('order', 'descend'); + $format = $request->get('format', 'normal'); + $page_size = $request->get('pageSize', $format === 'tree' ? 1000 : 10); + $order = $order === 'ascend' ? 'asc' : 'desc'; + $where = $request->get(); + $table = $this->model->getTable(); + + $allow_column = Util::db()->select("desc `$table`"); + if (!$allow_column) { + return $this->json(2, '表不存在'); + } + $allow_column = array_column($allow_column, 'Field', 'Field'); + if (!in_array($field, $allow_column)) { + $field = current($allow_column); + } + foreach ($where as $column => $value) { + if (!$value || !isset($allow_column[$column]) || + (is_array($value) && ($value[0] == 'undefined' || $value[1] == 'undefined'))) { + unset($where[$column]); + } + } + + return [$where, $format, $page_size, $field, $order]; + } + + /** + * 树 + * + * @param $items + * @return \support\Response + */ + protected function formatTree($items) + { + $items_map = []; + foreach ($items as $item) { + $items_map[$item->id] = [ + 'title' => $item->title ?? $item->name ?? $item->id, + 'value' => (string)$item->id, + 'key' => (string)$item->id, + 'pid' => $item->pid, + ]; + } + $formatted_items = []; + foreach ($items_map as $index => $item) { + if ($item['pid'] && isset($items_map[$item['pid']])) { + $items_map[$item['pid']]['children'][] = &$items_map[$index]; + } + } + foreach ($items_map as $item) { + if (!$item['pid']) { + $formatted_items[] = $item; + } + } + return $this->json(0, 'ok', $formatted_items); + } + + /** + * 表格树 + * + * @param $items + * @return \support\Response + */ + protected function formatTableTree($items) + { + $items_map = []; + foreach ($items as $item) { + $items_map[$item->id] = $item->toArray(); + } + $formatted_items = []; + foreach ($items_map as $item) { + if ($item['pid'] && isset($items_map[$item['pid']])) { + $items_map[$item['pid']]['children'][] = $item; + } + } + foreach ($items_map as $item) { + if (!$item['pid']) { + $formatted_items[] = $item; + } + } + return $this->json(0, 'ok', $formatted_items); + } + + /** + * @param $items + * @return \support\Response + */ + protected function formatSelect($items) + { + $formatted_items = []; + foreach ($items as $item) { + $formatted_items[] = [ + 'title' => $item->title ?? $item->name ?? $item->id, + 'value' => $item->id, + 'key' => $item->id, + ]; + } + return $this->json(0, 'ok', $formatted_items); + } + + protected function json(int $code, string $msg = 'ok', array $data = []) + { + return json(['code' => $code, 'result' => $data, 'message' => $msg, 'type' => $code ? 'error' : 'success']); + } + +} diff --git a/plugin/admin/app/controller/IndexController.php b/plugin/admin/app/controller/IndexController.php new file mode 100644 index 0000000..0779193 --- /dev/null +++ b/plugin/admin/app/controller/IndexController.php @@ -0,0 +1,37 @@ +queryString()) { + // 检查是否安装了admin + $database_config_file = base_path() . '/plugin/admin/config/database.php'; + clearstatcache(); + if (!is_file($database_config_file)) { + return redirect('/app/admin?install#install'); + } + } + return response()->file(base_path() . '/plugin/admin/public/index.html'); + } + +} diff --git a/plugin/admin/app/controller/auth/AdminController.php b/plugin/admin/app/controller/auth/AdminController.php new file mode 100644 index 0000000..a6e4fb1 --- /dev/null +++ b/plugin/admin/app/controller/auth/AdminController.php @@ -0,0 +1,52 @@ +model = new Admin; + } + + /** + * 删除 + * + * @param Request $request + * @return \support\Response + * @throws \Support\Exception\BusinessException + */ + public function delete(Request $request) + { + $column = $request->post('column'); + $value = $request->post('value'); + if ($value == admin_id()) { + return $this->json(1, '不能删除自己'); + } + $this->model->where([$column => $value])->delete(); + return $this->json(0); + } + +} diff --git a/plugin/admin/app/controller/auth/AdminRoleController.php b/plugin/admin/app/controller/auth/AdminRoleController.php new file mode 100644 index 0000000..7c10e61 --- /dev/null +++ b/plugin/admin/app/controller/auth/AdminRoleController.php @@ -0,0 +1,102 @@ +model = new AdminRole; + } + + /** + * 更新 + * + * @param Request $request + * @return \support\Response + */ + public function update(Request $request) + { + $column = $request->post('column'); + $value = $request->post('value'); + $data = $request->post('data'); + $table = $this->model->getTable(); + $allow_column = Util::db()->select("desc $table"); + if (!$allow_column) { + return $this->json(2, '表不存在'); + } + + $data['rules'] = array_filter(array_unique((array)$data['rules'])); + + $item = $this->model->where($column, $value)->first(); + if (!$item) { + return $this->json(1, '记录不存在'); + } + if ($item->id == 1) { + $data['rules'] = '*'; + } + + foreach ($data as $col => $item) { + if (is_array($item)) { + $data[$col] = implode(',', $item); + } + if ($col === 'password') { + // 密码为空,则不更新密码 + if ($item == '') { + unset($data[$col]); + continue; + } + $data[$col] = Util::passwordHash($item); + } + } + + $this->model->where($column, $value)->update($data); + return $this->json(0); + } + + /** + * 删除 + * @param Request $request + * @return \support\Response + * @throws \Support\Exception\BusinessException + */ + public function delete(Request $request) + { + $column = $request->post('column'); + $value = $request->post('value'); + $item = $this->model->where($column, $value)->first(); + if (!$item) { + return $this->json(0); + } + if ($item->id == 1) { + return $this->json(1, '无法删除超级管理员角色'); + } + $this->model->where('id', $item->id)->delete(); + return $this->json(0); + } + +} diff --git a/plugin/admin/app/controller/auth/AdminRuleController.php b/plugin/admin/app/controller/auth/AdminRuleController.php new file mode 100644 index 0000000..a90ae11 --- /dev/null +++ b/plugin/admin/app/controller/auth/AdminRuleController.php @@ -0,0 +1,474 @@ +model = new AdminRule; + } + + /** + * 获取权限树 + * + * @param Request $request + * @return \support\Response + */ + public function tree(Request $request) + { + $this->syncRules(); + $items = $this->model->get(); + return $this->formatTree($items); + } + + /** + * 根据类同步规则到数据库 + * + * @return void + */ + protected function syncRules() + { + $items = $this->model->where('name', 'like', '%\\\\%')->get()->keyBy('name'); + $methods_in_db = []; + $methods_in_files = []; + foreach ($items as $item) { + $class = $item->name; + if (strpos($class, '@')) { + $methods_in_db[$class] = $class; + continue; + } + if (class_exists($class)) { + $reflection = new \ReflectionClass($class); + $properties = $reflection->getDefaultProperties(); + $no_need_auth = array_merge($properties['noNeedLogin'] ?? [], $properties['noNeedAuth'] ?? []); + $class = $reflection->getName(); + $pid = $item->id; + $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC); + foreach ($methods as $method) { + $method_name = $method->getName(); + if (strpos($method_name, '__') === 0 || in_array($method_name, $no_need_auth)) { + continue; + } + $name = "$class@$method_name"; + $methods_in_files[$name] = $name; + $title = Util::getCommentFirstLine($method->getDocComment()) ?? $method_name; + $menu = $items[$name] ?? []; + if ($menu) { + if ($menu->title != $title) { + AdminRule::where('name', $name)->update(['title' => $title]); + } + continue; + } + $menu = new AdminRule; + $menu->pid = $pid; + $menu->name = $name; + $menu->path = ''; + $menu->component = ''; + $menu->title = $title; + $menu->is_menu = 0; + $menu->save(); + } + } + } + // 从数据库中删除已经不存在的方法 + $menu_names_to_del = array_diff($methods_in_db, $methods_in_files); + if ($menu_names_to_del) { + AdminRule::whereIn('name', $menu_names_to_del)->delete(); + } + } + + /** + * 查询 + * + * @param Request $request + * @return \support\Response + */ + public function select(Request $request) + { + [$where, $format, $page_size, $field, $order] = $this->selectInput($request); + $where['is_menu'] = 1; + $model = $this->model; + foreach ($where as $column => $value) { + if (is_array($value)) { + if (in_array($value[0], ['>', '=', '<', '<>'])) { + $model = $model->where($column, $value[0], $value[1]); + } elseif ($value[0] == 'in') { + $model = $model->whereIn($column, $value[1]); + } else { + $model = $model->whereBetween($column, $value); + } + } else { + $model = $model->where($column, $value); + } + } + $model = $model->orderBy($field, $order); + if (in_array($format, ['select', 'tree', 'table_tree'])) { + $items = $model->get(); + if ($format == 'select') { + return $this->formatSelect($items); + } elseif ($format == 'tree') { + return $this->formatTree($items); + } + return $this->formatTableTree($items); + } + + $paginator = $model->paginate($page_size); + return $this->json(0, 'ok', [ + 'items' => $paginator->items(), + 'total' => $paginator->total() + ]); + } + + /** + * 添加 + * @param Request $request + * @return \support\Response + */ + public function insert(Request $request) + { + $data = $request->post('data'); + $table = $this->model->getTable(); + $allow_column = Util::db()->select("desc $table"); + if (!$allow_column) { + return $this->json(2, '表不存在'); + } + $name = $data['name']; + if ($this->model->where('name', $name)->first()) { + return $this->json(1, "菜单key $name 已经存在"); + } + $columns = array_column($allow_column, 'Field', 'Field'); + foreach ($data as $col => $item) { + if (is_array($item)) { + $data[$col] = implode(',', $item); + } + } + $datetime = date('Y-m-d H:i:s'); + if (isset($columns['created_at']) && !isset($data['created_at'])) { + $data['created_at'] = $datetime; + } + if (isset($columns['updated_at']) && !isset($data['updated_at'])) { + $data['updated_at'] = $datetime; + } + $id = $this->model->insertGetId($data); + return $this->json(0, $id); + } + + /** + * 删除 + * @param Request $request + * @return \support\Response + * @throws \Support\Exception\BusinessException + */ + public function delete(Request $request) + { + $column = $request->post('column'); + $value = $request->post('value'); + $item = $this->model->where($column, $value)->first(); + if (!$item) { + return $this->json(1, '记录不存在'); + } + // 子规则一起删除 + $delete_ids = $children_ids = [$item['id']]; + while($children_ids) { + $children_ids = $this->model->whereIn('pid', $children_ids)->pluck('id')->toArray(); + $delete_ids = array_merge($delete_ids, $children_ids); + } + $this->model->whereIn('id', $delete_ids)->delete(); + return $this->json(0); + } + + /** + * 一键生成菜单 + * + * @param Request $request + * @return \support\Response + * @throws \Support\Exception\BusinessException + */ + public function create(Request $request) + { + $table_name = $request->input('table'); + Util::checkTableName($table_name); + $name = $request->post('name'); + $pid = $request->post('pid', 0); + $icon = $request->post('icon', ''); + $path = ''; + $overwrite = $request->post('overwrite'); + + $pid = (int)$pid; + if ($pid) { + $parent_menu = AdminRule::find($pid); + if (!$parent_menu) { + return $this->json(1, '父菜单不存在'); + } + $path = $parent_menu['path']; + } + + $table_basename = strpos($table_name, 'wa_') === 0 ? substr($table_name, 3) : $table_name; + $model_class = Util::camel($table_basename); + $suffix = substr($model_class, -2); + if ($suffix != 'ss' && $suffix != 'es') { + $model_class = rtrim($model_class, 's'); + } + + $controller_class = $model_class . config('plugin.admin.app.controller_suffix'); + $path = trim($path, '/'); + $path_backslash = str_replace('/', '\\', $path); + if ($path_backslash) { + $controller_namespace = "plugin\\admin\\app\\controller\\$path_backslash"; + } else { + $controller_namespace = "plugin\\admin\\app\\controller"; + } + $controller_file = base_path() . '/' . str_replace('\\', '/', $controller_namespace) . "/$controller_class.php"; + + $model_file = base_path() . "/plugin/admin/app/model/$model_class.php"; + if (!$overwrite) { + if (is_file($controller_file)) { + return $this->json(1, substr($controller_file, strlen(base_path())) . '已经存在'); + } + if (is_file($model_file)) { + return $this->json(1, substr($model_file, strlen(base_path())) . '已经存在'); + } + } + + // 创建model + $this->createModel($model_class, "plugin\\admin\\app\\model", $model_file, $table_name); + + // 创建controller + $this->createController($controller_class, $controller_namespace, $controller_file, $model_class, $name); + + // 菜单相关参数 + $menu_path = str_replace('_', '', $table_basename); + $suffix = substr($menu_path, -2); + if ($suffix != 'ss' && $suffix != 'es') { + $menu_path = rtrim($menu_path, 's'); + } + $componet = '/database/table/View'; + $reflection = new \ReflectionClass("$controller_namespace\\$controller_class"); + $controller_class_with_nsp = $reflection->getName(); + + $menu = AdminRule::where('name', $controller_class_with_nsp)->first(); + if (!$menu) { + $menu = new AdminRule; + } + $menu->pid = $pid; + $menu->name = $controller_class_with_nsp; + $menu->path = $pid ? $menu_path : "/$menu_path"; + $menu->component = $componet; + $menu->title = $name; + $menu->icon = $icon; + $menu->save(); + + $roles = admin('roles'); + $rules = AdminRole::whereIn('id', $roles)->pluck('rules'); + $rule_ids = []; + foreach ($rules as $rule_string) { + if (!$rule_string) { + continue; + } + $rule_ids = array_merge($rule_ids, explode(',', $rule_string)); + } + + // 不是超级管理员,则需要给当前管理员这个菜单的权限 + if (!in_array('*', $rule_ids) && $roles){ + $role = AdminRole::find(current($roles)); + if ($role) { + $role->rules .= ",{$menu->id}"; + } + $role->save(); + } + + return $this->json(0); + } + + /** + * 创建model + * + * @param $class + * @param $namespace + * @param $file + * @param $table + * @return void + */ + protected function createModel($class, $namespace, $file, $table) + { + $this->mkdir($file); + $table_val = "'$table'"; + $pk = 'id'; + $properties = ''; + $timestamps = ''; + $columns = []; + try { + $database = config('database.connections')['plugin.admin.mysql']['database']; + //plugin.admin.mysql + foreach (Util::db()->select("select COLUMN_NAME,DATA_TYPE,COLUMN_KEY,COLUMN_COMMENT from INFORMATION_SCHEMA.COLUMNS where table_name = '$table' and table_schema = '$database'") as $item) { + if ($item->COLUMN_KEY === 'PRI') { + $pk = $item->COLUMN_NAME; + $item->COLUMN_COMMENT .= "(主键)"; + } + $type = $this->getType($item->DATA_TYPE); + $properties .= " * @property $type \${$item->COLUMN_NAME} {$item->COLUMN_COMMENT}\n"; + $columns[$item->COLUMN_NAME] = $item->COLUMN_NAME; + } + } catch (\Throwable $e) {} + if (!isset($columns['created_at']) || !isset($columns['updated_at'])) { + $timestamps = <<mkdir($file); + $controller_content = <<model = new $model_class; + } + +} + +EOF; + file_put_contents($file, $controller_content); + } + + protected function mkdir($file) + { + $path = pathinfo($file, PATHINFO_DIRNAME); + if (!is_dir($path)) { + mkdir($path, 0777, true); + } + } + + /** + * 字段类型到php类型映射 + * + * @param string $type + * @return string + */ + protected function getType(string $type) + { + if (strpos($type, 'int') !== false) { + return 'integer'; + } + switch ($type) { + case 'varchar': + case 'string': + case 'text': + case 'date': + case 'time': + case 'guid': + case 'datetimetz': + case 'datetime': + case 'decimal': + case 'enum': + return 'string'; + case 'boolean': + return 'integer'; + case 'float': + return 'float'; + default: + return 'mixed'; + } + } + +} diff --git a/plugin/admin/app/controller/common/AccountController.php b/plugin/admin/app/controller/common/AccountController.php new file mode 100644 index 0000000..26aa99c --- /dev/null +++ b/plugin/admin/app/controller/common/AccountController.php @@ -0,0 +1,245 @@ +model = new Admin; + } + + /** + * 登录 + * + * @param Request $request + * @return Response + */ + public function login(Request $request) + { + $captcha = $request->post('captcha'); + if (strtolower($captcha) !== session('captcha-login')) { + return $this->json(1, '验证码错误'); + } + $request->session()->forget('captcha-login'); + $username = $request->post('username', ''); + $password = $request->post('password', ''); + if (!$username) { + return $this->json(1, '用户名不能为空'); + } + $this->checkLoginLimit($username); + $admin = Admin::where('username', $username)->first(); + if (!$admin || !Util::passwordVerify($password, $admin->password)) { + return $this->json(1, '账户不存在或密码错误'); + } + $this->removeLoginLimit($username); + $admin = $admin->toArray(); + $session = $request->session(); + unset($admin['password']); + $admin['roles'] = $admin['roles'] ? explode(',', $admin['roles']) : []; + $session->set('admin', $admin); + return $this->json(0, '登录成功', [ + 'nickname' => $admin['nickname'], + 'token' => $request->sessionId(), + ]); + } + + /** + * 退出 + * + * @param Request $request + * @return Response + */ + public function logout(Request $request) + { + $request->session()->delete('admin'); + return $this->json(0); + } + + + /** + * 获取登录信息 + * + * @param Request $request + * @return Response + */ + public function info(Request $request) + { + $admin = admin(); + if (!$admin) { + return $this->json(1); + } + $info = [ + 'nickname' => $admin['nickname'], + 'desc' => 'manager', + 'avatar' => $admin['avatar'], + 'token' => $request->sessionId(), + 'userId' => $admin['id'], + 'username' => $admin['username'], + 'email' => $admin['email'], + 'mobile' => $admin['mobile'], + 'roles' => [] + ]; + return $this->json(0, 'ok', $info); + } + + /** + * 验证码 + * @param Request $request + * @param $type + * @return Response + */ + public function captcha(Request $request, $type = 'login') + { + $builder = new CaptchaBuilder; + $builder->build(); + $request->session()->set("captcha-$type", strtolower($builder->getPhrase())); + $img_content = $builder->get(); + return response($img_content, 200, ['Content-Type' => 'image/jpeg']); + } + + /** + * 获取权限码(目前没作用) + * @return Response + */ + public function getPermCode() + { + return $this->json(0, 'ok', ['1000', '3000', '5000']); + } + + /** + * 更新 + * + * @param Request $request + * @return Response + */ + public function update(Request $request) + { + $allow_column = [ + 'nickname' => 'nickname', + 'avatar' => 'avatar', + 'email' => 'email', + 'mobile' => 'mobile', + ]; + + $data = $request->post(); + $update_data = []; + foreach ($allow_column as $key => $column) { + if (isset($data[$key])) { + $update_data[$column] = $data[$key]; + } + } + if (isset($update_data['password'])) { + $update_data['password'] = Util::passwordHash($update_data['password']); + } + Admin::where('id', admin_id())->update($update_data); + return $this->json(0); + } + + /** + * 修改密码 + * + * @param Request $request + * @return Response + */ + public function password(Request $request) + { + $hash = admin('password'); + $password = $request->post('password'); + if (!$password) { + return $this->json(2, '密码不能为空'); + } + if (!Util::passwordVerify($request->post('old_password'), $hash)) { + return $this->json(1, '原始密码不正确'); + } + $update_data = [ + 'password' => Util::passwordHash($password) + ]; + Admin::where('id', admin_id())->update($update_data); + return $this->json(0); + } + + /** + * 检查登录频率限制 + * + * @param $username + * @return void + * @throws BusinessException + */ + protected function checkLoginLimit($username) + { + $limit_log_path = runtime_path() . '/login'; + if (!is_dir($limit_log_path)) { + mkdir($limit_log_path, 0777, true); + } + $limit_file = $limit_log_path . '/' . md5($username) . '.limit'; + $time = date('YmdH') . ceil(date('i')/5); + $limit_info = []; + if (is_file($limit_file)) { + $json_str = file_get_contents($limit_file); + $limit_info = json_decode($json_str, true); + } + + if (!$limit_info || $limit_info['time'] != $time) { + $limit_info = [ + 'username' => $username, + 'count' => 0, + 'time' => $time + ]; + } + $limit_info['count']++; + file_put_contents($limit_file, json_encode($limit_info)); + if ($limit_info['count'] >= 5) { + throw new BusinessException('登录失败次数过多,请5分钟后再试'); + } + } + + + /** + * 解除登录限制 + * + * @param $username + * @return void + */ + protected function removeLoginLimit($username) + { + $limit_log_path = runtime_path() . '/login'; + $limit_file = $limit_log_path . '/' . md5($username) . '.limit'; + if (is_file($limit_file)) { + unlink($limit_file); + } + } + +} diff --git a/plugin/admin/app/controller/common/InstallController.php b/plugin/admin/app/controller/common/InstallController.php new file mode 100644 index 0000000..79500e0 --- /dev/null +++ b/plugin/admin/app/controller/common/InstallController.php @@ -0,0 +1,226 @@ +json(1, '管理后台已经安装!如需重新安装,请删除该插件数据库配置文件并重启'); + } + + if (!class_exists(CaptchaBuilder::class) || !class_exists(Manager::class)) { + return $this->json(1, '请先restart重启webman后再进行此页面的设置'); + } + + $user = $request->post('user'); + $password = $request->post('password'); + $database = $request->post('database'); + $host = $request->post('host'); + $port = $request->post('port'); + $overwrite = $request->post('overwrite'); + + $dsn = "mysql:dbname=$database;host=$host;port=$port;"; + try { + $params = [ + \PDO::MYSQL_ATTR_INIT_COMMAND => "set names utf8mb4", //设置编码 + \PDO::ATTR_EMULATE_PREPARES => false, + \PDO::ATTR_TIMEOUT => 5 + ]; + $db = new \PDO($dsn, $user, $password, $params); + $smt = $db->query("show tables"); + $tables = $smt->fetchAll(); + } catch (\Throwable $e) { + if (stripos($e, 'Access denied for user')) { + return $this->json(1, '数据库用户名或密码错误'); + } + if (stripos($e, 'Connection refused')) { + return $this->json(1, 'Connection refused. 请确认数据库IP端口是否正确,数据库已经启动'); + } + if (stripos($e, 'timed out')) { + return $this->json(1, '数据库连接超时,请确认数据库IP端口是否正确,安全组及防火墙已经放行端口'); + } + throw $e; + } + + $tables_to_install = [ + 'wa_admins', + 'wa_admin_roles', + 'wa_admin_rules', + 'wa_options', + 'wa_users', + ]; + + if (!$overwrite) { + $tables_exist = []; + foreach ($tables as $table) { + $tables_exist[] = current($table); + } + $tables_conflict = array_intersect($tables_to_install, $tables_exist); + if ($tables_conflict) { + return $this->json(1, '以下表' . implode(',', $tables_conflict) . '已经存在,如需覆盖请选择强制覆盖'); + } + } + + $sql_file = base_path() . '/plugin/admin/webman-admin.sql'; + if (!is_file($sql_file)) { + return $this->json(1, '数据库SQL文件不存在'); + } + + $sql_query = file_get_contents($sql_file); + $sql_query = $this->removeComments($sql_query); + $sql_query = $this->splitSqlFile($sql_query, ';'); + foreach ($sql_query as $sql) { + $db->exec($sql); + } + + $config_content = << 'mysql', + 'connections' => [ + 'mysql' => [ + 'driver' => 'mysql', + 'host' => '$host', + 'port' => '$port', + 'database' => '$database', + 'username' => '$user', + 'password' => '$password', + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_general_ci', + 'prefix' => '', + 'strict' => true, + 'engine' => null, + ], + ], +]; +EOF; + + file_put_contents($database_config_file, $config_content); + + // 尝试reload + if (function_exists('posix_kill')) { + set_error_handler(function () {}); + posix_kill(posix_getppid(), SIGUSR1); + restore_error_handler(); + } + + return $this->json(0); + } + + /** + * 设置管理员 + * + * @param Request $request + * @return Response + * @throws BusinessException + */ + public function step2(Request $request) + { + $username = $request->post('username'); + $password = $request->post('password'); + $password2 = $request->post('password2'); + if ($password != $password2) { + return $this->json(1, '两次密码不一致'); + } + if (Admin::first()) { + return $this->json(1, '后台已经安装完毕,无法通过此页面创建管理员'); + } + $admin = new Admin; + $admin->username = $username; + $admin->password = Util::passwordHash($password); + $admin->nickname = '超级管理员'; + $admin->roles = '1'; + $admin->save(); + return $this->json(0); + } + + /** + * 去除sql文件中的注释 + * + * @param $sql + * @return string + */ + protected function removeComments($sql) + { + return preg_replace("/(\n--[^\n]*)/","", $sql); + } + + /** + * @param $sql + * @param $delimiter + * @return array + */ + function splitSqlFile($sql, $delimiter) + { + $tokens = explode($delimiter, $sql); + $output = array(); + $matches = array(); + $token_count = count($tokens); + for ($i = 0; $i < $token_count; $i++) { + if (($i != ($token_count - 1)) || (strlen($tokens[$i] > 0))) { + $total_quotes = preg_match_all("/'/", $tokens[$i], $matches); + $escaped_quotes = preg_match_all("/(?model = new AdminRule; + } + + /** + * 获取菜单 + * + * @return \support\Response + */ + function get() + { + [$rules, $items] = $this->getRulesAndItems(); + $items_map = []; + foreach ($items as $item) { + $items_map[$item['id']] = $item; + } + $formatted_items = []; + foreach ($items_map as $index => $item) { + foreach (['title', 'icon', 'hide_menu', 'frame_src'] as $name) { + $value = $item[$name]; + unset($items_map[$index][$name]); + if (!$value) { + continue; + } + $items_map[$index]['meta'][Util::smCamel($name)] = $value; + } + if ($item['pid'] && isset($items_map[$item['pid']])) { + $items_map[$item['pid']]['children'][] = &$items_map[$index]; + } + } + foreach ($items_map as $item) { + if (!$item['pid']) { + $formatted_items[] = $item; + } + } + + // 超级管理员权限为 * + if (!in_array('*', $rules)) { + $this->removeUncontain($formatted_items, 'id', $rules); + } + $this->removeUncontain($formatted_items, 'is_menu', [1]); + $formatted_items = array_values($formatted_items); + foreach ($formatted_items as &$item) { + $this->arrayValues($item); + } + return $this->json(0, 'ok', $formatted_items); + } + + /** + * 获取菜单树 + * + * @return \support\Response + */ + function tree() + { + [$rules, $items] = $this->getRulesAndItems(); + + $items_map = []; + foreach ($items as $item) { + if ($item['hide_menu']) { + continue; + } + $items_map[$item['id']] = [ + 'title' => $item['title'], + 'value' => (string)$item['id'], + 'key' => (string)$item['id'], + 'id' => $item['id'], + 'is_menu' => $item['is_menu'], + 'pid' => $item['pid'], + ]; + } + $formatted_items = []; + foreach ($items_map as $index => $item) { + if ($item['pid'] && isset($items_map[$item['pid']])) { + $items_map[$item['pid']]['children'][] = &$items_map[$index]; + } + } + foreach ($items_map as $item) { + if (!$item['pid']) { + $formatted_items[] = $item; + } + } + + // 超级管理员权限为 * + if (!in_array('*', $rules)) { + $this->removeUncontain($formatted_items, 'id', $rules); + } + $this->removeUncontain($formatted_items, 'is_menu', [1]); + $this->recursiveRemove($formatted_items, ['id', 'pid', 'is_menu']); + $formatted_items = array_values($formatted_items); + foreach ($formatted_items as &$item) { + $this->arrayValues($item); + } + + return $this->json(0, 'ok', $formatted_items); + } + + /** + * 移除不包含某些数据的数组 + * + * @param $array + * @param $key + * @param $values + * @return void + */ + protected function removeUncontain(&$array, $key, $values) + { + foreach ($array as $k => &$item) { + if (!is_array($item)) { + continue; + } + if (!$this->arrayContain($item, $key, $values)) { + unset($array[$k]); + } else { + if (!isset($item['children'])) { + continue; + } + $this->removeUncontain($item['children'], $key, $values); + } + } + } + + /** + * 判断数组是否包含某些数据 + * + * @param $array + * @param $key + * @param $values + * @return bool + */ + protected function arrayContain(&$array, $key, $values) + { + if (!is_array($array)) { + return false; + } + if (isset($array[$key]) && in_array($array[$key], $values)) { + return true; + } + if (!isset($array['children'])) { + return false; + } + foreach ($array['children'] as $item) { + if ($this->arrayContain($item, $key, $values)) { + return true; + } + } + return false; + } + + /** + * 递归删除某些key + * + * @param $array + * @param $keys + * @return void + */ + protected function recursiveRemove(&$array, $keys) + { + if (!is_array($array)) { + return; + } + foreach ($keys as $key) { + unset($array[$key]); + } + foreach ($array as &$item) { + $this->recursiveRemove($item, $keys); + } + } + + /** + * 获取权限规则 + * @return array + */ + protected function getRulesAndItems() + { + $roles = admin('roles'); + $rules_strings = $roles ? AdminRole::whereIn('id', $roles)->pluck('rules') : []; + $rules = []; + foreach ($rules_strings as $rule_string) { + if (!$rule_string) { + continue; + } + $rules = array_merge($rules, explode(',', $rule_string)); + } + + $items = AdminRule::get()->toArray(); + return [$rules, $items]; + } + + /** + * 递归重建数组下标 + * + * @return void + */ + protected function arrayValues(&$array) + { + if (!is_array($array) || !isset($array['children'])) { + return; + } + $array['children'] = array_values($array['children']); + + foreach ($array['children'] as &$child) { + $this->arrayValues($child); + } + } + +} diff --git a/plugin/admin/app/controller/common/UploadController.php b/plugin/admin/app/controller/common/UploadController.php new file mode 100644 index 0000000..1f0918f --- /dev/null +++ b/plugin/admin/app/controller/common/UploadController.php @@ -0,0 +1,195 @@ +file()); + if (!$file || !$file->isValid()) { + return $this->json(1, '未找到文件'); + } + $img_exts = [ + 'jpg', + 'jpeg', + 'png', + 'gif' + ]; + if (in_array($file->getUploadExtension(), $img_exts)) { + return $this->image($request); + } + $data = $this->base($request, '/upload/files/'.date('Ymd')); + if ($data['code']) { + return $this->json($data['code'], $data['message']); + } + return json(['code' => 0, 'message' => '上传成功', 'url' => $data['data']['src']]); + } + + /** + * 上传头像 + * + * @param Request $request + * @return \support\Response + * @throws \Exception + */ + public function avatar(Request $request) + { + $file = current($request->file()); + if ($file && $file->isValid()) { + $ext = strtolower($file->getUploadExtension()); + if (!in_array($ext, ['jpg', 'jpeg', 'gif', 'png'])) { + return json(['code' => 2, 'msg' => '仅支持 jpg jpeg gif png格式']); + } + $image = Image::make($file); + $width = $image->width(); + $height = $image->height(); + $size = $width > $height ? $height : $width; + $relative_path = 'upload/avatar/' . date('Ym'); + $real_path = base_path() . "/plugin/admin/public/$relative_path"; + if (!is_dir($real_path)) { + mkdir($real_path, 0777, true); + } + $name = bin2hex(pack('Nn',time(), random_int(1, 65535))); + $ext = $file->getUploadExtension(); + + $image->crop($size, $size)->resize(300, 300); + $path = base_path() . "/plugin/admin/public/$relative_path/$name.lg.$ext"; + $image->save($path); + + $image->resize(120, 120); + $path = base_path() . "/plugin/admin/public/$relative_path/$name.md.$ext"; + $image->save($path); + + $image->resize(60, 60); + $path = base_path() . "/plugin/admin/public/$relative_path/$name.$ext"; + $image->save($path); + + $image->resize(30, 30); + $path = base_path() . "/plugin/admin/public/$relative_path/$name.sm.$ext"; + $image->save($path); + + return json([ + 'code' => 0, + 'message' => '上传成功', + 'url' => "/app/admin/$relative_path/$name.$ext" + ]); + } + return json(['code' => 1, 'msg' => 'file not found']); + } + + /** + * 上传图片 + * + * @param Request $request + * @return \support\Response + */ + public function image(Request $request) + { + $data = $this->base($request, '/upload/img/'.date('Ymd')); + if ($data['code']) { + return json(['code' => $data['code'], 'message' => $data['msg']]); + } + $realpath = $data['data']['realpath']; + try { + $img = Image::make($realpath); + $max_height = 1170; + $max_width = 1170; + $width = $img->width(); + $height = $img->height(); + $ratio = 1; + if ($height > $max_height || $width > $max_width) { + $ratio = $width > $height ? $max_width / $width : $max_height / $height; + } + $img->resize($width*$ratio, $height*$ratio)->save($realpath); + } catch (\Exception $e) { + unlink($realpath); + return json( [ + 'code' => 500, + 'message' => '处理图片发生错误' + ]); + } + return json( [ + 'code' => 0, + 'message' => '上传成功', + 'url' => $data['data']['src'] + ]); + } + + /** + * 获取上传数据 + * + * @param Request $request + * @param $relative_dir + * @return array + * @throws \Exception + */ + protected function base(Request $request, $relative_dir) + { + $relative_dir = ltrim($relative_dir, '/'); + $file = current($request->file()); + if (!$file || !$file->isValid()) { + return ['code' => 400, 'message' => '未找到上传文件']; + } + + $base_dir = base_path() . '/plugin/admin/public/'; + $full_dir = $base_dir . $relative_dir; + if (!is_dir($full_dir)) { + mkdir($full_dir, 0777, true); + } + + $ext = strtolower($file->getUploadExtension()); + $ext_forbidden_map = ['php', 'php3', 'php5', 'css', 'js', 'html', 'htm', 'asp', 'jsp']; + if (in_array($ext, $ext_forbidden_map)) { + return ['code' => 400, 'message' => '不支持该格式的文件上传']; + } + + $relative_path = $relative_dir . '/' . bin2hex(pack('Nn',time(), random_int(1, 65535))) . ".$ext"; + $full_path = $base_dir . $relative_path; + $file_size = $file->getSize(); + $file_name = $file->getUploadName(); + $file->move($full_path); + return [ + 'code' => 0, + 'msg' => '上传成功', + 'data' => [ + 'src' => "/app/admin/$relative_path", + 'name' => $file_name, + 'realpath' => $full_path, + 'size' => $this->formatSize($file_size) + ] + ]; + } + + /** + * 格式化文件大小 + * + * @param $file_size + * @return string + */ + protected function formatSize($file_size) { + $size = sprintf("%u", $file_size); + if($size == 0) { + return("0 Bytes"); + } + $sizename = array(" Bytes", " KB", " MB", " GB", " TB", " PB", " EB", " ZB", " YB"); + return round($size/pow(1024, ($i = floor(log($size, 1024)))), 2) . $sizename[$i]; + } + +} diff --git a/plugin/admin/app/controller/database/TableController.php b/plugin/admin/app/controller/database/TableController.php new file mode 100644 index 0000000..f3ee5a7 --- /dev/null +++ b/plugin/admin/app/controller/database/TableController.php @@ -0,0 +1,711 @@ +get('field', 'TABLE_NAME'); + $order = $request->get('order', 'ascend'); + $allow_column = ['TABLE_NAME','TABLE_COMMENT','ENGINE','TABLE_ROWS','CREATE_TIME','UPDATE_TIME','TABLE_COLLATION']; + if (!in_array($field, $allow_column)) { + $field = 'TABLE_NAME'; + } + $order = $order === 'ascend' ? 'asc' : 'desc'; + $tables = Util::db()->select("SELECT TABLE_NAME,TABLE_COMMENT,ENGINE,TABLE_ROWS,CREATE_TIME,UPDATE_TIME,TABLE_COLLATION FROM information_schema.`TABLES` WHERE TABLE_SCHEMA='$database' order by $field $order"); + + if ($tables) { + $table_names = array_column($tables, 'TABLE_NAME'); + $table_rows_count = []; + foreach ($table_names as $table_name) { + $table_rows_count[$table_name] = Util::db()->table($table_name)->count(); + } + foreach ($tables as $key => $table) { + $tables[$key]->TABLE_ROWS = $table_rows_count[$table->TABLE_NAME] ?? $table->TABLE_ROWS; + } + } + + return $this->json(0, 'ok', $tables); + } + + /** + * 创建表 + * + * @param Request $request + * @return \support\Response + */ + public function create(Request $request) + { + $data = $request->post(); + $table_name = $data['table']['name']; + $table_comment = $data['table']['comment']; + $columns = $data['columns']; + $keys = $data['keys']; + Util::schema()->create($table_name, function (Blueprint $table) use ($columns) { + $type_method_map = Util::methodControlMap(); + foreach ($columns as $column) { + if (!isset($column['type'])) { + throw new BusinessException("请为{$column['field']}选择类型"); + } + if (!isset($type_method_map[$column['type']])) { + throw new BusinessException("不支持的类型{$column['type']}"); + } + $this->createColumn($column, $table); + } + $table->charset = 'utf8mb4'; + $table->collation = 'utf8mb4_general_ci'; + $table->engine = 'InnoDB'; + }); + // @todo 防注入 + Util::db()->statement("ALTER TABLE `$table_name` COMMENT '$table_comment'"); + + // 索引 + Util::schema()->table($table_name, function (Blueprint $table) use ($keys) { + foreach ($keys as $key) { + $name = $key['name']; + $columns = $key['columns']; + $type = $key['type']; + if ($type == 'unique') { + $table->unique($columns, $name); + continue; + } + $table->index($columns, $name); + } + }); + + $form_schema = $request->post('forms', []); + $form_schema_map = []; + foreach ($form_schema as $item) { + $form_schema_map[$item['field']] = $item; + } + $form_schema_map = json_encode($form_schema_map, JSON_UNESCAPED_UNICODE); + $this->updateSchemaOption($table_name, $form_schema_map); + return $this->json(0, 'ok'); + } + + /** + * 修改表 + * + * @param Request $request + * @return \support\Response + * @throws BusinessException + */ + public function modify(Request $request) + { + $data = $request->post(); + $old_table_name = $data['table']['old_name']; + $table_name = $data['table']['name']; + $table_comment = $data['table']['comment']; + $columns = $data['columns']; + $keys = $data['keys'] ?? []; + // 改表名 + if ($table_name != $old_table_name) { + Util::checkTableName($table_name); + Util::schema()->rename($old_table_name, $table_name); + } + + $old_columns = $this->getSchema($table_name, 'columns'); + $type_method_map = Util::methodControlMap(); + foreach ($columns as $column) { + if (!isset($type_method_map[$column['type']])) { + throw new BusinessException("不支持的类型{$column['type']}"); + } + $field = $column['field']; + + // 重命名的字段 mysql8才支持? + if (isset($column['old_field']) && $column['old_field'] !== $field) { + //Util::db()->statement("ALTER TABLE $table_name RENAME COLUMN {$column['old_field']} to $field"); + } + + $old_column = $old_columns[$field] ?? []; + // 类型更改 + foreach ($old_column as $key => $value) { + if (isset($column[$key]) && $column[$key] != $value) { + $this->modifyColumn($column, $table_name); + break; + } + } + } + + $table = $this->getSchema($table_name, 'table'); + // @todo $table_comment 防止SQL注入 + if ($table_comment !== $table['comment']) { + Util::db()->statement("ALTER TABLE `$table_name` COMMENT '$table_comment'"); + } + + $old_columns = $this->getSchema($table_name, 'columns'); + Util::schema()->table($table_name, function (Blueprint $table) use ($columns, $old_columns, $keys, $table_name) { + foreach ($columns as $column) { + $field = $column['field']; + // 新字段 + if (!isset($old_columns[$field])) { + $this->createColumn($column, $table); + } + } + // 更新索引名字 + foreach ($keys as $key) { + if (!empty($key['old_name']) && $key['old_name'] !== $key['name']) { + $table->renameIndex($key['old_name'], $key['name']); + } + } + }); + + // 找到删除的字段 + $old_columns = $this->getSchema($table_name, 'columns'); + $exists_column_names = array_column($columns, 'field', 'field'); + $old_columns_names = array_column($old_columns, 'field'); + $drop_column_names = array_diff($old_columns_names, $exists_column_names); + foreach ($drop_column_names as $drop_column_name) { + //$table->dropColumn($drop_column_name); 无法使用 + Util::db()->statement("ALTER TABLE $table_name DROP COLUMN $drop_column_name"); + } + + $old_keys = $this->getSchema($table_name, 'keys'); + Util::schema()->table($table_name, function (Blueprint $table) use ($keys, $old_keys, $table_name) { + foreach ($keys as $key) { + $key_name = $key['name']; + $old_key = $old_keys[$key_name] ?? []; + // 如果索引有变动,则删除索引,重新建立索引 + if ($old_key && ($key['type'] != $old_key['type'] || $key['columns'] != $old_key['columns'])) { + $old_key = []; + unset($old_keys[$key_name]); + $table->dropIndex($key_name); + } + // 重新建立索引 + if (!$old_key) { + $name = $key['name']; + $columns = $key['columns']; + $type = $key['type']; + if ($type == 'unique') { + $table->unique($columns, $name); + continue; + } + $table->index($columns, $name); + } + } + + // 找到删除的索引 + $exists_key_names = array_column($keys, 'name', 'name'); + $old_keys_names = array_column($old_keys, 'name'); + $drop_keys_names = array_diff($old_keys_names, $exists_key_names); + foreach ($drop_keys_names as $name) { + $table->dropIndex($name); + } + }); + + $form_schema = $request->post('forms', []); + $form_schema_map = []; + foreach ($form_schema as $item) { + $form_schema_map[$item['field']] = $item; + } + $form_schema_map = json_encode($form_schema_map, JSON_UNESCAPED_UNICODE); + $option_name = $this->updateSchemaOption($table_name, $form_schema_map); + + return $this->json(0,$option_name); + } + + /** + * 查询记录 + * + * @param Request $request + * @return \support\Response + */ + public function select(Request $request) + { + $page = $request->get('page', 1); + $field = $request->get('field'); + $order = $request->get('order', 'descend'); + $table = $request->get('table'); + $format = $request->get('format', 'normal'); + $page_size = $request->get('pageSize', $format === 'tree' ? 1000 : 10); + + if (!preg_match('/[a-zA-Z_0-9]+/', $table)) { + return $this->json(1, '表不存在'); + } + $allow_column = Util::db()->select("desc $table"); + if (!$allow_column) { + return $this->json(2, '表不存在'); + } + $allow_column = array_column($allow_column, 'Field', 'Field'); + if (!in_array($field, $allow_column)) { + $field = current($allow_column); + } + $order = $order === 'ascend' ? 'asc' : 'desc'; + $paginator = Util::db()->table($table); + foreach ($request->get() as $column => $value) { + if (!$value) { + continue; + } + if (isset($allow_column[$column])) { + if (is_array($value)) { + if ($value[0] == 'undefined' || $value[1] == 'undefined') { + continue; + } + $paginator = $paginator->whereBetween($column, $value); + } else { + $paginator = $paginator->where($column, $value); + } + } + } + $paginator = $paginator->orderBy($field, $order)->paginate($page_size, '*', 'page', $page); + + $items = $paginator->items(); + if ($format == 'tree') { + $items_map = []; + foreach ($items as $item) { + $items_map[$item->id] = (array)$item; + } + $formatted_items = []; + foreach ($items_map as $item) { + if ($item['pid'] && isset($items_map[$item['pid']])) { + $items_map[$item['pid']]['children'][] = $item; + } + } + foreach ($items_map as $item) { + if (!$item['pid']) { + $formatted_items[] = $item; + } + } + $items = $formatted_items; + } + + return $this->json(0, 'ok', [ + 'items' => $items, + 'total' => $paginator->total() + ]); + } + + /** + * 插入记录 + * + * @param Request $request + * @return \support\Response + */ + public function insert(Request $request) + { + $table = $request->input('table'); + $data = $request->post('data'); + $columns = $this->getSchema($table, 'columns'); + foreach ($data as $col => $item) { + if (is_array($item)) { + $data[$col] = implode(',', $item); + continue; + } + if ($col === 'password') { + $data[$col] = Util::passwordHash($item); + } + } + $datetime = date('Y-m-d H:i:s'); + if (isset($columns['created_at']) && !isset($data['created_at'])) { + $data['created_at'] = $datetime; + } + if (isset($columns['updated_at']) && !isset($data['updated_at'])) { + $data['updated_at'] = $datetime; + } + $id = Util::db()->table($table)->insertGetId($data); + return $this->json(0, $id); + } + + /** + * 更新记录 + * + * @param Request $request + * @return \support\Response + * @throws BusinessException + */ + public function update(Request $request) + { + $table = $request->input('table'); + $column = $request->post('column'); + $value = $request->post('value'); + $data = $request->post('data'); + $columns = $this->getSchema($table, 'columns'); + foreach ($data as $col => $item) { + if (is_array($item)) { + $data[$col] = implode(',', $item); + } + if ($col === 'password') { + // 密码为空,则不更新密码 + if ($item == '') { + unset($data[$col]); + continue; + } + $data[$col] = Util::passwordHash($item); + } + } + $datetime = date('Y-m-d H:i:s'); + if (isset($columns['updated_at']) && !isset($data['updated_at'])) { + $data['updated_at'] = $datetime; + } + var_export($data); + Util::checkTableName($table); + Util::db()->table($table)->where($column, $value)->update($data); + return $this->json(0); + } + + /** + * 删除记录 + * + * @param Request $request + * @return \support\Response + * @throws BusinessException + */ + public function delete(Request $request) + { + $table = $request->input('table'); + $column = $request->post('column'); + $value = $request->post('value'); + Util::checkTableName($table); + Util::db()->table($table)->where([$column => $value])->delete(); + return $this->json(0); + } + + /** + * 表摘要 + * + * @param Request $request + * @return \support\Response + * @throws BusinessException + */ + public function schema(Request $request) + { + $table = $request->get('table'); + Util::checkTableName($table); + $schema = Option::where('name', "table_form_schema_$table")->value('value'); + $form_schema_map = $schema ? json_decode($schema, true) : []; + + $data = $this->getSchema($table); + foreach ($data['forms'] as $field => $item) { + if (isset($form_schema_map[$field])) { + $data['forms'][$field] = $form_schema_map[$field]; + } + } + + return $this->json(0, 'ok', [ + 'table' => $data['table'], + 'columns' => array_values($data['columns']), + 'forms' => array_values($data['forms']), + 'keys' => array_values($data['keys']), + ]); + } + + /** + * 获取摘要 + * + * @param $table + * @param $section + * @return array|mixed + */ + protected function getSchema($table, $section = null) + { + $database = config('database.connections')['plugin.admin.mysql']['database']; + $schema_raw = $section !== 'table' ? Util::db()->select("select * from information_schema.COLUMNS where TABLE_SCHEMA = '$database' and table_name = '$table'") : []; + $forms = []; + $columns = []; + foreach ($schema_raw as $item) { + $field = $item->COLUMN_NAME; + $columns[$field] = [ + 'field' => $field, + 'type' => Util::typeToMethod($item->DATA_TYPE, (bool)strpos($item->COLUMN_TYPE, 'unsigned')), + 'comment' => $item->COLUMN_COMMENT, + 'default' => $item->COLUMN_DEFAULT, + 'length' => $this->getLengthValue($item), + 'nullable' => $item->IS_NULLABLE !== 'NO', + 'primary_key' => $item->COLUMN_KEY === 'PRI', + 'auto_increment' => strpos($item->EXTRA, 'auto_increment') !== false + ]; + + $forms[$field] = [ + 'field' => $field, + 'comment' => $item->COLUMN_COMMENT, + 'control' => Util::typeToControl($item->DATA_TYPE), + 'form_show' => $item->COLUMN_KEY !== 'PRI', + 'list_show' => true, + 'enable_sort' => false, + 'readonly' => $item->COLUMN_KEY === 'PRI', + 'searchable' => false, + 'search_type' => 'normal', + 'control_args' => '', + ]; + } + $table_schema = $section == 'table' || !$section ? Util::db()->select("SELECT TABLE_COMMENT FROM information_schema.`TABLES` WHERE TABLE_SCHEMA='$database' and TABLE_NAME='$table'") : []; + $indexes = $section == 'keys' || !$section ? Util::db()->select("SHOW INDEX FROM $table") : []; + $keys = []; + foreach ($indexes as $index) { + $key_name = $index->Key_name; + if ($key_name == 'PRIMARY') { + continue; + } + if (!isset($keys[$key_name])) { + $keys[$key_name] = [ + 'name' => $key_name, + 'columns' => [], + 'type' => $index->Non_unique == 0 ? 'unique' : 'normal' + ]; + } + $keys[$key_name]['columns'][] = $index->Column_name; + } + + $data = [ + 'table' => ['name' => $table, 'comment' => $table_schema[0]->TABLE_COMMENT ?? ''], + 'columns' => $columns, + 'forms' => $forms, + 'keys' => array_reverse($keys, true) + ]; + return $section ? $data[$section] : $data; + } + + /** + * 获取字段长度 + * + * @param $schema + * @return string + */ + protected function getLengthValue($schema) + { + $type = $schema->DATA_TYPE; + if (in_array($type, ['float', 'decimal', 'double'])) { + return "{$schema->NUMERIC_PRECISION},{$schema->NUMERIC_SCALE}"; + } + if ($type === 'enum') { + return implode(',', array_map(function($item){ + return trim($item, "'"); + }, explode(',', substr($schema->COLUMN_TYPE, 5, -1)))); + } + if (in_array($type, ['varchar', 'text', 'char'])) { + return $schema->CHARACTER_MAXIMUM_LENGTH; + } + if (in_array($type, ['time', 'datetime', 'timestamp'])) { + return $schema->CHARACTER_MAXIMUM_LENGTH; + } + return ''; + } + + /** + * 删除表 + * + * @param Request $request + * @return \support\Response + */ + public function drop(Request $request) + { + $table_name = $request->post('table'); + if (!$table_name) { + return $this->json(0, 'not found'); + } + $table_not_allow_drop = ['wa_admins', 'wa_users', 'wa_options', 'wa_admin_roles', 'wa_admin_rules']; + if (in_array($table_name, $table_not_allow_drop)) { + return $this->json(400, "$table_name 不允许删除"); + } + Util::schema()->drop($table_name); + // 删除schema + Util::db()->table('wa_options')->where('name', "table_form_schema_$table_name")->delete(); + return $this->json(0, 'ok'); + } + + /** + * 创建字段 + * + * @param $column + * @param Blueprint $table + * @return mixed + */ + protected function createColumn($column, Blueprint $table) + { + $method = $column['type']; + $args = [$column['field']]; + if (stripos($method, 'int') !== false) { + // auto_increment 会自动成为主键 + if ($column['auto_increment']) { + $column['nullable'] = false; + $column['default'] = ''; + $args[] = true; + } + } elseif (in_array($method, ['string', 'char']) || stripos($method, 'time') !== false) { + if ($column['length']) { + $args[] = $column['length']; + } + } elseif ($method === 'enum') { + $args[] = array_map('trim', explode(',', $column['length'])); + } elseif (in_array($method, ['float', 'decimal', 'double'])) { + if ($column['length']) { + $args = array_merge($args, array_map('trim', explode(',', $column['length']))); + } + } else { + $column['auto_increment'] = false; + } + + $column_def = [$table, $method](...$args); + if (!empty($column['comment'])) { + $column_def = $column_def->comment($column['comment']); + } + + if (!$column['auto_increment'] && $column['primary_key']) { + $column_def = $column_def->primary(true); + } + + if ($column['auto_increment'] && !$column['primary_key']) { + $column_def = $column_def->primary(false); + } + $column_def = $column_def->nullable($column['nullable']); + + if ($column['primary_key']) { + $column_def = $column_def->nullable(false); + } + + if ($column['default'] && !in_array($method, ['text'])) { + $column_def->default($column['default']); + } + return $column_def; + } + + /** + * 更改字段 + * + * @param $column + * @param Blueprint $table + * @return mixed + */ + protected function modifyColumn($column, $table) + { + $method = $column['type']; + $field = $column['field']; + $nullable = $column['nullable']; + $default = $column['default']; + $comment = $column['comment']; + $auto_increment = $column['auto_increment']; + $length = (int)$column['length']; + $primary_key = $column['primary_key']; + // @todo 防止SQL注入 + if (isset($column['old_field']) && $column['old_field'] !== $field) { + $sql = "ALTER TABLE $table CHANGE COLUMN {$column['old_field']} $field "; + } else { + $sql = "ALTER TABLE $table MODIFY $field "; + } + + if (stripos($method, 'integer') !== false) { + $type = str_ireplace('integer', 'int', $method); + if (stripos($method, 'unsigned') !== false) { + $type = str_ireplace('unsigned', '', $type); + $sql .= "$type "; + $sql .= 'unsigned '; + } else { + $sql .= "$type "; + } + if ($auto_increment) { + $column['nullable'] = false; + $column['default'] = ''; + $sql .= 'AUTO_INCREMENT '; + } + } else { + switch ($method) { + case 'string': + $length = $length ?: 255 +; $sql .= "varchar($length) "; + break; + case 'char': + case 'time': + $sql .= $length ? "$method($length) " : "$method "; + break; + case 'enum': + // @todo 防止SQL注入 + $args = array_map('trim', explode(',', $column['length'])); + $sql .= "enum('" . implode("','", $args) . "') "; + break; + case 'double': + case 'float': + case 'decimal': + if (trim($column['length'])) { + $args = array_map('intval', explode(',', $column['length'])); + $args[1] = $args[1] ?? $args[0]; + $sql .= "$method({$args[0]}, {$args[1]}) "; + break; + } + $sql .= "$method "; + break; + default : + $sql .= "$method "; + + } + } + + if (!$nullable) { + $sql .= "NOT NULL "; + } + + if ($default !== null && !in_array($method, ['text'])) { + $sql .= "DEFAULT '$default' "; + } + + if ($comment !== null) { + $sql .= "COMMENT '$comment' "; + } + + Util::db()->statement($sql); + } + + /** + * 字段类型列表 + * + * @param Request $request + * @return \support\Response + */ + public function types(Request $request) + { + $types = Util::methodControlMap(); + return $this->json(0, 'ok', $types); + } + + /** + * 获取在options对用的name + * + * @param $table_name + * @return string + */ + protected function getSchemaOptionName($table_name) + { + return "table_form_schema_$table_name"; + } + + /** + * 更新表的form schema信息 + * + * @param $table_name + * @param $data + * @return string + */ + protected function updateSchemaOption($table_name, $data) + { + $option_name = $this->getSchemaOptionName($table_name); + $option = Option::where('name', $option_name)->first(); + if ($option) { + Option::where('name', $option_name)->update(['value' => $data]); + } else { + Option::insert(['name' => $option_name, 'value' => $data]); + } + return $option_name; + } + +} diff --git a/plugin/admin/app/controller/user/UserController.php b/plugin/admin/app/controller/user/UserController.php new file mode 100644 index 0000000..89b1ff8 --- /dev/null +++ b/plugin/admin/app/controller/user/UserController.php @@ -0,0 +1,33 @@ +model = new User; + } + +} diff --git a/plugin/admin/app/exception/Handler.php b/plugin/admin/app/exception/Handler.php new file mode 100644 index 0000000..481bb71 --- /dev/null +++ b/plugin/admin/app/exception/Handler.php @@ -0,0 +1,39 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace plugin\admin\app\exception; + +use Throwable; +use Webman\Http\Request; +use Webman\Http\Response; + +/** + * Class Handler + * @package Support\Exception + */ +class Handler extends \Support\Exception\Handler +{ + public function render(Request $request, Throwable $exception): Response + { + $code = $exception->getCode(); + if ($request->expectsJson()) { + $json = ['code' => $code ? $code : 500, 'message' => $this->_debug ? $exception->getMessage() : 'Server internal error', 'type' => 'failed']; + $this->_debug && $json['traces'] = (string)$exception; + return new Response(200, ['Content-Type' => 'application/json'], + \json_encode($json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + } + $error = $this->_debug ? \nl2br((string)$exception) : 'Server internal error'; + return new Response(500, [], $error); + } +} diff --git a/plugin/admin/app/functions.php b/plugin/admin/app/functions.php new file mode 100644 index 0000000..7cf7f18 --- /dev/null +++ b/plugin/admin/app/functions.php @@ -0,0 +1,72 @@ +controller; + $action = $request->action; + + $code = 0; + $msg = ''; + if (!Admin::canAccess($controller, $action, $code, $msg)) { + $response = json(['code' => $code, 'message' => $msg, 'type' => 'error']); + } else { + $response = $request->method() == 'OPTIONS' ? response('') : $handler($request); + } + + return $response->withHeaders([ + 'Access-Control-Allow-Credentials' => 'true', + 'Access-Control-Allow-Origin' => $request->header('Origin', '*'), + 'Access-Control-Allow-Methods' => '*', + 'Access-Control-Allow-Headers' => '*', + ]); + + } + +} \ No newline at end of file diff --git a/plugin/admin/app/model/Admin.php b/plugin/admin/app/model/Admin.php new file mode 100644 index 0000000..19084e5 --- /dev/null +++ b/plugin/admin/app/model/Admin.php @@ -0,0 +1,35 @@ +format('Y-m-d H:i:s'); + } +} diff --git a/plugin/admin/app/model/Option.php b/plugin/admin/app/model/Option.php new file mode 100644 index 0000000..97018b0 --- /dev/null +++ b/plugin/admin/app/model/Option.php @@ -0,0 +1,29 @@ + + + + + + + Arco Design Pro - 开箱即用的中台前端/设计解决方案 + + + + + + + +
+ + + diff --git a/plugin/admin/config/app.php b/plugin/admin/config/app.php new file mode 100644 index 0000000..8d265aa --- /dev/null +++ b/plugin/admin/config/app.php @@ -0,0 +1,21 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\Request; + +return [ + 'debug' => true, + 'request_class' => Request::class, + 'controller_suffix' => 'Controller', +]; diff --git a/plugin/admin/config/autoload.php b/plugin/admin/config/autoload.php new file mode 100644 index 0000000..23b4980 --- /dev/null +++ b/plugin/admin/config/autoload.php @@ -0,0 +1,19 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'files' => [ + base_path() . '/plugin/admin/app/functions.php', + ] +]; diff --git a/plugin/admin/config/bootstrap.php b/plugin/admin/config/bootstrap.php new file mode 100644 index 0000000..7dc463a --- /dev/null +++ b/plugin/admin/config/bootstrap.php @@ -0,0 +1,15 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return []; diff --git a/plugin/admin/config/container.php b/plugin/admin/config/container.php new file mode 100644 index 0000000..7857701 --- /dev/null +++ b/plugin/admin/config/container.php @@ -0,0 +1,15 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return new Webman\Container; diff --git a/plugin/admin/config/database.php b/plugin/admin/config/database.php new file mode 100644 index 0000000..ed28275 --- /dev/null +++ b/plugin/admin/config/database.php @@ -0,0 +1,19 @@ + 'mysql', + 'connections' => [ + 'mysql' => [ + 'driver' => 'mysql', + 'host' => '127.0.0.1', + 'port' => '3306', + 'database' => 'admin', + 'username' => 'root', + 'password' => 'qwe904799077', + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_general_ci', + 'prefix' => '', + 'strict' => true, + 'engine' => null, + ], + ], +]; \ No newline at end of file diff --git a/plugin/admin/config/dependence.php b/plugin/admin/config/dependence.php new file mode 100644 index 0000000..8e964ed --- /dev/null +++ b/plugin/admin/config/dependence.php @@ -0,0 +1,15 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return []; \ No newline at end of file diff --git a/plugin/admin/config/event.php b/plugin/admin/config/event.php new file mode 100644 index 0000000..ca5d8ed --- /dev/null +++ b/plugin/admin/config/event.php @@ -0,0 +1,5 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use plugin\admin\app\exception\Handler; + +return [ + '' => Handler::class, +]; \ No newline at end of file diff --git a/plugin/admin/config/log.php b/plugin/admin/config/log.php new file mode 100644 index 0000000..7f05de5 --- /dev/null +++ b/plugin/admin/config/log.php @@ -0,0 +1,32 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'default' => [ + 'handlers' => [ + [ + 'class' => Monolog\Handler\RotatingFileHandler::class, + 'constructor' => [ + runtime_path() . '/logs/webman.log', + 7, //$maxFiles + Monolog\Logger::DEBUG, + ], + 'formatter' => [ + 'class' => Monolog\Formatter\LineFormatter::class, + 'constructor' => [null, 'Y-m-d H:i:s', true], + ], + ] + ], + ], +]; diff --git a/plugin/admin/config/middleware.php b/plugin/admin/config/middleware.php new file mode 100644 index 0000000..7be60ac --- /dev/null +++ b/plugin/admin/config/middleware.php @@ -0,0 +1,21 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use plugin\admin\app\middleware\AccessControl; + +return [ + '' => [ + AccessControl::class, + ] +]; \ No newline at end of file diff --git a/plugin/admin/config/process.php b/plugin/admin/config/process.php new file mode 100644 index 0000000..580d468 --- /dev/null +++ b/plugin/admin/config/process.php @@ -0,0 +1,37 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return []; +return [ + // File update detection and automatic reload + 'monitor' => [ + 'handler' => process\Monitor::class, + 'reloadable' => false, + 'constructor' => [ + // Monitor these directories + 'monitor_dir' => [ + app_path(), + config_path(), + base_path() . '/process', + base_path() . '/support', + base_path() . '/resource', + base_path() . '/.env', + ], + // Files with these suffixes will be monitored + 'monitor_extensions' => [ + 'php', 'html', 'htm', 'env' + ] + ] + ] +]; diff --git a/plugin/admin/config/redis.php b/plugin/admin/config/redis.php new file mode 100644 index 0000000..2f9757a --- /dev/null +++ b/plugin/admin/config/redis.php @@ -0,0 +1,22 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'default' => [ + 'host' => '127.0.0.1', + 'password' => null, + 'port' => 6379, + 'database' => 0, + ], +]; diff --git a/plugin/admin/config/route.php b/plugin/admin/config/route.php new file mode 100644 index 0000000..d39a372 --- /dev/null +++ b/plugin/admin/config/route.php @@ -0,0 +1,19 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use Webman\Route; +use plugin\admin\app\controller\common\AccountController; + +Route::any('/app/admin/common/account/captcha/{type}', [AccountController::class, 'captcha']); + diff --git a/plugin/admin/config/server.php b/plugin/admin/config/server.php new file mode 100644 index 0000000..4f4d7f2 --- /dev/null +++ b/plugin/admin/config/server.php @@ -0,0 +1,30 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'listen' => 'http://0.0.0.0:8080', + 'transport' => 'tcp', + 'context' => [], + 'name' => 'webman', + 'count' => cpu_count() * 2, + 'user' => '', + 'group' => '', + 'reusePort' => false, + 'event_loop' => '', + 'pid_file' => runtime_path() . '/webman.pid', + 'status_file' => runtime_path() . '/webman.status', + 'stdout_file' => runtime_path() . '/logs/stdout.log', + 'log_file' => runtime_path() . '/logs/workerman.log', + 'max_package_size' => 10 * 1024 * 1024 +]; diff --git a/plugin/admin/config/session.php b/plugin/admin/config/session.php new file mode 100644 index 0000000..c2962a7 --- /dev/null +++ b/plugin/admin/config/session.php @@ -0,0 +1,42 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + + 'type' => 'file', // or redis or redis_cluster + + 'handler' => Webman\FileSessionHandler::class, + + 'config' => [ + 'file' => [ + 'save_path' => runtime_path() . '/sessions', + ], + 'redis' => [ + 'host' => '127.0.0.1', + 'port' => 6379, + 'auth' => '', + 'timeout' => 2, + 'database' => '', + 'prefix' => 'redis_session_', + ], + 'redis_cluster' => [ + 'host' => ['127.0.0.1:7000', '127.0.0.1:7001', '127.0.0.1:7001'], + 'timeout' => 2, + 'auth' => '', + 'prefix' => 'redis_session_', + ] + ], + + 'session_name' => 'PHPSID', +]; diff --git a/plugin/admin/config/static.php b/plugin/admin/config/static.php new file mode 100644 index 0000000..2f76cf3 --- /dev/null +++ b/plugin/admin/config/static.php @@ -0,0 +1,23 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Static file settings + */ +return [ + 'enable' => true, + 'middleware' => [ // Static file Middleware + //app\middleware\StaticFile::class, + ], +]; \ No newline at end of file diff --git a/plugin/admin/config/translation.php b/plugin/admin/config/translation.php new file mode 100644 index 0000000..96589b2 --- /dev/null +++ b/plugin/admin/config/translation.php @@ -0,0 +1,25 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Multilingual configuration + */ +return [ + // Default language + 'locale' => 'zh_CN', + // Fallback language + 'fallback_locale' => ['zh_CN', 'en'], + // Folder where language files are stored + 'path' => base_path() . '/resource/translations', +]; \ No newline at end of file diff --git a/plugin/admin/config/view.php b/plugin/admin/config/view.php new file mode 100644 index 0000000..e3a7b85 --- /dev/null +++ b/plugin/admin/config/view.php @@ -0,0 +1,22 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\view\Raw; +use support\view\Twig; +use support\view\Blade; +use support\view\ThinkPHP; + +return [ + 'handler' => Raw::class +]; diff --git a/plugin/admin/public/avatar.png b/plugin/admin/public/avatar.png new file mode 100644 index 0000000..319ca4a Binary files /dev/null and b/plugin/admin/public/avatar.png differ diff --git a/plugin/admin/webman-admin.sql b/plugin/admin/webman-admin.sql new file mode 100644 index 0000000..582cb37 --- /dev/null +++ b/plugin/admin/webman-admin.sql @@ -0,0 +1,183 @@ +-- MySQL dump 10.13 Distrib 5.7.21, for osx10.13 (x86_64) +-- +-- Host: localhost Database: webman_admin +-- ------------------------------------------------------ +-- Server version 5.7.21 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `wa_admin_roles` +-- + +DROP TABLE IF EXISTS `wa_admin_roles`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `wa_admin_roles` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` varchar(80) NOT NULL COMMENT '角色名', + `rules` text COMMENT '权限', + `created_at` datetime NOT NULL COMMENT '创建时间', + `updated_at` datetime NOT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='管理员角色'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `wa_admin_roles` +-- + +LOCK TABLES `wa_admin_roles` WRITE; +/*!40000 ALTER TABLE `wa_admin_roles` DISABLE KEYS */; +INSERT INTO `wa_admin_roles` VALUES (1,'超级管理员','*','2022-08-13 16:15:01','2022-08-13 16:15:01'); +/*!40000 ALTER TABLE `wa_admin_roles` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `wa_admin_rules` +-- + +DROP TABLE IF EXISTS `wa_admin_rules`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `wa_admin_rules` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `title` varchar(255) NOT NULL COMMENT '标题', + `name` varchar(255) NOT NULL COMMENT 'key', + `pid` int(10) unsigned DEFAULT '0' COMMENT '上级菜单', + `component` varchar(255) DEFAULT 'LAYOUT' COMMENT '前端组件', + `path` varchar(255) NOT NULL COMMENT '路径', + `icon` varchar(255) DEFAULT NULL COMMENT '图标', + `created_at` datetime NOT NULL COMMENT '创建时间', + `updated_at` datetime NOT NULL COMMENT '更新时间', + `frame_src` varchar(255) DEFAULT NULL COMMENT 'url', + `hide_menu` tinyint(4) DEFAULT '0' COMMENT '隐藏菜单', + `is_menu` int(11) NOT NULL DEFAULT '1' COMMENT '是否菜单', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=192 DEFAULT CHARSET=utf8mb4 COMMENT='权限规则'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `wa_admin_rules` +-- + +LOCK TABLES `wa_admin_rules` WRITE; +/*!40000 ALTER TABLE `wa_admin_rules` DISABLE KEYS */; +INSERT INTO `wa_admin_rules` VALUES (1,'数据库','Database',0,'LAYOUT','/database','ant-design:database-filled','2022-08-10 15:17:51','2022-08-13 10:31:19',NULL,0,1),(2,'所有表','plugin\\admin\\app\\controller\\database\\TableController',1,'/database/table/index','table','','2022-08-10 16:24:53','2022-08-13 10:54:32',NULL,0,1),(3,'表详情','TableView',1,'/database/table/View','table/view/:id',NULL,'2022-08-10 21:55:28','2022-08-24 22:36:10',NULL,1,1),(4,'权限管理','Auth',0,'LAYOUT','/auth','ant-design:setting-filled','2022-08-10 22:01:17','2022-08-13 10:30:33',NULL,0,1),(5,'账户管理','plugin\\admin\\app\\controller\\auth\\AdminController',4,'/auth/admin/index','admin',NULL,'2022-08-10 22:03:15','2022-08-13 11:09:30',NULL,0,1),(11,'用户管理','User',0,'LAYOUT','/user','ant-design:smile-filled','2022-08-11 09:46:04','2022-08-11 09:46:06',NULL,0,1),(12,'用户','plugin\\admin\\app\\controller\\user\\UserController',11,'/user/user/index','user',NULL,'2022-08-11 09:51:31','2022-08-13 22:15:24',NULL,0,1),(22,'角色管理','plugin\\admin\\app\\controller\\auth\\AdminRoleController',4,'/auth/admin-role/index','admin-role',NULL,'2022-08-13 11:16:30','2022-08-23 21:18:03',NULL,0,1),(23,'菜单管理','plugin\\admin\\app\\controller\\auth\\AdminRuleController',4,'/auth/admin-rule/index','admin-rule',NULL,'2022-08-13 11:50:25','2022-08-13 11:50:25',NULL,0,1),(66,'通用设置','Common',0,'LAYOUT','/common','ant-design:setting-filled','2022-08-14 16:18:29','2022-08-14 16:18:32',NULL,0,1),(67,'个人资料','plugin\\admin\\app\\controller\\user\\AccountController',66,'/common/account/index','account',NULL,'2022-08-14 16:21:44','2022-08-14 16:21:47',NULL,0,1),(148,'查询表','plugin\\admin\\app\\controller\\database\\TableController@show',2,'','',NULL,'2022-08-27 11:54:33','2022-08-27 15:37:42',NULL,0,0),(149,'查询记录','plugin\\admin\\app\\controller\\database\\TableController@select',2,'','',NULL,'2022-08-27 11:54:33','2022-08-27 15:37:42',NULL,0,0),(150,'插入记录','plugin\\admin\\app\\controller\\database\\TableController@insert',2,'','',NULL,'2022-08-27 11:54:33','2022-08-27 15:37:42',NULL,0,0),(151,'更新记录','plugin\\admin\\app\\controller\\database\\TableController@update',2,'','',NULL,'2022-08-27 11:54:33','2022-08-27 15:37:42',NULL,0,0),(152,'删除记录','plugin\\admin\\app\\controller\\database\\TableController@delete',2,'','',NULL,'2022-08-27 11:54:33','2022-08-27 15:37:42',NULL,0,0),(153,'创建表','plugin\\admin\\app\\controller\\database\\TableController@create',2,'','',NULL,'2022-08-27 11:54:33','2022-08-27 15:37:42',NULL,0,0),(154,'修改表','plugin\\admin\\app\\controller\\database\\TableController@modify',2,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(155,'表摘要','plugin\\admin\\app\\controller\\database\\TableController@schema',2,'','',NULL,'2022-08-27 11:54:33','2022-08-27 15:37:42',NULL,0,0),(156,'删除表','plugin\\admin\\app\\controller\\database\\TableController@drop',2,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(158,'删除','plugin\\admin\\app\\controller\\auth\\AdminController@delete',5,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(159,'查询','plugin\\admin\\app\\controller\\auth\\AdminController@select',5,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(160,'添加','plugin\\admin\\app\\controller\\auth\\AdminController@insert',5,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(161,'更新','plugin\\admin\\app\\controller\\auth\\AdminController@update',5,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(162,'摘要','plugin\\admin\\app\\controller\\auth\\AdminController@schema',5,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(163,'查询','plugin\\admin\\app\\controller\\user\\UserController@select',12,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(164,'添加','plugin\\admin\\app\\controller\\user\\UserController@insert',12,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(165,'更新','plugin\\admin\\app\\controller\\user\\UserController@update',12,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(166,'删除','plugin\\admin\\app\\controller\\user\\UserController@delete',12,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(167,'摘要','plugin\\admin\\app\\controller\\user\\UserController@schema',12,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(168,'更新','plugin\\admin\\app\\controller\\auth\\AdminRoleController@update',22,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(169,'查询','plugin\\admin\\app\\controller\\auth\\AdminRoleController@select',22,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(170,'添加','plugin\\admin\\app\\controller\\auth\\AdminRoleController@insert',22,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(171,'删除','plugin\\admin\\app\\controller\\auth\\AdminRoleController@delete',22,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(172,'摘要','plugin\\admin\\app\\controller\\auth\\AdminRoleController@schema',22,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(173,'获取权限树','plugin\\admin\\app\\controller\\auth\\AdminRuleController@tree',23,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(174,'添加','plugin\\admin\\app\\controller\\auth\\AdminRuleController@insert',23,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(175,'删除','plugin\\admin\\app\\controller\\auth\\AdminRuleController@delete',23,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(176,'一键生成菜单','plugin\\admin\\app\\controller\\auth\\AdminRuleController@create',23,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(177,'查询','plugin\\admin\\app\\controller\\auth\\AdminRuleController@select',23,'','',NULL,'2022-08-27 11:54:33','2022-08-30 20:51:25',NULL,0,0),(178,'更新','plugin\\admin\\app\\controller\\auth\\AdminRuleController@update',23,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0),(179,'摘要','plugin\\admin\\app\\controller\\auth\\AdminRuleController@schema',23,'','',NULL,'2022-08-27 11:54:33','2022-08-27 11:54:33',NULL,0,0); +/*!40000 ALTER TABLE `wa_admin_rules` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `wa_admins` +-- + +DROP TABLE IF EXISTS `wa_admins`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `wa_admins` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', + `username` varchar(32) NOT NULL COMMENT '用户名', + `nickname` varchar(40) NOT NULL COMMENT '昵称', + `password` varchar(255) NOT NULL COMMENT '密码', + `avatar` varchar(255) DEFAULT '/app/admin/avatar.png' COMMENT '头像', + `email` varchar(100) DEFAULT NULL COMMENT '邮箱', + `mobile` varchar(16) DEFAULT NULL COMMENT '手机', + `created_at` datetime DEFAULT NULL COMMENT '创建时间', + `updated_at` datetime DEFAULT NULL COMMENT '更新时间', + `roles` varchar(255) DEFAULT NULL COMMENT '角色', + PRIMARY KEY (`id`), + UNIQUE KEY `username` (`username`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `wa_options` +-- + +DROP TABLE IF EXISTS `wa_options`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `wa_options` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL COMMENT '键', + `value` longtext NOT NULL COMMENT '值', + `created_at` datetime NOT NULL DEFAULT '2022-08-15 00:00:00' COMMENT '创建时间', + `updated_at` datetime NOT NULL DEFAULT '2022-08-15 00:00:00' COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=45 DEFAULT CHARSET=utf8mb4 COMMENT='选项表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `wa_options` +-- + +LOCK TABLES `wa_options` WRITE; +/*!40000 ALTER TABLE `wa_options` DISABLE KEYS */; +INSERT INTO `wa_options` VALUES (30,'table_form_schema_wa_users','{\"id\":{\"field\":\"id\",\"comment\":\"主键\",\"control\":\"InputNumber\",\"form_show\":false,\"list_show\":true,\"enable_sort\":true,\"readonly\":false,\"searchable\":true,\"search_type\":\"普通查询\",\"control_args\":null},\"username\":{\"field\":\"username\",\"comment\":\"用户名\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"normal\",\"control_args\":null},\"nickname\":{\"field\":\"nickname\",\"comment\":\"昵称\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"normal\",\"control_args\":null},\"password\":{\"field\":\"password\",\"comment\":\"密码\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":null},\"sex\":{\"field\":\"sex\",\"comment\":\"性别\",\"control\":\"Select\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"normal\",\"control_args\":\"options:男:男,女:女\"},\"avatar\":{\"field\":\"avatar\",\"comment\":\"头像\",\"control\":\"Upload\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":null},\"email\":{\"field\":\"email\",\"comment\":\"邮箱\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"normal\",\"control_args\":null},\"mobile\":{\"field\":\"mobile\",\"comment\":\"手机\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"normal\",\"control_args\":null},\"level\":{\"field\":\"level\",\"comment\":\"等级\",\"control\":\"InputNumber\",\"form_show\":true,\"list_show\":true,\"enable_sort\":true,\"readonly\":false,\"searchable\":true,\"search_type\":\"normal\",\"control_args\":null},\"birthday\":{\"field\":\"birthday\",\"comment\":\"生日\",\"control\":\"DatePicker\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"normal\",\"control_args\":null},\"money\":{\"field\":\"money\",\"comment\":\"余额\",\"control\":\"InputNumber\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"normal\",\"control_args\":null},\"score\":{\"field\":\"score\",\"comment\":\"积分\",\"control\":\"InputNumber\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":true,\"searchable\":true,\"search_type\":\"normal\",\"control_args\":null},\"last_time\":{\"field\":\"last_time\",\"comment\":\"上次登录时间\",\"control\":\"DatePicker\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"normal\",\"control_args\":null},\"last_ip\":{\"field\":\"last_ip\",\"comment\":\"上次登录ip\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"normal\",\"control_args\":null},\"join_time\":{\"field\":\"join_time\",\"comment\":\"注册时间\",\"control\":\"DatePicker\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"normal\",\"control_args\":null},\"join_ip\":{\"field\":\"join_ip\",\"comment\":\"注册ip\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"normal\",\"control_args\":null},\"token\":{\"field\":\"token\",\"comment\":\"token\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":null},\"created_at\":{\"field\":\"created_at\",\"comment\":\"创建时间\",\"control\":\"DatePicker\",\"form_show\":false,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":null},\"updated_at\":{\"field\":\"updated_at\",\"comment\":\"更新时间\",\"control\":\"DatePicker\",\"form_show\":false,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":null},\"role\":{\"field\":\"role\",\"comment\":\"角色\",\"control\":\"InputNumber\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"普通查询\",\"control_args\":null}}','2022-08-15 00:00:00','2022-08-30 21:49:22'),(31,'table_form_schema_wa_admin_roles','{\"id\":{\"field\":\"id\",\"comment\":\"主键\",\"control\":\"InputNumber\",\"form_show\":false,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"普通查询\",\"control_args\":null},\"name\":{\"field\":\"name\",\"comment\":\"角色名\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"普通查询\",\"control_args\":null},\"rules\":{\"field\":\"rules\",\"comment\":\"权限\",\"control\":\"ApiTree\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"普通查询\",\"control_args\":\"url:\\/app\\/admin\\/auth\\/adminrule\\/tree;multiple:true;checkable:true;checkStrictly:false\"},\"created_at\":{\"field\":\"created_at\",\"comment\":\"创建时间\",\"control\":\"DatePicker\",\"form_show\":false,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"between\",\"control_args\":null},\"updated_at\":{\"field\":\"updated_at\",\"comment\":\"更新时间\",\"control\":\"DatePicker\",\"form_show\":false,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"普通查询\",\"control_args\":null}}','2022-08-15 00:00:00','2022-08-29 18:04:47'),(38,'table_form_schema_wa_admin_rules','{\"id\":{\"field\":\"id\",\"comment\":\"主键\",\"control\":\"InputNumber\",\"form_show\":false,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"普通查询\",\"control_args\":null},\"title\":{\"field\":\"title\",\"comment\":\"标题\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"normal\",\"control_args\":null},\"name\":{\"field\":\"name\",\"comment\":\"key\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"normal\",\"control_args\":null},\"pid\":{\"field\":\"pid\",\"comment\":\"上级菜单\",\"control\":\"ApiTreeSelect\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":null},\"component\":{\"field\":\"component\",\"comment\":\"前端组件\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":null},\"path\":{\"field\":\"path\",\"comment\":\"路径\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":null},\"icon\":{\"field\":\"icon\",\"comment\":\"图标\",\"control\":\"IconPicker\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":null},\"status\":{\"field\":\"status\",\"comment\":\"状态\",\"control\":\"Select\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":\"options:normal:正常,disabled:禁用\"},\"created_at\":{\"field\":\"created_at\",\"comment\":\"创建时间\",\"control\":\"DatePicker\",\"form_show\":false,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"普通查询\",\"control_args\":null},\"updated_at\":{\"field\":\"updated_at\",\"comment\":\"更新时间\",\"control\":\"DatePicker\",\"form_show\":false,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"普通查询\",\"control_args\":null},\"frame_src\":{\"field\":\"frame_src\",\"comment\":\"url\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":null},\"hide_menu\":{\"field\":\"hide_menu\",\"comment\":\"隐藏菜单\",\"control\":\"Switch\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":null},\"is_menu\":{\"field\":\"is_menu\",\"comment\":\"是否菜单\",\"control\":\"Select\",\"form_show\":false,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"普通查询\",\"control_args\":\"options:0:否,1:是\"}}','2022-08-15 00:00:00','2022-08-29 16:55:27'),(39,'table_form_schema_wa_admins','{\"id\":{\"field\":\"id\",\"comment\":\"ID\",\"control\":\"InputNumber\",\"form_show\":false,\"list_show\":true,\"enable_sort\":false,\"readonly\":true,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":null},\"username\":{\"field\":\"username\",\"comment\":\"用户名\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"normal\",\"control_args\":null},\"nickname\":{\"field\":\"nickname\",\"comment\":\"昵称\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"normal\",\"control_args\":null},\"password\":{\"field\":\"password\",\"comment\":\"密码\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":null},\"avatar\":{\"field\":\"avatar\",\"comment\":\"头像\",\"control\":\"Upload\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":\"url:\\/app\\/admin\\/common\\/upload\\/avatar;maxNumber:1\"},\"email\":{\"field\":\"email\",\"comment\":\"邮箱\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"normal\",\"control_args\":null},\"mobile\":{\"field\":\"mobile\",\"comment\":\"手机\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"normal\",\"control_args\":null},\"created_at\":{\"field\":\"created_at\",\"comment\":\"创建时间\",\"control\":\"DatePicker\",\"form_show\":false,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":true,\"search_type\":\"between\",\"control_args\":null},\"updated_at\":{\"field\":\"updated_at\",\"comment\":\"更新时间\",\"control\":\"DatePicker\",\"form_show\":false,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":null},\"roles\":{\"field\":\"roles\",\"comment\":\"角色\",\"control\":\"ApiTreeSelect\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":\"url:\\/app\\/admin\\/auth\\/adminrole\\/select?format=tree&status=normal\"}}','2022-08-15 00:00:00','2022-08-29 18:03:29'),(40,'table_form_schema_wa_options','{\"id\":{\"field\":\"id\",\"comment\":null,\"control\":\"InputNumber\",\"form_show\":false,\"list_show\":true,\"enable_sort\":false,\"readonly\":true,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":null},\"name\":{\"field\":\"name\",\"comment\":\"键\",\"control\":\"Input\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":null},\"value\":{\"field\":\"value\",\"comment\":\"值\",\"control\":\"InputTextArea\",\"form_show\":true,\"list_show\":true,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"normal\",\"control_args\":null},\"created_at\":{\"field\":\"created_at\",\"comment\":\"创建时间\",\"control\":\"DatePicker\",\"form_show\":false,\"list_show\":false,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"普通查询\",\"control_args\":null},\"updated_at\":{\"field\":\"updated_at\",\"comment\":\"更新时间\",\"control\":\"DatePicker\",\"form_show\":false,\"list_show\":false,\"enable_sort\":false,\"readonly\":false,\"searchable\":false,\"search_type\":\"普通查询\",\"control_args\":null}}','2022-08-15 00:00:00','2022-08-30 20:46:56'); +/*!40000 ALTER TABLE `wa_options` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `wa_users` +-- + +DROP TABLE IF EXISTS `wa_users`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `wa_users` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `username` varchar(32) NOT NULL COMMENT '用户名', + `nickname` varchar(40) NOT NULL COMMENT '昵称', + `password` varchar(255) NOT NULL COMMENT '密码', + `sex` enum('男','女') NOT NULL DEFAULT '男' COMMENT '性别', + `avatar` varchar(255) NOT NULL COMMENT '头像', + `email` varchar(128) NOT NULL COMMENT '邮箱', + `mobile` varchar(16) NOT NULL COMMENT '手机', + `level` tinyint(4) NOT NULL COMMENT '等级', + `birthday` date NOT NULL COMMENT '生日', + `money` int(10) unsigned NOT NULL COMMENT '余额', + `score` int(11) NOT NULL COMMENT '积分', + `last_time` datetime NOT NULL COMMENT '上次登录时间', + `last_ip` varchar(50) NOT NULL COMMENT '上次登录ip', + `join_time` datetime NOT NULL COMMENT '注册时间', + `join_ip` varchar(50) NOT NULL COMMENT '注册ip', + `token` varchar(50) DEFAULT NULL COMMENT 'token', + `created_at` datetime NOT NULL COMMENT '创建时间', + `updated_at` datetime NOT NULL COMMENT '更新时间', + `role` tinyint(4) DEFAULT NULL COMMENT '角色', + PRIMARY KEY (`id`), + UNIQUE KEY `username` (`username`), + KEY `email` (`email`), + KEY `mobile` (`mobile`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `wa_users` +-- + +LOCK TABLES `wa_users` WRITE; +/*!40000 ALTER TABLE `wa_users` DISABLE KEYS */; +/*!40000 ALTER TABLE `wa_users` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2022-08-31 15:09:32 diff --git a/process/Monitor.php b/process/Monitor.php new file mode 100644 index 0000000..2550d45 --- /dev/null +++ b/process/Monitor.php @@ -0,0 +1,195 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace process; + +use Workerman\Timer; +use Workerman\Worker; + +/** + * Class FileMonitor + * @package process + */ +class Monitor +{ + /** + * @var array + */ + protected $_paths = []; + + /** + * @var array + */ + protected $_extensions = []; + + /** + * FileMonitor constructor. + * @param $monitor_dir + * @param $monitor_extensions + * @param $memory_limit + */ + public function __construct($monitor_dir, $monitor_extensions, $memory_limit = null) + { + $this->_paths = (array)$monitor_dir; + $this->_extensions = $monitor_extensions; + if (!Worker::getAllWorkers()) { + return; + } + $disable_functions = explode(',', ini_get('disable_functions')); + if (in_array('exec', $disable_functions, true)) { + echo "\nMonitor file change turned off because exec() has been disabled by disable_functions setting in " . PHP_CONFIG_FILE_PATH . "/php.ini\n"; + } else { + if (!Worker::$daemonize) { + Timer::add(1, function () { + $this->checkAllFilesChange(); + }); + } + } + + $memory_limit = $this->getMemoryLimit($memory_limit); + if ($memory_limit && DIRECTORY_SEPARATOR === '/') { + Timer::add(60, [$this, 'checkMemory'], [$memory_limit]); + } + } + + /** + * @param $monitor_dir + */ + public function checkFilesChange($monitor_dir) + { + static $last_mtime, $too_many_files_check; + if (!$last_mtime) { + $last_mtime = time(); + } + clearstatcache(); + if (!is_dir($monitor_dir)) { + if (!is_file($monitor_dir)) { + return; + } + $iterator = [new \SplFileInfo($monitor_dir)]; + } else { + // recursive traversal directory + $dir_iterator = new \RecursiveDirectoryIterator($monitor_dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS); + $iterator = new \RecursiveIteratorIterator($dir_iterator); + } + $count = 0; + foreach ($iterator as $file) { + $count ++; + /** var SplFileInfo $file */ + if (is_dir($file)) { + continue; + } + // check mtime + if ($last_mtime < $file->getMTime() && in_array($file->getExtension(), $this->_extensions, true)) { + $var = 0; + exec('"'.PHP_BINARY . '" -l ' . $file, $out, $var); + if ($var) { + $last_mtime = $file->getMTime(); + continue; + } + $last_mtime = $file->getMTime(); + echo $file . " update and reload\n"; + // send SIGUSR1 signal to master process for reload + if (DIRECTORY_SEPARATOR === '/') { + posix_kill(posix_getppid(), SIGUSR1); + } else { + return true; + } + break; + } + } + if (!$too_many_files_check && $count > 1000) { + echo "Monitor: There are too many files ($count files) in $monitor_dir which makes file monitoring very slow\n"; + $too_many_files_check = 1; + } + } + + /** + * @return bool + */ + public function checkAllFilesChange() + { + foreach ($this->_paths as $path) { + if ($this->checkFilesChange($path)) { + return true; + } + } + return false; + } + + /** + * @param $memory_limit + * @return void + */ + public function checkMemory($memory_limit) + { + $ppid = posix_getppid(); + $children_file = "/proc/$ppid/task/$ppid/children"; + if (!is_file($children_file) || !($children = file_get_contents($children_file))) { + return; + } + foreach (explode(' ', $children) as $pid) { + $pid = (int)$pid; + $status_file = "/proc/$pid/status"; + if (!is_file($status_file) || !($status = file_get_contents($status_file))) { + continue; + } + $mem = 0; + if (preg_match('/VmRSS\s*?:\s*?(\d+?)\s*?kB/', $status, $match)) { + $mem = $match[1]; + } + $mem = (int)($mem / 1024); + if ($mem >= $memory_limit) { + posix_kill($pid, SIGINT); + } + } + } + + /** + * Get memory limit + * @return float + */ + protected function getMemoryLimit($memory_limit) + { + if ($memory_limit === 0) { + return 0; + } + $use_php_ini = false; + if (!$memory_limit) { + $memory_limit = ini_get('memory_limit'); + $use_php_ini = true; + } + + if ($memory_limit == -1) { + return 0; + } + $unit = $memory_limit[strlen($memory_limit) - 1]; + if ($unit == 'G') { + $memory_limit = 1024 * (int)$memory_limit; + } else if ($unit == 'M') { + $memory_limit = (int)$memory_limit; + } else if ($unit == 'K') { + $memory_limit = (int)($memory_limit / 1024); + } else { + $memory_limit = (int)($memory_limit / (1024 * 1024)); + } + if ($memory_limit < 30) { + $memory_limit = 30; + } + if ($use_php_ini) { + $memory_limit = (int)(0.8 * $memory_limit); + } + return $memory_limit; + } +} diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..da9eace --- /dev/null +++ b/public/404.html @@ -0,0 +1,12 @@ + + + 404 Not Found - webman + + +
+

404 Not Found

+
+
+
webman
+ + diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..b9f722e Binary files /dev/null and b/public/favicon.ico differ diff --git a/runtime/.gitignore b/runtime/.gitignore new file mode 100644 index 0000000..1283e48 --- /dev/null +++ b/runtime/.gitignore @@ -0,0 +1,4 @@ +* +!logs +!views +!.gitignore diff --git a/runtime/logs/.gitignore b/runtime/logs/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/runtime/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/runtime/views/.gitignore b/runtime/views/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/runtime/views/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/start.php b/start.php new file mode 100755 index 0000000..489e447 --- /dev/null +++ b/start.php @@ -0,0 +1,4 @@ +#!/usr/bin/env php +getOperation(); + $autoload = method_exists($operation, 'getPackage') ? $operation->getPackage()->getAutoload() : $operation->getTargetPackage()->getAutoload(); + if (!isset($autoload['psr-4'])) { + return; + } + $namespace = key($autoload['psr-4']); + $install_function = "\\{$namespace}Install::install"; + $plugin_const = "\\{$namespace}Install::WEBMAN_PLUGIN"; + if (defined($plugin_const) && is_callable($install_function)) { + $install_function(); + } + } + + public static function update($event) + { + static::install($event); + } + + public static function uninstall($event) + { + static::findHepler(); + $autoload = $event->getOperation()->getPackage()->getAutoload(); + if (!isset($autoload['psr-4'])) { + return; + } + $namespace = key($autoload['psr-4']); + $uninstall_function = "\\{$namespace}Install::uninstall"; + $plugin_const = "\\{$namespace}Install::WEBMAN_PLUGIN"; + if (defined($plugin_const) && is_callable($uninstall_function)) { + $uninstall_function(); + } + } + + protected static function findHepler() + { + // Plugin.php in vendor + $file = __DIR__ . '/../../../../../support/helpers.php'; + if (is_file($file)) { + require_once $file; + return; + } + // Plugin.php in webman + require_once __DIR__ . '/helpers.php'; + } + +} diff --git a/support/Request.php b/support/Request.php new file mode 100644 index 0000000..e3f6ac3 --- /dev/null +++ b/support/Request.php @@ -0,0 +1,24 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support; + +/** + * Class Request + * @package support + */ +class Request extends \Webman\Http\Request +{ + +} \ No newline at end of file diff --git a/support/Response.php b/support/Response.php new file mode 100644 index 0000000..9bc4e1e --- /dev/null +++ b/support/Response.php @@ -0,0 +1,24 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support; + +/** + * Class Response + * @package support + */ +class Response extends \Webman\Http\Response +{ + +} \ No newline at end of file diff --git a/support/bootstrap.php b/support/bootstrap.php new file mode 100644 index 0000000..7831755 --- /dev/null +++ b/support/bootstrap.php @@ -0,0 +1,132 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use Dotenv\Dotenv; +use support\Log; +use Webman\Bootstrap; +use Webman\Config; +use Webman\Route; +use Webman\Middleware; +use Webman\Util; + +$worker = $worker ?? null; + +if ($timezone = config('app.default_timezone')) { + date_default_timezone_set($timezone); +} + +set_error_handler(function ($level, $message, $file = '', $line = 0) { + if (error_reporting() & $level) { + throw new ErrorException($message, 0, $level, $file, $line); + } +}); + +if ($worker) { + register_shutdown_function(function ($start_time) { + if (time() - $start_time <= 1) { + sleep(1); + } + }, time()); +} + +if (class_exists('Dotenv\Dotenv') && file_exists(base_path() . '/.env')) { + if (method_exists('Dotenv\Dotenv', 'createUnsafeImmutable')) { + Dotenv::createUnsafeImmutable(base_path())->load(); + } else { + Dotenv::createMutable(base_path())->load(); + } +} + +support\App::loadAllConfig(['route']); + +foreach (config('autoload.files', []) as $file) { + include_once $file; +} +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['autoload']['files'] ?? [] as $file) { + include_once $file; + } + } + foreach ($projects['autoload']['files'] ?? [] as $file) { + include_once $file; + } +} + +Middleware::load(config('middleware', []), ''); +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project) || $name === 'static') { + continue; + } + Middleware::load($project['middleware'] ?? [], ''); + } + Middleware::load($projects['middleware'] ?? [], $firm); + if ($static_middlewares = config("plugin.$firm.static.middleware")) { + Middleware::load(['__static__' => $static_middlewares], $firm); + } +} +Middleware::load(['__static__' => config('static.middleware', [])], ''); + +foreach (config('bootstrap', []) as $class_name) { + if (!class_exists($class_name)) { + $log = "Warning: Class $class_name setting in config/bootstrap.php not found\r\n"; + echo $log; + Log::error($log); + continue; + } + /** @var Bootstrap $class_name */ + $class_name::start($worker); +} + +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['bootstrap'] ?? [] as $class_name) { + if (!class_exists($class_name)) { + $log = "Warning: Class $class_name setting in config/plugin/$firm/$name/bootstrap.php not found\r\n"; + echo $log; + Log::error($log); + continue; + } + /** @var Bootstrap $class_name */ + $class_name::start($worker); + } + } + foreach ($projects['bootstrap'] ?? [] as $class_name) { + if (!class_exists($class_name)) { + $log = "Warning: Class $class_name setting in plugin/$firm/config/bootstrap.php not found\r\n"; + echo $log; + Log::error($log); + continue; + } + /** @var Bootstrap $class_name */ + $class_name::start($worker); + } +} + +$directory = base_path() . '/plugin'; +$paths = [config_path()]; +foreach (Util::scanDir($directory) as $path) { + if (is_dir($path = "$path/config")) { + $paths[] = $path; + } +} +Route::load($paths); + diff --git a/support/helpers.php b/support/helpers.php new file mode 100644 index 0000000..af97b15 --- /dev/null +++ b/support/helpers.php @@ -0,0 +1,482 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\Request; +use support\Response; +use support\Translation; +use support\Container; +use support\view\Raw; +use support\view\Blade; +use support\view\ThinkPHP; +use support\view\Twig; +use Workerman\Worker; +use Webman\App; +use Webman\Config; +use Webman\Route; + +// Phar support. +if (\is_phar()) { + \define('BASE_PATH', dirname(__DIR__)); +} else { + \define('BASE_PATH', realpath(__DIR__ . '/../')); +} +\define('WEBMAN_VERSION', '1.4'); + +/** + * @param $return_phar + * @return false|string + */ +function base_path(bool $return_phar = true) +{ + static $real_path = ''; + if (!$real_path) { + $real_path = \is_phar() ? \dirname(Phar::running(false)) : BASE_PATH; + } + return $return_phar ? BASE_PATH : $real_path; +} + +/** + * @return string + */ +function app_path() +{ + return BASE_PATH . DIRECTORY_SEPARATOR . 'app'; +} + +/** + * @return string + */ +function public_path() +{ + static $path = ''; + if (!$path) { + $path = \config('app.public_path', BASE_PATH . DIRECTORY_SEPARATOR . 'public'); + } + return $path; +} + +/** + * @return string + */ +function config_path() +{ + return BASE_PATH . DIRECTORY_SEPARATOR . 'config'; +} + +/** + * Phar support. + * Compatible with the 'realpath' function in the phar file. + * + * @return string + */ +function runtime_path() +{ + static $path = ''; + if (!$path) { + $path = \config('app.runtime_path', BASE_PATH . DIRECTORY_SEPARATOR . 'runtime'); + } + return $path; +} + +/** + * @param int $status + * @param array $headers + * @param string $body + * @return Response + */ +function response($body = '', $status = 200, $headers = []) +{ + return new Response($status, $headers, $body); +} + +/** + * @param $data + * @param int $options + * @return Response + */ +function json($data, $options = JSON_UNESCAPED_UNICODE) +{ + return new Response(200, ['Content-Type' => 'application/json'], \json_encode($data, $options)); +} + +/** + * @param $xml + * @return Response + */ +function xml($xml) +{ + if ($xml instanceof SimpleXMLElement) { + $xml = $xml->asXML(); + } + return new Response(200, ['Content-Type' => 'text/xml'], $xml); +} + +/** + * @param $data + * @param string $callback_name + * @return Response + */ +function jsonp($data, $callback_name = 'callback') +{ + if (!\is_scalar($data) && null !== $data) { + $data = \json_encode($data); + } + return new Response(200, [], "$callback_name($data)"); +} + +/** + * @param string $location + * @param int $status + * @param array $headers + * @return Response + */ +function redirect(string $location, int $status = 302, array $headers = []) +{ + $response = new Response($status, ['Location' => $location]); + if (!empty($headers)) { + $response->withHeaders($headers); + } + return $response; +} + +/** + * @param $template + * @param array $vars + * @param null $app + * @return Response + */ +function view(string $template, array $vars = [], string $app = null) +{ + $request = \request(); + $plugin = $request->plugin ?? ''; + $handler = \config($plugin ? "plugin.$plugin.view.handler" : 'view.handler'); + return new Response(200, [], $handler::render($template, $vars, $app)); +} + +/** + * @param string $template + * @param array $vars + * @param string|null $app + * @return Response + * @throws Throwable + */ +function raw_view(string $template, array $vars = [], string $app = null) +{ + return new Response(200, [], Raw::render($template, $vars, $app)); +} + +/** + * @param string $template + * @param array $vars + * @param string|null $app + * @return Response + */ +function blade_view(string $template, array $vars = [], string $app = null) +{ + return new Response(200, [], Blade::render($template, $vars, $app)); +} + +/** + * @param string $template + * @param array $vars + * @param string|null $app + * @return Response + */ +function think_view(string $template, array $vars = [], string $app = null) +{ + return new Response(200, [], ThinkPHP::render($template, $vars, $app)); +} + +/** + * @param string $template + * @param array $vars + * @param string|null $app + * @return Response + */ +function twig_view(string $template, array $vars = [], string $app = null) +{ + return new Response(200, [], Twig::render($template, $vars, $app)); +} + +/** + * @return \Webman\Http\Request|null + */ +function request() +{ + return App::request(); +} + +/** + * @param string|null $key + * @param $default + * @return array|mixed|null + */ +function config(string $key = null, $default = null) +{ + return Config::get($key, $default); +} + +/** + * @param string $name + * @param ...$parameters + * @return string + */ +function route(string $name, ...$parameters) +{ + $route = Route::getByName($name); + if (!$route) { + return ''; + } + + if (!$parameters) { + return $route->url(); + } + + if (\is_array(\current($parameters))) { + $parameters = \current($parameters); + } + + return $route->url($parameters); +} + +/** + * @param mixed $key + * @param mixed $default + * @return mixed + */ +function session($key = null, $default = null) +{ + $session = \request()->session(); + if (null === $key) { + return $session; + } + if (\is_array($key)) { + $session->put($key); + return null; + } + if (\strpos($key, '.')) { + $key_array = \explode('.', $key); + $value = $session->all(); + foreach ($key_array as $index) { + if (!isset($value[$index])) { + return $default; + } + $value = $value[$index]; + } + return $value; + } + return $session->get($key, $default); +} + +/** + * @param string $id + * @param array $parameters + * @param string|null $domain + * @param string|null $locale + * @return string + */ +function trans(string $id, array $parameters = [], string $domain = null, string $locale = null) +{ + $res = Translation::trans($id, $parameters, $domain, $locale); + return $res === '' ? $id : $res; +} + +/** + * @param null|string $locale + * @return string + */ +function locale(string $locale = null) +{ + if (!$locale) { + return Translation::getLocale(); + } + Translation::setLocale($locale); +} + +/** + * 404 not found + * + * @return Response + */ +function not_found() +{ + return new Response(404, [], \file_get_contents(public_path() . '/404.html')); +} + +/** + * Copy dir. + * + * @param string $source + * @param string $dest + * @param bool $overwrite + * @return void + */ +function copy_dir(string $source, string $dest, bool $overwrite = false) +{ + if (\is_dir($source)) { + if (!is_dir($dest)) { + \mkdir($dest); + } + $files = \scandir($source); + foreach ($files as $file) { + if ($file !== "." && $file !== "..") { + \copy_dir("$source/$file", "$dest/$file"); + } + } + } else if (\file_exists($source) && ($overwrite || !\file_exists($dest))) { + \copy($source, $dest); + } +} + +/** + * Remove dir. + * + * @param string $dir + * @return bool + */ +function remove_dir(string $dir) +{ + if (\is_link($dir) || \is_file($dir)) { + return \unlink($dir); + } + $files = \array_diff(\scandir($dir), array('.', '..')); + foreach ($files as $file) { + (\is_dir("$dir/$file") && !\is_link($dir)) ? \remove_dir("$dir/$file") : \unlink("$dir/$file"); + } + return \rmdir($dir); +} + +/** + * @param $worker + * @param $class + */ +function worker_bind($worker, $class) +{ + $callback_map = [ + 'onConnect', + 'onMessage', + 'onClose', + 'onError', + 'onBufferFull', + 'onBufferDrain', + 'onWorkerStop', + 'onWebSocketConnect' + ]; + foreach ($callback_map as $name) { + if (\method_exists($class, $name)) { + $worker->$name = [$class, $name]; + } + } + if (\method_exists($class, 'onWorkerStart')) { + \call_user_func([$class, 'onWorkerStart'], $worker); + } +} + +/** + * @param $process_name + * @param $config + * @return void + */ +function worker_start($process_name, $config) +{ + $worker = new Worker($config['listen'] ?? null, $config['context'] ?? []); + $property_map = [ + 'count', + 'user', + 'group', + 'reloadable', + 'reusePort', + 'transport', + 'protocol', + ]; + $worker->name = $process_name; + foreach ($property_map as $property) { + if (isset($config[$property])) { + $worker->$property = $config[$property]; + } + } + + $worker->onWorkerStart = function ($worker) use ($config) { + require_once \base_path() . '/support/bootstrap.php'; + + foreach ($config['services'] ?? [] as $server) { + if (!\class_exists($server['handler'])) { + echo "process error: class {$server['handler']} not exists\r\n"; + continue; + } + $listen = new Worker($server['listen'] ?? null, $server['context'] ?? []); + if (isset($server['listen'])) { + echo "listen: {$server['listen']}\n"; + } + $instance = Container::make($server['handler'], $server['constructor'] ?? []); + \worker_bind($listen, $instance); + $listen->listen(); + } + + if (isset($config['handler'])) { + if (!\class_exists($config['handler'])) { + echo "process error: class {$config['handler']} not exists\r\n"; + return; + } + + $instance = Container::make($config['handler'], $config['constructor'] ?? []); + \worker_bind($worker, $instance); + } + + }; +} + +/** + * Phar support. + * Compatible with the 'realpath' function in the phar file. + * + * @param string $file_path + * @return string + */ +function get_realpath(string $file_path): string +{ + if (\strpos($file_path, 'phar://') === 0) { + return $file_path; + } else { + return \realpath($file_path); + } +} + +/** + * @return bool + */ +function is_phar() +{ + return \class_exists(\Phar::class, false) && Phar::running(); +} + +/** + * @return int + */ +function cpu_count() +{ + // Windows does not support the number of processes setting. + if (\DIRECTORY_SEPARATOR === '\\') { + return 1; + } + $count = 4; + if (\is_callable('shell_exec')) { + if (\strtolower(PHP_OS) === 'darwin') { + $count = (int)\shell_exec('sysctl -n machdep.cpu.core_count'); + } else { + $count = (int)\shell_exec('nproc'); + } + } + return $count > 0 ? $count : 4; +} diff --git a/windows.bat b/windows.bat new file mode 100644 index 0000000..f07ce53 --- /dev/null +++ b/windows.bat @@ -0,0 +1,3 @@ +CHCP 65001 +php windows.php +pause \ No newline at end of file diff --git a/windows.php b/windows.php new file mode 100644 index 0000000..08cd4ec --- /dev/null +++ b/windows.php @@ -0,0 +1,116 @@ +load(); + } else { + Dotenv::createMutable(base_path())->load(); + } +} + +App::loadAllConfig(['route']); + +$error_reporting = config('app.error_reporting'); +if (isset($error_reporting)) { + error_reporting($error_reporting); +} + +$runtime_process_path = runtime_path() . DIRECTORY_SEPARATOR . '/windows'; +if (!is_dir($runtime_process_path)) { + mkdir($runtime_process_path); +} +$process_files = [ + __DIR__ . DIRECTORY_SEPARATOR . 'start.php' +]; +foreach (config('process', []) as $process_name => $config) { + $process_files[] = write_process_file($runtime_process_path, $process_name, ''); +} + +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['process'] ?? [] as $process_name => $config) { + $process_files[] = write_process_file($runtime_process_path, $process_name, "$firm.$name"); + } + } + foreach ($projects['process'] ?? [] as $process_name => $config) { + $process_files[] = write_process_file($runtime_process_path, $process_name, $firm); + } +} + +function write_process_file($runtime_process_path, $process_name, $firm) +{ + $process_param = $firm ? "plugin.$firm.$process_name" : $process_name; + $config_param = $firm ? "config('plugin.$firm.process')['$process_name']" : "config('process')['$process_name']"; + $file_content = <<checkAllFilesChange()) { + $status = proc_get_status($resource); + $pid = $status['pid']; + shell_exec("taskkill /F /T /PID $pid"); + proc_close($resource); + $resource = popen_processes($process_files); + } +}