王新凯 3 years ago
parent
commit
93c1c5b59b
100 changed files with 19259 additions and 19 deletions
  1. 1 1
      composer.json
  2. 3 3
      vendor/composer/autoload_files.php
  3. 4 4
      vendor/composer/autoload_psr4.php
  4. 10 9
      vendor/composer/autoload_static.php
  5. 74 0
      vendor/composer/installed.json
  6. 11 2
      vendor/composer/installed.php
  7. 7 0
      vendor/topthink/framework/.gitignore
  8. 35 0
      vendor/topthink/framework/.travis.yml
  9. 119 0
      vendor/topthink/framework/CONTRIBUTING.md
  10. 32 0
      vendor/topthink/framework/LICENSE.txt
  11. 86 0
      vendor/topthink/framework/README.md
  12. 54 0
      vendor/topthink/framework/composer.json
  13. BIN
      vendor/topthink/framework/logo.png
  14. 25 0
      vendor/topthink/framework/phpunit.xml.dist
  15. 663 0
      vendor/topthink/framework/src/helper.php
  16. 148 0
      vendor/topthink/framework/src/lang/zh-cn.php
  17. 639 0
      vendor/topthink/framework/src/think/App.php
  18. 197 0
      vendor/topthink/framework/src/think/Cache.php
  19. 197 0
      vendor/topthink/framework/src/think/Config.php
  20. 787 0
      vendor/topthink/framework/src/think/Console.php
  21. 554 0
      vendor/topthink/framework/src/think/Container.php
  22. 230 0
      vendor/topthink/framework/src/think/Cookie.php
  23. 117 0
      vendor/topthink/framework/src/think/Db.php
  24. 181 0
      vendor/topthink/framework/src/think/Env.php
  25. 272 0
      vendor/topthink/framework/src/think/Event.php
  26. 60 0
      vendor/topthink/framework/src/think/Exception.php
  27. 98 0
      vendor/topthink/framework/src/think/Facade.php
  28. 187 0
      vendor/topthink/framework/src/think/File.php
  29. 89 0
      vendor/topthink/framework/src/think/Filesystem.php
  30. 288 0
      vendor/topthink/framework/src/think/Http.php
  31. 294 0
      vendor/topthink/framework/src/think/Lang.php
  32. 342 0
      vendor/topthink/framework/src/think/Log.php
  33. 177 0
      vendor/topthink/framework/src/think/Manager.php
  34. 257 0
      vendor/topthink/framework/src/think/Middleware.php
  35. 107 0
      vendor/topthink/framework/src/think/Pipeline.php
  36. 2169 0
      vendor/topthink/framework/src/think/Request.php
  37. 410 0
      vendor/topthink/framework/src/think/Response.php
  38. 926 0
      vendor/topthink/framework/src/think/Route.php
  39. 66 0
      vendor/topthink/framework/src/think/Service.php
  40. 65 0
      vendor/topthink/framework/src/think/Session.php
  41. 1688 0
      vendor/topthink/framework/src/think/Validate.php
  42. 191 0
      vendor/topthink/framework/src/think/View.php
  43. 357 0
      vendor/topthink/framework/src/think/cache/Driver.php
  44. 132 0
      vendor/topthink/framework/src/think/cache/TagSet.php
  45. 304 0
      vendor/topthink/framework/src/think/cache/driver/File.php
  46. 209 0
      vendor/topthink/framework/src/think/cache/driver/Memcache.php
  47. 221 0
      vendor/topthink/framework/src/think/cache/driver/Memcached.php
  48. 249 0
      vendor/topthink/framework/src/think/cache/driver/Redis.php
  49. 175 0
      vendor/topthink/framework/src/think/cache/driver/Wincache.php
  50. 504 0
      vendor/topthink/framework/src/think/console/Command.php
  51. 465 0
      vendor/topthink/framework/src/think/console/Input.php
  52. 19 0
      vendor/topthink/framework/src/think/console/LICENSE
  53. 231 0
      vendor/topthink/framework/src/think/console/Output.php
  54. 300 0
      vendor/topthink/framework/src/think/console/Table.php
  55. 1 0
      vendor/topthink/framework/src/think/console/bin/README.md
  56. BIN
      vendor/topthink/framework/src/think/console/bin/hiddeninput.exe
  57. 85 0
      vendor/topthink/framework/src/think/console/command/Clear.php
  58. 70 0
      vendor/topthink/framework/src/think/console/command/Help.php
  59. 74 0
      vendor/topthink/framework/src/think/console/command/Lists.php
  60. 99 0
      vendor/topthink/framework/src/think/console/command/Make.php
  61. 129 0
      vendor/topthink/framework/src/think/console/command/RouteList.php
  62. 72 0
      vendor/topthink/framework/src/think/console/command/RunServer.php
  63. 52 0
      vendor/topthink/framework/src/think/console/command/ServiceDiscover.php
  64. 69 0
      vendor/topthink/framework/src/think/console/command/VendorPublish.php
  65. 33 0
      vendor/topthink/framework/src/think/console/command/Version.php
  66. 55 0
      vendor/topthink/framework/src/think/console/command/make/Command.php
  67. 56 0
      vendor/topthink/framework/src/think/console/command/make/Controller.php
  68. 35 0
      vendor/topthink/framework/src/think/console/command/make/Event.php
  69. 35 0
      vendor/topthink/framework/src/think/console/command/make/Listener.php
  70. 36 0
      vendor/topthink/framework/src/think/console/command/make/Middleware.php
  71. 36 0
      vendor/topthink/framework/src/think/console/command/make/Model.php
  72. 36 0
      vendor/topthink/framework/src/think/console/command/make/Service.php
  73. 35 0
      vendor/topthink/framework/src/think/console/command/make/Subscribe.php
  74. 39 0
      vendor/topthink/framework/src/think/console/command/make/Validate.php
  75. 26 0
      vendor/topthink/framework/src/think/console/command/make/stubs/command.stub
  76. 64 0
      vendor/topthink/framework/src/think/console/command/make/stubs/controller.api.stub
  77. 9 0
      vendor/topthink/framework/src/think/console/command/make/stubs/controller.plain.stub
  78. 85 0
      vendor/topthink/framework/src/think/console/command/make/stubs/controller.stub
  79. 8 0
      vendor/topthink/framework/src/think/console/command/make/stubs/event.stub
  80. 17 0
      vendor/topthink/framework/src/think/console/command/make/stubs/listener.stub
  81. 19 0
      vendor/topthink/framework/src/think/console/command/make/stubs/middleware.stub
  82. 14 0
      vendor/topthink/framework/src/think/console/command/make/stubs/model.stub
  83. 27 0
      vendor/topthink/framework/src/think/console/command/make/stubs/service.stub
  84. 8 0
      vendor/topthink/framework/src/think/console/command/make/stubs/subscribe.stub
  85. 25 0
      vendor/topthink/framework/src/think/console/command/make/stubs/validate.stub
  86. 66 0
      vendor/topthink/framework/src/think/console/command/optimize/Route.php
  87. 104 0
      vendor/topthink/framework/src/think/console/command/optimize/Schema.php
  88. 138 0
      vendor/topthink/framework/src/think/console/input/Argument.php
  89. 375 0
      vendor/topthink/framework/src/think/console/input/Definition.php
  90. 221 0
      vendor/topthink/framework/src/think/console/input/Option.php
  91. 336 0
      vendor/topthink/framework/src/think/console/output/Ask.php
  92. 323 0
      vendor/topthink/framework/src/think/console/output/Descriptor.php
  93. 198 0
      vendor/topthink/framework/src/think/console/output/Formatter.php
  94. 211 0
      vendor/topthink/framework/src/think/console/output/Question.php
  95. 153 0
      vendor/topthink/framework/src/think/console/output/descriptor/Console.php
  96. 52 0
      vendor/topthink/framework/src/think/console/output/driver/Buffer.php
  97. 368 0
      vendor/topthink/framework/src/think/console/output/driver/Console.php
  98. 33 0
      vendor/topthink/framework/src/think/console/output/driver/Nothing.php
  99. 116 0
      vendor/topthink/framework/src/think/console/output/formatter/Stack.php
  100. 190 0
      vendor/topthink/framework/src/think/console/output/formatter/Style.php

+ 1 - 1
composer.json

@@ -9,7 +9,7 @@
     }
   ],
   "require": {
-    "php": ">=5.6",
+    "php": ">=7.1.0",
     "ext-gd": "*",
     "ext-curl": "*",
     "ext-json": "*",

+ 3 - 3
vendor/composer/autoload_files.php

@@ -13,14 +13,14 @@ return array(
     '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
     'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
     '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
+    '9b552a3cc426e3287cc811caefa3cf53' => $vendorDir . '/topthink/think-helper/src/helper.php',
     '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
     'd767e4fc2dc52fe66584ab8c6684783e' => $vendorDir . '/adbario/php-dot-notation/src/helpers.php',
     '65fec9ebcfbb3cbb4fd0d519687aea01' => $vendorDir . '/danielstjules/stringy/src/Create.php',
     'b067bc7112e384b61c701452d53a14a8' => $vendorDir . '/mtdowling/jmespath.php/src/JmesPath.php',
-    '66453932bc1be9fb2f910a27947d11b6' => $vendorDir . '/alibabacloud/client/src/Functions.php',
-    '9b552a3cc426e3287cc811caefa3cf53' => $vendorDir . '/topthink/think-helper/src/helper.php',
-    '8dafcc6956460bc297e00381fed53e11' => $vendorDir . '/zoujingli/think-library/src/common.php',
     '35fab96057f1bf5e7aba31a8a6d5fdde' => $vendorDir . '/topthink/think-orm/stubs/load_stubs.php',
+    '66453932bc1be9fb2f910a27947d11b6' => $vendorDir . '/alibabacloud/client/src/Functions.php',
     'f0e7e63bbb278a92db02393536748c5f' => $vendorDir . '/overtrue/wechat/src/Kernel/Support/Helpers.php',
     '6747f579ad6817f318cc3a7e7a0abb93' => $vendorDir . '/overtrue/wechat/src/Kernel/Helpers.php',
+    '8dafcc6956460bc297e00381fed53e11' => $vendorDir . '/zoujingli/think-library/src/common.php',
 );

+ 4 - 4
vendor/composer/autoload_psr4.php

@@ -7,14 +7,14 @@ $baseDir = dirname($vendorDir);
 
 return array(
     'think\\admin\\' => array($vendorDir . '/zoujingli/think-library/src'),
-    'think\\' => array($vendorDir . '/topthink/think-helper/src', $vendorDir . '/topthink/think-orm/src'),
+    'think\\' => array($vendorDir . '/topthink/think-helper/src', $vendorDir . '/topthink/think-orm/src', $vendorDir . '/topthink/framework/src/think'),
     'hg\\apidoc\\' => array($vendorDir . '/hg/apidoc/src'),
     'clagiordano\\weblibs\\configmanager\\' => array($vendorDir . '/clagiordano/weblibs-configmanager/src'),
     'WePay\\' => array($vendorDir . '/zoujingli/wechat-developer/WePay'),
     'WePayV3\\' => array($vendorDir . '/zoujingli/wechat-developer/WePayV3'),
     'WeOpen\\' => array($vendorDir . '/zoujingli/weopen-developer/WeOpen'),
-    'WeMini\\' => array($vendorDir . '/zoujingli/weopen-developer/WeMini', $vendorDir . '/zoujingli/wechat-developer/WeMini'),
-    'WeChat\\' => array($vendorDir . '/zoujingli/weopen-developer/WeChat', $vendorDir . '/zoujingli/wechat-developer/WeChat'),
+    'WeMini\\' => array($vendorDir . '/zoujingli/wechat-developer/WeMini', $vendorDir . '/zoujingli/weopen-developer/WeMini'),
+    'WeChat\\' => array($vendorDir . '/zoujingli/wechat-developer/WeChat', $vendorDir . '/zoujingli/weopen-developer/WeChat'),
     'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
     'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'),
     'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
@@ -32,7 +32,7 @@ return array(
     'Stringy\\' => array($vendorDir . '/danielstjules/stringy/src'),
     'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
     'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
-    'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src', $vendorDir . '/psr/http-factory/src'),
+    'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
     'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
     'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'),
     'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),

+ 10 - 9
vendor/composer/autoload_static.php

@@ -14,16 +14,16 @@ class ComposerStaticInit4d241e9f8bb10d006cd7432f945fdb5b
         '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
         'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
         '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
+        '9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php',
         '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
         'd767e4fc2dc52fe66584ab8c6684783e' => __DIR__ . '/..' . '/adbario/php-dot-notation/src/helpers.php',
         '65fec9ebcfbb3cbb4fd0d519687aea01' => __DIR__ . '/..' . '/danielstjules/stringy/src/Create.php',
         'b067bc7112e384b61c701452d53a14a8' => __DIR__ . '/..' . '/mtdowling/jmespath.php/src/JmesPath.php',
-        '66453932bc1be9fb2f910a27947d11b6' => __DIR__ . '/..' . '/alibabacloud/client/src/Functions.php',
-        '9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php',
-        '8dafcc6956460bc297e00381fed53e11' => __DIR__ . '/..' . '/zoujingli/think-library/src/common.php',
         '35fab96057f1bf5e7aba31a8a6d5fdde' => __DIR__ . '/..' . '/topthink/think-orm/stubs/load_stubs.php',
+        '66453932bc1be9fb2f910a27947d11b6' => __DIR__ . '/..' . '/alibabacloud/client/src/Functions.php',
         'f0e7e63bbb278a92db02393536748c5f' => __DIR__ . '/..' . '/overtrue/wechat/src/Kernel/Support/Helpers.php',
         '6747f579ad6817f318cc3a7e7a0abb93' => __DIR__ . '/..' . '/overtrue/wechat/src/Kernel/Helpers.php',
+        '8dafcc6956460bc297e00381fed53e11' => __DIR__ . '/..' . '/zoujingli/think-library/src/common.php',
     );
 
     public static $prefixLengthsPsr4 = array (
@@ -128,6 +128,7 @@ class ComposerStaticInit4d241e9f8bb10d006cd7432f945fdb5b
         array (
             0 => __DIR__ . '/..' . '/topthink/think-helper/src',
             1 => __DIR__ . '/..' . '/topthink/think-orm/src',
+            2 => __DIR__ . '/..' . '/topthink/framework/src/think',
         ),
         'hg\\apidoc\\' => 
         array (
@@ -151,13 +152,13 @@ class ComposerStaticInit4d241e9f8bb10d006cd7432f945fdb5b
         ),
         'WeMini\\' => 
         array (
-            0 => __DIR__ . '/..' . '/zoujingli/weopen-developer/WeMini',
-            1 => __DIR__ . '/..' . '/zoujingli/wechat-developer/WeMini',
+            0 => __DIR__ . '/..' . '/zoujingli/wechat-developer/WeMini',
+            1 => __DIR__ . '/..' . '/zoujingli/weopen-developer/WeMini',
         ),
         'WeChat\\' => 
         array (
-            0 => __DIR__ . '/..' . '/zoujingli/weopen-developer/WeChat',
-            1 => __DIR__ . '/..' . '/zoujingli/wechat-developer/WeChat',
+            0 => __DIR__ . '/..' . '/zoujingli/wechat-developer/WeChat',
+            1 => __DIR__ . '/..' . '/zoujingli/weopen-developer/WeChat',
         ),
         'Symfony\\Polyfill\\Php80\\' => 
         array (
@@ -229,8 +230,8 @@ class ComposerStaticInit4d241e9f8bb10d006cd7432f945fdb5b
         ),
         'Psr\\Http\\Message\\' => 
         array (
-            0 => __DIR__ . '/..' . '/psr/http-message/src',
-            1 => __DIR__ . '/..' . '/psr/http-factory/src',
+            0 => __DIR__ . '/..' . '/psr/http-factory/src',
+            1 => __DIR__ . '/..' . '/psr/http-message/src',
         ),
         'Psr\\Http\\Client\\' => 
         array (

+ 74 - 0
vendor/composer/installed.json

@@ -3525,6 +3525,80 @@
             "install-path": "../symfony/yaml"
         },
         {
+            "name": "topthink/framework",
+            "version": "v6.0.9",
+            "version_normalized": "6.0.9.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/framework.git",
+                "reference": "0b5fb453f0e533de3af3a1ab6a202510b61be617"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/framework/zipball/0b5fb453f0e533de3af3a1ab6a202510b61be617",
+                "reference": "0b5fb453f0e533de3af3a1ab6a202510b61be617",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-json": "*",
+                "ext-mbstring": "*",
+                "league/flysystem": "^1.1.4",
+                "league/flysystem-cached-adapter": "^1.0",
+                "php": ">=7.2.5",
+                "psr/container": "~1.0",
+                "psr/log": "~1.0",
+                "psr/simple-cache": "^1.0",
+                "topthink/think-helper": "^3.1.1",
+                "topthink/think-orm": "^2.0"
+            },
+            "require-dev": {
+                "mikey179/vfsstream": "^1.6",
+                "mockery/mockery": "^1.2",
+                "phpunit/phpunit": "^7.0"
+            },
+            "time": "2021-07-22T03:24:49+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "files": [],
+                "psr-4": {
+                    "think\\": "src/think/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                },
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "The ThinkPHP Framework.",
+            "homepage": "http://thinkphp.cn/",
+            "keywords": [
+                "framework",
+                "orm",
+                "thinkphp"
+            ],
+            "support": {
+                "issues": "https://github.com/top-think/framework/issues",
+                "source": "https://github.com/top-think/framework/tree/v6.0.9"
+            },
+            "install-path": "../topthink/framework"
+        },
+        {
             "name": "topthink/think-helper",
             "version": "v3.1.5",
             "version_normalized": "3.1.5.0",

+ 11 - 2
vendor/composer/installed.php

@@ -5,7 +5,7 @@
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
-        'reference' => 'dc382eb35c191afcd597d224a9d5e26df2234986',
+        'reference' => '7cede6dc9231057980f8725e7d84dd00917ecde4',
         'name' => 'zoujingli/thinkadmin',
         'dev' => true,
     ),
@@ -1477,6 +1477,15 @@
             'reference' => '02c1859112aa779d9ab394ae4f3381911d84052b',
             'dev_requirement' => false,
         ),
+        'topthink/framework' => array(
+            'pretty_version' => 'v6.0.9',
+            'version' => '6.0.9.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../topthink/framework',
+            'aliases' => array(),
+            'reference' => '0b5fb453f0e533de3af3a1ab6a202510b61be617',
+            'dev_requirement' => false,
+        ),
         'topthink/think-helper' => array(
             'pretty_version' => 'v3.1.5',
             'version' => '3.1.5.0',
@@ -1521,7 +1530,7 @@
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
-            'reference' => 'dc382eb35c191afcd597d224a9d5e26df2234986',
+            'reference' => '7cede6dc9231057980f8725e7d84dd00917ecde4',
             'dev_requirement' => false,
         ),
         'zoujingli/wechat-developer' => array(

+ 7 - 0
vendor/topthink/framework/.gitignore

@@ -0,0 +1,7 @@
+/vendor
+composer.phar
+composer.lock
+.DS_Store
+Thumbs.db
+/.idea
+/.vscode

+ 35 - 0
vendor/topthink/framework/.travis.yml

@@ -0,0 +1,35 @@
+dist: xenial
+language: php
+
+matrix:
+  fast_finish: true
+  include:
+    - php: 7.2
+    - php: 7.3
+    - php: 8.0
+
+cache:
+  directories:
+    - $HOME/.composer/cache
+
+services:
+  - memcached
+  - redis-server
+  - mysql
+
+before_install:
+  - echo "extension = memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
+  - echo 'xdebug.mode = coverage' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
+  - printf "\n" | pecl install -f redis
+  - travis_retry composer self-update
+  - mysql -e 'CREATE DATABASE test;'
+
+install:
+  - travis_retry composer update --prefer-dist --no-interaction --prefer-stable --no-suggest
+
+script:
+  - vendor/bin/phpunit --coverage-clover build/logs/coverage.xml
+
+after_script:
+  - travis_retry wget https://scrutinizer-ci.com/ocular.phar
+  - php ocular.phar code-coverage:upload --format=php-clover build/logs/coverage.xml

+ 119 - 0
vendor/topthink/framework/CONTRIBUTING.md

@@ -0,0 +1,119 @@
+如何贡献我的源代码
+===
+
+此文档介绍了 ThinkPHP 团队的组成以及运转机制,您提交的代码将给 ThinkPHP 项目带来什么好处,以及如何才能加入我们的行列。
+
+## 通过 Github 贡献代码
+
+ThinkPHP 目前使用 Git 来控制程序版本,如果你想为 ThinkPHP 贡献源代码,请先大致了解 Git 的使用方法。我们目前把项目托管在 GitHub 上,任何 GitHub 用户都可以向我们贡献代码。
+
+参与的方式很简单,`fork`一份 ThinkPHP 的代码到你的仓库中,修改后提交,并向我们发起`pull request`申请,我们会及时对代码进行审查并处理你的申请并。审查通过后,你的代码将被`merge`进我们的仓库中,这样你就会自动出现在贡献者名单里了,非常方便。
+
+我们希望你贡献的代码符合:
+
+* ThinkPHP 的编码规范
+* 适当的注释,能让其他人读懂
+* 遵循 Apache2 开源协议
+
+**如果想要了解更多细节或有任何疑问,请继续阅读下面的内容**
+
+### 注意事项
+
+* 本项目代码格式化标准选用 [**PSR-2**](http://www.kancloud.cn/thinkphp/php-fig-psr/3141);
+* 类名和类文件名遵循 [**PSR-4**](http://www.kancloud.cn/thinkphp/php-fig-psr/3144);
+* 对于 Issues 的处理,请使用诸如 `fix #xxx(Issue ID)` 的 commit title 直接关闭 issue。
+* 系统会自动在 PHP 7.1 ~ 7.3 上测试修改,请确保你的修改符合 PHP 7.1 ~ 7.3 的语法规范;
+* 管理员不会合并造成 CI faild 的修改,若出现 CI faild 请检查自己的源代码或修改相应的[单元测试文件](tests);
+
+## GitHub Issue
+
+GitHub 提供了 Issue 功能,该功能可以用于:
+
+* 提出 bug
+* 提出功能改进
+* 反馈使用体验
+
+该功能不应该用于:
+
+ * 提出修改意见(涉及代码署名和修订追溯问题)
+ * 不友善的言论
+
+## 快速修改
+
+**GitHub 提供了快速编辑文件的功能**
+
+1. 登录 GitHub 帐号;
+2. 浏览项目文件,找到要进行修改的文件;
+3. 点击右上角铅笔图标进行修改;
+4. 填写 `Commit changes` 相关内容(Title 必填);
+5. 提交修改,等待 CI 验证和管理员合并。
+
+**若您需要一次提交大量修改,请继续阅读下面的内容**
+
+## 完整流程
+
+1. `fork`本项目;
+2. 克隆(`clone`)你 `fork` 的项目到本地;
+3. 新建分支(`branch`)并检出(`checkout`)新分支;
+4. 添加本项目到你的本地 git 仓库作为上游(`upstream`);
+5. 进行修改,若你的修改包含方法或函数的增减,请记得修改[单元测试文件](tests);
+6. 变基(衍合 `rebase`)你的分支到上游 master 分支;
+7. `push` 你的本地仓库到 GitHub;
+8. 提交 `pull request`;
+9. 等待 CI 验证(若不通过则重复 5~7,GitHub 会自动更新你的 `pull request`);
+10. 等待管理员处理,并及时 `rebase` 你的分支到上游 master 分支(若上游 master 分支有修改)。
+
+*若有必要,可以 `git push -f` 强行推送 rebase 后的分支到自己的 `fork`*
+
+*绝对不可以使用 `git push -f` 强行推送修改到上游*
+
+### 注意事项
+
+* 若对上述流程有任何不清楚的地方,请查阅 GIT 教程,如 [这个](http://backlogtool.com/git-guide/cn/);
+* 对于代码**不同方面**的修改,请在自己 `fork` 的项目中**创建不同的分支**(原因参见`完整流程`第9条备注部分);
+* 变基及交互式变基操作参见 [Git 交互式变基](http://pakchoi.me/2015/03/17/git-interactive-rebase/)
+
+## 推荐资源
+
+### 开发环境
+
+* XAMPP for Windows 5.5.x
+* WampServer (for Windows)
+* upupw Apache PHP5.4 ( for Windows)
+
+或自行安装
+
+- Apache / Nginx
+- PHP 7.1 ~ 7.3
+- MySQL / MariaDB
+
+*Windows 用户推荐添加 PHP bin 目录到 PATH,方便使用 composer*
+
+*Linux 用户自行配置环境, Mac 用户推荐使用内置 Apache 配合 Homebrew 安装 PHP 和 MariaDB*
+
+### 编辑器
+
+Sublime Text 3 + phpfmt 插件
+
+phpfmt 插件参数
+
+```json
+{
+	"autocomplete": true,
+	"enable_auto_align": true,
+	"format_on_save": true,
+	"indent_with_space": true,
+	"psr1_naming": false,
+	"psr2": true,
+	"version": 4
+}
+```
+
+或其他 编辑器 / IDE 配合 PSR2 自动格式化工具
+
+### Git GUI
+
+* SourceTree
+* GitHub Desktop
+
+或其他 Git 图形界面客户端

+ 32 - 0
vendor/topthink/framework/LICENSE.txt

@@ -0,0 +1,32 @@
+
+ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
+版权所有Copyright © 2006-2019 by ThinkPHP (http://thinkphp.cn)
+All rights reserved。
+ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
+
+Apache Licence是著名的非盈利开源组织Apache采用的协议。
+该协议和BSD类似,鼓励代码共享和尊重原作者的著作权,
+允许代码修改,再作为开源或商业软件发布。需要满足
+的条件: 
+1. 需要给代码的用户一份Apache Licence ;
+2. 如果你修改了代码,需要在被修改的文件中说明;
+3. 在延伸的代码中(修改和有源代码衍生的代码中)需要
+带有原来代码中的协议,商标,专利声明和其他原来作者规
+定需要包含的说明;
+4. 如果再发布的产品中包含一个Notice文件,则在Notice文
+件中需要带有本协议内容。你可以在Notice中增加自己的
+许可,但不可以表现为对Apache Licence构成更改。 
+具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.

+ 86 - 0
vendor/topthink/framework/README.md

@@ -0,0 +1,86 @@
+![](https://box.kancloud.cn/5a0aaa69a5ff42657b5c4715f3d49221) 
+
+ThinkPHP 6.0
+===============
+
+[![Build Status](https://travis-ci.org/top-think/framework.svg?branch=6.0)](https://travis-ci.org/top-think/framework)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/top-think/framework/badges/quality-score.png?b=6.0)](https://scrutinizer-ci.com/g/top-think/framework/?branch=6.0)
+[![Code Coverage](https://scrutinizer-ci.com/g/top-think/framework/badges/coverage.png?b=6.0)](https://scrutinizer-ci.com/g/top-think/framework/?branch=6.0)
+[![Total Downloads](https://poser.pugx.org/topthink/framework/downloads)](https://packagist.org/packages/topthink/framework)
+[![Latest Stable Version](https://poser.pugx.org/topthink/framework/v/stable)](https://packagist.org/packages/topthink/framework)
+[![PHP Version](https://img.shields.io/badge/php-%3E%3D7.1-8892BF.svg)](http://www.php.net/)
+[![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework)
+
+ThinkPHP6.0底层架构采用PHP7.1改写和进一步优化。
+
+[官方应用服务市场](https://market.topthink.com) | [`ThinkAPI`——官方统一API服务](https://docs.topthink.com/think-api/)
+
+## 主要新特性
+
+* 采用`PHP7`强类型(严格模式)
+* 支持更多的`PSR`规范
+* 原生多应用支持
+* 系统服务注入支持
+* ORM作为独立组件使用
+* 增加Filesystem
+* 全新的事件系统
+* 模板引擎分离出核心
+* 内部功能中间件化
+* SESSION机制改进
+* 日志多通道支持
+* 规范扩展接口
+* 更强大的控制台
+* 对Swoole以及协程支持改进
+* 对IDE更加友好
+* 统一和精简大量用法
+
+
+> ThinkPHP6.0的运行环境要求PHP7.1+,兼容PHP8.0。
+
+## 安装
+
+~~~
+composer create-project topthink/think tp
+~~~
+
+启动服务
+
+~~~
+cd tp
+php think run
+~~~
+
+然后就可以在浏览器中访问
+
+~~~
+http://localhost:8000
+~~~
+
+如果需要更新框架使用
+~~~
+composer update topthink/framework
+~~~
+
+## 文档
+
+[完全开发手册](https://www.kancloud.cn/manual/thinkphp6_0/content)
+
+## 命名规范
+
+`ThinkPHP6`遵循PSR-2命名规范和PSR-4自动加载规范。
+
+## 参与开发
+
+直接提交PR或者Issue即可
+
+## 版权信息
+
+ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
+
+本项目包含的第三方源码和二进制文件之版权信息另行标注。
+
+版权所有Copyright © 2006-2021 by ThinkPHP (http://thinkphp.cn) All rights reserved。
+
+ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
+
+更多细节参阅 [LICENSE.txt](LICENSE.txt)

+ 54 - 0
vendor/topthink/framework/composer.json

@@ -0,0 +1,54 @@
+{
+    "name": "topthink/framework",
+    "description": "The ThinkPHP Framework.",
+    "keywords": [
+        "framework",
+        "thinkphp",
+        "ORM"
+    ],
+    "homepage": "http://thinkphp.cn/",
+    "license": "Apache-2.0",
+    "authors": [
+        {
+            "name": "liu21st",
+            "email": "liu21st@gmail.com"
+        },
+        {
+            "name": "yunwuxin",
+            "email": "448901948@qq.com"
+        }
+    ],
+    "require": {
+        "php": ">=7.2.5",
+        "ext-json": "*",
+        "ext-mbstring": "*",
+        "league/flysystem": "^1.1.4",
+        "league/flysystem-cached-adapter": "^1.0",
+        "psr/log": "~1.0",
+        "psr/container": "~1.0",
+        "psr/simple-cache": "^1.0",
+        "topthink/think-orm": "^2.0",
+        "topthink/think-helper": "^3.1.1"
+    },
+    "require-dev": {
+        "mikey179/vfsstream": "^1.6",
+        "mockery/mockery": "^1.2",
+        "phpunit/phpunit": "^7.0"
+    },
+    "autoload": {
+        "files": [],
+        "psr-4": {
+            "think\\": "src/think/"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "think\\tests\\": "tests/"
+        }
+    },
+    "minimum-stability": "dev",
+    "prefer-stable": true,
+    "config": {
+        "sort-packages": true
+    }
+}

BIN
vendor/topthink/framework/logo.png


+ 25 - 0
vendor/topthink/framework/phpunit.xml.dist

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit backupGlobals="false"
+         backupStaticAttributes="false"
+         beStrictAboutTestsThatDoNotTestAnything="false"
+         bootstrap="tests/bootstrap.php"
+         colors="true"
+         convertErrorsToExceptions="true"
+         convertNoticesToExceptions="true"
+         convertWarningsToExceptions="true"
+         processIsolation="false"
+         stopOnError="false"
+         stopOnFailure="false"
+         verbose="true"
+>
+    <testsuites>
+        <testsuite name="ThinkPHP Test Suite">
+            <directory suffix="Test.php">./tests</directory>
+        </testsuite>
+    </testsuites>
+    <filter>
+        <whitelist processUncoveredFilesFromWhitelist="true">
+            <directory suffix=".php">./src/think</directory>
+        </whitelist>
+    </filter>
+</phpunit>

+ 663 - 0
vendor/topthink/framework/src/helper.php

@@ -0,0 +1,663 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+//------------------------
+// ThinkPHP 助手函数
+//-------------------------
+
+use think\App;
+use think\Container;
+use think\exception\HttpException;
+use think\exception\HttpResponseException;
+use think\facade\Cache;
+use think\facade\Config;
+use think\facade\Cookie;
+use think\facade\Env;
+use think\facade\Event;
+use think\facade\Lang;
+use think\facade\Log;
+use think\facade\Request;
+use think\facade\Route;
+use think\facade\Session;
+use think\Response;
+use think\response\File;
+use think\response\Json;
+use think\response\Jsonp;
+use think\response\Redirect;
+use think\response\View;
+use think\response\Xml;
+use think\route\Url as UrlBuild;
+use think\Validate;
+
+if (!function_exists('abort')) {
+    /**
+     * 抛出HTTP异常
+     * @param integer|Response $code    状态码 或者 Response对象实例
+     * @param string           $message 错误信息
+     * @param array            $header  参数
+     */
+    function abort($code, string $message = '', array $header = [])
+    {
+        if ($code instanceof Response) {
+            throw new HttpResponseException($code);
+        } else {
+            throw new HttpException($code, $message, null, $header);
+        }
+    }
+}
+
+if (!function_exists('app')) {
+    /**
+     * 快速获取容器中的实例 支持依赖注入
+     * @param string $name        类名或标识 默认获取当前应用实例
+     * @param array  $args        参数
+     * @param bool   $newInstance 是否每次创建新的实例
+     * @return object|App
+     */
+    function app(string $name = '', array $args = [], bool $newInstance = false)
+    {
+        return Container::getInstance()->make($name ?: App::class, $args, $newInstance);
+    }
+}
+
+if (!function_exists('bind')) {
+    /**
+     * 绑定一个类到容器
+     * @param string|array $abstract 类标识、接口(支持批量绑定)
+     * @param mixed        $concrete 要绑定的类、闭包或者实例
+     * @return Container
+     */
+    function bind($abstract, $concrete = null)
+    {
+        return Container::getInstance()->bind($abstract, $concrete);
+    }
+}
+
+if (!function_exists('cache')) {
+    /**
+     * 缓存管理
+     * @param string $name    缓存名称
+     * @param mixed  $value   缓存值
+     * @param mixed  $options 缓存参数
+     * @param string $tag     缓存标签
+     * @return mixed
+     */
+    function cache(string $name = null, $value = '', $options = null, $tag = null)
+    {
+        if (is_null($name)) {
+            return app('cache');
+        }
+
+        if ('' === $value) {
+            // 获取缓存
+            return 0 === strpos($name, '?') ? Cache::has(substr($name, 1)) : Cache::get($name);
+        } elseif (is_null($value)) {
+            // 删除缓存
+            return Cache::delete($name);
+        }
+
+        // 缓存数据
+        if (is_array($options)) {
+            $expire = $options['expire'] ?? null; //修复查询缓存无法设置过期时间
+        } else {
+            $expire = $options;
+        }
+
+        if (is_null($tag)) {
+            return Cache::set($name, $value, $expire);
+        } else {
+            return Cache::tag($tag)->set($name, $value, $expire);
+        }
+    }
+}
+
+if (!function_exists('config')) {
+    /**
+     * 获取和设置配置参数
+     * @param string|array $name  参数名
+     * @param mixed        $value 参数值
+     * @return mixed
+     */
+    function config($name = '', $value = null)
+    {
+        if (is_array($name)) {
+            return Config::set($name, $value);
+        }
+
+        return 0 === strpos($name, '?') ? Config::has(substr($name, 1)) : Config::get($name, $value);
+    }
+}
+
+if (!function_exists('cookie')) {
+    /**
+     * Cookie管理
+     * @param string $name   cookie名称
+     * @param mixed  $value  cookie值
+     * @param mixed  $option 参数
+     * @return mixed
+     */
+    function cookie(string $name, $value = '', $option = null)
+    {
+        if (is_null($value)) {
+            // 删除
+            Cookie::delete($name);
+        } elseif ('' === $value) {
+            // 获取
+            return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1)) : Cookie::get($name);
+        } else {
+            // 设置
+            return Cookie::set($name, $value, $option);
+        }
+    }
+}
+
+if (!function_exists('download')) {
+    /**
+     * 获取\think\response\Download对象实例
+     * @param string $filename 要下载的文件
+     * @param string $name     显示文件名
+     * @param bool   $content  是否为内容
+     * @param int    $expire   有效期(秒)
+     * @return \think\response\File
+     */
+    function download(string $filename, string $name = '', bool $content = false, int $expire = 180): File
+    {
+        return Response::create($filename, 'file')->name($name)->isContent($content)->expire($expire);
+    }
+}
+
+if (!function_exists('dump')) {
+    /**
+     * 浏览器友好的变量输出
+     * @param mixed $vars 要输出的变量
+     * @return void
+     */
+    function dump(...$vars)
+    {
+        ob_start();
+        var_dump(...$vars);
+
+        $output = ob_get_clean();
+        $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output);
+
+        if (PHP_SAPI == 'cli') {
+            $output = PHP_EOL . $output . PHP_EOL;
+        } else {
+            if (!extension_loaded('xdebug')) {
+                $output = htmlspecialchars($output, ENT_SUBSTITUTE);
+            }
+            $output = '<pre>' . $output . '</pre>';
+        }
+
+        echo $output;
+    }
+}
+
+if (!function_exists('env')) {
+    /**
+     * 获取环境变量值
+     * @access public
+     * @param string $name    环境变量名(支持二级 .号分割)
+     * @param string $default 默认值
+     * @return mixed
+     */
+    function env(string $name = null, $default = null)
+    {
+        return Env::get($name, $default);
+    }
+}
+
+if (!function_exists('event')) {
+    /**
+     * 触发事件
+     * @param mixed $event 事件名(或者类名)
+     * @param mixed $args  参数
+     * @return mixed
+     */
+    function event($event, $args = null)
+    {
+        return Event::trigger($event, $args);
+    }
+}
+
+if (!function_exists('halt')) {
+    /**
+     * 调试变量并且中断输出
+     * @param mixed $vars 调试变量或者信息
+     */
+    function halt(...$vars)
+    {
+        dump(...$vars);
+
+        throw new HttpResponseException(Response::create());
+    }
+}
+
+if (!function_exists('input')) {
+    /**
+     * 获取输入数据 支持默认值和过滤
+     * @param string $key     获取的变量名
+     * @param mixed  $default 默认值
+     * @param string $filter  过滤方法
+     * @return mixed
+     */
+    function input(string $key = '', $default = null, $filter = '')
+    {
+        if (0 === strpos($key, '?')) {
+            $key = substr($key, 1);
+            $has = true;
+        }
+
+        if ($pos = strpos($key, '.')) {
+            // 指定参数来源
+            $method = substr($key, 0, $pos);
+            if (in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) {
+                $key = substr($key, $pos + 1);
+                if ('server' == $method && is_null($default)) {
+                    $default = '';
+                }
+            } else {
+                $method = 'param';
+            }
+        } else {
+            // 默认为自动判断
+            $method = 'param';
+        }
+
+        return isset($has) ?
+        request()->has($key, $method) :
+        request()->$method($key, $default, $filter);
+    }
+}
+
+if (!function_exists('invoke')) {
+    /**
+     * 调用反射实例化对象或者执行方法 支持依赖注入
+     * @param mixed $call 类名或者callable
+     * @param array $args 参数
+     * @return mixed
+     */
+    function invoke($call, array $args = [])
+    {
+        if (is_callable($call)) {
+            return Container::getInstance()->invoke($call, $args);
+        }
+
+        return Container::getInstance()->invokeClass($call, $args);
+    }
+}
+
+if (!function_exists('json')) {
+    /**
+     * 获取\think\response\Json对象实例
+     * @param mixed $data    返回的数据
+     * @param int   $code    状态码
+     * @param array $header  头部
+     * @param array $options 参数
+     * @return \think\response\Json
+     */
+    function json($data = [], $code = 200, $header = [], $options = []): Json
+    {
+        return Response::create($data, 'json', $code)->header($header)->options($options);
+    }
+}
+
+if (!function_exists('jsonp')) {
+    /**
+     * 获取\think\response\Jsonp对象实例
+     * @param mixed $data    返回的数据
+     * @param int   $code    状态码
+     * @param array $header  头部
+     * @param array $options 参数
+     * @return \think\response\Jsonp
+     */
+    function jsonp($data = [], $code = 200, $header = [], $options = []): Jsonp
+    {
+        return Response::create($data, 'jsonp', $code)->header($header)->options($options);
+    }
+}
+
+if (!function_exists('lang')) {
+    /**
+     * 获取语言变量值
+     * @param string $name 语言变量名
+     * @param array  $vars 动态变量值
+     * @param string $lang 语言
+     * @return mixed
+     */
+    function lang(string $name, array $vars = [], string $lang = '')
+    {
+        return Lang::get($name, $vars, $lang);
+    }
+}
+
+if (!function_exists('parse_name')) {
+    /**
+     * 字符串命名风格转换
+     * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格
+     * @param string $name    字符串
+     * @param int    $type    转换类型
+     * @param bool   $ucfirst 首字母是否大写(驼峰规则)
+     * @return string
+     */
+    function parse_name(string $name, int $type = 0, bool $ucfirst = true): string
+    {
+        if ($type) {
+            $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) {
+                return strtoupper($match[1]);
+            }, $name);
+
+            return $ucfirst ? ucfirst($name) : lcfirst($name);
+        }
+
+        return strtolower(trim(preg_replace('/[A-Z]/', '_\\0', $name), '_'));
+    }
+}
+
+if (!function_exists('redirect')) {
+    /**
+     * 获取\think\response\Redirect对象实例
+     * @param string $url  重定向地址
+     * @param int    $code 状态码
+     * @return \think\response\Redirect
+     */
+    function redirect(string $url = '', int $code = 302): Redirect
+    {
+        return Response::create($url, 'redirect', $code);
+    }
+}
+
+if (!function_exists('request')) {
+    /**
+     * 获取当前Request对象实例
+     * @return Request
+     */
+    function request(): \think\Request
+    {
+        return app('request');
+    }
+}
+
+if (!function_exists('response')) {
+    /**
+     * 创建普通 Response 对象实例
+     * @param mixed      $data   输出数据
+     * @param int|string $code   状态码
+     * @param array      $header 头信息
+     * @param string     $type
+     * @return Response
+     */
+    function response($data = '', $code = 200, $header = [], $type = 'html'): Response
+    {
+        return Response::create($data, $type, $code)->header($header);
+    }
+}
+
+if (!function_exists('session')) {
+    /**
+     * Session管理
+     * @param string $name  session名称
+     * @param mixed  $value session值
+     * @return mixed
+     */
+    function session($name = '', $value = '')
+    {
+        if (is_null($name)) {
+            // 清除
+            Session::clear();
+        } elseif ('' === $name) {
+            return Session::all();
+        } elseif (is_null($value)) {
+            // 删除
+            Session::delete($name);
+        } elseif ('' === $value) {
+            // 判断或获取
+            return 0 === strpos($name, '?') ? Session::has(substr($name, 1)) : Session::get($name);
+        } else {
+            // 设置
+            Session::set($name, $value);
+        }
+    }
+}
+
+if (!function_exists('token')) {
+    /**
+     * 获取Token令牌
+     * @param string $name 令牌名称
+     * @param mixed  $type 令牌生成方法
+     * @return string
+     */
+    function token(string $name = '__token__', string $type = 'md5'): string
+    {
+        return Request::buildToken($name, $type);
+    }
+}
+
+if (!function_exists('token_field')) {
+    /**
+     * 生成令牌隐藏表单
+     * @param string $name 令牌名称
+     * @param mixed  $type 令牌生成方法
+     * @return string
+     */
+    function token_field(string $name = '__token__', string $type = 'md5'): string
+    {
+        $token = Request::buildToken($name, $type);
+
+        return '<input type="hidden" name="' . $name . '" value="' . $token . '" />';
+    }
+}
+
+if (!function_exists('token_meta')) {
+    /**
+     * 生成令牌meta
+     * @param string $name 令牌名称
+     * @param mixed  $type 令牌生成方法
+     * @return string
+     */
+    function token_meta(string $name = '__token__', string $type = 'md5'): string
+    {
+        $token = Request::buildToken($name, $type);
+
+        return '<meta name="csrf-token" content="' . $token . '">';
+    }
+}
+
+if (!function_exists('trace')) {
+    /**
+     * 记录日志信息
+     * @param mixed  $log   log信息 支持字符串和数组
+     * @param string $level 日志级别
+     * @return array|void
+     */
+    function trace($log = '[think]', string $level = 'log')
+    {
+        if ('[think]' === $log) {
+            return Log::getLog();
+        }
+
+        Log::record($log, $level);
+    }
+}
+
+if (!function_exists('url')) {
+    /**
+     * Url生成
+     * @param string      $url    路由地址
+     * @param array       $vars   变量
+     * @param bool|string $suffix 生成的URL后缀
+     * @param bool|string $domain 域名
+     * @return UrlBuild
+     */
+    function url(string $url = '', array $vars = [], $suffix = true, $domain = false): UrlBuild
+    {
+        return Route::buildUrl($url, $vars)->suffix($suffix)->domain($domain);
+    }
+}
+
+if (!function_exists('validate')) {
+    /**
+     * 生成验证对象
+     * @param string|array $validate      验证器类名或者验证规则数组
+     * @param array        $message       错误提示信息
+     * @param bool         $batch         是否批量验证
+     * @param bool         $failException 是否抛出异常
+     * @return Validate
+     */
+    function validate($validate = '', array $message = [], bool $batch = false, bool $failException = true): Validate
+    {
+        if (is_array($validate) || '' === $validate) {
+            $v = new Validate();
+            if (is_array($validate)) {
+                $v->rule($validate);
+            }
+        } else {
+            if (strpos($validate, '.')) {
+                // 支持场景
+                [$validate, $scene] = explode('.', $validate);
+            }
+
+            $class = false !== strpos($validate, '\\') ? $validate : app()->parseClass('validate', $validate);
+
+            $v = new $class();
+
+            if (!empty($scene)) {
+                $v->scene($scene);
+            }
+        }
+
+        return $v->message($message)->batch($batch)->failException($failException);
+    }
+}
+
+if (!function_exists('view')) {
+    /**
+     * 渲染模板输出
+     * @param string   $template 模板文件
+     * @param array    $vars     模板变量
+     * @param int      $code     状态码
+     * @param callable $filter   内容过滤
+     * @return \think\response\View
+     */
+    function view(string $template = '', $vars = [], $code = 200, $filter = null): View
+    {
+        return Response::create($template, 'view', $code)->assign($vars)->filter($filter);
+    }
+}
+
+if (!function_exists('display')) {
+    /**
+     * 渲染模板输出
+     * @param string   $content 渲染内容
+     * @param array    $vars    模板变量
+     * @param int      $code    状态码
+     * @param callable $filter  内容过滤
+     * @return \think\response\View
+     */
+    function display(string $content, $vars = [], $code = 200, $filter = null): View
+    {
+        return Response::create($content, 'view', $code)->isContent(true)->assign($vars)->filter($filter);
+    }
+}
+
+if (!function_exists('xml')) {
+    /**
+     * 获取\think\response\Xml对象实例
+     * @param mixed $data    返回的数据
+     * @param int   $code    状态码
+     * @param array $header  头部
+     * @param array $options 参数
+     * @return \think\response\Xml
+     */
+    function xml($data = [], $code = 200, $header = [], $options = []): Xml
+    {
+        return Response::create($data, 'xml', $code)->header($header)->options($options);
+    }
+}
+
+if (!function_exists('app_path')) {
+    /**
+     * 获取当前应用目录
+     *
+     * @param string $path
+     * @return string
+     */
+    function app_path($path = '')
+    {
+        return app()->getAppPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
+    }
+}
+
+if (!function_exists('base_path')) {
+    /**
+     * 获取应用基础目录
+     *
+     * @param string $path
+     * @return string
+     */
+    function base_path($path = '')
+    {
+        return app()->getBasePath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
+    }
+}
+
+if (!function_exists('config_path')) {
+    /**
+     * 获取应用配置目录
+     *
+     * @param string $path
+     * @return string
+     */
+    function config_path($path = '')
+    {
+        return app()->getConfigPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
+    }
+}
+
+if (!function_exists('public_path')) {
+    /**
+     * 获取web根目录
+     *
+     * @param string $path
+     * @return string
+     */
+    function public_path($path = '')
+    {
+        return app()->getRootPath() . 'public' . DIRECTORY_SEPARATOR . ($path ? ltrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $path);
+    }
+}
+
+if (!function_exists('runtime_path')) {
+    /**
+     * 获取应用运行时目录
+     *
+     * @param string $path
+     * @return string
+     */
+    function runtime_path($path = '')
+    {
+        return app()->getRuntimePath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
+    }
+}
+
+if (!function_exists('root_path')) {
+    /**
+     * 获取项目根目录
+     *
+     * @param string $path
+     * @return string
+     */
+    function root_path($path = '')
+    {
+        return app()->getRootPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path);
+    }
+}

+ 148 - 0
vendor/topthink/framework/src/lang/zh-cn.php

@@ -0,0 +1,148 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+// 核心中文语言包
+return [
+    // 系统错误提示
+    'Undefined variable'                                        => '未定义变量',
+    'Undefined index'                                           => '未定义数组索引',
+    'Undefined offset'                                          => '未定义数组下标',
+    'Parse error'                                               => '语法解析错误',
+    'Type error'                                                => '类型错误',
+    'Fatal error'                                               => '致命错误',
+    'syntax error'                                              => '语法错误',
+
+    // 框架核心错误提示
+    'dispatch type not support'                                 => '不支持的调度类型',
+    'method param miss'                                         => '方法参数错误',
+    'method not exists'                                         => '方法不存在',
+    'function not exists'                                       => '函数不存在',
+    'app not exists'                                            => '应用不存在',
+    'controller not exists'                                     => '控制器不存在',
+    'class not exists'                                          => '类不存在',
+    'property not exists'                                       => '类的属性不存在',
+    'template not exists'                                       => '模板文件不存在',
+    'illegal controller name'                                   => '非法的控制器名称',
+    'illegal action name'                                       => '非法的操作名称',
+    'url suffix deny'                                           => '禁止的URL后缀访问',
+    'Undefined cache config'                                    => '缓存配置未定义',
+    'Route Not Found'                                           => '当前访问路由未定义或不匹配',
+    'Undefined db config'                                       => '数据库配置未定义',
+    'Undefined log config'                                      => '日志配置未定义',
+    'Undefined db type'                                         => '未定义数据库类型',
+    'variable type error'                                       => '变量类型错误',
+    'PSR-4 error'                                               => 'PSR-4 规范错误',
+    'not support type'                                          => '不支持的分页索引字段类型',
+    'not support total'                                         => '简洁模式下不能获取数据总数',
+    'not support last'                                          => '简洁模式下不能获取最后一页',
+    'error session handler'                                     => '错误的SESSION处理器类',
+    'not allow php tag'                                         => '模板不允许使用PHP语法',
+    'not support'                                               => '不支持',
+    'database config error'                                     => '数据库配置信息错误',
+    'redisd master'                                             => 'Redisd 主服务器错误',
+    'redisd slave'                                              => 'Redisd 从服务器错误',
+    'must run at sae'                                           => '必须在SAE运行',
+    'memcache init error'                                       => '未开通Memcache服务,请在SAE管理平台初始化Memcache服务',
+    'KVDB init error'                                           => '没有初始化KVDB,请在SAE管理平台初始化KVDB服务',
+    'fields not exists'                                         => '数据表字段不存在',
+    'where express error'                                       => '查询表达式错误',
+    'no data to update'                                         => '没有任何数据需要更新',
+    'miss data to insert'                                       => '缺少需要写入的数据',
+    'miss complex primary data'                                 => '缺少复合主键数据',
+    'miss update condition'                                     => '缺少更新条件',
+    'model data Not Found'                                      => '模型数据不存在',
+    'table data not Found'                                      => '表数据不存在',
+    'delete without condition'                                  => '没有条件不会执行删除操作',
+    'miss relation data'                                        => '缺少关联表数据',
+    'tag attr must'                                             => '模板标签属性必须',
+    'tag error'                                                 => '模板标签错误',
+    'cache write error'                                         => '缓存写入失败',
+    'sae mc write error'                                        => 'SAE mc 写入错误',
+    'route name not exists'                                     => '路由标识不存在(或参数不够)',
+    'invalid request'                                           => '非法请求',
+    'bind attr has exists'                                      => '模型的属性已经存在',
+    'relation data not exists'                                  => '关联数据不存在',
+    'relation not support'                                      => '关联不支持',
+    'chunk not support order'                                   => 'Chunk不支持调用order方法',
+    'route pattern error'                                       => '路由变量规则定义错误',
+    'route behavior will not support'                           => '路由行为废弃(使用中间件替代)',
+    'closure not support cache(true)'                           => '使用闭包查询不支持cache(true),请指定缓存Key',
+
+    // 上传错误信息
+    'unknown upload error'                                      => '未知上传错误!',
+    'file write error'                                          => '文件写入失败!',
+    'upload temp dir not found'                                 => '找不到临时文件夹!',
+    'no file to uploaded'                                       => '没有文件被上传!',
+    'only the portion of file is uploaded'                      => '文件只有部分被上传!',
+    'upload File size exceeds the maximum value'                => '上传文件大小超过了最大值!',
+    'upload write error'                                        => '文件上传保存错误!',
+    'has the same filename: {:filename}'                        => '存在同名文件:{:filename}',
+    'upload illegal files'                                      => '非法上传文件',
+    'illegal image files'                                       => '非法图片文件',
+    'extensions to upload is not allowed'                       => '上传文件后缀不允许',
+    'mimetype to upload is not allowed'                         => '上传文件MIME类型不允许!',
+    'filesize not match'                                        => '上传文件大小不符!',
+    'directory {:path} creation failed'                         => '目录 {:path} 创建失败!',
+
+    'The middleware must return Response instance'              => '中间件方法必须返回Response对象实例',
+    'The queue was exhausted, with no response returned'        => '中间件队列为空',
+    // Validate Error Message
+    ':attribute require'                                        => ':attribute不能为空',
+    ':attribute must'                                           => ':attribute必须',
+    ':attribute must be numeric'                                => ':attribute必须是数字',
+    ':attribute must be integer'                                => ':attribute必须是整数',
+    ':attribute must be float'                                  => ':attribute必须是浮点数',
+    ':attribute must be bool'                                   => ':attribute必须是布尔值',
+    ':attribute not a valid email address'                      => ':attribute格式不符',
+    ':attribute not a valid mobile'                             => ':attribute格式不符',
+    ':attribute must be a array'                                => ':attribute必须是数组',
+    ':attribute must be yes,on or 1'                            => ':attribute必须是yes、on或者1',
+    ':attribute not a valid datetime'                           => ':attribute不是一个有效的日期或时间格式',
+    ':attribute not a valid file'                               => ':attribute不是有效的上传文件',
+    ':attribute not a valid image'                              => ':attribute不是有效的图像文件',
+    ':attribute must be alpha'                                  => ':attribute只能是字母',
+    ':attribute must be alpha-numeric'                          => ':attribute只能是字母和数字',
+    ':attribute must be alpha-numeric, dash, underscore'        => ':attribute只能是字母、数字和下划线_及破折号-',
+    ':attribute not a valid domain or ip'                       => ':attribute不是有效的域名或者IP',
+    ':attribute must be chinese'                                => ':attribute只能是汉字',
+    ':attribute must be chinese or alpha'                       => ':attribute只能是汉字、字母',
+    ':attribute must be chinese,alpha-numeric'                  => ':attribute只能是汉字、字母和数字',
+    ':attribute must be chinese,alpha-numeric,underscore, dash' => ':attribute只能是汉字、字母、数字和下划线_及破折号-',
+    ':attribute not a valid url'                                => ':attribute不是有效的URL地址',
+    ':attribute not a valid ip'                                 => ':attribute不是有效的IP地址',
+    ':attribute must be dateFormat of :rule'                    => ':attribute必须使用日期格式 :rule',
+    ':attribute must be in :rule'                               => ':attribute必须在 :rule 范围内',
+    ':attribute be notin :rule'                                 => ':attribute不能在 :rule 范围内',
+    ':attribute must between :1 - :2'                           => ':attribute只能在 :1 - :2 之间',
+    ':attribute not between :1 - :2'                            => ':attribute不能在 :1 - :2 之间',
+    'size of :attribute must be :rule'                          => ':attribute长度不符合要求 :rule',
+    'max size of :attribute must be :rule'                      => ':attribute长度不能超过 :rule',
+    'min size of :attribute must be :rule'                      => ':attribute长度不能小于 :rule',
+    ':attribute cannot be less than :rule'                      => ':attribute日期不能小于 :rule',
+    ':attribute cannot exceed :rule'                            => ':attribute日期不能超过 :rule',
+    ':attribute not within :rule'                               => '不在有效期内 :rule',
+    'access IP is not allowed'                                  => '不允许的IP访问',
+    'access IP denied'                                          => '禁止的IP访问',
+    ':attribute out of accord with :2'                          => ':attribute和确认字段:2不一致',
+    ':attribute cannot be same with :2'                         => ':attribute和比较字段:2不能相同',
+    ':attribute must greater than or equal :rule'               => ':attribute必须大于等于 :rule',
+    ':attribute must greater than :rule'                        => ':attribute必须大于 :rule',
+    ':attribute must less than or equal :rule'                  => ':attribute必须小于等于 :rule',
+    ':attribute must less than :rule'                           => ':attribute必须小于 :rule',
+    ':attribute must equal :rule'                               => ':attribute必须等于 :rule',
+    ':attribute has exists'                                     => ':attribute已存在',
+    ':attribute not conform to the rules'                       => ':attribute不符合指定规则',
+    'invalid Request method'                                    => '无效的请求类型',
+    'invalid token'                                             => '令牌数据无效',
+    'not conform to the rules'                                  => '规则错误',
+
+    'record has update'                                         => '记录已经被更新了',
+];

+ 639 - 0
vendor/topthink/framework/src/think/App.php

@@ -0,0 +1,639 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use think\event\AppInit;
+use think\helper\Str;
+use think\initializer\BootService;
+use think\initializer\Error;
+use think\initializer\RegisterService;
+
+/**
+ * App 基础类
+ * @property Route      $route
+ * @property Config     $config
+ * @property Cache      $cache
+ * @property Request    $request
+ * @property Http       $http
+ * @property Console    $console
+ * @property Env        $env
+ * @property Event      $event
+ * @property Middleware $middleware
+ * @property Log        $log
+ * @property Lang       $lang
+ * @property Db         $db
+ * @property Cookie     $cookie
+ * @property Session    $session
+ * @property Validate   $validate
+ * @property Filesystem $filesystem
+ */
+class App extends Container
+{
+    const VERSION = '6.0.9';
+
+    /**
+     * 应用调试模式
+     * @var bool
+     */
+    protected $appDebug = false;
+
+    /**
+     * 环境变量标识
+     * @var string
+     */
+    protected $envName = '';
+
+    /**
+     * 应用开始时间
+     * @var float
+     */
+    protected $beginTime;
+
+    /**
+     * 应用内存初始占用
+     * @var integer
+     */
+    protected $beginMem;
+
+    /**
+     * 当前应用类库命名空间
+     * @var string
+     */
+    protected $namespace = 'app';
+
+    /**
+     * 应用根目录
+     * @var string
+     */
+    protected $rootPath = '';
+
+    /**
+     * 框架目录
+     * @var string
+     */
+    protected $thinkPath = '';
+
+    /**
+     * 应用目录
+     * @var string
+     */
+    protected $appPath = '';
+
+    /**
+     * Runtime目录
+     * @var string
+     */
+    protected $runtimePath = '';
+
+    /**
+     * 路由定义目录
+     * @var string
+     */
+    protected $routePath = '';
+
+    /**
+     * 配置后缀
+     * @var string
+     */
+    protected $configExt = '.php';
+
+    /**
+     * 应用初始化器
+     * @var array
+     */
+    protected $initializers = [
+        Error::class,
+        RegisterService::class,
+        BootService::class,
+    ];
+
+    /**
+     * 注册的系统服务
+     * @var array
+     */
+    protected $services = [];
+
+    /**
+     * 初始化
+     * @var bool
+     */
+    protected $initialized = false;
+
+    /**
+     * 容器绑定标识
+     * @var array
+     */
+    protected $bind = [
+        'app'                     => App::class,
+        'cache'                   => Cache::class,
+        'config'                  => Config::class,
+        'console'                 => Console::class,
+        'cookie'                  => Cookie::class,
+        'db'                      => Db::class,
+        'env'                     => Env::class,
+        'event'                   => Event::class,
+        'http'                    => Http::class,
+        'lang'                    => Lang::class,
+        'log'                     => Log::class,
+        'middleware'              => Middleware::class,
+        'request'                 => Request::class,
+        'response'                => Response::class,
+        'route'                   => Route::class,
+        'session'                 => Session::class,
+        'validate'                => Validate::class,
+        'view'                    => View::class,
+        'filesystem'              => Filesystem::class,
+        'think\DbManager'         => Db::class,
+        'think\LogManager'        => Log::class,
+        'think\CacheManager'      => Cache::class,
+
+        // 接口依赖注入
+        'Psr\Log\LoggerInterface' => Log::class,
+    ];
+
+    /**
+     * 架构方法
+     * @access public
+     * @param string $rootPath 应用根目录
+     */
+    public function __construct(string $rootPath = '')
+    {
+        $this->thinkPath   = dirname(__DIR__) . DIRECTORY_SEPARATOR;
+        $this->rootPath    = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath();
+        $this->appPath     = $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
+        $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
+
+        if (is_file($this->appPath . 'provider.php')) {
+            $this->bind(include $this->appPath . 'provider.php');
+        }
+
+        static::setInstance($this);
+
+        $this->instance('app', $this);
+        $this->instance('think\Container', $this);
+    }
+
+    /**
+     * 注册服务
+     * @access public
+     * @param Service|string $service 服务
+     * @param bool           $force   强制重新注册
+     * @return Service|null
+     */
+    public function register($service, bool $force = false)
+    {
+        $registered = $this->getService($service);
+
+        if ($registered && !$force) {
+            return $registered;
+        }
+
+        if (is_string($service)) {
+            $service = new $service($this);
+        }
+
+        if (method_exists($service, 'register')) {
+            $service->register();
+        }
+
+        if (property_exists($service, 'bind')) {
+            $this->bind($service->bind);
+        }
+
+        $this->services[] = $service;
+    }
+
+    /**
+     * 执行服务
+     * @access public
+     * @param Service $service 服务
+     * @return mixed
+     */
+    public function bootService($service)
+    {
+        if (method_exists($service, 'boot')) {
+            return $this->invoke([$service, 'boot']);
+        }
+    }
+
+    /**
+     * 获取服务
+     * @param string|Service $service
+     * @return Service|null
+     */
+    public function getService($service)
+    {
+        $name = is_string($service) ? $service : get_class($service);
+        return array_values(array_filter($this->services, function ($value) use ($name) {
+            return $value instanceof $name;
+        }, ARRAY_FILTER_USE_BOTH))[0] ?? null;
+    }
+
+    /**
+     * 开启应用调试模式
+     * @access public
+     * @param bool $debug 开启应用调试模式
+     * @return $this
+     */
+    public function debug(bool $debug = true)
+    {
+        $this->appDebug = $debug;
+        return $this;
+    }
+
+    /**
+     * 是否为调试模式
+     * @access public
+     * @return bool
+     */
+    public function isDebug(): bool
+    {
+        return $this->appDebug;
+    }
+
+    /**
+     * 设置应用命名空间
+     * @access public
+     * @param string $namespace 应用命名空间
+     * @return $this
+     */
+    public function setNamespace(string $namespace)
+    {
+        $this->namespace = $namespace;
+        return $this;
+    }
+
+    /**
+     * 获取应用类库命名空间
+     * @access public
+     * @return string
+     */
+    public function getNamespace(): string
+    {
+        return $this->namespace;
+    }
+
+    /**
+     * 设置环境变量标识
+     * @access public
+     * @param string $name 环境标识
+     * @return $this
+     */
+    public function setEnvName(string $name)
+    {
+        $this->envName = $name;
+        return $this;
+    }
+
+    /**
+     * 获取框架版本
+     * @access public
+     * @return string
+     */
+    public function version(): string
+    {
+        return static::VERSION;
+    }
+
+    /**
+     * 获取应用根目录
+     * @access public
+     * @return string
+     */
+    public function getRootPath(): string
+    {
+        return $this->rootPath;
+    }
+
+    /**
+     * 获取应用基础目录
+     * @access public
+     * @return string
+     */
+    public function getBasePath(): string
+    {
+        return $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
+    }
+
+    /**
+     * 获取当前应用目录
+     * @access public
+     * @return string
+     */
+    public function getAppPath(): string
+    {
+        return $this->appPath;
+    }
+
+    /**
+     * 设置应用目录
+     * @param string $path 应用目录
+     */
+    public function setAppPath(string $path)
+    {
+        $this->appPath = $path;
+    }
+
+    /**
+     * 获取应用运行时目录
+     * @access public
+     * @return string
+     */
+    public function getRuntimePath(): string
+    {
+        return $this->runtimePath;
+    }
+
+    /**
+     * 设置runtime目录
+     * @param string $path 定义目录
+     */
+    public function setRuntimePath(string $path): void
+    {
+        $this->runtimePath = $path;
+    }
+
+    /**
+     * 获取核心框架目录
+     * @access public
+     * @return string
+     */
+    public function getThinkPath(): string
+    {
+        return $this->thinkPath;
+    }
+
+    /**
+     * 获取应用配置目录
+     * @access public
+     * @return string
+     */
+    public function getConfigPath(): string
+    {
+        return $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
+    }
+
+    /**
+     * 获取配置后缀
+     * @access public
+     * @return string
+     */
+    public function getConfigExt(): string
+    {
+        return $this->configExt;
+    }
+
+    /**
+     * 获取应用开启时间
+     * @access public
+     * @return float
+     */
+    public function getBeginTime(): float
+    {
+        return $this->beginTime;
+    }
+
+    /**
+     * 获取应用初始内存占用
+     * @access public
+     * @return integer
+     */
+    public function getBeginMem(): int
+    {
+        return $this->beginMem;
+    }
+
+    /**
+     * 加载环境变量定义
+     * @access public
+     * @param string $envName 环境标识
+     * @return void
+     */
+    public function loadEnv(string $envName = ''): void
+    {
+        // 加载环境变量
+        $envFile = $envName ? $this->rootPath . '.env.' . $envName : $this->rootPath . '.env';
+
+        if (is_file($envFile)) {
+            $this->env->load($envFile);
+        }
+    }
+
+    /**
+     * 初始化应用
+     * @access public
+     * @return $this
+     */
+    public function initialize()
+    {
+        $this->initialized = true;
+
+        $this->beginTime = microtime(true);
+        $this->beginMem  = memory_get_usage();
+
+        $this->loadEnv($this->envName);
+
+        $this->configExt = $this->env->get('config_ext', '.php');
+
+        $this->debugModeInit();
+
+        // 加载全局初始化文件
+        $this->load();
+
+        // 加载框架默认语言包
+        $langSet = $this->lang->defaultLangSet();
+
+        $this->lang->load($this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $langSet . '.php');
+
+        // 加载应用默认语言包
+        $this->loadLangPack($langSet);
+
+        // 监听AppInit
+        $this->event->trigger(AppInit::class);
+
+        date_default_timezone_set($this->config->get('app.default_timezone', 'Asia/Shanghai'));
+
+        // 初始化
+        foreach ($this->initializers as $initializer) {
+            $this->make($initializer)->init($this);
+        }
+
+        return $this;
+    }
+
+    /**
+     * 是否初始化过
+     * @return bool
+     */
+    public function initialized()
+    {
+        return $this->initialized;
+    }
+
+    /**
+     * 加载语言包
+     * @param string $langset 语言
+     * @return void
+     */
+    public function loadLangPack($langset)
+    {
+        if (empty($langset)) {
+            return;
+        }
+
+        // 加载系统语言包
+        $files = glob($this->appPath . 'lang' . DIRECTORY_SEPARATOR . $langset . '.*');
+        $this->lang->load($files);
+
+        // 加载扩展(自定义)语言包
+        $list = $this->config->get('lang.extend_list', []);
+
+        if (isset($list[$langset])) {
+            $this->lang->load($list[$langset]);
+        }
+    }
+
+    /**
+     * 引导应用
+     * @access public
+     * @return void
+     */
+    public function boot(): void
+    {
+        array_walk($this->services, function ($service) {
+            $this->bootService($service);
+        });
+    }
+
+    /**
+     * 加载应用文件和配置
+     * @access protected
+     * @return void
+     */
+    protected function load(): void
+    {
+        $appPath = $this->getAppPath();
+
+        if (is_file($appPath . 'common.php')) {
+            include_once $appPath . 'common.php';
+        }
+
+        include_once $this->thinkPath . 'helper.php';
+
+        $configPath = $this->getConfigPath();
+
+        $files = [];
+
+        if (is_dir($configPath)) {
+            $files = glob($configPath . '*' . $this->configExt);
+        }
+
+        foreach ($files as $file) {
+            $this->config->load($file, pathinfo($file, PATHINFO_FILENAME));
+        }
+
+        if (is_file($appPath . 'event.php')) {
+            $this->loadEvent(include $appPath . 'event.php');
+        }
+
+        if (is_file($appPath . 'service.php')) {
+            $services = include $appPath . 'service.php';
+            foreach ($services as $service) {
+                $this->register($service);
+            }
+        }
+    }
+
+    /**
+     * 调试模式设置
+     * @access protected
+     * @return void
+     */
+    protected function debugModeInit(): void
+    {
+        // 应用调试模式
+        if (!$this->appDebug) {
+            $this->appDebug = $this->env->get('app_debug') ? true : false;
+            ini_set('display_errors', 'Off');
+        }
+
+        if (!$this->runningInConsole()) {
+            //重新申请一块比较大的buffer
+            if (ob_get_level() > 0) {
+                $output = ob_get_clean();
+            }
+            ob_start();
+            if (!empty($output)) {
+                echo $output;
+            }
+        }
+    }
+
+    /**
+     * 注册应用事件
+     * @access protected
+     * @param array $event 事件数据
+     * @return void
+     */
+    public function loadEvent(array $event): void
+    {
+        if (isset($event['bind'])) {
+            $this->event->bind($event['bind']);
+        }
+
+        if (isset($event['listen'])) {
+            $this->event->listenEvents($event['listen']);
+        }
+
+        if (isset($event['subscribe'])) {
+            $this->event->subscribe($event['subscribe']);
+        }
+    }
+
+    /**
+     * 解析应用类的类名
+     * @access public
+     * @param string $layer 层名 controller model ...
+     * @param string $name  类名
+     * @return string
+     */
+    public function parseClass(string $layer, string $name): string
+    {
+        $name  = str_replace(['/', '.'], '\\', $name);
+        $array = explode('\\', $name);
+        $class = Str::studly(array_pop($array));
+        $path  = $array ? implode('\\', $array) . '\\' : '';
+
+        return $this->namespace . '\\' . $layer . '\\' . $path . $class;
+    }
+
+    /**
+     * 是否运行在命令行下
+     * @return bool
+     */
+    public function runningInConsole(): bool
+    {
+        return php_sapi_name() === 'cli' || php_sapi_name() === 'phpdbg';
+    }
+
+    /**
+     * 获取应用根目录
+     * @access protected
+     * @return string
+     */
+    protected function getDefaultRootPath(): string
+    {
+        return dirname($this->thinkPath, 4) . DIRECTORY_SEPARATOR;
+    }
+
+}

+ 197 - 0
vendor/topthink/framework/src/think/Cache.php

@@ -0,0 +1,197 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use Psr\SimpleCache\CacheInterface;
+use think\cache\Driver;
+use think\cache\TagSet;
+use think\exception\InvalidArgumentException;
+use think\helper\Arr;
+
+/**
+ * 缓存管理类
+ * @mixin Driver
+ * @mixin \think\cache\driver\File
+ */
+class Cache extends Manager implements CacheInterface
+{
+
+    protected $namespace = '\\think\\cache\\driver\\';
+
+    /**
+     * 默认驱动
+     * @return string|null
+     */
+    public function getDefaultDriver()
+    {
+        return $this->getConfig('default');
+    }
+
+    /**
+     * 获取缓存配置
+     * @access public
+     * @param null|string $name    名称
+     * @param mixed       $default 默认值
+     * @return mixed
+     */
+    public function getConfig(string $name = null, $default = null)
+    {
+        if (!is_null($name)) {
+            return $this->app->config->get('cache.' . $name, $default);
+        }
+
+        return $this->app->config->get('cache');
+    }
+
+    /**
+     * 获取驱动配置
+     * @param string $store
+     * @param string $name
+     * @param null   $default
+     * @return array
+     */
+    public function getStoreConfig(string $store, string $name = null, $default = null)
+    {
+        if ($config = $this->getConfig("stores.{$store}")) {
+            return Arr::get($config, $name, $default);
+        }
+
+        throw new \InvalidArgumentException("Store [$store] not found.");
+    }
+
+    protected function resolveType(string $name)
+    {
+        return $this->getStoreConfig($name, 'type', 'file');
+    }
+
+    protected function resolveConfig(string $name)
+    {
+        return $this->getStoreConfig($name);
+    }
+
+    /**
+     * 连接或者切换缓存
+     * @access public
+     * @param string $name 连接配置名
+     * @return Driver
+     */
+    public function store(string $name = null)
+    {
+        return $this->driver($name);
+    }
+
+    /**
+     * 清空缓冲池
+     * @access public
+     * @return bool
+     */
+    public function clear(): bool
+    {
+        return $this->store()->clear();
+    }
+
+    /**
+     * 读取缓存
+     * @access public
+     * @param string $key     缓存变量名
+     * @param mixed  $default 默认值
+     * @return mixed
+     */
+    public function get($key, $default = null)
+    {
+        return $this->store()->get($key, $default);
+    }
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param string        $key   缓存变量名
+     * @param mixed         $value 存储数据
+     * @param int|\DateTime $ttl   有效时间 0为永久
+     * @return bool
+     */
+    public function set($key, $value, $ttl = null): bool
+    {
+        return $this->store()->set($key, $value, $ttl);
+    }
+
+    /**
+     * 删除缓存
+     * @access public
+     * @param string $key 缓存变量名
+     * @return bool
+     */
+    public function delete($key): bool
+    {
+        return $this->store()->delete($key);
+    }
+
+    /**
+     * 读取缓存
+     * @access public
+     * @param iterable $keys    缓存变量名
+     * @param mixed    $default 默认值
+     * @return iterable
+     * @throws InvalidArgumentException
+     */
+    public function getMultiple($keys, $default = null): iterable
+    {
+        return $this->store()->getMultiple($keys, $default);
+    }
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param iterable               $values 缓存数据
+     * @param null|int|\DateInterval $ttl    有效时间 0为永久
+     * @return bool
+     */
+    public function setMultiple($values, $ttl = null): bool
+    {
+        return $this->store()->setMultiple($values, $ttl);
+    }
+
+    /**
+     * 删除缓存
+     * @access public
+     * @param iterable $keys 缓存变量名
+     * @return bool
+     * @throws InvalidArgumentException
+     */
+    public function deleteMultiple($keys): bool
+    {
+        return $this->store()->deleteMultiple($keys);
+    }
+
+    /**
+     * 判断缓存是否存在
+     * @access public
+     * @param string $key 缓存变量名
+     * @return bool
+     */
+    public function has($key): bool
+    {
+        return $this->store()->has($key);
+    }
+
+    /**
+     * 缓存标签
+     * @access public
+     * @param string|array $name 标签名
+     * @return TagSet
+     */
+    public function tag($name): TagSet
+    {
+        return $this->store()->tag($name);
+    }
+}

+ 197 - 0
vendor/topthink/framework/src/think/Config.php

@@ -0,0 +1,197 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+/**
+ * 配置管理类
+ * @package think
+ */
+class Config
+{
+    /**
+     * 配置参数
+     * @var array
+     */
+    protected $config = [];
+
+    /**
+     * 配置文件目录
+     * @var string
+     */
+    protected $path;
+
+    /**
+     * 配置文件后缀
+     * @var string
+     */
+    protected $ext;
+
+    /**
+     * 构造方法
+     * @access public
+     */
+    public function __construct(string $path = null, string $ext = '.php')
+    {
+        $this->path = $path ?: '';
+        $this->ext  = $ext;
+    }
+
+    public static function __make(App $app)
+    {
+        $path = $app->getConfigPath();
+        $ext  = $app->getConfigExt();
+
+        return new static($path, $ext);
+    }
+
+    /**
+     * 加载配置文件(多种格式)
+     * @access public
+     * @param  string $file 配置文件名
+     * @param  string $name 一级配置名
+     * @return array
+     */
+    public function load(string $file, string $name = ''): array
+    {
+        if (is_file($file)) {
+            $filename = $file;
+        } elseif (is_file($this->path . $file . $this->ext)) {
+            $filename = $this->path . $file . $this->ext;
+        }
+
+        if (isset($filename)) {
+            return $this->parse($filename, $name);
+        }
+
+        return $this->config;
+    }
+
+    /**
+     * 解析配置文件
+     * @access public
+     * @param  string $file 配置文件名
+     * @param  string $name 一级配置名
+     * @return array
+     */
+    protected function parse(string $file, string $name): array
+    {
+        $type   = pathinfo($file, PATHINFO_EXTENSION);
+        $config = [];
+        switch ($type) {
+            case 'php':
+                $config = include $file;
+                break;
+            case 'yml':
+            case 'yaml':
+                if (function_exists('yaml_parse_file')) {
+                    $config = yaml_parse_file($file);
+                }
+                break;
+            case 'ini':
+                $config = parse_ini_file($file, true, INI_SCANNER_TYPED) ?: [];
+                break;
+            case 'json':
+                $config = json_decode(file_get_contents($file), true);
+                break;
+        }
+
+        return is_array($config) ? $this->set($config, strtolower($name)) : [];
+    }
+
+    /**
+     * 检测配置是否存在
+     * @access public
+     * @param  string $name 配置参数名(支持多级配置 .号分割)
+     * @return bool
+     */
+    public function has(string $name): bool
+    {
+        if (false === strpos($name, '.') && !isset($this->config[strtolower($name)])) {
+            return false;
+        }
+
+        return !is_null($this->get($name));
+    }
+
+    /**
+     * 获取一级配置
+     * @access protected
+     * @param  string $name 一级配置名
+     * @return array
+     */
+    protected function pull(string $name): array
+    {
+        $name = strtolower($name);
+
+        return $this->config[$name] ?? [];
+    }
+
+    /**
+     * 获取配置参数 为空则获取所有配置
+     * @access public
+     * @param  string $name    配置参数名(支持多级配置 .号分割)
+     * @param  mixed  $default 默认值
+     * @return mixed
+     */
+    public function get(string $name = null, $default = null)
+    {
+        // 无参数时获取所有
+        if (empty($name)) {
+            return $this->config;
+        }
+
+        if (false === strpos($name, '.')) {
+            return $this->pull($name);
+        }
+
+        $name    = explode('.', $name);
+        $name[0] = strtolower($name[0]);
+        $config  = $this->config;
+
+        // 按.拆分成多维数组进行判断
+        foreach ($name as $val) {
+            if (isset($config[$val])) {
+                $config = $config[$val];
+            } else {
+                return $default;
+            }
+        }
+
+        return $config;
+    }
+
+    /**
+     * 设置配置参数 name为数组则为批量设置
+     * @access public
+     * @param  array  $config 配置参数
+     * @param  string $name 配置名
+     * @return array
+     */
+    public function set(array $config, string $name = null): array
+    {
+        if (!empty($name)) {
+            if (isset($this->config[$name])) {
+                $result = array_merge($this->config[$name], $config);
+            } else {
+                $result = $config;
+            }
+
+            $this->config[$name] = $result;
+        } else {
+            $result = $this->config = array_merge($this->config, array_change_key_case($config));
+        }
+
+        return $result;
+    }
+
+}

+ 787 - 0
vendor/topthink/framework/src/think/Console.php

@@ -0,0 +1,787 @@
+<?php
+// +----------------------------------------------------------------------
+// | TopThink [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2015 http://www.topthink.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: zhangyajun <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use Closure;
+use InvalidArgumentException;
+use LogicException;
+use think\console\Command;
+use think\console\command\Clear;
+use think\console\command\Help;
+use think\console\command\Help as HelpCommand;
+use think\console\command\Lists;
+use think\console\command\make\Command as MakeCommand;
+use think\console\command\make\Controller;
+use think\console\command\make\Event;
+use think\console\command\make\Listener;
+use think\console\command\make\Middleware;
+use think\console\command\make\Model;
+use think\console\command\make\Service;
+use think\console\command\make\Subscribe;
+use think\console\command\make\Validate;
+use think\console\command\optimize\Route;
+use think\console\command\optimize\Schema;
+use think\console\command\RouteList;
+use think\console\command\RunServer;
+use think\console\command\ServiceDiscover;
+use think\console\command\VendorPublish;
+use think\console\command\Version;
+use think\console\Input;
+use think\console\input\Argument as InputArgument;
+use think\console\input\Definition as InputDefinition;
+use think\console\input\Option as InputOption;
+use think\console\Output;
+use think\console\output\driver\Buffer;
+
+/**
+ * 控制台应用管理类
+ */
+class Console
+{
+
+    protected $app;
+
+    /** @var Command[] */
+    protected $commands = [];
+
+    protected $wantHelps = false;
+
+    protected $catchExceptions = true;
+    protected $autoExit        = true;
+    protected $definition;
+    protected $defaultCommand  = 'list';
+
+    protected $defaultCommands = [
+        'help'             => Help::class,
+        'list'             => Lists::class,
+        'clear'            => Clear::class,
+        'make:command'     => MakeCommand::class,
+        'make:controller'  => Controller::class,
+        'make:model'       => Model::class,
+        'make:middleware'  => Middleware::class,
+        'make:validate'    => Validate::class,
+        'make:event'       => Event::class,
+        'make:listener'    => Listener::class,
+        'make:service'     => Service::class,
+        'make:subscribe'   => Subscribe::class,
+        'optimize:route'   => Route::class,
+        'optimize:schema'  => Schema::class,
+        'run'              => RunServer::class,
+        'version'          => Version::class,
+        'route:list'       => RouteList::class,
+        'service:discover' => ServiceDiscover::class,
+        'vendor:publish'   => VendorPublish::class,
+    ];
+
+    /**
+     * 启动器
+     * @var array
+     */
+    protected static $startCallbacks = [];
+
+    public function __construct(App $app)
+    {
+        $this->app = $app;
+
+        $this->initialize();
+
+        $this->definition = $this->getDefaultInputDefinition();
+
+        //加载指令
+        $this->loadCommands();
+
+        $this->start();
+    }
+
+    /**
+     * 初始化
+     */
+    protected function initialize()
+    {
+        if (!$this->app->initialized()) {
+            $this->app->initialize();
+        }
+        $this->makeRequest();
+    }
+
+    /**
+     * 构造request
+     */
+    protected function makeRequest()
+    {
+        $uri = $this->app->config->get('app.url', 'http://localhost');
+
+        $components = parse_url($uri);
+
+        $server = $_SERVER;
+
+        if (isset($components['path'])) {
+            $server = array_merge($server, [
+                'SCRIPT_FILENAME' => $components['path'],
+                'SCRIPT_NAME'     => $components['path'],
+            ]);
+        }
+
+        if (isset($components['host'])) {
+            $server['SERVER_NAME'] = $components['host'];
+            $server['HTTP_HOST']   = $components['host'];
+        }
+
+        if (isset($components['scheme'])) {
+            if ('https' === $components['scheme']) {
+                $server['HTTPS']       = 'on';
+                $server['SERVER_PORT'] = 443;
+            } else {
+                unset($server['HTTPS']);
+                $server['SERVER_PORT'] = 80;
+            }
+        }
+
+        if (isset($components['port'])) {
+            $server['SERVER_PORT'] = $components['port'];
+            $server['HTTP_HOST'] .= ':' . $components['port'];
+        }
+
+        $server['REQUEST_URI'] = $uri;
+
+        /** @var Request $request */
+        $request = $this->app->make('request');
+
+        $request->withServer($server);
+    }
+
+    /**
+     * 添加初始化器
+     * @param Closure $callback
+     */
+    public static function starting(Closure $callback): void
+    {
+        static::$startCallbacks[] = $callback;
+    }
+
+    /**
+     * 清空启动器
+     */
+    public static function flushStartCallbacks(): void
+    {
+        static::$startCallbacks = [];
+    }
+
+    /**
+     * 设置执行用户
+     * @param $user
+     */
+    public static function setUser(string $user): void
+    {
+        if (extension_loaded('posix')) {
+            $user = posix_getpwnam($user);
+
+            if (!empty($user)) {
+                posix_setgid($user['gid']);
+                posix_setuid($user['uid']);
+            }
+        }
+    }
+
+    /**
+     * 启动
+     */
+    protected function start(): void
+    {
+        foreach (static::$startCallbacks as $callback) {
+            $callback($this);
+        }
+    }
+
+    /**
+     * 加载指令
+     * @access protected
+     */
+    protected function loadCommands(): void
+    {
+        $commands = $this->app->config->get('console.commands', []);
+        $commands = array_merge($this->defaultCommands, $commands);
+
+        $this->addCommands($commands);
+    }
+
+    /**
+     * @access public
+     * @param string $command
+     * @param array $parameters
+     * @param string $driver
+     * @return Output|Buffer
+     */
+    public function call(string $command, array $parameters = [], string $driver = 'buffer')
+    {
+        array_unshift($parameters, $command);
+
+        $input  = new Input($parameters);
+        $output = new Output($driver);
+
+        $this->setCatchExceptions(false);
+        $this->find($command)->run($input, $output);
+
+        return $output;
+    }
+
+    /**
+     * 执行当前的指令
+     * @access public
+     * @return int
+     * @throws \Exception
+     * @api
+     */
+    public function run()
+    {
+        $input  = new Input();
+        $output = new Output();
+
+        $this->configureIO($input, $output);
+
+        try {
+            $exitCode = $this->doRun($input, $output);
+        } catch (\Exception $e) {
+            if (!$this->catchExceptions) {
+                throw $e;
+            }
+
+            $output->renderException($e);
+
+            $exitCode = $e->getCode();
+            if (is_numeric($exitCode)) {
+                $exitCode = (int) $exitCode;
+                if (0 === $exitCode) {
+                    $exitCode = 1;
+                }
+            } else {
+                $exitCode = 1;
+            }
+        }
+
+        if ($this->autoExit) {
+            if ($exitCode > 255) {
+                $exitCode = 255;
+            }
+
+            exit($exitCode);
+        }
+
+        return $exitCode;
+    }
+
+    /**
+     * 执行指令
+     * @access public
+     * @param Input $input
+     * @param Output $output
+     * @return int
+     */
+    public function doRun(Input $input, Output $output)
+    {
+        if (true === $input->hasParameterOption(['--version', '-V'])) {
+            $output->writeln($this->getLongVersion());
+
+            return 0;
+        }
+
+        $name = $this->getCommandName($input);
+
+        if (true === $input->hasParameterOption(['--help', '-h'])) {
+            if (!$name) {
+                $name  = 'help';
+                $input = new Input(['help']);
+            } else {
+                $this->wantHelps = true;
+            }
+        }
+
+        if (!$name) {
+            $name  = $this->defaultCommand;
+            $input = new Input([$this->defaultCommand]);
+        }
+
+        $command = $this->find($name);
+
+        return $this->doRunCommand($command, $input, $output);
+    }
+
+    /**
+     * 设置输入参数定义
+     * @access public
+     * @param InputDefinition $definition
+     */
+    public function setDefinition(InputDefinition $definition): void
+    {
+        $this->definition = $definition;
+    }
+
+    /**
+     * 获取输入参数定义
+     * @access public
+     * @return InputDefinition The InputDefinition instance
+     */
+    public function getDefinition(): InputDefinition
+    {
+        return $this->definition;
+    }
+
+    /**
+     * Gets the help message.
+     * @access public
+     * @return string A help message.
+     */
+    public function getHelp(): string
+    {
+        return $this->getLongVersion();
+    }
+
+    /**
+     * 是否捕获异常
+     * @access public
+     * @param bool $boolean
+     * @api
+     */
+    public function setCatchExceptions(bool $boolean): void
+    {
+        $this->catchExceptions = $boolean;
+    }
+
+    /**
+     * 是否自动退出
+     * @access public
+     * @param bool $boolean
+     * @api
+     */
+    public function setAutoExit(bool $boolean): void
+    {
+        $this->autoExit = $boolean;
+    }
+
+    /**
+     * 获取完整的版本号
+     * @access public
+     * @return string
+     */
+    public function getLongVersion(): string
+    {
+        if ($this->app->version()) {
+            return sprintf('version <comment>%s</comment>', $this->app->version());
+        }
+
+        return '<info>Console Tool</info>';
+    }
+
+    /**
+     * 添加指令集
+     * @access public
+     * @param array $commands
+     */
+    public function addCommands(array $commands): void
+    {
+        foreach ($commands as $key => $command) {
+            if (is_subclass_of($command, Command::class)) {
+                // 注册指令
+                $this->addCommand($command, is_numeric($key) ? '' : $key);
+            }
+        }
+    }
+
+    /**
+     * 添加一个指令
+     * @access public
+     * @param string|Command $command 指令对象或者指令类名
+     * @param string $name 指令名 留空则自动获取
+     * @return Command|void
+     */
+    public function addCommand($command, string $name = '')
+    {
+        if ($name) {
+            $this->commands[$name] = $command;
+            return;
+        }
+
+        if (is_string($command)) {
+            $command = $this->app->invokeClass($command);
+        }
+
+        $command->setConsole($this);
+
+        if (!$command->isEnabled()) {
+            $command->setConsole(null);
+            return;
+        }
+
+        $command->setApp($this->app);
+
+        if (null === $command->getDefinition()) {
+            throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
+        }
+
+        $this->commands[$command->getName()] = $command;
+
+        foreach ($command->getAliases() as $alias) {
+            $this->commands[$alias] = $command;
+        }
+
+        return $command;
+    }
+
+    /**
+     * 获取指令
+     * @access public
+     * @param string $name 指令名称
+     * @return Command
+     * @throws InvalidArgumentException
+     */
+    public function getCommand(string $name): Command
+    {
+        if (!isset($this->commands[$name])) {
+            throw new InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
+        }
+
+        $command = $this->commands[$name];
+
+        if (is_string($command)) {
+            $command = $this->app->invokeClass($command);
+            /** @var Command $command */
+            $command->setConsole($this);
+            $command->setApp($this->app);
+        }
+
+        if ($this->wantHelps) {
+            $this->wantHelps = false;
+
+            /** @var HelpCommand $helpCommand */
+            $helpCommand = $this->getCommand('help');
+            $helpCommand->setCommand($command);
+
+            return $helpCommand;
+        }
+
+        return $command;
+    }
+
+    /**
+     * 某个指令是否存在
+     * @access public
+     * @param string $name 指令名称
+     * @return bool
+     */
+    public function hasCommand(string $name): bool
+    {
+        return isset($this->commands[$name]);
+    }
+
+    /**
+     * 获取所有的命名空间
+     * @access public
+     * @return array
+     */
+    public function getNamespaces(): array
+    {
+        $namespaces = [];
+        foreach ($this->commands as $key => $command) {
+            if (is_string($command)) {
+                $namespaces = array_merge($namespaces, $this->extractAllNamespaces($key));
+            } else {
+                $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
+
+                foreach ($command->getAliases() as $alias) {
+                    $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
+                }
+            }
+        }
+
+        return array_values(array_unique(array_filter($namespaces)));
+    }
+
+    /**
+     * 查找注册命名空间中的名称或缩写。
+     * @access public
+     * @param string $namespace
+     * @return string
+     * @throws InvalidArgumentException
+     */
+    public function findNamespace(string $namespace): string
+    {
+        $allNamespaces = $this->getNamespaces();
+        $expr          = preg_replace_callback('{([^:]+|)}', function ($matches) {
+            return preg_quote($matches[1]) . '[^:]*';
+        }, $namespace);
+        $namespaces    = preg_grep('{^' . $expr . '}', $allNamespaces);
+
+        if (empty($namespaces)) {
+            $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
+
+            if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
+                if (1 == count($alternatives)) {
+                    $message .= "\n\nDid you mean this?\n    ";
+                } else {
+                    $message .= "\n\nDid you mean one of these?\n    ";
+                }
+
+                $message .= implode("\n    ", $alternatives);
+            }
+
+            throw new InvalidArgumentException($message);
+        }
+
+        $exact = in_array($namespace, $namespaces, true);
+        if (count($namespaces) > 1 && !$exact) {
+            throw new InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))));
+        }
+
+        return $exact ? $namespace : reset($namespaces);
+    }
+
+    /**
+     * 查找指令
+     * @access public
+     * @param string $name 名称或者别名
+     * @return Command
+     * @throws InvalidArgumentException
+     */
+    public function find(string $name): Command
+    {
+        $allCommands = array_keys($this->commands);
+
+        $expr = preg_replace_callback('{([^:]+|)}', function ($matches) {
+            return preg_quote($matches[1]) . '[^:]*';
+        }, $name);
+
+        $commands = preg_grep('{^' . $expr . '}', $allCommands);
+
+        if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) {
+            if (false !== $pos = strrpos($name, ':')) {
+                $this->findNamespace(substr($name, 0, $pos));
+            }
+
+            $message = sprintf('Command "%s" is not defined.', $name);
+
+            if ($alternatives = $this->findAlternatives($name, $allCommands)) {
+                if (1 == count($alternatives)) {
+                    $message .= "\n\nDid you mean this?\n    ";
+                } else {
+                    $message .= "\n\nDid you mean one of these?\n    ";
+                }
+                $message .= implode("\n    ", $alternatives);
+            }
+
+            throw new InvalidArgumentException($message);
+        }
+
+        $exact = in_array($name, $commands, true);
+        if (count($commands) > 1 && !$exact) {
+            $suggestions = $this->getAbbreviationSuggestions(array_values($commands));
+
+            throw new InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions));
+        }
+
+        return $this->getCommand($exact ? $name : reset($commands));
+    }
+
+    /**
+     * 获取所有的指令
+     * @access public
+     * @param string $namespace 命名空间
+     * @return Command[]
+     * @api
+     */
+    public function all(string $namespace = null): array
+    {
+        if (null === $namespace) {
+            return $this->commands;
+        }
+
+        $commands = [];
+        foreach ($this->commands as $name => $command) {
+            if ($this->extractNamespace($name, substr_count($namespace, ':') + 1) === $namespace) {
+                $commands[$name] = $command;
+            }
+        }
+
+        return $commands;
+    }
+
+    /**
+     * 配置基于用户的参数和选项的输入和输出实例。
+     * @access protected
+     * @param Input $input 输入实例
+     * @param Output $output 输出实例
+     */
+    protected function configureIO(Input $input, Output $output): void
+    {
+        if (true === $input->hasParameterOption(['--ansi'])) {
+            $output->setDecorated(true);
+        } elseif (true === $input->hasParameterOption(['--no-ansi'])) {
+            $output->setDecorated(false);
+        }
+
+        if (true === $input->hasParameterOption(['--no-interaction', '-n'])) {
+            $input->setInteractive(false);
+        }
+
+        if (true === $input->hasParameterOption(['--quiet', '-q'])) {
+            $output->setVerbosity(Output::VERBOSITY_QUIET);
+        } elseif ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) {
+            $output->setVerbosity(Output::VERBOSITY_DEBUG);
+        } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) {
+            $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE);
+        } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) {
+            $output->setVerbosity(Output::VERBOSITY_VERBOSE);
+        }
+    }
+
+    /**
+     * 执行指令
+     * @access protected
+     * @param Command $command 指令实例
+     * @param Input $input 输入实例
+     * @param Output $output 输出实例
+     * @return int
+     * @throws \Exception
+     */
+    protected function doRunCommand(Command $command, Input $input, Output $output)
+    {
+        return $command->run($input, $output);
+    }
+
+    /**
+     * 获取指令的基础名称
+     * @access protected
+     * @param Input $input
+     * @return string
+     */
+    protected function getCommandName(Input $input): string
+    {
+        return $input->getFirstArgument() ?: '';
+    }
+
+    /**
+     * 获取默认输入定义
+     * @access protected
+     * @return InputDefinition
+     */
+    protected function getDefaultInputDefinition(): InputDefinition
+    {
+        return new InputDefinition([
+            new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
+            new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
+            new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this console version'),
+            new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
+            new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
+            new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
+            new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
+            new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
+        ]);
+    }
+
+    /**
+     * 获取可能的建议
+     * @access private
+     * @param array $abbrevs
+     * @return string
+     */
+    private function getAbbreviationSuggestions(array $abbrevs): string
+    {
+        return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
+    }
+
+    /**
+     * 返回命名空间部分
+     * @access public
+     * @param string $name 指令
+     * @param int $limit 部分的命名空间的最大数量
+     * @return string
+     */
+    public function extractNamespace(string $name, int $limit = 0): string
+    {
+        $parts = explode(':', $name);
+        array_pop($parts);
+
+        return implode(':', 0 === $limit ? $parts : array_slice($parts, 0, $limit));
+    }
+
+    /**
+     * 查找可替代的建议
+     * @access private
+     * @param string $name
+     * @param array|\Traversable $collection
+     * @return array
+     */
+    private function findAlternatives(string $name, $collection): array
+    {
+        $threshold    = 1e3;
+        $alternatives = [];
+
+        $collectionParts = [];
+        foreach ($collection as $item) {
+            $collectionParts[$item] = explode(':', $item);
+        }
+
+        foreach (explode(':', $name) as $i => $subname) {
+            foreach ($collectionParts as $collectionName => $parts) {
+                $exists = isset($alternatives[$collectionName]);
+                if (!isset($parts[$i]) && $exists) {
+                    $alternatives[$collectionName] += $threshold;
+                    continue;
+                } elseif (!isset($parts[$i])) {
+                    continue;
+                }
+
+                $lev = levenshtein($subname, $parts[$i]);
+                if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) {
+                    $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
+                } elseif ($exists) {
+                    $alternatives[$collectionName] += $threshold;
+                }
+            }
+        }
+
+        foreach ($collection as $item) {
+            $lev = levenshtein($name, $item);
+            if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
+                $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
+            }
+        }
+
+        $alternatives = array_filter($alternatives, function ($lev) use ($threshold) {
+            return $lev < 2 * $threshold;
+        });
+        asort($alternatives);
+
+        return array_keys($alternatives);
+    }
+
+    /**
+     * 返回所有的命名空间
+     * @access private
+     * @param string $name
+     * @return array
+     */
+    private function extractAllNamespaces(string $name): array
+    {
+        $parts      = explode(':', $name, -1);
+        $namespaces = [];
+
+        foreach ($parts as $part) {
+            if (count($namespaces)) {
+                $namespaces[] = end($namespaces) . ':' . $part;
+            } else {
+                $namespaces[] = $part;
+            }
+        }
+
+        return $namespaces;
+    }
+
+}

+ 554 - 0
vendor/topthink/framework/src/think/Container.php

@@ -0,0 +1,554 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use ArrayAccess;
+use ArrayIterator;
+use Closure;
+use Countable;
+use InvalidArgumentException;
+use IteratorAggregate;
+use Psr\Container\ContainerInterface;
+use ReflectionClass;
+use ReflectionException;
+use ReflectionFunction;
+use ReflectionFunctionAbstract;
+use ReflectionMethod;
+use think\exception\ClassNotFoundException;
+use think\exception\FuncNotFoundException;
+use think\helper\Str;
+
+/**
+ * 容器管理类 支持PSR-11
+ */
+class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, Countable
+{
+    /**
+     * 容器对象实例
+     * @var Container|Closure
+     */
+    protected static $instance;
+
+    /**
+     * 容器中的对象实例
+     * @var array
+     */
+    protected $instances = [];
+
+    /**
+     * 容器绑定标识
+     * @var array
+     */
+    protected $bind = [];
+
+    /**
+     * 容器回调
+     * @var array
+     */
+    protected $invokeCallback = [];
+
+    /**
+     * 获取当前容器的实例(单例)
+     * @access public
+     * @return static
+     */
+    public static function getInstance()
+    {
+        if (is_null(static::$instance)) {
+            static::$instance = new static;
+        }
+
+        if (static::$instance instanceof Closure) {
+            return (static::$instance)();
+        }
+
+        return static::$instance;
+    }
+
+    /**
+     * 设置当前容器的实例
+     * @access public
+     * @param object|Closure $instance
+     * @return void
+     */
+    public static function setInstance($instance): void
+    {
+        static::$instance = $instance;
+    }
+
+    /**
+     * 注册一个容器对象回调
+     *
+     * @param string|Closure $abstract
+     * @param Closure|null   $callback
+     * @return void
+     */
+    public function resolving($abstract, Closure $callback = null): void
+    {
+        if ($abstract instanceof Closure) {
+            $this->invokeCallback['*'][] = $abstract;
+            return;
+        }
+
+        $abstract = $this->getAlias($abstract);
+
+        $this->invokeCallback[$abstract][] = $callback;
+    }
+
+    /**
+     * 获取容器中的对象实例 不存在则创建
+     * @access public
+     * @param string     $abstract    类名或者标识
+     * @param array|true $vars        变量
+     * @param bool       $newInstance 是否每次创建新的实例
+     * @return object
+     */
+    public static function pull(string $abstract, array $vars = [], bool $newInstance = false)
+    {
+        return static::getInstance()->make($abstract, $vars, $newInstance);
+    }
+
+    /**
+     * 获取容器中的对象实例
+     * @access public
+     * @param string $abstract 类名或者标识
+     * @return object
+     */
+    public function get($abstract)
+    {
+        if ($this->has($abstract)) {
+            return $this->make($abstract);
+        }
+
+        throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract);
+    }
+
+    /**
+     * 绑定一个类、闭包、实例、接口实现到容器
+     * @access public
+     * @param string|array $abstract 类标识、接口
+     * @param mixed        $concrete 要绑定的类、闭包或者实例
+     * @return $this
+     */
+    public function bind($abstract, $concrete = null)
+    {
+        if (is_array($abstract)) {
+            foreach ($abstract as $key => $val) {
+                $this->bind($key, $val);
+            }
+        } elseif ($concrete instanceof Closure) {
+            $this->bind[$abstract] = $concrete;
+        } elseif (is_object($concrete)) {
+            $this->instance($abstract, $concrete);
+        } else {
+            $abstract = $this->getAlias($abstract);
+            if ($abstract != $concrete) {
+                $this->bind[$abstract] = $concrete;
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * 根据别名获取真实类名
+     * @param  string $abstract
+     * @return string
+     */
+    public function getAlias(string $abstract): string
+    {
+        if (isset($this->bind[$abstract])) {
+            $bind = $this->bind[$abstract];
+
+            if (is_string($bind)) {
+                return $this->getAlias($bind);
+            }
+        }
+
+        return $abstract;
+    }
+
+    /**
+     * 绑定一个类实例到容器
+     * @access public
+     * @param string $abstract 类名或者标识
+     * @param object $instance 类的实例
+     * @return $this
+     */
+    public function instance(string $abstract, $instance)
+    {
+        $abstract = $this->getAlias($abstract);
+
+        $this->instances[$abstract] = $instance;
+
+        return $this;
+    }
+
+    /**
+     * 判断容器中是否存在类及标识
+     * @access public
+     * @param string $abstract 类名或者标识
+     * @return bool
+     */
+    public function bound(string $abstract): bool
+    {
+        return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
+    }
+
+    /**
+     * 判断容器中是否存在类及标识
+     * @access public
+     * @param string $name 类名或者标识
+     * @return bool
+     */
+    public function has($name): bool
+    {
+        return $this->bound($name);
+    }
+
+    /**
+     * 判断容器中是否存在对象实例
+     * @access public
+     * @param string $abstract 类名或者标识
+     * @return bool
+     */
+    public function exists(string $abstract): bool
+    {
+        $abstract = $this->getAlias($abstract);
+
+        return isset($this->instances[$abstract]);
+    }
+
+    /**
+     * 创建类的实例 已经存在则直接获取
+     * @access public
+     * @param string $abstract    类名或者标识
+     * @param array  $vars        变量
+     * @param bool   $newInstance 是否每次创建新的实例
+     * @return mixed
+     */
+    public function make(string $abstract, array $vars = [], bool $newInstance = false)
+    {
+        $abstract = $this->getAlias($abstract);
+
+        if (isset($this->instances[$abstract]) && !$newInstance) {
+            return $this->instances[$abstract];
+        }
+
+        if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
+            $object = $this->invokeFunction($this->bind[$abstract], $vars);
+        } else {
+            $object = $this->invokeClass($abstract, $vars);
+        }
+
+        if (!$newInstance) {
+            $this->instances[$abstract] = $object;
+        }
+
+        return $object;
+    }
+
+    /**
+     * 删除容器中的对象实例
+     * @access public
+     * @param string $name 类名或者标识
+     * @return void
+     */
+    public function delete($name)
+    {
+        $name = $this->getAlias($name);
+
+        if (isset($this->instances[$name])) {
+            unset($this->instances[$name]);
+        }
+    }
+
+    /**
+     * 执行函数或者闭包方法 支持参数调用
+     * @access public
+     * @param string|Closure $function 函数或者闭包
+     * @param array          $vars     参数
+     * @return mixed
+     */
+    public function invokeFunction($function, array $vars = [])
+    {
+        try {
+            $reflect = new ReflectionFunction($function);
+        } catch (ReflectionException $e) {
+            throw new FuncNotFoundException("function not exists: {$function}()", $function, $e);
+        }
+
+        $args = $this->bindParams($reflect, $vars);
+
+        return $function(...$args);
+    }
+
+    /**
+     * 调用反射执行类的方法 支持参数绑定
+     * @access public
+     * @param mixed $method     方法
+     * @param array $vars       参数
+     * @param bool  $accessible 设置是否可访问
+     * @return mixed
+     */
+    public function invokeMethod($method, array $vars = [], bool $accessible = false)
+    {
+        if (is_array($method)) {
+            [$class, $method] = $method;
+
+            $class = is_object($class) ? $class : $this->invokeClass($class);
+        } else {
+            // 静态方法
+            [$class, $method] = explode('::', $method);
+        }
+
+        try {
+            $reflect = new ReflectionMethod($class, $method);
+        } catch (ReflectionException $e) {
+            $class = is_object($class) ? get_class($class) : $class;
+            throw new FuncNotFoundException('method not exists: ' . $class . '::' . $method . '()', "{$class}::{$method}", $e);
+        }
+
+        $args = $this->bindParams($reflect, $vars);
+
+        if ($accessible) {
+            $reflect->setAccessible($accessible);
+        }
+
+        return $reflect->invokeArgs(is_object($class) ? $class : null, $args);
+    }
+
+    /**
+     * 调用反射执行类的方法 支持参数绑定
+     * @access public
+     * @param object $instance 对象实例
+     * @param mixed  $reflect  反射类
+     * @param array  $vars     参数
+     * @return mixed
+     */
+    public function invokeReflectMethod($instance, $reflect, array $vars = [])
+    {
+        $args = $this->bindParams($reflect, $vars);
+
+        return $reflect->invokeArgs($instance, $args);
+    }
+
+    /**
+     * 调用反射执行callable 支持参数绑定
+     * @access public
+     * @param mixed $callable
+     * @param array $vars       参数
+     * @param bool  $accessible 设置是否可访问
+     * @return mixed
+     */
+    public function invoke($callable, array $vars = [], bool $accessible = false)
+    {
+        if ($callable instanceof Closure) {
+            return $this->invokeFunction($callable, $vars);
+        } elseif (is_string($callable) && false === strpos($callable, '::')) {
+            return $this->invokeFunction($callable, $vars);
+        } else {
+            return $this->invokeMethod($callable, $vars, $accessible);
+        }
+    }
+
+    /**
+     * 调用反射执行类的实例化 支持依赖注入
+     * @access public
+     * @param string $class 类名
+     * @param array  $vars  参数
+     * @return mixed
+     */
+    public function invokeClass(string $class, array $vars = [])
+    {
+        try {
+            $reflect = new ReflectionClass($class);
+        } catch (ReflectionException $e) {
+            throw new ClassNotFoundException('class not exists: ' . $class, $class, $e);
+        }
+
+        if ($reflect->hasMethod('__make')) {
+            $method = $reflect->getMethod('__make');
+            if ($method->isPublic() && $method->isStatic()) {
+                $args   = $this->bindParams($method, $vars);
+                $object = $method->invokeArgs(null, $args);
+                $this->invokeAfter($class, $object);
+                return $object;
+            }
+        }
+
+        $constructor = $reflect->getConstructor();
+
+        $args = $constructor ? $this->bindParams($constructor, $vars) : [];
+
+        $object = $reflect->newInstanceArgs($args);
+
+        $this->invokeAfter($class, $object);
+
+        return $object;
+    }
+
+    /**
+     * 执行invokeClass回调
+     * @access protected
+     * @param string $class  对象类名
+     * @param object $object 容器对象实例
+     * @return void
+     */
+    protected function invokeAfter(string $class, $object): void
+    {
+        if (isset($this->invokeCallback['*'])) {
+            foreach ($this->invokeCallback['*'] as $callback) {
+                $callback($object, $this);
+            }
+        }
+
+        if (isset($this->invokeCallback[$class])) {
+            foreach ($this->invokeCallback[$class] as $callback) {
+                $callback($object, $this);
+            }
+        }
+    }
+
+    /**
+     * 绑定参数
+     * @access protected
+     * @param ReflectionFunctionAbstract $reflect 反射类
+     * @param array                      $vars    参数
+     * @return array
+     */
+    protected function bindParams(ReflectionFunctionAbstract $reflect, array $vars = []): array
+    {
+        if ($reflect->getNumberOfParameters() == 0) {
+            return [];
+        }
+
+        // 判断数组类型 数字数组时按顺序绑定参数
+        reset($vars);
+        $type   = key($vars) === 0 ? 1 : 0;
+        $params = $reflect->getParameters();
+        $args   = [];
+
+        foreach ($params as $param) {
+            $name           = $param->getName();
+            $lowerName      = Str::snake($name);
+            $reflectionType = $param->getType();
+
+            if ($reflectionType && $reflectionType->isBuiltin() === false) {
+                $args[] = $this->getObjectParam($reflectionType->getName(), $vars);
+            } elseif (1 == $type && !empty($vars)) {
+                $args[] = array_shift($vars);
+            } elseif (0 == $type && array_key_exists($name, $vars)) {
+                $args[] = $vars[$name];
+            } elseif (0 == $type && array_key_exists($lowerName, $vars)) {
+                $args[] = $vars[$lowerName];
+            } elseif ($param->isDefaultValueAvailable()) {
+                $args[] = $param->getDefaultValue();
+            } else {
+                throw new InvalidArgumentException('method param miss:' . $name);
+            }
+        }
+
+        return $args;
+    }
+
+    /**
+     * 创建工厂对象实例
+     * @param string $name      工厂类名
+     * @param string $namespace 默认命名空间
+     * @param array  $args
+     * @return mixed
+     * @deprecated
+     * @access public
+     */
+    public static function factory(string $name, string $namespace = '', ...$args)
+    {
+        $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name);
+
+        return Container::getInstance()->invokeClass($class, $args);
+    }
+
+    /**
+     * 获取对象类型的参数值
+     * @access protected
+     * @param string $className 类名
+     * @param array  $vars      参数
+     * @return mixed
+     */
+    protected function getObjectParam(string $className, array &$vars)
+    {
+        $array = $vars;
+        $value = array_shift($array);
+
+        if ($value instanceof $className) {
+            $result = $value;
+            array_shift($vars);
+        } else {
+            $result = $this->make($className);
+        }
+
+        return $result;
+    }
+
+    public function __set($name, $value)
+    {
+        $this->bind($name, $value);
+    }
+
+    public function __get($name)
+    {
+        return $this->get($name);
+    }
+
+    public function __isset($name): bool
+    {
+        return $this->exists($name);
+    }
+
+    public function __unset($name)
+    {
+        $this->delete($name);
+    }
+
+    public function offsetExists($key)
+    {
+        return $this->exists($key);
+    }
+
+    public function offsetGet($key)
+    {
+        return $this->make($key);
+    }
+
+    public function offsetSet($key, $value)
+    {
+        $this->bind($key, $value);
+    }
+
+    public function offsetUnset($key)
+    {
+        $this->delete($key);
+    }
+
+    //Countable
+    public function count()
+    {
+        return count($this->instances);
+    }
+
+    //IteratorAggregate
+    public function getIterator()
+    {
+        return new ArrayIterator($this->instances);
+    }
+}

+ 230 - 0
vendor/topthink/framework/src/think/Cookie.php

@@ -0,0 +1,230 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use DateTimeInterface;
+
+/**
+ * Cookie管理类
+ * @package think
+ */
+class Cookie
+{
+    /**
+     * 配置参数
+     * @var array
+     */
+    protected $config = [
+        // cookie 保存时间
+        'expire'   => 0,
+        // cookie 保存路径
+        'path'     => '/',
+        // cookie 有效域名
+        'domain'   => '',
+        //  cookie 启用安全传输
+        'secure'   => false,
+        // httponly设置
+        'httponly' => false,
+        // samesite 设置,支持 'strict' 'lax'
+        'samesite' => '',
+    ];
+
+    /**
+     * Cookie写入数据
+     * @var array
+     */
+    protected $cookie = [];
+
+    /**
+     * 当前Request对象
+     * @var Request
+     */
+    protected $request;
+
+    /**
+     * 构造方法
+     * @access public
+     */
+    public function __construct(Request $request, array $config = [])
+    {
+        $this->request = $request;
+        $this->config  = array_merge($this->config, array_change_key_case($config));
+    }
+
+    public static function __make(Request $request, Config $config)
+    {
+        return new static($request, $config->get('cookie'));
+    }
+
+    /**
+     * 获取cookie
+     * @access public
+     * @param  mixed  $name 数据名称
+     * @param  string $default 默认值
+     * @return mixed
+     */
+    public function get(string $name = '', $default = null)
+    {
+        return $this->request->cookie($name, $default);
+    }
+
+    /**
+     * 是否存在Cookie参数
+     * @access public
+     * @param  string $name 变量名
+     * @return bool
+     */
+    public function has(string $name): bool
+    {
+        return $this->request->has($name, 'cookie');
+    }
+
+    /**
+     * Cookie 设置
+     *
+     * @access public
+     * @param  string $name  cookie名称
+     * @param  string $value cookie值
+     * @param  mixed  $option 可选参数
+     * @return void
+     */
+    public function set(string $name, string $value, $option = null): void
+    {
+        // 参数设置(会覆盖黙认设置)
+        if (!is_null($option)) {
+            if (is_numeric($option) || $option instanceof DateTimeInterface) {
+                $option = ['expire' => $option];
+            }
+
+            $config = array_merge($this->config, array_change_key_case($option));
+        } else {
+            $config = $this->config;
+        }
+
+        if ($config['expire'] instanceof DateTimeInterface) {
+            $expire = $config['expire']->getTimestamp();
+        } else {
+            $expire = !empty($config['expire']) ? time() + intval($config['expire']) : 0;
+        }
+
+        $this->setCookie($name, $value, $expire, $config);
+    }
+
+    /**
+     * Cookie 保存
+     *
+     * @access public
+     * @param  string $name  cookie名称
+     * @param  string $value cookie值
+     * @param  int    $expire 有效期
+     * @param  array  $option 可选参数
+     * @return void
+     */
+    protected function setCookie(string $name, string $value, int $expire, array $option = []): void
+    {
+        $this->cookie[$name] = [$value, $expire, $option];
+    }
+
+    /**
+     * 永久保存Cookie数据
+     * @access public
+     * @param  string $name  cookie名称
+     * @param  string $value cookie值
+     * @param  mixed  $option 可选参数 可能会是 null|integer|string
+     * @return void
+     */
+    public function forever(string $name, string $value = '', $option = null): void
+    {
+        if (is_null($option) || is_numeric($option)) {
+            $option = [];
+        }
+
+        $option['expire'] = 315360000;
+
+        $this->set($name, $value, $option);
+    }
+
+    /**
+     * Cookie删除
+     * @access public
+     * @param  string $name cookie名称
+     * @return void
+     */
+    public function delete(string $name): void
+    {
+        $this->setCookie($name, '', time() - 3600, $this->config);
+    }
+
+    /**
+     * 获取cookie保存数据
+     * @access public
+     * @return array
+     */
+    public function getCookie(): array
+    {
+        return $this->cookie;
+    }
+
+    /**
+     * 保存Cookie
+     * @access public
+     * @return void
+     */
+    public function save(): void
+    {
+        foreach ($this->cookie as $name => $val) {
+            [$value, $expire, $option] = $val;
+
+            $this->saveCookie(
+                $name,
+                $value,
+                $expire,
+                $option['path'],
+                $option['domain'],
+                $option['secure'] ? true : false,
+                $option['httponly'] ? true : false,
+                $option['samesite']
+            );
+        }
+    }
+
+    /**
+     * 保存Cookie
+     * @access public
+     * @param  string $name cookie名称
+     * @param  string $value cookie值
+     * @param  int    $expire cookie过期时间
+     * @param  string $path 有效的服务器路径
+     * @param  string $domain 有效域名/子域名
+     * @param  bool   $secure 是否仅仅通过HTTPS
+     * @param  bool   $httponly 仅可通过HTTP访问
+     * @param  string $samesite 防止CSRF攻击和用户追踪
+     * @return void
+     */
+    protected function saveCookie(string $name, string $value, int $expire, string $path, string $domain, bool $secure, bool $httponly, string $samesite): void
+    {
+        if (version_compare(PHP_VERSION, '7.3.0', '>=')) {
+            setcookie($name, $value, [
+                'expires'  => $expire,
+                'path'     => $path,
+                'domain'   => $domain,
+                'secure'   => $secure,
+                'httponly' => $httponly,
+                'samesite' => $samesite,
+            ]);
+        } else {
+            setcookie($name, $value, $expire, $path, $domain, $secure, $httponly);
+        }
+    }
+
+}

+ 117 - 0
vendor/topthink/framework/src/think/Db.php

@@ -0,0 +1,117 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+/**
+ * 数据库管理类
+ * @package think
+ * @property Config $config
+ */
+class Db extends DbManager
+{
+    /**
+     * @param Event  $event
+     * @param Config $config
+     * @param Log    $log
+     * @param Cache  $cache
+     * @return Db
+     * @codeCoverageIgnore
+     */
+    public static function __make(Event $event, Config $config, Log $log, Cache $cache)
+    {
+        $db = new static();
+        $db->setConfig($config);
+        $db->setEvent($event);
+        $db->setLog($log);
+
+        $store = $db->getConfig('cache_store');
+        $db->setCache($cache->store($store));
+        $db->triggerSql();
+
+        return $db;
+    }
+
+    /**
+     * 注入模型对象
+     * @access public
+     * @return void
+     */
+    protected function modelMaker()
+    {
+    }
+
+    /**
+     * 设置配置对象
+     * @access public
+     * @param Config $config 配置对象
+     * @return void
+     */
+    public function setConfig($config): void
+    {
+        $this->config = $config;
+    }
+
+    /**
+     * 获取配置参数
+     * @access public
+     * @param string $name    配置参数
+     * @param mixed  $default 默认值
+     * @return mixed
+     */
+    public function getConfig(string $name = '', $default = null)
+    {
+        if ('' !== $name) {
+            return $this->config->get('database.' . $name, $default);
+        }
+
+        return $this->config->get('database', []);
+    }
+
+    /**
+     * 设置Event对象
+     * @param Event $event
+     */
+    public function setEvent(Event $event): void
+    {
+        $this->event = $event;
+    }
+
+    /**
+     * 注册回调方法
+     * @access public
+     * @param string   $event    事件名
+     * @param callable $callback 回调方法
+     * @return void
+     */
+    public function event(string $event, callable $callback): void
+    {
+        if ($this->event) {
+            $this->event->listen('db.' . $event, $callback);
+        }
+    }
+
+    /**
+     * 触发事件
+     * @access public
+     * @param string $event  事件名
+     * @param mixed  $params 传入参数
+     * @param bool   $once
+     * @return mixed
+     */
+    public function trigger(string $event, $params = null, bool $once = false)
+    {
+        if ($this->event) {
+            return $this->event->trigger('db.' . $event, $params, $once);
+        }
+    }
+}

+ 181 - 0
vendor/topthink/framework/src/think/Env.php

@@ -0,0 +1,181 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use ArrayAccess;
+
+/**
+ * Env管理类
+ * @package think
+ */
+class Env implements ArrayAccess
+{
+    /**
+     * 环境变量数据
+     * @var array
+     */
+    protected $data = [];
+
+    public function __construct()
+    {
+        $this->data = $_ENV;
+    }
+
+    /**
+     * 读取环境变量定义文件
+     * @access public
+     * @param string $file 环境变量定义文件
+     * @return void
+     */
+    public function load(string $file): void
+    {
+        $env = parse_ini_file($file, true) ?: [];
+        $this->set($env);
+    }
+
+    /**
+     * 获取环境变量值
+     * @access public
+     * @param string $name    环境变量名
+     * @param mixed  $default 默认值
+     * @return mixed
+     */
+    public function get(string $name = null, $default = null)
+    {
+        if (is_null($name)) {
+            return $this->data;
+        }
+
+        $name = strtoupper(str_replace('.', '_', $name));
+
+        if (isset($this->data[$name])) {
+            return $this->data[$name];
+        }
+
+        return $this->getEnv($name, $default);
+    }
+
+    protected function getEnv(string $name, $default = null)
+    {
+        $result = getenv('PHP_' . $name);
+
+        if (false === $result) {
+            return $default;
+        }
+
+        if ('false' === $result) {
+            $result = false;
+        } elseif ('true' === $result) {
+            $result = true;
+        }
+
+        if (!isset($this->data[$name])) {
+            $this->data[$name] = $result;
+        }
+
+        return $result;
+    }
+
+    /**
+     * 设置环境变量值
+     * @access public
+     * @param string|array $env   环境变量
+     * @param mixed        $value 值
+     * @return void
+     */
+    public function set($env, $value = null): void
+    {
+        if (is_array($env)) {
+            $env = array_change_key_case($env, CASE_UPPER);
+
+            foreach ($env as $key => $val) {
+                if (is_array($val)) {
+                    foreach ($val as $k => $v) {
+                        $this->data[$key . '_' . strtoupper($k)] = $v;
+                    }
+                } else {
+                    $this->data[$key] = $val;
+                }
+            }
+        } else {
+            $name = strtoupper(str_replace('.', '_', $env));
+
+            $this->data[$name] = $value;
+        }
+    }
+
+    /**
+     * 检测是否存在环境变量
+     * @access public
+     * @param string $name 参数名
+     * @return bool
+     */
+    public function has(string $name): bool
+    {
+        return !is_null($this->get($name));
+    }
+
+    /**
+     * 设置环境变量
+     * @access public
+     * @param string $name  参数名
+     * @param mixed  $value 值
+     */
+    public function __set(string $name, $value): void
+    {
+        $this->set($name, $value);
+    }
+
+    /**
+     * 获取环境变量
+     * @access public
+     * @param string $name 参数名
+     * @return mixed
+     */
+    public function __get(string $name)
+    {
+        return $this->get($name);
+    }
+
+    /**
+     * 检测是否存在环境变量
+     * @access public
+     * @param string $name 参数名
+     * @return bool
+     */
+    public function __isset(string $name): bool
+    {
+        return $this->has($name);
+    }
+
+    // ArrayAccess
+    public function offsetSet($name, $value): void
+    {
+        $this->set($name, $value);
+    }
+
+    public function offsetExists($name): bool
+    {
+        return $this->__isset($name);
+    }
+
+    public function offsetUnset($name)
+    {
+        throw new Exception('not support: unset');
+    }
+
+    public function offsetGet($name)
+    {
+        return $this->get($name);
+    }
+}

+ 272 - 0
vendor/topthink/framework/src/think/Event.php

@@ -0,0 +1,272 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use ReflectionClass;
+use ReflectionMethod;
+
+/**
+ * 事件管理类
+ * @package think
+ */
+class Event
+{
+    /**
+     * 监听者
+     * @var array
+     */
+    protected $listener = [];
+
+    /**
+     * 事件别名
+     * @var array
+     */
+    protected $bind = [
+        'AppInit'     => event\AppInit::class,
+        'HttpRun'     => event\HttpRun::class,
+        'HttpEnd'     => event\HttpEnd::class,
+        'RouteLoaded' => event\RouteLoaded::class,
+        'LogWrite'    => event\LogWrite::class,
+        'LogRecord'   => event\LogRecord::class,
+    ];
+
+    /**
+     * 应用对象
+     * @var App
+     */
+    protected $app;
+
+    public function __construct(App $app)
+    {
+        $this->app = $app;
+    }
+
+    /**
+     * 批量注册事件监听
+     * @access public
+     * @param array $events 事件定义
+     * @return $this
+     */
+    public function listenEvents(array $events)
+    {
+        foreach ($events as $event => $listeners) {
+            if (isset($this->bind[$event])) {
+                $event = $this->bind[$event];
+            }
+
+            $this->listener[$event] = array_merge($this->listener[$event] ?? [], $listeners);
+        }
+
+        return $this;
+    }
+
+    /**
+     * 注册事件监听
+     * @access public
+     * @param string $event    事件名称
+     * @param mixed  $listener 监听操作(或者类名)
+     * @param bool   $first    是否优先执行
+     * @return $this
+     */
+    public function listen(string $event, $listener, bool $first = false)
+    {
+        if (isset($this->bind[$event])) {
+            $event = $this->bind[$event];
+        }
+
+        if ($first && isset($this->listener[$event])) {
+            array_unshift($this->listener[$event], $listener);
+        } else {
+            $this->listener[$event][] = $listener;
+        }
+
+        return $this;
+    }
+
+    /**
+     * 是否存在事件监听
+     * @access public
+     * @param string $event 事件名称
+     * @return bool
+     */
+    public function hasListener(string $event): bool
+    {
+        if (isset($this->bind[$event])) {
+            $event = $this->bind[$event];
+        }
+
+        return isset($this->listener[$event]);
+    }
+
+    /**
+     * 移除事件监听
+     * @access public
+     * @param string $event 事件名称
+     * @return void
+     */
+    public function remove(string $event): void
+    {
+        if (isset($this->bind[$event])) {
+            $event = $this->bind[$event];
+        }
+
+        unset($this->listener[$event]);
+    }
+
+    /**
+     * 指定事件别名标识 便于调用
+     * @access public
+     * @param array $events 事件别名
+     * @return $this
+     */
+    public function bind(array $events)
+    {
+        $this->bind = array_merge($this->bind, $events);
+
+        return $this;
+    }
+
+    /**
+     * 注册事件订阅者
+     * @access public
+     * @param mixed $subscriber 订阅者
+     * @return $this
+     */
+    public function subscribe($subscriber)
+    {
+        $subscribers = (array) $subscriber;
+
+        foreach ($subscribers as $subscriber) {
+            if (is_string($subscriber)) {
+                $subscriber = $this->app->make($subscriber);
+            }
+
+            if (method_exists($subscriber, 'subscribe')) {
+                // 手动订阅
+                $subscriber->subscribe($this);
+            } else {
+                // 智能订阅
+                $this->observe($subscriber);
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * 自动注册事件观察者
+     * @access public
+     * @param string|object $observer 观察者
+     * @param null|string   $prefix   事件名前缀
+     * @return $this
+     */
+    public function observe($observer, string $prefix = '')
+    {
+        if (is_string($observer)) {
+            $observer = $this->app->make($observer);
+        }
+
+        $reflect = new ReflectionClass($observer);
+        $methods = $reflect->getMethods(ReflectionMethod::IS_PUBLIC);
+
+        if (empty($prefix) && $reflect->hasProperty('eventPrefix')) {
+            $reflectProperty = $reflect->getProperty('eventPrefix');
+            $reflectProperty->setAccessible(true);
+            $prefix = $reflectProperty->getValue($observer);
+        }
+
+        foreach ($methods as $method) {
+            $name = $method->getName();
+            if (0 === strpos($name, 'on')) {
+                $this->listen($prefix . substr($name, 2), [$observer, $name]);
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * 触发事件
+     * @access public
+     * @param string|object $event  事件名称
+     * @param mixed         $params 传入参数
+     * @param bool          $once   只获取一个有效返回值
+     * @return mixed
+     */
+    public function trigger($event, $params = null, bool $once = false)
+    {
+        if (is_object($event)) {
+            $params = $event;
+            $event  = get_class($event);
+        }
+
+        if (isset($this->bind[$event])) {
+            $event = $this->bind[$event];
+        }
+
+        $result    = [];
+        $listeners = $this->listener[$event] ?? [];
+
+        if (strpos($event, '.')) {
+            [$prefix, $event] = explode('.', $event, 2);
+            if (isset($this->listener[$prefix . '.*'])) {
+                $listeners = array_merge($listeners, $this->listener[$prefix . '.*']);
+            }
+        }
+
+        $listeners = array_unique($listeners, SORT_REGULAR);
+
+        foreach ($listeners as $key => $listener) {
+            $result[$key] = $this->dispatch($listener, $params);
+
+            if (false === $result[$key] || (!is_null($result[$key]) && $once)) {
+                break;
+            }
+        }
+
+        return $once ? end($result) : $result;
+    }
+
+    /**
+     * 触发事件(只获取一个有效返回值)
+     * @param      $event
+     * @param null $params
+     * @return mixed
+     */
+    public function until($event, $params = null)
+    {
+        return $this->trigger($event, $params, true);
+    }
+
+    /**
+     * 执行事件调度
+     * @access protected
+     * @param mixed $event  事件方法
+     * @param mixed $params 参数
+     * @return mixed
+     */
+    protected function dispatch($event, $params = null)
+    {
+        if (!is_string($event)) {
+            $call = $event;
+        } elseif (strpos($event, '::')) {
+            $call = $event;
+        } else {
+            $obj  = $this->app->make($event);
+            $call = [$obj, 'handle'];
+        }
+
+        return $this->app->invoke($call, [$params]);
+    }
+
+}

+ 60 - 0
vendor/topthink/framework/src/think/Exception.php

@@ -0,0 +1,60 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+/**
+ * 异常基础类
+ * @package think
+ */
+class Exception extends \Exception
+{
+    /**
+     * 保存异常页面显示的额外Debug数据
+     * @var array
+     */
+    protected $data = [];
+
+    /**
+     * 设置异常额外的Debug数据
+     * 数据将会显示为下面的格式
+     *
+     * Exception Data
+     * --------------------------------------------------
+     * Label 1
+     *   key1      value1
+     *   key2      value2
+     * Label 2
+     *   key1      value1
+     *   key2      value2
+     *
+     * @access protected
+     * @param  string $label 数据分类,用于异常页面显示
+     * @param  array  $data  需要显示的数据,必须为关联数组
+     */
+    final protected function setData(string $label, array $data)
+    {
+        $this->data[$label] = $data;
+    }
+
+    /**
+     * 获取异常额外Debug数据
+     * 主要用于输出到异常页面便于调试
+     * @access public
+     * @return array 由setData设置的Debug数据
+     */
+    final public function getData()
+    {
+        return $this->data;
+    }
+
+}

+ 98 - 0
vendor/topthink/framework/src/think/Facade.php

@@ -0,0 +1,98 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+namespace think;
+
+/**
+ * Facade管理类
+ */
+class Facade
+{
+    /**
+     * 始终创建新的对象实例
+     * @var bool
+     */
+    protected static $alwaysNewInstance;
+
+    /**
+     * 创建Facade实例
+     * @static
+     * @access protected
+     * @param  string $class       类名或标识
+     * @param  array  $args        变量
+     * @param  bool   $newInstance 是否每次创建新的实例
+     * @return object
+     */
+    protected static function createFacade(string $class = '', array $args = [], bool $newInstance = false)
+    {
+        $class = $class ?: static::class;
+
+        $facadeClass = static::getFacadeClass();
+
+        if ($facadeClass) {
+            $class = $facadeClass;
+        }
+
+        if (static::$alwaysNewInstance) {
+            $newInstance = true;
+        }
+
+        return Container::getInstance()->make($class, $args, $newInstance);
+    }
+
+    /**
+     * 获取当前Facade对应类名
+     * @access protected
+     * @return string
+     */
+    protected static function getFacadeClass()
+    {}
+
+    /**
+     * 带参数实例化当前Facade类
+     * @access public
+     * @return object
+     */
+    public static function instance(...$args)
+    {
+        if (__CLASS__ != static::class) {
+            return self::createFacade('', $args);
+        }
+    }
+
+    /**
+     * 调用类的实例
+     * @access public
+     * @param  string     $class       类名或者标识
+     * @param  array|true $args        变量
+     * @param  bool       $newInstance 是否每次创建新的实例
+     * @return object
+     */
+    public static function make(string $class, $args = [], $newInstance = false)
+    {
+        if (__CLASS__ != static::class) {
+            return self::__callStatic('make', func_get_args());
+        }
+
+        if (true === $args) {
+            // 总是创建新的实例化对象
+            $newInstance = true;
+            $args        = [];
+        }
+
+        return self::createFacade($class, $args, $newInstance);
+    }
+
+    // 调用实际类的方法
+    public static function __callStatic($method, $params)
+    {
+        return call_user_func_array([static::createFacade(), $method], $params);
+    }
+}

+ 187 - 0
vendor/topthink/framework/src/think/File.php

@@ -0,0 +1,187 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use SplFileInfo;
+use think\exception\FileException;
+
+/**
+ * 文件上传类
+ * @package think
+ */
+class File extends SplFileInfo
+{
+
+    /**
+     * 文件hash规则
+     * @var array
+     */
+    protected $hash = [];
+
+    protected $hashName;
+
+    public function __construct(string $path, bool $checkPath = true)
+    {
+        if ($checkPath && !is_file($path)) {
+            throw new FileException(sprintf('The file "%s" does not exist', $path));
+        }
+
+        parent::__construct($path);
+    }
+
+    /**
+     * 获取文件的哈希散列值
+     * @access public
+     * @param string $type
+     * @return string
+     */
+    public function hash(string $type = 'sha1'): string
+    {
+        if (!isset($this->hash[$type])) {
+            $this->hash[$type] = hash_file($type, $this->getPathname());
+        }
+
+        return $this->hash[$type];
+    }
+
+    /**
+     * 获取文件的MD5值
+     * @access public
+     * @return string
+     */
+    public function md5(): string
+    {
+        return $this->hash('md5');
+    }
+
+    /**
+     * 获取文件的SHA1值
+     * @access public
+     * @return string
+     */
+    public function sha1(): string
+    {
+        return $this->hash('sha1');
+    }
+
+    /**
+     * 获取文件类型信息
+     * @access public
+     * @return string
+     */
+    public function getMime(): string
+    {
+        $finfo = finfo_open(FILEINFO_MIME_TYPE);
+
+        return finfo_file($finfo, $this->getPathname());
+    }
+
+    /**
+     * 移动文件
+     * @access public
+     * @param string      $directory 保存路径
+     * @param string|null $name      保存的文件名
+     * @return File
+     */
+    public function move(string $directory, string $name = null): File
+    {
+        $target = $this->getTargetFile($directory, $name);
+
+        set_error_handler(function ($type, $msg) use (&$error) {
+            $error = $msg;
+        });
+        $renamed = rename($this->getPathname(), (string) $target);
+        restore_error_handler();
+        if (!$renamed) {
+            throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error)));
+        }
+
+        @chmod((string) $target, 0666 & ~umask());
+
+        return $target;
+    }
+
+    /**
+     * 实例化一个新文件
+     * @param string      $directory
+     * @param null|string $name
+     * @return File
+     */
+    protected function getTargetFile(string $directory, string $name = null): File
+    {
+        if (!is_dir($directory)) {
+            if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) {
+                throw new FileException(sprintf('Unable to create the "%s" directory', $directory));
+            }
+        } elseif (!is_writable($directory)) {
+            throw new FileException(sprintf('Unable to write in the "%s" directory', $directory));
+        }
+
+        $target = rtrim($directory, '/\\') . \DIRECTORY_SEPARATOR . (null === $name ? $this->getBasename() : $this->getName($name));
+
+        return new self($target, false);
+    }
+
+    /**
+     * 获取文件名
+     * @param string $name
+     * @return string
+     */
+    protected function getName(string $name): string
+    {
+        $originalName = str_replace('\\', '/', $name);
+        $pos          = strrpos($originalName, '/');
+        $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1);
+
+        return $originalName;
+    }
+
+    /**
+     * 文件扩展名
+     * @return string
+     */
+    public function extension(): string
+    {
+        return $this->getExtension();
+    }
+
+    /**
+     * 自动生成文件名
+     * @access public
+     * @param string|\Closure $rule
+     * @return string
+     */
+    public function hashName($rule = ''): string
+    {
+        if (!$this->hashName) {
+            if ($rule instanceof \Closure) {
+                $this->hashName = call_user_func_array($rule, [$this]);
+            } else {
+                switch (true) {
+                    case in_array($rule, hash_algos()):
+                        $hash           = $this->hash($rule);
+                        $this->hashName = substr($hash, 0, 2) . DIRECTORY_SEPARATOR . substr($hash, 2);
+                        break;
+                    case is_callable($rule):
+                        $this->hashName = call_user_func($rule);
+                        break;
+                    default:
+                        $this->hashName = date('Ymd') . DIRECTORY_SEPARATOR . md5((string) microtime(true));
+                        break;
+                }
+            }
+        }
+
+        return $this->hashName . '.' . $this->extension();
+    }
+}

+ 89 - 0
vendor/topthink/framework/src/think/Filesystem.php

@@ -0,0 +1,89 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use InvalidArgumentException;
+use think\filesystem\Driver;
+use think\filesystem\driver\Local;
+use think\helper\Arr;
+
+/**
+ * Class Filesystem
+ * @package think
+ * @mixin Driver
+ * @mixin Local
+ */
+class Filesystem extends Manager
+{
+    protected $namespace = '\\think\\filesystem\\driver\\';
+
+    /**
+     * @param null|string $name
+     * @return Driver
+     */
+    public function disk(string $name = null): Driver
+    {
+        return $this->driver($name);
+    }
+
+    protected function resolveType(string $name)
+    {
+        return $this->getDiskConfig($name, 'type', 'local');
+    }
+
+    protected function resolveConfig(string $name)
+    {
+        return $this->getDiskConfig($name);
+    }
+
+    /**
+     * 获取缓存配置
+     * @access public
+     * @param null|string $name    名称
+     * @param mixed       $default 默认值
+     * @return mixed
+     */
+    public function getConfig(string $name = null, $default = null)
+    {
+        if (!is_null($name)) {
+            return $this->app->config->get('filesystem.' . $name, $default);
+        }
+
+        return $this->app->config->get('filesystem');
+    }
+
+    /**
+     * 获取磁盘配置
+     * @param string $disk
+     * @param null   $name
+     * @param null   $default
+     * @return array
+     */
+    public function getDiskConfig($disk, $name = null, $default = null)
+    {
+        if ($config = $this->getConfig("disks.{$disk}")) {
+            return Arr::get($config, $name, $default);
+        }
+
+        throw new InvalidArgumentException("Disk [$disk] not found.");
+    }
+
+    /**
+     * 默认驱动
+     * @return string|null
+     */
+    public function getDefaultDriver()
+    {
+        return $this->getConfig('default');
+    }
+}

+ 288 - 0
vendor/topthink/framework/src/think/Http.php

@@ -0,0 +1,288 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use think\event\HttpEnd;
+use think\event\HttpRun;
+use think\event\RouteLoaded;
+use think\exception\Handle;
+use Throwable;
+
+/**
+ * Web应用管理类
+ * @package think
+ */
+class Http
+{
+
+    /**
+     * @var App
+     */
+    protected $app;
+
+    /**
+     * 应用名称
+     * @var string
+     */
+    protected $name;
+
+    /**
+     * 应用路径
+     * @var string
+     */
+    protected $path;
+
+    /**
+     * 路由路径
+     * @var string
+     */
+    protected $routePath;
+
+    /**
+     * 是否绑定应用
+     * @var bool
+     */
+    protected $isBind = false;
+
+    public function __construct(App $app)
+    {
+        $this->app = $app;
+
+        $this->routePath = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR;
+    }
+
+    /**
+     * 设置应用名称
+     * @access public
+     * @param string $name 应用名称
+     * @return $this
+     */
+    public function name(string $name)
+    {
+        $this->name = $name;
+        return $this;
+    }
+
+    /**
+     * 获取应用名称
+     * @access public
+     * @return string
+     */
+    public function getName(): string
+    {
+        return $this->name ?: '';
+    }
+
+    /**
+     * 设置应用目录
+     * @access public
+     * @param string $path 应用目录
+     * @return $this
+     */
+    public function path(string $path)
+    {
+        if (substr($path, -1) != DIRECTORY_SEPARATOR) {
+            $path .= DIRECTORY_SEPARATOR;
+        }
+
+        $this->path = $path;
+        return $this;
+    }
+
+    /**
+     * 获取应用路径
+     * @access public
+     * @return string
+     */
+    public function getPath(): string
+    {
+        return $this->path ?: '';
+    }
+
+    /**
+     * 获取路由目录
+     * @access public
+     * @return string
+     */
+    public function getRoutePath(): string
+    {
+        return $this->routePath;
+    }
+
+    /**
+     * 设置路由目录
+     * @access public
+     * @param string $path 路由定义目录
+     */
+    public function setRoutePath(string $path): void
+    {
+        $this->routePath = $path;
+    }
+
+    /**
+     * 设置应用绑定
+     * @access public
+     * @param bool $bind 是否绑定
+     * @return $this
+     */
+    public function setBind(bool $bind = true)
+    {
+        $this->isBind = $bind;
+        return $this;
+    }
+
+    /**
+     * 是否绑定应用
+     * @access public
+     * @return bool
+     */
+    public function isBind(): bool
+    {
+        return $this->isBind;
+    }
+
+    /**
+     * 执行应用程序
+     * @access public
+     * @param Request|null $request
+     * @return Response
+     */
+    public function run(Request $request = null): Response
+    {
+        //初始化
+        $this->initialize();
+
+        //自动创建request对象
+        $request = $request ?? $this->app->make('request', [], true);
+        $this->app->instance('request', $request);
+
+        try {
+            $response = $this->runWithRequest($request);
+        } catch (Throwable $e) {
+            $this->reportException($e);
+
+            $response = $this->renderException($request, $e);
+        }
+
+        return $response;
+    }
+
+    /**
+     * 初始化
+     */
+    protected function initialize()
+    {
+        if (!$this->app->initialized()) {
+            $this->app->initialize();
+        }
+    }
+
+    /**
+     * 执行应用程序
+     * @param Request $request
+     * @return mixed
+     */
+    protected function runWithRequest(Request $request)
+    {
+        // 加载全局中间件
+        $this->loadMiddleware();
+
+        // 监听HttpRun
+        $this->app->event->trigger(HttpRun::class);
+
+        return $this->app->middleware->pipeline()
+            ->send($request)
+            ->then(function ($request) {
+                return $this->dispatchToRoute($request);
+            });
+    }
+
+    protected function dispatchToRoute($request)
+    {
+        $withRoute = $this->app->config->get('app.with_route', true) ? function () {
+            $this->loadRoutes();
+        } : null;
+
+        return $this->app->route->dispatch($request, $withRoute);
+    }
+
+    /**
+     * 加载全局中间件
+     */
+    protected function loadMiddleware(): void
+    {
+        if (is_file($this->app->getBasePath() . 'middleware.php')) {
+            $this->app->middleware->import(include $this->app->getBasePath() . 'middleware.php');
+        }
+    }
+
+    /**
+     * 加载路由
+     * @access protected
+     * @return void
+     */
+    protected function loadRoutes(): void
+    {
+        // 加载路由定义
+        $routePath = $this->getRoutePath();
+
+        if (is_dir($routePath)) {
+            $files = glob($routePath . '*.php');
+            foreach ($files as $file) {
+                include $file;
+            }
+        }
+
+        $this->app->event->trigger(RouteLoaded::class);
+    }
+
+    /**
+     * Report the exception to the exception handler.
+     *
+     * @param Throwable $e
+     * @return void
+     */
+    protected function reportException(Throwable $e)
+    {
+        $this->app->make(Handle::class)->report($e);
+    }
+
+    /**
+     * Render the exception to a response.
+     *
+     * @param Request   $request
+     * @param Throwable $e
+     * @return Response
+     */
+    protected function renderException($request, Throwable $e)
+    {
+        return $this->app->make(Handle::class)->render($request, $e);
+    }
+
+    /**
+     * HttpEnd
+     * @param Response $response
+     * @return void
+     */
+    public function end(Response $response): void
+    {
+        $this->app->event->trigger(HttpEnd::class, $response);
+
+        //执行中间件
+        $this->app->middleware->end($response);
+
+        // 写入日志
+        $this->app->log->save();
+    }
+
+}

+ 294 - 0
vendor/topthink/framework/src/think/Lang.php

@@ -0,0 +1,294 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+/**
+ * 多语言管理类
+ * @package think
+ */
+class Lang
+{
+    /**
+     * 配置参数
+     * @var array
+     */
+    protected $config = [
+        // 默认语言
+        'default_lang'    => 'zh-cn',
+        // 允许的语言列表
+        'allow_lang_list' => [],
+        // 是否使用Cookie记录
+        'use_cookie'      => true,
+        // 扩展语言包
+        'extend_list'     => [],
+        // 多语言cookie变量
+        'cookie_var'      => 'think_lang',
+        // 多语言header变量
+        'header_var'      => 'think-lang',
+        // 多语言自动侦测变量名
+        'detect_var'      => 'lang',
+        // Accept-Language转义为对应语言包名称
+        'accept_language' => [
+            'zh-hans-cn' => 'zh-cn',
+        ],
+        // 是否支持语言分组
+        'allow_group'     => false,
+    ];
+
+    /**
+     * 多语言信息
+     * @var array
+     */
+    private $lang = [];
+
+    /**
+     * 当前语言
+     * @var string
+     */
+    private $range = 'zh-cn';
+
+    /**
+     * 构造方法
+     * @access public
+     * @param array $config
+     */
+    public function __construct(array $config = [])
+    {
+        $this->config = array_merge($this->config, array_change_key_case($config));
+        $this->range  = $this->config['default_lang'];
+    }
+
+    public static function __make(Config $config)
+    {
+        return new static($config->get('lang'));
+    }
+
+    /**
+     * 设置当前语言
+     * @access public
+     * @param string $lang 语言
+     * @return void
+     */
+    public function setLangSet(string $lang): void
+    {
+        $this->range = $lang;
+    }
+
+    /**
+     * 获取当前语言
+     * @access public
+     * @return string
+     */
+    public function getLangSet(): string
+    {
+        return $this->range;
+    }
+
+    /**
+     * 获取默认语言
+     * @access public
+     * @return string
+     */
+    public function defaultLangSet()
+    {
+        return $this->config['default_lang'];
+    }
+
+    /**
+     * 加载语言定义(不区分大小写)
+     * @access public
+     * @param string|array $file  语言文件
+     * @param string       $range 语言作用域
+     * @return array
+     */
+    public function load($file, $range = ''): array
+    {
+        $range = $range ?: $this->range;
+        if (!isset($this->lang[$range])) {
+            $this->lang[$range] = [];
+        }
+
+        $lang = [];
+
+        foreach ((array) $file as $name) {
+            if (is_file($name)) {
+                $result = $this->parse($name);
+                $lang   = array_change_key_case($result) + $lang;
+            }
+        }
+
+        if (!empty($lang)) {
+            $this->lang[$range] = $lang + $this->lang[$range];
+        }
+
+        return $this->lang[$range];
+    }
+
+    /**
+     * 解析语言文件
+     * @access protected
+     * @param string $file 语言文件名
+     * @return array
+     */
+    protected function parse(string $file): array
+    {
+        $type = pathinfo($file, PATHINFO_EXTENSION);
+
+        switch ($type) {
+            case 'php':
+                $result = include $file;
+                break;
+            case 'yml':
+            case 'yaml':
+                if (function_exists('yaml_parse_file')) {
+                    $result = yaml_parse_file($file);
+                }
+                break;
+            case 'json':
+                $data = file_get_contents($file);
+
+                if (false !== $data) {
+                    $data = json_decode($data, true);
+
+                    if (json_last_error() === JSON_ERROR_NONE) {
+                        $result = $data;
+                    }
+                }
+
+                break;
+        }
+
+        return isset($result) && is_array($result) ? $result : [];
+    }
+
+    /**
+     * 判断是否存在语言定义(不区分大小写)
+     * @access public
+     * @param string|null $name  语言变量
+     * @param string      $range 语言作用域
+     * @return bool
+     */
+    public function has(string $name, string $range = ''): bool
+    {
+        $range = $range ?: $this->range;
+
+        if ($this->config['allow_group'] && strpos($name, '.')) {
+            [$name1, $name2] = explode('.', $name, 2);
+            return isset($this->lang[$range][strtolower($name1)][$name2]);
+        }
+
+        return isset($this->lang[$range][strtolower($name)]);
+    }
+
+    /**
+     * 获取语言定义(不区分大小写)
+     * @access public
+     * @param string|null $name  语言变量
+     * @param array       $vars  变量替换
+     * @param string      $range 语言作用域
+     * @return mixed
+     */
+    public function get(string $name = null, array $vars = [], string $range = '')
+    {
+        $range = $range ?: $this->range;
+
+        // 空参数返回所有定义
+        if (is_null($name)) {
+            return $this->lang[$range] ?? [];
+        }
+
+        if ($this->config['allow_group'] && strpos($name, '.')) {
+            [$name1, $name2] = explode('.', $name, 2);
+
+            $value = $this->lang[$range][strtolower($name1)][$name2] ?? $name;
+        } else {
+            $value = $this->lang[$range][strtolower($name)] ?? $name;
+        }
+
+        // 变量解析
+        if (!empty($vars) && is_array($vars)) {
+            /**
+             * Notes:
+             * 为了检测的方便,数字索引的判断仅仅是参数数组的第一个元素的key为数字0
+             * 数字索引采用的是系统的 sprintf 函数替换,用法请参考 sprintf 函数
+             */
+            if (key($vars) === 0) {
+                // 数字索引解析
+                array_unshift($vars, $value);
+                $value = call_user_func_array('sprintf', $vars);
+            } else {
+                // 关联索引解析
+                $replace = array_keys($vars);
+                foreach ($replace as &$v) {
+                    $v = "{:{$v}}";
+                }
+                $value = str_replace($replace, $vars, $value);
+            }
+        }
+
+        return $value;
+    }
+
+    /**
+     * 自动侦测设置获取语言选择
+     * @access public
+     * @param Request $request
+     * @return string
+     */
+    public function detect(Request $request): string
+    {
+        // 自动侦测设置获取语言选择
+        $langSet = '';
+
+        if ($request->get($this->config['detect_var'])) {
+            // url中设置了语言变量
+            $langSet = strtolower($request->get($this->config['detect_var']));
+        } elseif ($request->header($this->config['header_var'])) {
+            // Header中设置了语言变量
+            $langSet = strtolower($request->header($this->config['header_var']));
+        } elseif ($request->cookie($this->config['cookie_var'])) {
+            // Cookie中设置了语言变量
+            $langSet = strtolower($request->cookie($this->config['cookie_var']));
+        } elseif ($request->server('HTTP_ACCEPT_LANGUAGE')) {
+            // 自动侦测浏览器语言
+            $match = preg_match('/^([a-z\d\-]+)/i', $request->server('HTTP_ACCEPT_LANGUAGE'), $matches);
+            if ($match) {
+                $langSet = strtolower($matches[1]);
+                if (isset($this->config['accept_language'][$langSet])) {
+                    $langSet = $this->config['accept_language'][$langSet];
+                }
+            }
+        }
+
+        if (empty($this->config['allow_lang_list']) || in_array($langSet, $this->config['allow_lang_list'])) {
+            // 合法的语言
+            $this->range = $langSet;
+        }
+
+        return $this->range;
+    }
+
+    /**
+     * 保存当前语言到Cookie
+     * @access public
+     * @param Cookie $cookie Cookie对象
+     * @return void
+     */
+    public function saveToCookie(Cookie $cookie)
+    {
+        if ($this->config['use_cookie']) {
+            $cookie->set($this->config['cookie_var'], $this->range);
+        }
+    }
+
+}

+ 342 - 0
vendor/topthink/framework/src/think/Log.php

@@ -0,0 +1,342 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use InvalidArgumentException;
+use Psr\Log\LoggerInterface;
+use think\event\LogWrite;
+use think\helper\Arr;
+use think\log\Channel;
+use think\log\ChannelSet;
+
+/**
+ * 日志管理类
+ * @package think
+ * @mixin Channel
+ */
+class Log extends Manager implements LoggerInterface
+{
+    const EMERGENCY = 'emergency';
+    const ALERT     = 'alert';
+    const CRITICAL  = 'critical';
+    const ERROR     = 'error';
+    const WARNING   = 'warning';
+    const NOTICE    = 'notice';
+    const INFO      = 'info';
+    const DEBUG     = 'debug';
+    const SQL       = 'sql';
+
+    protected $namespace = '\\think\\log\\driver\\';
+
+    /**
+     * 默认驱动
+     * @return string|null
+     */
+    public function getDefaultDriver()
+    {
+        return $this->getConfig('default');
+    }
+
+    /**
+     * 获取日志配置
+     * @access public
+     * @param null|string $name    名称
+     * @param mixed       $default 默认值
+     * @return mixed
+     */
+    public function getConfig(string $name = null, $default = null)
+    {
+        if (!is_null($name)) {
+            return $this->app->config->get('log.' . $name, $default);
+        }
+
+        return $this->app->config->get('log');
+    }
+
+    /**
+     * 获取渠道配置
+     * @param string $channel
+     * @param null   $name
+     * @param null   $default
+     * @return array
+     */
+    public function getChannelConfig($channel, $name = null, $default = null)
+    {
+        if ($config = $this->getConfig("channels.{$channel}")) {
+            return Arr::get($config, $name, $default);
+        }
+
+        throw new InvalidArgumentException("Channel [$channel] not found.");
+    }
+
+    /**
+     * driver()的别名
+     * @param string|array $name 渠道名
+     * @return Channel|ChannelSet
+     */
+    public function channel($name = null)
+    {
+        if (is_array($name)) {
+            return new ChannelSet($this, $name);
+        }
+
+        return $this->driver($name);
+    }
+
+    protected function resolveType(string $name)
+    {
+        return $this->getChannelConfig($name, 'type', 'file');
+    }
+
+    public function createDriver(string $name)
+    {
+        $driver = parent::createDriver($name);
+
+        $lazy  = !$this->getChannelConfig($name, "realtime_write", false) && !$this->app->runningInConsole();
+        $allow = array_merge($this->getConfig("level", []), $this->getChannelConfig($name, "level", []));
+
+        return new Channel($name, $driver, $allow, $lazy, $this->app->event);
+    }
+
+    protected function resolveConfig(string $name)
+    {
+        return $this->getChannelConfig($name);
+    }
+
+    /**
+     * 清空日志信息
+     * @access public
+     * @param string|array $channel 日志通道名
+     * @return $this
+     */
+    public function clear($channel = '*')
+    {
+        if ('*' == $channel) {
+            $channel = array_keys($this->drivers);
+        }
+
+        $this->channel($channel)->clear();
+
+        return $this;
+    }
+
+    /**
+     * 关闭本次请求日志写入
+     * @access public
+     * @param string|array $channel 日志通道名
+     * @return $this
+     */
+    public function close($channel = '*')
+    {
+        if ('*' == $channel) {
+            $channel = array_keys($this->drivers);
+        }
+
+        $this->channel($channel)->close();
+
+        return $this;
+    }
+
+    /**
+     * 获取日志信息
+     * @access public
+     * @param string $channel 日志通道名
+     * @return array
+     */
+    public function getLog(string $channel = null): array
+    {
+        return $this->channel($channel)->getLog();
+    }
+
+    /**
+     * 保存日志信息
+     * @access public
+     * @return bool
+     */
+    public function save(): bool
+    {
+        /** @var Channel $channel */
+        foreach ($this->drivers as $channel) {
+            $channel->save();
+        }
+
+        return true;
+    }
+
+    /**
+     * 记录日志信息
+     * @access public
+     * @param mixed  $msg     日志信息
+     * @param string $type    日志级别
+     * @param array  $context 替换内容
+     * @param bool   $lazy
+     * @return $this
+     */
+    public function record($msg, string $type = 'info', array $context = [], bool $lazy = true)
+    {
+        $channel = $this->getConfig('type_channel.' . $type);
+
+        $this->channel($channel)->record($msg, $type, $context, $lazy);
+
+        return $this;
+    }
+
+    /**
+     * 实时写入日志信息
+     * @access public
+     * @param mixed  $msg     调试信息
+     * @param string $type    日志级别
+     * @param array  $context 替换内容
+     * @return $this
+     */
+    public function write($msg, string $type = 'info', array $context = [])
+    {
+        return $this->record($msg, $type, $context, false);
+    }
+
+    /**
+     * 注册日志写入事件监听
+     * @param $listener
+     * @return Event
+     */
+    public function listen($listener)
+    {
+        return $this->app->event->listen(LogWrite::class, $listener);
+    }
+
+    /**
+     * 记录日志信息
+     * @access public
+     * @param string $level   日志级别
+     * @param mixed  $message 日志信息
+     * @param array  $context 替换内容
+     * @return void
+     */
+    public function log($level, $message, array $context = []): void
+    {
+        $this->record($message, $level, $context);
+    }
+
+    /**
+     * 记录emergency信息
+     * @access public
+     * @param mixed $message 日志信息
+     * @param array $context 替换内容
+     * @return void
+     */
+    public function emergency($message, array $context = []): void
+    {
+        $this->log(__FUNCTION__, $message, $context);
+    }
+
+    /**
+     * 记录警报信息
+     * @access public
+     * @param mixed $message 日志信息
+     * @param array $context 替换内容
+     * @return void
+     */
+    public function alert($message, array $context = []): void
+    {
+        $this->log(__FUNCTION__, $message, $context);
+    }
+
+    /**
+     * 记录紧急情况
+     * @access public
+     * @param mixed $message 日志信息
+     * @param array $context 替换内容
+     * @return void
+     */
+    public function critical($message, array $context = []): void
+    {
+        $this->log(__FUNCTION__, $message, $context);
+    }
+
+    /**
+     * 记录错误信息
+     * @access public
+     * @param mixed $message 日志信息
+     * @param array $context 替换内容
+     * @return void
+     */
+    public function error($message, array $context = []): void
+    {
+        $this->log(__FUNCTION__, $message, $context);
+    }
+
+    /**
+     * 记录warning信息
+     * @access public
+     * @param mixed $message 日志信息
+     * @param array $context 替换内容
+     * @return void
+     */
+    public function warning($message, array $context = []): void
+    {
+        $this->log(__FUNCTION__, $message, $context);
+    }
+
+    /**
+     * 记录notice信息
+     * @access public
+     * @param mixed $message 日志信息
+     * @param array $context 替换内容
+     * @return void
+     */
+    public function notice($message, array $context = []): void
+    {
+        $this->log(__FUNCTION__, $message, $context);
+    }
+
+    /**
+     * 记录一般信息
+     * @access public
+     * @param mixed $message 日志信息
+     * @param array $context 替换内容
+     * @return void
+     */
+    public function info($message, array $context = []): void
+    {
+        $this->log(__FUNCTION__, $message, $context);
+    }
+
+    /**
+     * 记录调试信息
+     * @access public
+     * @param mixed $message 日志信息
+     * @param array $context 替换内容
+     * @return void
+     */
+    public function debug($message, array $context = []): void
+    {
+        $this->log(__FUNCTION__, $message, $context);
+    }
+
+    /**
+     * 记录sql信息
+     * @access public
+     * @param mixed $message 日志信息
+     * @param array $context 替换内容
+     * @return void
+     */
+    public function sql($message, array $context = []): void
+    {
+        $this->log(__FUNCTION__, $message, $context);
+    }
+
+    public function __call($method, $parameters)
+    {
+        $this->log($method, ...$parameters);
+    }
+}

+ 177 - 0
vendor/topthink/framework/src/think/Manager.php

@@ -0,0 +1,177 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use InvalidArgumentException;
+use think\helper\Str;
+
+abstract class Manager
+{
+    /** @var App */
+    protected $app;
+
+    /**
+     * 驱动
+     * @var array
+     */
+    protected $drivers = [];
+
+    /**
+     * 驱动的命名空间
+     * @var string
+     */
+    protected $namespace = null;
+
+    public function __construct(App $app)
+    {
+        $this->app = $app;
+    }
+
+    /**
+     * 获取驱动实例
+     * @param null|string $name
+     * @return mixed
+     */
+    protected function driver(string $name = null)
+    {
+        $name = $name ?: $this->getDefaultDriver();
+
+        if (is_null($name)) {
+            throw new InvalidArgumentException(sprintf(
+                'Unable to resolve NULL driver for [%s].',
+                static::class
+            ));
+        }
+
+        return $this->drivers[$name] = $this->getDriver($name);
+    }
+
+    /**
+     * 获取驱动实例
+     * @param string $name
+     * @return mixed
+     */
+    protected function getDriver(string $name)
+    {
+        return $this->drivers[$name] ?? $this->createDriver($name);
+    }
+
+    /**
+     * 获取驱动类型
+     * @param string $name
+     * @return mixed
+     */
+    protected function resolveType(string $name)
+    {
+        return $name;
+    }
+
+    /**
+     * 获取驱动配置
+     * @param string $name
+     * @return mixed
+     */
+    protected function resolveConfig(string $name)
+    {
+        return $name;
+    }
+
+    /**
+     * 获取驱动类
+     * @param string $type
+     * @return string
+     */
+    protected function resolveClass(string $type): string
+    {
+        if ($this->namespace || false !== strpos($type, '\\')) {
+            $class = false !== strpos($type, '\\') ? $type : $this->namespace . Str::studly($type);
+
+            if (class_exists($class)) {
+                return $class;
+            }
+        }
+
+        throw new InvalidArgumentException("Driver [$type] not supported.");
+    }
+
+    /**
+     * 获取驱动参数
+     * @param $name
+     * @return array
+     */
+    protected function resolveParams($name): array
+    {
+        $config = $this->resolveConfig($name);
+        return [$config];
+    }
+
+    /**
+     * 创建驱动
+     *
+     * @param string $name
+     * @return mixed
+     *
+     */
+    protected function createDriver(string $name)
+    {
+        $type = $this->resolveType($name);
+
+        $method = 'create' . Str::studly($type) . 'Driver';
+
+        $params = $this->resolveParams($name);
+
+        if (method_exists($this, $method)) {
+            return $this->$method(...$params);
+        }
+
+        $class = $this->resolveClass($type);
+
+        return $this->app->invokeClass($class, $params);
+    }
+
+    /**
+     * 移除一个驱动实例
+     *
+     * @param array|string|null $name
+     * @return $this
+     */
+    public function forgetDriver($name = null)
+    {
+        $name = $name ?? $this->getDefaultDriver();
+
+        foreach ((array) $name as $cacheName) {
+            if (isset($this->drivers[$cacheName])) {
+                unset($this->drivers[$cacheName]);
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * 默认驱动
+     * @return string|null
+     */
+    abstract public function getDefaultDriver();
+
+    /**
+     * 动态调用
+     * @param string $method
+     * @param array  $parameters
+     * @return mixed
+     */
+    public function __call($method, $parameters)
+    {
+        return $this->driver()->$method(...$parameters);
+    }
+}

+ 257 - 0
vendor/topthink/framework/src/think/Middleware.php

@@ -0,0 +1,257 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: Slince <taosikai@yeah.net>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use Closure;
+use InvalidArgumentException;
+use LogicException;
+use think\exception\Handle;
+use Throwable;
+
+/**
+ * 中间件管理类
+ * @package think
+ */
+class Middleware
+{
+    /**
+     * 中间件执行队列
+     * @var array
+     */
+    protected $queue = [];
+
+    /**
+     * 应用对象
+     * @var App
+     */
+    protected $app;
+
+    public function __construct(App $app)
+    {
+        $this->app = $app;
+    }
+
+    /**
+     * 导入中间件
+     * @access public
+     * @param array  $middlewares
+     * @param string $type 中间件类型
+     * @return void
+     */
+    public function import(array $middlewares = [], string $type = 'global'): void
+    {
+        foreach ($middlewares as $middleware) {
+            $this->add($middleware, $type);
+        }
+    }
+
+    /**
+     * 注册中间件
+     * @access public
+     * @param mixed  $middleware
+     * @param string $type 中间件类型
+     * @return void
+     */
+    public function add($middleware, string $type = 'global'): void
+    {
+        $middleware = $this->buildMiddleware($middleware, $type);
+
+        if (!empty($middleware)) {
+            $this->queue[$type][] = $middleware;
+            $this->queue[$type]   = array_unique($this->queue[$type], SORT_REGULAR);
+        }
+    }
+
+    /**
+     * 注册路由中间件
+     * @access public
+     * @param mixed $middleware
+     * @return void
+     */
+    public function route($middleware): void
+    {
+        $this->add($middleware, 'route');
+    }
+
+    /**
+     * 注册控制器中间件
+     * @access public
+     * @param mixed $middleware
+     * @return void
+     */
+    public function controller($middleware): void
+    {
+        $this->add($middleware, 'controller');
+    }
+
+    /**
+     * 注册中间件到开始位置
+     * @access public
+     * @param mixed  $middleware
+     * @param string $type 中间件类型
+     */
+    public function unshift($middleware, string $type = 'global')
+    {
+        $middleware = $this->buildMiddleware($middleware, $type);
+
+        if (!empty($middleware)) {
+            if (!isset($this->queue[$type])) {
+                $this->queue[$type] = [];
+            }
+
+            array_unshift($this->queue[$type], $middleware);
+        }
+    }
+
+    /**
+     * 获取注册的中间件
+     * @access public
+     * @param string $type 中间件类型
+     * @return array
+     */
+    public function all(string $type = 'global'): array
+    {
+        return $this->queue[$type] ?? [];
+    }
+
+    /**
+     * 调度管道
+     * @access public
+     * @param string $type 中间件类型
+     * @return Pipeline
+     */
+    public function pipeline(string $type = 'global')
+    {
+        return (new Pipeline())
+            ->through(array_map(function ($middleware) {
+                return function ($request, $next) use ($middleware) {
+                    [$call, $params] = $middleware;
+                    if (is_array($call) && is_string($call[0])) {
+                        $call = [$this->app->make($call[0]), $call[1]];
+                    }
+                    $response = call_user_func($call, $request, $next, ...$params);
+
+                    if (!$response instanceof Response) {
+                        throw new LogicException('The middleware must return Response instance');
+                    }
+                    return $response;
+                };
+            }, $this->sortMiddleware($this->queue[$type] ?? [])))
+            ->whenException([$this, 'handleException']);
+    }
+
+    /**
+     * 结束调度
+     * @param Response $response
+     */
+    public function end(Response $response)
+    {
+        foreach ($this->queue as $queue) {
+            foreach ($queue as $middleware) {
+                [$call] = $middleware;
+                if (is_array($call) && is_string($call[0])) {
+                    $instance = $this->app->make($call[0]);
+                    if (method_exists($instance, 'end')) {
+                        $instance->end($response);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 异常处理
+     * @param Request   $passable
+     * @param Throwable $e
+     * @return Response
+     */
+    public function handleException($passable, Throwable $e)
+    {
+        /** @var Handle $handler */
+        $handler = $this->app->make(Handle::class);
+
+        $handler->report($e);
+
+        return $handler->render($passable, $e);
+    }
+
+    /**
+     * 解析中间件
+     * @access protected
+     * @param mixed  $middleware
+     * @param string $type 中间件类型
+     * @return array
+     */
+    protected function buildMiddleware($middleware, string $type): array
+    {
+        if (is_array($middleware)) {
+            [$middleware, $params] = $middleware;
+        }
+
+        if ($middleware instanceof Closure) {
+            return [$middleware, $params ?? []];
+        }
+
+        if (!is_string($middleware)) {
+            throw new InvalidArgumentException('The middleware is invalid');
+        }
+
+        //中间件别名检查
+        $alias = $this->app->config->get('middleware.alias', []);
+
+        if (isset($alias[$middleware])) {
+            $middleware = $alias[$middleware];
+        }
+
+        if (is_array($middleware)) {
+            $this->import($middleware, $type);
+            return [];
+        }
+
+        return [[$middleware, 'handle'], $params ?? []];
+    }
+
+    /**
+     * 中间件排序
+     * @param array $middlewares
+     * @return array
+     */
+    protected function sortMiddleware(array $middlewares)
+    {
+        $priority = $this->app->config->get('middleware.priority', []);
+        uasort($middlewares, function ($a, $b) use ($priority) {
+            $aPriority = $this->getMiddlewarePriority($priority, $a);
+            $bPriority = $this->getMiddlewarePriority($priority, $b);
+            return $bPriority - $aPriority;
+        });
+
+        return $middlewares;
+    }
+
+    /**
+     * 获取中间件优先级
+     * @param $priority
+     * @param $middleware
+     * @return int
+     */
+    protected function getMiddlewarePriority($priority, $middleware)
+    {
+        [$call] = $middleware;
+        if (is_array($call) && is_string($call[0])) {
+            $index = array_search($call[0], array_reverse($priority));
+            return false === $index ? -1 : $index;
+        }
+        return -1;
+    }
+
+}

+ 107 - 0
vendor/topthink/framework/src/think/Pipeline.php

@@ -0,0 +1,107 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace think;
+
+use Closure;
+use Exception;
+use Throwable;
+
+class Pipeline
+{
+    protected $passable;
+
+    protected $pipes = [];
+
+    protected $exceptionHandler;
+
+    /**
+     * 初始数据
+     * @param $passable
+     * @return $this
+     */
+    public function send($passable)
+    {
+        $this->passable = $passable;
+        return $this;
+    }
+
+    /**
+     * 调用栈
+     * @param $pipes
+     * @return $this
+     */
+    public function through($pipes)
+    {
+        $this->pipes = is_array($pipes) ? $pipes : func_get_args();
+        return $this;
+    }
+
+    /**
+     * 执行
+     * @param Closure $destination
+     * @return mixed
+     */
+    public function then(Closure $destination)
+    {
+        $pipeline = array_reduce(
+            array_reverse($this->pipes),
+            $this->carry(),
+            function ($passable) use ($destination) {
+                try {
+                    return $destination($passable);
+                } catch (Throwable | Exception $e) {
+                    return $this->handleException($passable, $e);
+                }
+            }
+        );
+
+        return $pipeline($this->passable);
+    }
+
+    /**
+     * 设置异常处理器
+     * @param callable $handler
+     * @return $this
+     */
+    public function whenException($handler)
+    {
+        $this->exceptionHandler = $handler;
+        return $this;
+    }
+
+    protected function carry()
+    {
+        return function ($stack, $pipe) {
+            return function ($passable) use ($stack, $pipe) {
+                try {
+                    return $pipe($passable, $stack);
+                } catch (Throwable | Exception $e) {
+                    return $this->handleException($passable, $e);
+                }
+            };
+        };
+    }
+
+    /**
+     * 异常处理
+     * @param $passable
+     * @param $e
+     * @return mixed
+     */
+    protected function handleException($passable, Throwable $e)
+    {
+        if ($this->exceptionHandler) {
+            return call_user_func($this->exceptionHandler, $passable, $e);
+        }
+        throw $e;
+    }
+
+}

+ 2169 - 0
vendor/topthink/framework/src/think/Request.php

@@ -0,0 +1,2169 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use ArrayAccess;
+use think\file\UploadedFile;
+use think\route\Rule;
+
+/**
+ * 请求管理类
+ * @package think
+ */
+class Request implements ArrayAccess
+{
+    /**
+     * 兼容PATH_INFO获取
+     * @var array
+     */
+    protected $pathinfoFetch = ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'];
+
+    /**
+     * PATHINFO变量名 用于兼容模式
+     * @var string
+     */
+    protected $varPathinfo = 's';
+
+    /**
+     * 请求类型
+     * @var string
+     */
+    protected $varMethod = '_method';
+
+    /**
+     * 表单ajax伪装变量
+     * @var string
+     */
+    protected $varAjax = '_ajax';
+
+    /**
+     * 表单pjax伪装变量
+     * @var string
+     */
+    protected $varPjax = '_pjax';
+
+    /**
+     * 域名根
+     * @var string
+     */
+    protected $rootDomain = '';
+
+    /**
+     * HTTPS代理标识
+     * @var string
+     */
+    protected $httpsAgentName = '';
+
+    /**
+     * 前端代理服务器IP
+     * @var array
+     */
+    protected $proxyServerIp = [];
+
+    /**
+     * 前端代理服务器真实IP头
+     * @var array
+     */
+    protected $proxyServerIpHeader = ['HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP'];
+
+    /**
+     * 请求类型
+     * @var string
+     */
+    protected $method;
+
+    /**
+     * 域名(含协议及端口)
+     * @var string
+     */
+    protected $domain;
+
+    /**
+     * HOST(含端口)
+     * @var string
+     */
+    protected $host;
+
+    /**
+     * 子域名
+     * @var string
+     */
+    protected $subDomain;
+
+    /**
+     * 泛域名
+     * @var string
+     */
+    protected $panDomain;
+
+    /**
+     * 当前URL地址
+     * @var string
+     */
+    protected $url;
+
+    /**
+     * 基础URL
+     * @var string
+     */
+    protected $baseUrl;
+
+    /**
+     * 当前执行的文件
+     * @var string
+     */
+    protected $baseFile;
+
+    /**
+     * 访问的ROOT地址
+     * @var string
+     */
+    protected $root;
+
+    /**
+     * pathinfo
+     * @var string
+     */
+    protected $pathinfo;
+
+    /**
+     * pathinfo(不含后缀)
+     * @var string
+     */
+    protected $path;
+
+    /**
+     * 当前请求的IP地址
+     * @var string
+     */
+    protected $realIP;
+
+    /**
+     * 当前控制器名
+     * @var string
+     */
+    protected $controller;
+
+    /**
+     * 当前操作名
+     * @var string
+     */
+    protected $action;
+
+    /**
+     * 当前请求参数
+     * @var array
+     */
+    protected $param = [];
+
+    /**
+     * 当前GET参数
+     * @var array
+     */
+    protected $get = [];
+
+    /**
+     * 当前POST参数
+     * @var array
+     */
+    protected $post = [];
+
+    /**
+     * 当前REQUEST参数
+     * @var array
+     */
+    protected $request = [];
+
+    /**
+     * 当前路由对象
+     * @var Rule
+     */
+    protected $rule;
+
+    /**
+     * 当前ROUTE参数
+     * @var array
+     */
+    protected $route = [];
+
+    /**
+     * 中间件传递的参数
+     * @var array
+     */
+    protected $middleware = [];
+
+    /**
+     * 当前PUT参数
+     * @var array
+     */
+    protected $put;
+
+    /**
+     * SESSION对象
+     * @var Session
+     */
+    protected $session;
+
+    /**
+     * COOKIE数据
+     * @var array
+     */
+    protected $cookie = [];
+
+    /**
+     * ENV对象
+     * @var Env
+     */
+    protected $env;
+
+    /**
+     * 当前SERVER参数
+     * @var array
+     */
+    protected $server = [];
+
+    /**
+     * 当前FILE参数
+     * @var array
+     */
+    protected $file = [];
+
+    /**
+     * 当前HEADER参数
+     * @var array
+     */
+    protected $header = [];
+
+    /**
+     * 资源类型定义
+     * @var array
+     */
+    protected $mimeType = [
+        'xml'   => 'application/xml,text/xml,application/x-xml',
+        'json'  => 'application/json,text/x-json,application/jsonrequest,text/json',
+        'js'    => 'text/javascript,application/javascript,application/x-javascript',
+        'css'   => 'text/css',
+        'rss'   => 'application/rss+xml',
+        'yaml'  => 'application/x-yaml,text/yaml',
+        'atom'  => 'application/atom+xml',
+        'pdf'   => 'application/pdf',
+        'text'  => 'text/plain',
+        'image' => 'image/png,image/jpg,image/jpeg,image/pjpeg,image/gif,image/webp,image/*',
+        'csv'   => 'text/csv',
+        'html'  => 'text/html,application/xhtml+xml,*/*',
+    ];
+
+    /**
+     * 当前请求内容
+     * @var string
+     */
+    protected $content;
+
+    /**
+     * 全局过滤规则
+     * @var array
+     */
+    protected $filter;
+
+    /**
+     * php://input内容
+     * @var string
+     */
+    // php://input
+    protected $input;
+
+    /**
+     * 请求安全Key
+     * @var string
+     */
+    protected $secureKey;
+
+    /**
+     * 是否合并Param
+     * @var bool
+     */
+    protected $mergeParam = false;
+
+    /**
+     * 架构函数
+     * @access public
+     */
+    public function __construct()
+    {
+        // 保存 php://input
+        $this->input = file_get_contents('php://input');
+    }
+
+    public static function __make(App $app)
+    {
+        $request = new static();
+
+        if (function_exists('apache_request_headers') && $result = apache_request_headers()) {
+            $header = $result;
+        } else {
+            $header = [];
+            $server = $_SERVER;
+            foreach ($server as $key => $val) {
+                if (0 === strpos($key, 'HTTP_')) {
+                    $key          = str_replace('_', '-', strtolower(substr($key, 5)));
+                    $header[$key] = $val;
+                }
+            }
+            if (isset($server['CONTENT_TYPE'])) {
+                $header['content-type'] = $server['CONTENT_TYPE'];
+            }
+            if (isset($server['CONTENT_LENGTH'])) {
+                $header['content-length'] = $server['CONTENT_LENGTH'];
+            }
+        }
+
+        $request->header = array_change_key_case($header);
+        $request->server = $_SERVER;
+        $request->env    = $app->env;
+
+        $inputData = $request->getInputData($request->input);
+
+        $request->get     = $_GET;
+        $request->post    = $_POST ?: $inputData;
+        $request->put     = $inputData;
+        $request->request = $_REQUEST;
+        $request->cookie  = $_COOKIE;
+        $request->file    = $_FILES ?? [];
+
+        return $request;
+    }
+
+    /**
+     * 设置当前包含协议的域名
+     * @access public
+     * @param  string $domain 域名
+     * @return $this
+     */
+    public function setDomain(string $domain)
+    {
+        $this->domain = $domain;
+        return $this;
+    }
+
+    /**
+     * 获取当前包含协议的域名
+     * @access public
+     * @param  bool $port 是否需要去除端口号
+     * @return string
+     */
+    public function domain(bool $port = false): string
+    {
+        return $this->scheme() . '://' . $this->host($port);
+    }
+
+    /**
+     * 获取当前根域名
+     * @access public
+     * @return string
+     */
+    public function rootDomain(): string
+    {
+        $root = $this->rootDomain;
+
+        if (!$root) {
+            $item  = explode('.', $this->host());
+            $count = count($item);
+            $root  = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0];
+        }
+
+        return $root;
+    }
+
+    /**
+     * 设置当前泛域名的值
+     * @access public
+     * @param  string $domain 域名
+     * @return $this
+     */
+    public function setSubDomain(string $domain)
+    {
+        $this->subDomain = $domain;
+        return $this;
+    }
+
+    /**
+     * 获取当前子域名
+     * @access public
+     * @return string
+     */
+    public function subDomain(): string
+    {
+        if (is_null($this->subDomain)) {
+            // 获取当前主域名
+            $rootDomain = $this->rootDomain();
+
+            if ($rootDomain) {
+                $sub             = stristr($this->host(), $rootDomain, true);
+                $this->subDomain = $sub ? rtrim($sub, '.') : '';
+            } else {
+                $this->subDomain = '';
+            }
+        }
+
+        return $this->subDomain;
+    }
+
+    /**
+     * 设置当前泛域名的值
+     * @access public
+     * @param  string $domain 域名
+     * @return $this
+     */
+    public function setPanDomain(string $domain)
+    {
+        $this->panDomain = $domain;
+        return $this;
+    }
+
+    /**
+     * 获取当前泛域名的值
+     * @access public
+     * @return string
+     */
+    public function panDomain(): string
+    {
+        return $this->panDomain ?: '';
+    }
+
+    /**
+     * 设置当前完整URL 包括QUERY_STRING
+     * @access public
+     * @param  string $url URL地址
+     * @return $this
+     */
+    public function setUrl(string $url)
+    {
+        $this->url = $url;
+        return $this;
+    }
+
+    /**
+     * 获取当前完整URL 包括QUERY_STRING
+     * @access public
+     * @param  bool $complete 是否包含完整域名
+     * @return string
+     */
+    public function url(bool $complete = false): string
+    {
+        if ($this->url) {
+            $url = $this->url;
+        } elseif ($this->server('HTTP_X_REWRITE_URL')) {
+            $url = $this->server('HTTP_X_REWRITE_URL');
+        } elseif ($this->server('REQUEST_URI')) {
+            $url = $this->server('REQUEST_URI');
+        } elseif ($this->server('ORIG_PATH_INFO')) {
+            $url = $this->server('ORIG_PATH_INFO') . (!empty($this->server('QUERY_STRING')) ? '?' . $this->server('QUERY_STRING') : '');
+        } elseif (isset($_SERVER['argv'][1])) {
+            $url = $_SERVER['argv'][1];
+        } else {
+            $url = '';
+        }
+
+        return $complete ? $this->domain() . $url : $url;
+    }
+
+    /**
+     * 设置当前URL 不含QUERY_STRING
+     * @access public
+     * @param  string $url URL地址
+     * @return $this
+     */
+    public function setBaseUrl(string $url)
+    {
+        $this->baseUrl = $url;
+        return $this;
+    }
+
+    /**
+     * 获取当前URL 不含QUERY_STRING
+     * @access public
+     * @param  bool $complete 是否包含完整域名
+     * @return string
+     */
+    public function baseUrl(bool $complete = false): string
+    {
+        if (!$this->baseUrl) {
+            $str           = $this->url();
+            $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str;
+        }
+
+        return $complete ? $this->domain() . $this->baseUrl : $this->baseUrl;
+    }
+
+    /**
+     * 获取当前执行的文件 SCRIPT_NAME
+     * @access public
+     * @param  bool $complete 是否包含完整域名
+     * @return string
+     */
+    public function baseFile(bool $complete = false): string
+    {
+        if (!$this->baseFile) {
+            $url = '';
+            if (!$this->isCli()) {
+                $script_name = basename($this->server('SCRIPT_FILENAME'));
+                if (basename($this->server('SCRIPT_NAME')) === $script_name) {
+                    $url = $this->server('SCRIPT_NAME');
+                } elseif (basename($this->server('PHP_SELF')) === $script_name) {
+                    $url = $this->server('PHP_SELF');
+                } elseif (basename($this->server('ORIG_SCRIPT_NAME')) === $script_name) {
+                    $url = $this->server('ORIG_SCRIPT_NAME');
+                } elseif (($pos = strpos($this->server('PHP_SELF'), '/' . $script_name)) !== false) {
+                    $url = substr($this->server('SCRIPT_NAME'), 0, $pos) . '/' . $script_name;
+                } elseif ($this->server('DOCUMENT_ROOT') && strpos($this->server('SCRIPT_FILENAME'), $this->server('DOCUMENT_ROOT')) === 0) {
+                    $url = str_replace('\\', '/', str_replace($this->server('DOCUMENT_ROOT'), '', $this->server('SCRIPT_FILENAME')));
+                }
+            }
+            $this->baseFile = $url;
+        }
+
+        return $complete ? $this->domain() . $this->baseFile : $this->baseFile;
+    }
+
+    /**
+     * 设置URL访问根地址
+     * @access public
+     * @param  string $url URL地址
+     * @return $this
+     */
+    public function setRoot(string $url)
+    {
+        $this->root = $url;
+        return $this;
+    }
+
+    /**
+     * 获取URL访问根地址
+     * @access public
+     * @param  bool $complete 是否包含完整域名
+     * @return string
+     */
+    public function root(bool $complete = false): string
+    {
+        if (!$this->root) {
+            $file = $this->baseFile();
+            if ($file && 0 !== strpos($this->url(), $file)) {
+                $file = str_replace('\\', '/', dirname($file));
+            }
+            $this->root = rtrim($file, '/');
+        }
+
+        return $complete ? $this->domain() . $this->root : $this->root;
+    }
+
+    /**
+     * 获取URL访问根目录
+     * @access public
+     * @return string
+     */
+    public function rootUrl(): string
+    {
+        $base = $this->root();
+        $root = strpos($base, '.') ? ltrim(dirname($base), DIRECTORY_SEPARATOR) : $base;
+
+        if ('' != $root) {
+            $root = '/' . ltrim($root, '/');
+        }
+
+        return $root;
+    }
+
+    /**
+     * 设置当前请求的pathinfo
+     * @access public
+     * @param  string $pathinfo
+     * @return $this
+     */
+    public function setPathinfo(string $pathinfo)
+    {
+        $this->pathinfo = $pathinfo;
+        return $this;
+    }
+
+    /**
+     * 获取当前请求URL的pathinfo信息(含URL后缀)
+     * @access public
+     * @return string
+     */
+    public function pathinfo(): string
+    {
+        if (is_null($this->pathinfo)) {
+            if (isset($_GET[$this->varPathinfo])) {
+                // 判断URL里面是否有兼容模式参数
+                $pathinfo = $_GET[$this->varPathinfo];
+                unset($_GET[$this->varPathinfo]);
+                unset($this->get[$this->varPathinfo]);
+            } elseif ($this->server('PATH_INFO')) {
+                $pathinfo = $this->server('PATH_INFO');
+            } elseif (false !== strpos(PHP_SAPI, 'cli')) {
+                $pathinfo = strpos($this->server('REQUEST_URI'), '?') ? strstr($this->server('REQUEST_URI'), '?', true) : $this->server('REQUEST_URI');
+            }
+
+            // 分析PATHINFO信息
+            if (!isset($pathinfo)) {
+                foreach ($this->pathinfoFetch as $type) {
+                    if ($this->server($type)) {
+                        $pathinfo = (0 === strpos($this->server($type), $this->server('SCRIPT_NAME'))) ?
+                        substr($this->server($type), strlen($this->server('SCRIPT_NAME'))) : $this->server($type);
+                        break;
+                    }
+                }
+            }
+
+            if (!empty($pathinfo)) {
+                unset($this->get[$pathinfo], $this->request[$pathinfo]);
+            }
+
+            $this->pathinfo = empty($pathinfo) || '/' == $pathinfo ? '' : ltrim($pathinfo, '/');
+        }
+
+        return $this->pathinfo;
+    }
+
+    /**
+     * 当前URL的访问后缀
+     * @access public
+     * @return string
+     */
+    public function ext(): string
+    {
+        return pathinfo($this->pathinfo(), PATHINFO_EXTENSION);
+    }
+
+    /**
+     * 获取当前请求的时间
+     * @access public
+     * @param  bool $float 是否使用浮点类型
+     * @return integer|float
+     */
+    public function time(bool $float = false)
+    {
+        return $float ? $this->server('REQUEST_TIME_FLOAT') : $this->server('REQUEST_TIME');
+    }
+
+    /**
+     * 当前请求的资源类型
+     * @access public
+     * @return string
+     */
+    public function type(): string
+    {
+        $accept = $this->server('HTTP_ACCEPT');
+
+        if (empty($accept)) {
+            return '';
+        }
+
+        foreach ($this->mimeType as $key => $val) {
+            $array = explode(',', $val);
+            foreach ($array as $k => $v) {
+                if (stristr($accept, $v)) {
+                    return $key;
+                }
+            }
+        }
+
+        return '';
+    }
+
+    /**
+     * 设置资源类型
+     * @access public
+     * @param  string|array $type 资源类型名
+     * @param  string       $val 资源类型
+     * @return void
+     */
+    public function mimeType($type, $val = ''): void
+    {
+        if (is_array($type)) {
+            $this->mimeType = array_merge($this->mimeType, $type);
+        } else {
+            $this->mimeType[$type] = $val;
+        }
+    }
+
+    /**
+     * 设置请求类型
+     * @access public
+     * @param  string $method 请求类型
+     * @return $this
+     */
+    public function setMethod(string $method)
+    {
+        $this->method = strtoupper($method);
+        return $this;
+    }
+
+    /**
+     * 当前的请求类型
+     * @access public
+     * @param  bool $origin 是否获取原始请求类型
+     * @return string
+     */
+    public function method(bool $origin = false): string
+    {
+        if ($origin) {
+            // 获取原始请求类型
+            return $this->server('REQUEST_METHOD') ?: 'GET';
+        } elseif (!$this->method) {
+            if (isset($this->post[$this->varMethod])) {
+                $method = strtolower($this->post[$this->varMethod]);
+                if (in_array($method, ['get', 'post', 'put', 'patch', 'delete'])) {
+                    $this->method    = strtoupper($method);
+                    $this->{$method} = $this->post;
+                } else {
+                    $this->method = 'POST';
+                }
+                unset($this->post[$this->varMethod]);
+            } elseif ($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')) {
+                $this->method = strtoupper($this->server('HTTP_X_HTTP_METHOD_OVERRIDE'));
+            } else {
+                $this->method = $this->server('REQUEST_METHOD') ?: 'GET';
+            }
+        }
+
+        return $this->method;
+    }
+
+    /**
+     * 是否为GET请求
+     * @access public
+     * @return bool
+     */
+    public function isGet(): bool
+    {
+        return $this->method() == 'GET';
+    }
+
+    /**
+     * 是否为POST请求
+     * @access public
+     * @return bool
+     */
+    public function isPost(): bool
+    {
+        return $this->method() == 'POST';
+    }
+
+    /**
+     * 是否为PUT请求
+     * @access public
+     * @return bool
+     */
+    public function isPut(): bool
+    {
+        return $this->method() == 'PUT';
+    }
+
+    /**
+     * 是否为DELTE请求
+     * @access public
+     * @return bool
+     */
+    public function isDelete(): bool
+    {
+        return $this->method() == 'DELETE';
+    }
+
+    /**
+     * 是否为HEAD请求
+     * @access public
+     * @return bool
+     */
+    public function isHead(): bool
+    {
+        return $this->method() == 'HEAD';
+    }
+
+    /**
+     * 是否为PATCH请求
+     * @access public
+     * @return bool
+     */
+    public function isPatch(): bool
+    {
+        return $this->method() == 'PATCH';
+    }
+
+    /**
+     * 是否为OPTIONS请求
+     * @access public
+     * @return bool
+     */
+    public function isOptions(): bool
+    {
+        return $this->method() == 'OPTIONS';
+    }
+
+    /**
+     * 是否为cli
+     * @access public
+     * @return bool
+     */
+    public function isCli(): bool
+    {
+        return PHP_SAPI == 'cli';
+    }
+
+    /**
+     * 是否为cgi
+     * @access public
+     * @return bool
+     */
+    public function isCgi(): bool
+    {
+        return strpos(PHP_SAPI, 'cgi') === 0;
+    }
+
+    /**
+     * 获取当前请求的参数
+     * @access public
+     * @param  string|array $name 变量名
+     * @param  mixed        $default 默认值
+     * @param  string|array $filter 过滤方法
+     * @return mixed
+     */
+    public function param($name = '', $default = null, $filter = '')
+    {
+        if (empty($this->mergeParam)) {
+            $method = $this->method(true);
+
+            // 自动获取请求变量
+            switch ($method) {
+                case 'POST':
+                    $vars = $this->post(false);
+                    break;
+                case 'PUT':
+                case 'DELETE':
+                case 'PATCH':
+                    $vars = $this->put(false);
+                    break;
+                default:
+                    $vars = [];
+            }
+
+            // 当前请求参数和URL地址中的参数合并
+            $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));
+
+            $this->mergeParam = true;
+        }
+
+        if (is_array($name)) {
+            return $this->only($name, $this->param, $filter);
+        }
+
+        return $this->input($this->param, $name, $default, $filter);
+    }
+
+    /**
+     * 获取包含文件在内的请求参数
+     * @access public
+     * @param  string|array $name 变量名
+     * @param  string|array $filter 过滤方法
+     * @return mixed
+     */
+    public function all($name = '', $filter = '')
+    {
+        $data = array_merge($this->param(), $this->file() ?: []);
+
+        if (is_array($name)) {
+            $data = $this->only($name, $data, $filter);
+        } elseif ($name) {
+            $data = $data[$name] ?? null;
+        }
+
+        return $data;
+    }
+
+    /**
+     * 设置路由变量
+     * @access public
+     * @param  Rule $rule 路由对象
+     * @return $this
+     */
+    public function setRule(Rule $rule)
+    {
+        $this->rule = $rule;
+        return $this;
+    }
+
+    /**
+     * 获取当前路由对象
+     * @access public
+     * @return Rule|null
+     */
+    public function rule()
+    {
+        return $this->rule;
+    }
+
+    /**
+     * 设置路由变量
+     * @access public
+     * @param  array $route 路由变量
+     * @return $this
+     */
+    public function setRoute(array $route)
+    {
+        $this->route      = array_merge($this->route, $route);
+        $this->mergeParam = false;
+        return $this;
+    }
+
+    /**
+     * 获取路由参数
+     * @access public
+     * @param  string|array $name 变量名
+     * @param  mixed        $default 默认值
+     * @param  string|array $filter 过滤方法
+     * @return mixed
+     */
+    public function route($name = '', $default = null, $filter = '')
+    {
+        if (is_array($name)) {
+            return $this->only($name, $this->route, $filter);
+        }
+
+        return $this->input($this->route, $name, $default, $filter);
+    }
+
+    /**
+     * 获取GET参数
+     * @access public
+     * @param  string|array $name 变量名
+     * @param  mixed        $default 默认值
+     * @param  string|array $filter 过滤方法
+     * @return mixed
+     */
+    public function get($name = '', $default = null, $filter = '')
+    {
+        if (is_array($name)) {
+            return $this->only($name, $this->get, $filter);
+        }
+
+        return $this->input($this->get, $name, $default, $filter);
+    }
+
+    /**
+     * 获取中间件传递的参数
+     * @access public
+     * @param  mixed $name 变量名
+     * @param  mixed $default 默认值
+     * @return mixed
+     */
+    public function middleware($name, $default = null)
+    {
+        return $this->middleware[$name] ?? $default;
+    }
+
+    /**
+     * 获取POST参数
+     * @access public
+     * @param  string|array $name 变量名
+     * @param  mixed        $default 默认值
+     * @param  string|array $filter 过滤方法
+     * @return mixed
+     */
+    public function post($name = '', $default = null, $filter = '')
+    {
+        if (is_array($name)) {
+            return $this->only($name, $this->post, $filter);
+        }
+
+        return $this->input($this->post, $name, $default, $filter);
+    }
+
+    /**
+     * 获取PUT参数
+     * @access public
+     * @param  string|array $name 变量名
+     * @param  mixed        $default 默认值
+     * @param  string|array $filter 过滤方法
+     * @return mixed
+     */
+    public function put($name = '', $default = null, $filter = '')
+    {
+        if (is_array($name)) {
+            return $this->only($name, $this->put, $filter);
+        }
+
+        return $this->input($this->put, $name, $default, $filter);
+    }
+
+    protected function getInputData($content): array
+    {
+        $contentType = $this->contentType();
+        if ('application/x-www-form-urlencoded' == $contentType) {
+            parse_str($content, $data);
+            return $data;
+        } elseif (false !== strpos($contentType, 'json')) {
+            return (array) json_decode($content, true);
+        }
+
+        return [];
+    }
+
+    /**
+     * 设置获取DELETE参数
+     * @access public
+     * @param  mixed        $name 变量名
+     * @param  mixed        $default 默认值
+     * @param  string|array $filter 过滤方法
+     * @return mixed
+     */
+    public function delete($name = '', $default = null, $filter = '')
+    {
+        return $this->put($name, $default, $filter);
+    }
+
+    /**
+     * 设置获取PATCH参数
+     * @access public
+     * @param  mixed        $name 变量名
+     * @param  mixed        $default 默认值
+     * @param  string|array $filter 过滤方法
+     * @return mixed
+     */
+    public function patch($name = '', $default = null, $filter = '')
+    {
+        return $this->put($name, $default, $filter);
+    }
+
+    /**
+     * 获取request变量
+     * @access public
+     * @param  string|array $name 数据名称
+     * @param  mixed        $default 默认值
+     * @param  string|array $filter 过滤方法
+     * @return mixed
+     */
+    public function request($name = '', $default = null, $filter = '')
+    {
+        if (is_array($name)) {
+            return $this->only($name, $this->request, $filter);
+        }
+
+        return $this->input($this->request, $name, $default, $filter);
+    }
+
+    /**
+     * 获取环境变量
+     * @access public
+     * @param  string $name 数据名称
+     * @param  string $default 默认值
+     * @return mixed
+     */
+    public function env(string $name = '', string $default = null)
+    {
+        if (empty($name)) {
+            return $this->env->get();
+        } else {
+            $name = strtoupper($name);
+        }
+
+        return $this->env->get($name, $default);
+    }
+
+    /**
+     * 获取session数据
+     * @access public
+     * @param  string $name 数据名称
+     * @param  string $default 默认值
+     * @return mixed
+     */
+    public function session(string $name = '', $default = null)
+    {
+        if ('' === $name) {
+            return $this->session->all();
+        }
+        return $this->session->get($name, $default);
+    }
+
+    /**
+     * 获取cookie参数
+     * @access public
+     * @param  mixed        $name 数据名称
+     * @param  string       $default 默认值
+     * @param  string|array $filter 过滤方法
+     * @return mixed
+     */
+    public function cookie(string $name = '', $default = null, $filter = '')
+    {
+        if (!empty($name)) {
+            $data = $this->getData($this->cookie, $name, $default);
+        } else {
+            $data = $this->cookie;
+        }
+
+        // 解析过滤器
+        $filter = $this->getFilter($filter, $default);
+
+        if (is_array($data)) {
+            array_walk_recursive($data, [$this, 'filterValue'], $filter);
+        } else {
+            $this->filterValue($data, $name, $filter);
+        }
+
+        return $data;
+    }
+
+    /**
+     * 获取server参数
+     * @access public
+     * @param  string $name 数据名称
+     * @param  string $default 默认值
+     * @return mixed
+     */
+    public function server(string $name = '', string $default = '')
+    {
+        if (empty($name)) {
+            return $this->server;
+        } else {
+            $name = strtoupper($name);
+        }
+
+        return $this->server[$name] ?? $default;
+    }
+
+    /**
+     * 获取上传的文件信息
+     * @access public
+     * @param  string $name 名称
+     * @return null|array|UploadedFile
+     */
+    public function file(string $name = '')
+    {
+        $files = $this->file;
+        if (!empty($files)) {
+            if (strpos($name, '.')) {
+                [$name, $sub] = explode('.', $name);
+            }
+
+            // 处理上传文件
+            $array = $this->dealUploadFile($files, $name);
+
+            if ('' === $name) {
+                // 获取全部文件
+                return $array;
+            } elseif (isset($sub) && isset($array[$name][$sub])) {
+                return $array[$name][$sub];
+            } elseif (isset($array[$name])) {
+                return $array[$name];
+            }
+        }
+    }
+
+    protected function dealUploadFile(array $files, string $name): array
+    {
+        $array = [];
+        foreach ($files as $key => $file) {
+            if (is_array($file['name'])) {
+                $item  = [];
+                $keys  = array_keys($file);
+                $count = count($file['name']);
+
+                for ($i = 0; $i < $count; $i++) {
+                    if ($file['error'][$i] > 0) {
+                        if ($name == $key) {
+                            $this->throwUploadFileError($file['error'][$i]);
+                        } else {
+                            continue;
+                        }
+                    }
+
+                    $temp['key'] = $key;
+
+                    foreach ($keys as $_key) {
+                        $temp[$_key] = $file[$_key][$i];
+                    }
+
+                    $item[] = new UploadedFile($temp['tmp_name'], $temp['name'], $temp['type'], $temp['error']);
+                }
+
+                $array[$key] = $item;
+            } else {
+                if ($file instanceof File) {
+                    $array[$key] = $file;
+                } else {
+                    if ($file['error'] > 0) {
+                        if ($key == $name) {
+                            $this->throwUploadFileError($file['error']);
+                        } else {
+                            continue;
+                        }
+                    }
+
+                    $array[$key] = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['error']);
+                }
+            }
+        }
+
+        return $array;
+    }
+
+    protected function throwUploadFileError($error)
+    {
+        static $fileUploadErrors = [
+            1 => 'upload File size exceeds the maximum value',
+            2 => 'upload File size exceeds the maximum value',
+            3 => 'only the portion of file is uploaded',
+            4 => 'no file to uploaded',
+            6 => 'upload temp dir not found',
+            7 => 'file write error',
+        ];
+
+        $msg = $fileUploadErrors[$error];
+        throw new Exception($msg, $error);
+    }
+
+    /**
+     * 设置或者获取当前的Header
+     * @access public
+     * @param  string $name header名称
+     * @param  string $default 默认值
+     * @return string|array
+     */
+    public function header(string $name = '', string $default = null)
+    {
+        if ('' === $name) {
+            return $this->header;
+        }
+
+        $name = str_replace('_', '-', strtolower($name));
+
+        return $this->header[$name] ?? $default;
+    }
+
+    /**
+     * 获取变量 支持过滤和默认值
+     * @access public
+     * @param  array        $data 数据源
+     * @param  string|false $name 字段名
+     * @param  mixed        $default 默认值
+     * @param  string|array $filter 过滤函数
+     * @return mixed
+     */
+    public function input(array $data = [], $name = '', $default = null, $filter = '')
+    {
+        if (false === $name) {
+            // 获取原始数据
+            return $data;
+        }
+
+        $name = (string) $name;
+        if ('' != $name) {
+            // 解析name
+            if (strpos($name, '/')) {
+                [$name, $type] = explode('/', $name);
+            }
+
+            $data = $this->getData($data, $name);
+
+            if (is_null($data)) {
+                return $default;
+            }
+
+            if (is_object($data)) {
+                return $data;
+            }
+        }
+
+        $data = $this->filterData($data, $filter, $name, $default);
+
+        if (isset($type) && $data !== $default) {
+            // 强制类型转换
+            $this->typeCast($data, $type);
+        }
+
+        return $data;
+    }
+
+    protected function filterData($data, $filter, $name, $default)
+    {
+        // 解析过滤器
+        $filter = $this->getFilter($filter, $default);
+
+        if (is_array($data)) {
+            array_walk_recursive($data, [$this, 'filterValue'], $filter);
+        } else {
+            $this->filterValue($data, $name, $filter);
+        }
+
+        return $data;
+    }
+
+    /**
+     * 强制类型转换
+     * @access protected
+     * @param  mixed  $data
+     * @param  string $type
+     * @return mixed
+     */
+    protected function typeCast(&$data, string $type)
+    {
+        switch (strtolower($type)) {
+            // 数组
+            case 'a':
+                $data = (array) $data;
+                break;
+            // 数字
+            case 'd':
+                $data = (int) $data;
+                break;
+            // 浮点
+            case 'f':
+                $data = (float) $data;
+                break;
+            // 布尔
+            case 'b':
+                $data = (boolean) $data;
+                break;
+            // 字符串
+            case 's':
+                if (is_scalar($data)) {
+                    $data = (string) $data;
+                } else {
+                    throw new \InvalidArgumentException('variable type error:' . gettype($data));
+                }
+                break;
+        }
+    }
+
+    /**
+     * 获取数据
+     * @access protected
+     * @param  array  $data 数据源
+     * @param  string $name 字段名
+     * @param  mixed  $default 默认值
+     * @return mixed
+     */
+    protected function getData(array $data, string $name, $default = null)
+    {
+        foreach (explode('.', $name) as $val) {
+            if (isset($data[$val])) {
+                $data = $data[$val];
+            } else {
+                return $default;
+            }
+        }
+
+        return $data;
+    }
+
+    /**
+     * 设置或获取当前的过滤规则
+     * @access public
+     * @param  mixed $filter 过滤规则
+     * @return mixed
+     */
+    public function filter($filter = null)
+    {
+        if (is_null($filter)) {
+            return $this->filter;
+        }
+
+        $this->filter = $filter;
+
+        return $this;
+    }
+
+    protected function getFilter($filter, $default): array
+    {
+        if (is_null($filter)) {
+            $filter = [];
+        } else {
+            $filter = $filter ?: $this->filter;
+            if (is_string($filter) && false === strpos($filter, '/')) {
+                $filter = explode(',', $filter);
+            } else {
+                $filter = (array) $filter;
+            }
+        }
+
+        $filter[] = $default;
+
+        return $filter;
+    }
+
+    /**
+     * 递归过滤给定的值
+     * @access public
+     * @param  mixed $value 键值
+     * @param  mixed $key 键名
+     * @param  array $filters 过滤方法+默认值
+     * @return mixed
+     */
+    public function filterValue(&$value, $key, $filters)
+    {
+        $default = array_pop($filters);
+
+        foreach ($filters as $filter) {
+            if (is_callable($filter)) {
+                // 调用函数或者方法过滤
+                $value = call_user_func($filter, $value);
+            } elseif (is_scalar($value)) {
+                if (is_string($filter) && false !== strpos($filter, '/')) {
+                    // 正则过滤
+                    if (!preg_match($filter, $value)) {
+                        // 匹配不成功返回默认值
+                        $value = $default;
+                        break;
+                    }
+                } elseif (!empty($filter)) {
+                    // filter函数不存在时, 则使用filter_var进行过滤
+                    // filter为非整形值时, 调用filter_id取得过滤id
+                    $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter));
+                    if (false === $value) {
+                        $value = $default;
+                        break;
+                    }
+                }
+            }
+        }
+
+        return $value;
+    }
+
+    /**
+     * 是否存在某个请求参数
+     * @access public
+     * @param  string $name 变量名
+     * @param  string $type 变量类型
+     * @param  bool   $checkEmpty 是否检测空值
+     * @return bool
+     */
+    public function has(string $name, string $type = 'param', bool $checkEmpty = false): bool
+    {
+        if (!in_array($type, ['param', 'get', 'post', 'put', 'patch', 'route', 'delete', 'cookie', 'session', 'env', 'request', 'server', 'header', 'file'])) {
+            return false;
+        }
+
+        $param = empty($this->$type) ? $this->$type() : $this->$type;
+
+        if (is_object($param)) {
+            return $param->has($name);
+        }
+
+        // 按.拆分成多维数组进行判断
+        foreach (explode('.', $name) as $val) {
+            if (isset($param[$val])) {
+                $param = $param[$val];
+            } else {
+                return false;
+            }
+        }
+
+        return ($checkEmpty && '' === $param) ? false : true;
+    }
+
+    /**
+     * 获取指定的参数
+     * @access public
+     * @param  array        $name 变量名
+     * @param  mixed        $data 数据或者变量类型
+     * @param  string|array $filter 过滤方法
+     * @return array
+     */
+    public function only(array $name, $data = 'param', $filter = ''): array
+    {
+        $data = is_array($data) ? $data : $this->$data();
+
+        $item = [];
+        foreach ($name as $key => $val) {
+
+            if (is_int($key)) {
+                $default = null;
+                $key     = $val;
+                if (!isset($data[$key])) {
+                    continue;
+                }
+            } else {
+                $default = $val;
+            }
+
+            $item[$key] = $this->filterData($data[$key] ?? $default, $filter, $key, $default);
+        }
+
+        return $item;
+    }
+
+    /**
+     * 排除指定参数获取
+     * @access public
+     * @param  array  $name 变量名
+     * @param  string $type 变量类型
+     * @return mixed
+     */
+    public function except(array $name, string $type = 'param'): array
+    {
+        $param = $this->$type();
+
+        foreach ($name as $key) {
+            if (isset($param[$key])) {
+                unset($param[$key]);
+            }
+        }
+
+        return $param;
+    }
+
+    /**
+     * 当前是否ssl
+     * @access public
+     * @return bool
+     */
+    public function isSsl(): bool
+    {
+        if ($this->server('HTTPS') && ('1' == $this->server('HTTPS') || 'on' == strtolower($this->server('HTTPS')))) {
+            return true;
+        } elseif ('https' == $this->server('REQUEST_SCHEME')) {
+            return true;
+        } elseif ('443' == $this->server('SERVER_PORT')) {
+            return true;
+        } elseif ('https' == $this->server('HTTP_X_FORWARDED_PROTO')) {
+            return true;
+        } elseif ($this->httpsAgentName && $this->server($this->httpsAgentName)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * 当前是否JSON请求
+     * @access public
+     * @return bool
+     */
+    public function isJson(): bool
+    {
+        $acceptType = $this->type();
+
+        return false !== strpos($acceptType, 'json');
+    }
+
+    /**
+     * 当前是否Ajax请求
+     * @access public
+     * @param  bool $ajax true 获取原始ajax请求
+     * @return bool
+     */
+    public function isAjax(bool $ajax = false): bool
+    {
+        $value  = $this->server('HTTP_X_REQUESTED_WITH');
+        $result = $value && 'xmlhttprequest' == strtolower($value) ? true : false;
+
+        if (true === $ajax) {
+            return $result;
+        }
+
+        return $this->param($this->varAjax) ? true : $result;
+    }
+
+    /**
+     * 当前是否Pjax请求
+     * @access public
+     * @param  bool $pjax true 获取原始pjax请求
+     * @return bool
+     */
+    public function isPjax(bool $pjax = false): bool
+    {
+        $result = !empty($this->server('HTTP_X_PJAX')) ? true : false;
+
+        if (true === $pjax) {
+            return $result;
+        }
+
+        return $this->param($this->varPjax) ? true : $result;
+    }
+
+    /**
+     * 获取客户端IP地址
+     * @access public
+     * @return string
+     */
+    public function ip(): string
+    {
+        if (!empty($this->realIP)) {
+            return $this->realIP;
+        }
+
+        $this->realIP = $this->server('REMOTE_ADDR', '');
+
+        // 如果指定了前端代理服务器IP以及其会发送的IP头
+        // 则尝试获取前端代理服务器发送过来的真实IP
+        $proxyIp       = $this->proxyServerIp;
+        $proxyIpHeader = $this->proxyServerIpHeader;
+
+        if (count($proxyIp) > 0 && count($proxyIpHeader) > 0) {
+            // 从指定的HTTP头中依次尝试获取IP地址
+            // 直到获取到一个合法的IP地址
+            foreach ($proxyIpHeader as $header) {
+                $tempIP = $this->server($header);
+
+                if (empty($tempIP)) {
+                    continue;
+                }
+
+                $tempIP = trim(explode(',', $tempIP)[0]);
+
+                if (!$this->isValidIP($tempIP)) {
+                    $tempIP = null;
+                } else {
+                    break;
+                }
+            }
+
+            // tempIP不为空,说明获取到了一个IP地址
+            // 这时我们检查 REMOTE_ADDR 是不是指定的前端代理服务器之一
+            // 如果是的话说明该 IP头 是由前端代理服务器设置的
+            // 否则则是伪装的
+            if (!empty($tempIP)) {
+                $realIPBin = $this->ip2bin($this->realIP);
+
+                foreach ($proxyIp as $ip) {
+                    $serverIPElements = explode('/', $ip);
+                    $serverIP         = $serverIPElements[0];
+                    $serverIPPrefix   = $serverIPElements[1] ?? 128;
+                    $serverIPBin      = $this->ip2bin($serverIP);
+
+                    // IP类型不符
+                    if (strlen($realIPBin) !== strlen($serverIPBin)) {
+                        continue;
+                    }
+
+                    if (strncmp($realIPBin, $serverIPBin, (int) $serverIPPrefix) === 0) {
+                        $this->realIP = $tempIP;
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (!$this->isValidIP($this->realIP)) {
+            $this->realIP = '0.0.0.0';
+        }
+
+        return $this->realIP;
+    }
+
+    /**
+     * 检测是否是合法的IP地址
+     *
+     * @param string $ip   IP地址
+     * @param string $type IP地址类型 (ipv4, ipv6)
+     *
+     * @return boolean
+     */
+    public function isValidIP(string $ip, string $type = ''): bool
+    {
+        switch (strtolower($type)) {
+            case 'ipv4':
+                $flag = FILTER_FLAG_IPV4;
+                break;
+            case 'ipv6':
+                $flag = FILTER_FLAG_IPV6;
+                break;
+            default:
+                $flag = 0;
+                break;
+        }
+
+        return boolval(filter_var($ip, FILTER_VALIDATE_IP, $flag));
+    }
+
+    /**
+     * 将IP地址转换为二进制字符串
+     *
+     * @param string $ip
+     *
+     * @return string
+     */
+    public function ip2bin(string $ip): string
+    {
+        if ($this->isValidIP($ip, 'ipv6')) {
+            $IPHex = str_split(bin2hex(inet_pton($ip)), 4);
+            foreach ($IPHex as $key => $value) {
+                $IPHex[$key] = intval($value, 16);
+            }
+            $IPBin = vsprintf('%016b%016b%016b%016b%016b%016b%016b%016b', $IPHex);
+        } else {
+            $IPHex = str_split(bin2hex(inet_pton($ip)), 2);
+            foreach ($IPHex as $key => $value) {
+                $IPHex[$key] = intval($value, 16);
+            }
+            $IPBin = vsprintf('%08b%08b%08b%08b', $IPHex);
+        }
+
+        return $IPBin;
+    }
+
+    /**
+     * 检测是否使用手机访问
+     * @access public
+     * @return bool
+     */
+    public function isMobile(): bool
+    {
+        if ($this->server('HTTP_VIA') && stristr($this->server('HTTP_VIA'), "wap")) {
+            return true;
+        } elseif ($this->server('HTTP_ACCEPT') && strpos(strtoupper($this->server('HTTP_ACCEPT')), "VND.WAP.WML")) {
+            return true;
+        } elseif ($this->server('HTTP_X_WAP_PROFILE') || $this->server('HTTP_PROFILE')) {
+            return true;
+        } elseif ($this->server('HTTP_USER_AGENT') && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc_|htc-|iemobile|kindle|midp|mmp|motorola|mobile|nokia|opera mini|opera |Googlebot-Mobile|YahooSeeker\/M1A1-R2D2|android|iphone|ipod|mobi|palm|palmos|pocket|portalmmm|ppc;|smartphone|sonyericsson|sqh|spv|symbian|treo|up.browser|up.link|vodafone|windows ce|xda |xda_)/i', $this->server('HTTP_USER_AGENT'))) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * 当前URL地址中的scheme参数
+     * @access public
+     * @return string
+     */
+    public function scheme(): string
+    {
+        return $this->isSsl() ? 'https' : 'http';
+    }
+
+    /**
+     * 当前请求URL地址中的query参数
+     * @access public
+     * @return string
+     */
+    public function query(): string
+    {
+        return $this->server('QUERY_STRING', '');
+    }
+
+    /**
+     * 设置当前请求的host(包含端口)
+     * @access public
+     * @param  string $host 主机名(含端口)
+     * @return $this
+     */
+    public function setHost(string $host)
+    {
+        $this->host = $host;
+
+        return $this;
+    }
+
+    /**
+     * 当前请求的host
+     * @access public
+     * @param bool $strict  true 仅仅获取HOST
+     * @return string
+     */
+    public function host(bool $strict = false): string
+    {
+        if ($this->host) {
+            $host = $this->host;
+        } else {
+            $host = strval($this->server('HTTP_X_FORWARDED_HOST') ?: $this->server('HTTP_HOST'));
+        }
+
+        return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host;
+    }
+
+    /**
+     * 当前请求URL地址中的port参数
+     * @access public
+     * @return int
+     */
+    public function port(): int
+    {
+        return (int) ($this->server('HTTP_X_FORWARDED_PORT') ?: $this->server('SERVER_PORT', ''));
+    }
+
+    /**
+     * 当前请求 SERVER_PROTOCOL
+     * @access public
+     * @return string
+     */
+    public function protocol(): string
+    {
+        return $this->server('SERVER_PROTOCOL', '');
+    }
+
+    /**
+     * 当前请求 REMOTE_PORT
+     * @access public
+     * @return int
+     */
+    public function remotePort(): int
+    {
+        return (int) $this->server('REMOTE_PORT', '');
+    }
+
+    /**
+     * 当前请求 HTTP_CONTENT_TYPE
+     * @access public
+     * @return string
+     */
+    public function contentType(): string
+    {
+        $contentType = $this->header('Content-Type');
+
+        if ($contentType) {
+            if (strpos($contentType, ';')) {
+                [$type] = explode(';', $contentType);
+            } else {
+                $type = $contentType;
+            }
+            return trim($type);
+        }
+
+        return '';
+    }
+
+    /**
+     * 获取当前请求的安全Key
+     * @access public
+     * @return string
+     */
+    public function secureKey(): string
+    {
+        if (is_null($this->secureKey)) {
+            $this->secureKey = uniqid('', true);
+        }
+
+        return $this->secureKey;
+    }
+
+    /**
+     * 设置当前的控制器名
+     * @access public
+     * @param  string $controller 控制器名
+     * @return $this
+     */
+    public function setController(string $controller)
+    {
+        $this->controller = $controller;
+        return $this;
+    }
+
+    /**
+     * 设置当前的操作名
+     * @access public
+     * @param  string $action 操作名
+     * @return $this
+     */
+    public function setAction(string $action)
+    {
+        $this->action = $action;
+        return $this;
+    }
+
+    /**
+     * 获取当前的控制器名
+     * @access public
+     * @param  bool $convert 转换为小写
+     * @return string
+     */
+    public function controller(bool $convert = false): string
+    {
+        $name = $this->controller ?: '';
+        return $convert ? strtolower($name) : $name;
+    }
+
+    /**
+     * 获取当前的操作名
+     * @access public
+     * @param  bool $convert 转换为小写
+     * @return string
+     */
+    public function action(bool $convert = false): string
+    {
+        $name = $this->action ?: '';
+        return $convert ? strtolower($name) : $name;
+    }
+
+    /**
+     * 设置或者获取当前请求的content
+     * @access public
+     * @return string
+     */
+    public function getContent(): string
+    {
+        if (is_null($this->content)) {
+            $this->content = $this->input;
+        }
+
+        return $this->content;
+    }
+
+    /**
+     * 获取当前请求的php://input
+     * @access public
+     * @return string
+     */
+    public function getInput(): string
+    {
+        return $this->input;
+    }
+
+    /**
+     * 生成请求令牌
+     * @access public
+     * @param  string $name 令牌名称
+     * @param  mixed  $type 令牌生成方法
+     * @return string
+     */
+    public function buildToken(string $name = '__token__', $type = 'md5'): string
+    {
+        $type  = is_callable($type) ? $type : 'md5';
+        $token = call_user_func($type, $this->server('REQUEST_TIME_FLOAT'));
+
+        $this->session->set($name, $token);
+
+        return $token;
+    }
+
+    /**
+     * 检查请求令牌
+     * @access public
+     * @param  string $token 令牌名称
+     * @param  array  $data  表单数据
+     * @return bool
+     */
+    public function checkToken(string $token = '__token__', array $data = []): bool
+    {
+        if (in_array($this->method(), ['GET', 'HEAD', 'OPTIONS'], true)) {
+            return true;
+        }
+
+        if (!$this->session->has($token)) {
+            // 令牌数据无效
+            return false;
+        }
+
+        // Header验证
+        if ($this->header('X-CSRF-TOKEN') && $this->session->get($token) === $this->header('X-CSRF-TOKEN')) {
+            // 防止重复提交
+            $this->session->delete($token); // 验证完成销毁session
+            return true;
+        }
+
+        if (empty($data)) {
+            $data = $this->post();
+        }
+
+        // 令牌验证
+        if (isset($data[$token]) && $this->session->get($token) === $data[$token]) {
+            // 防止重复提交
+            $this->session->delete($token); // 验证完成销毁session
+            return true;
+        }
+
+        // 开启TOKEN重置
+        $this->session->delete($token);
+        return false;
+    }
+
+    /**
+     * 设置在中间件传递的数据
+     * @access public
+     * @param  array $middleware 数据
+     * @return $this
+     */
+    public function withMiddleware(array $middleware)
+    {
+        $this->middleware = array_merge($this->middleware, $middleware);
+        return $this;
+    }
+
+    /**
+     * 设置GET数据
+     * @access public
+     * @param  array $get 数据
+     * @return $this
+     */
+    public function withGet(array $get)
+    {
+        $this->get = $get;
+        return $this;
+    }
+
+    /**
+     * 设置POST数据
+     * @access public
+     * @param  array $post 数据
+     * @return $this
+     */
+    public function withPost(array $post)
+    {
+        $this->post = $post;
+        return $this;
+    }
+
+    /**
+     * 设置COOKIE数据
+     * @access public
+     * @param array $cookie 数据
+     * @return $this
+     */
+    public function withCookie(array $cookie)
+    {
+        $this->cookie = $cookie;
+        return $this;
+    }
+
+    /**
+     * 设置SESSION数据
+     * @access public
+     * @param Session $session 数据
+     * @return $this
+     */
+    public function withSession(Session $session)
+    {
+        $this->session = $session;
+        return $this;
+    }
+
+    /**
+     * 设置SERVER数据
+     * @access public
+     * @param  array $server 数据
+     * @return $this
+     */
+    public function withServer(array $server)
+    {
+        $this->server = array_change_key_case($server, CASE_UPPER);
+        return $this;
+    }
+
+    /**
+     * 设置HEADER数据
+     * @access public
+     * @param  array $header 数据
+     * @return $this
+     */
+    public function withHeader(array $header)
+    {
+        $this->header = array_change_key_case($header);
+        return $this;
+    }
+
+    /**
+     * 设置ENV数据
+     * @access public
+     * @param Env $env 数据
+     * @return $this
+     */
+    public function withEnv(Env $env)
+    {
+        $this->env = $env;
+        return $this;
+    }
+
+    /**
+     * 设置php://input数据
+     * @access public
+     * @param string $input RAW数据
+     * @return $this
+     */
+    public function withInput(string $input)
+    {
+        $this->input = $input;
+        if (!empty($input)) {
+            $inputData = $this->getInputData($input);
+            if (!empty($inputData)) {
+                $this->post = $inputData;
+                $this->put  = $inputData;
+            }
+        }
+        return $this;
+    }
+
+    /**
+     * 设置文件上传数据
+     * @access public
+     * @param  array $files 上传信息
+     * @return $this
+     */
+    public function withFiles(array $files)
+    {
+        $this->file = $files;
+        return $this;
+    }
+
+    /**
+     * 设置ROUTE变量
+     * @access public
+     * @param  array $route 数据
+     * @return $this
+     */
+    public function withRoute(array $route)
+    {
+        $this->route = $route;
+        return $this;
+    }
+
+    /**
+     * 设置中间传递数据
+     * @access public
+     * @param  string    $name  参数名
+     * @param  mixed     $value 值
+     */
+    public function __set(string $name, $value)
+    {
+        $this->middleware[$name] = $value;
+    }
+
+    /**
+     * 获取中间传递数据的值
+     * @access public
+     * @param  string $name 名称
+     * @return mixed
+     */
+    public function __get(string $name)
+    {
+        return $this->middleware($name);
+    }
+
+    /**
+     * 检测中间传递数据的值
+     * @access public
+     * @param  string $name 名称
+     * @return boolean
+     */
+    public function __isset(string $name): bool
+    {
+        return isset($this->middleware[$name]);
+    }
+
+    // ArrayAccess
+    public function offsetExists($name): bool
+    {
+        return $this->has($name);
+    }
+
+    public function offsetGet($name)
+    {
+        return $this->param($name);
+    }
+
+    public function offsetSet($name, $value)
+    {}
+
+    public function offsetUnset($name)
+    {}
+
+}

+ 410 - 0
vendor/topthink/framework/src/think/Response.php

@@ -0,0 +1,410 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+/**
+ * 响应输出基础类
+ * @package think
+ */
+abstract class Response
+{
+    /**
+     * 原始数据
+     * @var mixed
+     */
+    protected $data;
+
+    /**
+     * 当前contentType
+     * @var string
+     */
+    protected $contentType = 'text/html';
+
+    /**
+     * 字符集
+     * @var string
+     */
+    protected $charset = 'utf-8';
+
+    /**
+     * 状态码
+     * @var integer
+     */
+    protected $code = 200;
+
+    /**
+     * 是否允许请求缓存
+     * @var bool
+     */
+    protected $allowCache = true;
+
+    /**
+     * 输出参数
+     * @var array
+     */
+    protected $options = [];
+
+    /**
+     * header参数
+     * @var array
+     */
+    protected $header = [];
+
+    /**
+     * 输出内容
+     * @var string
+     */
+    protected $content = null;
+
+    /**
+     * Cookie对象
+     * @var Cookie
+     */
+    protected $cookie;
+
+    /**
+     * Session对象
+     * @var Session
+     */
+    protected $session;
+
+    /**
+     * 初始化
+     * @access protected
+     * @param  mixed  $data 输出数据
+     * @param  int    $code 状态码
+     */
+    protected function init($data = '', int $code = 200)
+    {
+        $this->data($data);
+        $this->code = $code;
+
+        $this->contentType($this->contentType, $this->charset);
+    }
+
+    /**
+     * 创建Response对象
+     * @access public
+     * @param  mixed  $data 输出数据
+     * @param  string $type 输出类型
+     * @param  int    $code 状态码
+     * @return Response
+     */
+    public static function create($data = '', string $type = 'html', int $code = 200): Response
+    {
+        $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type));
+
+        return Container::getInstance()->invokeClass($class, [$data, $code]);
+    }
+
+    /**
+     * 设置Session对象
+     * @access public
+     * @param  Session $session Session对象
+     * @return $this
+     */
+    public function setSession(Session $session)
+    {
+        $this->session = $session;
+        return $this;
+    }
+
+    /**
+     * 发送数据到客户端
+     * @access public
+     * @return void
+     * @throws \InvalidArgumentException
+     */
+    public function send(): void
+    {
+        // 处理输出数据
+        $data = $this->getContent();
+
+        if (!headers_sent() && !empty($this->header)) {
+            // 发送状态码
+            http_response_code($this->code);
+            // 发送头部信息
+            foreach ($this->header as $name => $val) {
+                header($name . (!is_null($val) ? ':' . $val : ''));
+            }
+        }
+        if ($this->cookie) {
+            $this->cookie->save();
+        }
+
+        $this->sendData($data);
+
+        if (function_exists('fastcgi_finish_request')) {
+            // 提高页面响应
+            fastcgi_finish_request();
+        }
+    }
+
+    /**
+     * 处理数据
+     * @access protected
+     * @param  mixed $data 要处理的数据
+     * @return mixed
+     */
+    protected function output($data)
+    {
+        return $data;
+    }
+
+    /**
+     * 输出数据
+     * @access protected
+     * @param string $data 要处理的数据
+     * @return void
+     */
+    protected function sendData(string $data): void
+    {
+        echo $data;
+    }
+
+    /**
+     * 输出的参数
+     * @access public
+     * @param  mixed $options 输出参数
+     * @return $this
+     */
+    public function options(array $options = [])
+    {
+        $this->options = array_merge($this->options, $options);
+
+        return $this;
+    }
+
+    /**
+     * 输出数据设置
+     * @access public
+     * @param  mixed $data 输出数据
+     * @return $this
+     */
+    public function data($data)
+    {
+        $this->data = $data;
+
+        return $this;
+    }
+
+    /**
+     * 是否允许请求缓存
+     * @access public
+     * @param  bool $cache 允许请求缓存
+     * @return $this
+     */
+    public function allowCache(bool $cache)
+    {
+        $this->allowCache = $cache;
+
+        return $this;
+    }
+
+    /**
+     * 是否允许请求缓存
+     * @access public
+     * @return bool
+     */
+    public function isAllowCache()
+    {
+        return $this->allowCache;
+    }
+
+    /**
+     * 设置Cookie
+     * @access public
+     * @param  string $name  cookie名称
+     * @param  string $value cookie值
+     * @param  mixed  $option 可选参数
+     * @return $this
+     */
+    public function cookie(string $name, string $value, $option = null)
+    {
+        $this->cookie->set($name, $value, $option);
+
+        return $this;
+    }
+
+    /**
+     * 设置响应头
+     * @access public
+     * @param  array $header  参数
+     * @return $this
+     */
+    public function header(array $header = [])
+    {
+        $this->header = array_merge($this->header, $header);
+
+        return $this;
+    }
+
+    /**
+     * 设置页面输出内容
+     * @access public
+     * @param  mixed $content
+     * @return $this
+     */
+    public function content($content)
+    {
+        if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([
+            $content,
+            '__toString',
+        ])
+        ) {
+            throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content)));
+        }
+
+        $this->content = (string) $content;
+
+        return $this;
+    }
+
+    /**
+     * 发送HTTP状态
+     * @access public
+     * @param  integer $code 状态码
+     * @return $this
+     */
+    public function code(int $code)
+    {
+        $this->code = $code;
+
+        return $this;
+    }
+
+    /**
+     * LastModified
+     * @access public
+     * @param  string $time
+     * @return $this
+     */
+    public function lastModified(string $time)
+    {
+        $this->header['Last-Modified'] = $time;
+
+        return $this;
+    }
+
+    /**
+     * Expires
+     * @access public
+     * @param  string $time
+     * @return $this
+     */
+    public function expires(string $time)
+    {
+        $this->header['Expires'] = $time;
+
+        return $this;
+    }
+
+    /**
+     * ETag
+     * @access public
+     * @param  string $eTag
+     * @return $this
+     */
+    public function eTag(string $eTag)
+    {
+        $this->header['ETag'] = $eTag;
+
+        return $this;
+    }
+
+    /**
+     * 页面缓存控制
+     * @access public
+     * @param  string $cache 状态码
+     * @return $this
+     */
+    public function cacheControl(string $cache)
+    {
+        $this->header['Cache-control'] = $cache;
+
+        return $this;
+    }
+
+    /**
+     * 页面输出类型
+     * @access public
+     * @param  string $contentType 输出类型
+     * @param  string $charset     输出编码
+     * @return $this
+     */
+    public function contentType(string $contentType, string $charset = 'utf-8')
+    {
+        $this->header['Content-Type'] = $contentType . '; charset=' . $charset;
+
+        return $this;
+    }
+
+    /**
+     * 获取头部信息
+     * @access public
+     * @param  string $name 头部名称
+     * @return mixed
+     */
+    public function getHeader(string $name = '')
+    {
+        if (!empty($name)) {
+            return $this->header[$name] ?? null;
+        }
+
+        return $this->header;
+    }
+
+    /**
+     * 获取原始数据
+     * @access public
+     * @return mixed
+     */
+    public function getData()
+    {
+        return $this->data;
+    }
+
+    /**
+     * 获取输出数据
+     * @access public
+     * @return string
+     */
+    public function getContent(): string
+    {
+        if (null == $this->content) {
+            $content = $this->output($this->data);
+
+            if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([
+                $content,
+                '__toString',
+            ])
+            ) {
+                throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content)));
+            }
+
+            $this->content = (string) $content;
+        }
+
+        return $this->content;
+    }
+
+    /**
+     * 获取状态码
+     * @access public
+     * @return integer
+     */
+    public function getCode(): int
+    {
+        return $this->code;
+    }
+}

+ 926 - 0
vendor/topthink/framework/src/think/Route.php

@@ -0,0 +1,926 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use Closure;
+use think\exception\RouteNotFoundException;
+use think\route\Dispatch;
+use think\route\dispatch\Callback;
+use think\route\dispatch\Url as UrlDispatch;
+use think\route\Domain;
+use think\route\Resource;
+use think\route\Rule;
+use think\route\RuleGroup;
+use think\route\RuleItem;
+use think\route\RuleName;
+use think\route\Url as UrlBuild;
+
+/**
+ * 路由管理类
+ * @package think
+ */
+class Route
+{
+    /**
+     * REST定义
+     * @var array
+     */
+    protected $rest = [
+        'index'  => ['get', '', 'index'],
+        'create' => ['get', '/create', 'create'],
+        'edit'   => ['get', '/<id>/edit', 'edit'],
+        'read'   => ['get', '/<id>', 'read'],
+        'save'   => ['post', '', 'save'],
+        'update' => ['put', '/<id>', 'update'],
+        'delete' => ['delete', '/<id>', 'delete'],
+    ];
+
+    /**
+     * 配置参数
+     * @var array
+     */
+    protected $config = [
+        // pathinfo分隔符
+        'pathinfo_depr'         => '/',
+        // 是否开启路由延迟解析
+        'url_lazy_route'        => false,
+        // 是否强制使用路由
+        'url_route_must'        => false,
+        // 合并路由规则
+        'route_rule_merge'      => false,
+        // 路由是否完全匹配
+        'route_complete_match'  => false,
+        // 去除斜杠
+        'remove_slash'          => false,
+        // 使用注解路由
+        'route_annotation'      => false,
+        // 默认的路由变量规则
+        'default_route_pattern' => '[\w\.]+',
+        // URL伪静态后缀
+        'url_html_suffix'       => 'html',
+        // 访问控制器层名称
+        'controller_layer'      => 'controller',
+        // 空控制器名
+        'empty_controller'      => 'Error',
+        // 是否使用控制器后缀
+        'controller_suffix'     => false,
+        // 默认控制器名
+        'default_controller'    => 'Index',
+        // 默认操作名
+        'default_action'        => 'index',
+        // 操作方法后缀
+        'action_suffix'         => '',
+        // 非路由变量是否使用普通参数方式(用于URL生成)
+        'url_common_param'      => true,
+    ];
+
+    /**
+     * 当前应用
+     * @var App
+     */
+    protected $app;
+
+    /**
+     * 请求对象
+     * @var Request
+     */
+    protected $request;
+
+    /**
+     * @var RuleName
+     */
+    protected $ruleName;
+
+    /**
+     * 当前HOST
+     * @var string
+     */
+    protected $host;
+
+    /**
+     * 当前分组对象
+     * @var RuleGroup
+     */
+    protected $group;
+
+    /**
+     * 路由绑定
+     * @var array
+     */
+    protected $bind = [];
+
+    /**
+     * 域名对象
+     * @var Domain[]
+     */
+    protected $domains = [];
+
+    /**
+     * 跨域路由规则
+     * @var RuleGroup
+     */
+    protected $cross;
+
+    /**
+     * 路由是否延迟解析
+     * @var bool
+     */
+    protected $lazy = false;
+
+    /**
+     * 路由是否测试模式
+     * @var bool
+     */
+    protected $isTest = false;
+
+    /**
+     * (分组)路由规则是否合并解析
+     * @var bool
+     */
+    protected $mergeRuleRegex = false;
+
+    /**
+     * 是否去除URL最后的斜线
+     * @var bool
+     */
+    protected $removeSlash = false;
+
+    public function __construct(App $app)
+    {
+        $this->app      = $app;
+        $this->ruleName = new RuleName();
+        $this->setDefaultDomain();
+
+        if (is_file($this->app->getRuntimePath() . 'route.php')) {
+            // 读取路由映射文件
+            $this->import(include $this->app->getRuntimePath() . 'route.php');
+        }
+
+        $this->config = array_merge($this->config, $this->app->config->get('route'));
+    }
+
+    protected function init()
+    {
+        if (!empty($this->config['middleware'])) {
+            $this->app->middleware->import($this->config['middleware'], 'route');
+        }
+
+        $this->lazy($this->config['url_lazy_route']);
+        $this->mergeRuleRegex = $this->config['route_rule_merge'];
+        $this->removeSlash    = $this->config['remove_slash'];
+
+        $this->group->removeSlash($this->removeSlash);
+    }
+
+    public function config(string $name = null)
+    {
+        if (is_null($name)) {
+            return $this->config;
+        }
+
+        return $this->config[$name] ?? null;
+    }
+
+    /**
+     * 设置路由域名及分组(包括资源路由)是否延迟解析
+     * @access public
+     * @param bool $lazy 路由是否延迟解析
+     * @return $this
+     */
+    public function lazy(bool $lazy = true)
+    {
+        $this->lazy = $lazy;
+        return $this;
+    }
+
+    /**
+     * 设置路由为测试模式
+     * @access public
+     * @param bool $test 路由是否测试模式
+     * @return void
+     */
+    public function setTestMode(bool $test): void
+    {
+        $this->isTest = $test;
+    }
+
+    /**
+     * 检查路由是否为测试模式
+     * @access public
+     * @return bool
+     */
+    public function isTest(): bool
+    {
+        return $this->isTest;
+    }
+
+    /**
+     * 设置路由域名及分组(包括资源路由)是否合并解析
+     * @access public
+     * @param bool $merge 路由是否合并解析
+     * @return $this
+     */
+    public function mergeRuleRegex(bool $merge = true)
+    {
+        $this->mergeRuleRegex = $merge;
+        $this->group->mergeRuleRegex($merge);
+
+        return $this;
+    }
+
+    /**
+     * 初始化默认域名
+     * @access protected
+     * @return void
+     */
+    protected function setDefaultDomain(): void
+    {
+        // 注册默认域名
+        $domain = new Domain($this);
+
+        $this->domains['-'] = $domain;
+
+        // 默认分组
+        $this->group = $domain;
+    }
+
+    /**
+     * 设置当前分组
+     * @access public
+     * @param RuleGroup $group 域名
+     * @return void
+     */
+    public function setGroup(RuleGroup $group): void
+    {
+        $this->group = $group;
+    }
+
+    /**
+     * 获取指定标识的路由分组 不指定则获取当前分组
+     * @access public
+     * @param string $name 分组标识
+     * @return RuleGroup
+     */
+    public function getGroup(string $name = null)
+    {
+        return $name ? $this->ruleName->getGroup($name) : $this->group;
+    }
+
+    /**
+     * 注册变量规则
+     * @access public
+     * @param array $pattern 变量规则
+     * @return $this
+     */
+    public function pattern(array $pattern)
+    {
+        $this->group->pattern($pattern);
+
+        return $this;
+    }
+
+    /**
+     * 注册路由参数
+     * @access public
+     * @param array $option 参数
+     * @return $this
+     */
+    public function option(array $option)
+    {
+        $this->group->option($option);
+
+        return $this;
+    }
+
+    /**
+     * 注册域名路由
+     * @access public
+     * @param string|array $name 子域名
+     * @param mixed        $rule 路由规则
+     * @return Domain
+     */
+    public function domain($name, $rule = null): Domain
+    {
+        // 支持多个域名使用相同路由规则
+        $domainName = is_array($name) ? array_shift($name) : $name;
+
+        if (!isset($this->domains[$domainName])) {
+            $domain = (new Domain($this, $domainName, $rule))
+                ->lazy($this->lazy)
+                ->removeSlash($this->removeSlash)
+                ->mergeRuleRegex($this->mergeRuleRegex);
+
+            $this->domains[$domainName] = $domain;
+        } else {
+            $domain = $this->domains[$domainName];
+            $domain->parseGroupRule($rule);
+        }
+
+        if (is_array($name) && !empty($name)) {
+            foreach ($name as $item) {
+                $this->domains[$item] = $domainName;
+            }
+        }
+
+        // 返回域名对象
+        return $domain;
+    }
+
+    /**
+     * 获取域名
+     * @access public
+     * @return array
+     */
+    public function getDomains(): array
+    {
+        return $this->domains;
+    }
+
+    /**
+     * 获取RuleName对象
+     * @access public
+     * @return RuleName
+     */
+    public function getRuleName(): RuleName
+    {
+        return $this->ruleName;
+    }
+
+    /**
+     * 设置路由绑定
+     * @access public
+     * @param string $bind   绑定信息
+     * @param string $domain 域名
+     * @return $this
+     */
+    public function bind(string $bind, string $domain = null)
+    {
+        $domain = is_null($domain) ? '-' : $domain;
+
+        $this->bind[$domain] = $bind;
+
+        return $this;
+    }
+
+    /**
+     * 读取路由绑定信息
+     * @access public
+     * @return array
+     */
+    public function getBind(): array
+    {
+        return $this->bind;
+    }
+
+    /**
+     * 读取路由绑定
+     * @access public
+     * @param string $domain 域名
+     * @return string|null
+     */
+    public function getDomainBind(string $domain = null)
+    {
+        if (is_null($domain)) {
+            $domain = $this->host;
+        } elseif (false === strpos($domain, '.') && $this->request) {
+            $domain .= '.' . $this->request->rootDomain();
+        }
+
+        if ($this->request) {
+            $subDomain = $this->request->subDomain();
+
+            if (strpos($subDomain, '.')) {
+                $name = '*' . strstr($subDomain, '.');
+            }
+        }
+
+        if (isset($this->bind[$domain])) {
+            $result = $this->bind[$domain];
+        } elseif (isset($name) && isset($this->bind[$name])) {
+            $result = $this->bind[$name];
+        } elseif (!empty($subDomain) && isset($this->bind['*'])) {
+            $result = $this->bind['*'];
+        } else {
+            $result = null;
+        }
+
+        return $result;
+    }
+
+    /**
+     * 读取路由标识
+     * @access public
+     * @param string $name   路由标识
+     * @param string $domain 域名
+     * @param string $method 请求类型
+     * @return array
+     */
+    public function getName(string $name = null, string $domain = null, string $method = '*'): array
+    {
+        return $this->ruleName->getName($name, $domain, $method);
+    }
+
+    /**
+     * 批量导入路由标识
+     * @access public
+     * @param array $name 路由标识
+     * @return void
+     */
+    public function import(array $name): void
+    {
+        $this->ruleName->import($name);
+    }
+
+    /**
+     * 注册路由标识
+     * @access public
+     * @param string   $name     路由标识
+     * @param RuleItem $ruleItem 路由规则
+     * @param bool     $first    是否优先
+     * @return void
+     */
+    public function setName(string $name, RuleItem $ruleItem, bool $first = false): void
+    {
+        $this->ruleName->setName($name, $ruleItem, $first);
+    }
+
+    /**
+     * 保存路由规则
+     * @access public
+     * @param string   $rule     路由规则
+     * @param RuleItem $ruleItem RuleItem对象
+     * @return void
+     */
+    public function setRule(string $rule, RuleItem $ruleItem = null): void
+    {
+        $this->ruleName->setRule($rule, $ruleItem);
+    }
+
+    /**
+     * 读取路由
+     * @access public
+     * @param string $rule 路由规则
+     * @return RuleItem[]
+     */
+    public function getRule(string $rule): array
+    {
+        return $this->ruleName->getRule($rule);
+    }
+
+    /**
+     * 读取路由列表
+     * @access public
+     * @return array
+     */
+    public function getRuleList(): array
+    {
+        return $this->ruleName->getRuleList();
+    }
+
+    /**
+     * 清空路由规则
+     * @access public
+     * @return void
+     */
+    public function clear(): void
+    {
+        $this->ruleName->clear();
+
+        if ($this->group) {
+            $this->group->clear();
+        }
+    }
+
+    /**
+     * 注册路由规则
+     * @access public
+     * @param string $rule   路由规则
+     * @param mixed  $route  路由地址
+     * @param string $method 请求类型
+     * @return RuleItem
+     */
+    public function rule(string $rule, $route = null, string $method = '*'): RuleItem
+    {
+        if ($route instanceof Response) {
+            // 兼容之前的路由到响应对象,感觉不需要,使用场景很少,闭包就能实现
+            $route = function () use ($route) {
+                return $route;
+            };
+        }
+        return $this->group->addRule($rule, $route, $method);
+    }
+
+    /**
+     * 设置跨域有效路由规则
+     * @access public
+     * @param Rule   $rule   路由规则
+     * @param string $method 请求类型
+     * @return $this
+     */
+    public function setCrossDomainRule(Rule $rule, string $method = '*')
+    {
+        if (!isset($this->cross)) {
+            $this->cross = (new RuleGroup($this))->mergeRuleRegex($this->mergeRuleRegex);
+        }
+
+        $this->cross->addRuleItem($rule, $method);
+
+        return $this;
+    }
+
+    /**
+     * 注册路由分组
+     * @access public
+     * @param string|\Closure $name  分组名称或者参数
+     * @param mixed           $route 分组路由
+     * @return RuleGroup
+     */
+    public function group($name, $route = null): RuleGroup
+    {
+        if ($name instanceof Closure) {
+            $route = $name;
+            $name  = '';
+        }
+
+        return (new RuleGroup($this, $this->group, $name, $route))
+            ->lazy($this->lazy)
+            ->removeSlash($this->removeSlash)
+            ->mergeRuleRegex($this->mergeRuleRegex);
+    }
+
+    /**
+     * 注册路由
+     * @access public
+     * @param string $rule  路由规则
+     * @param mixed  $route 路由地址
+     * @return RuleItem
+     */
+    public function any(string $rule, $route): RuleItem
+    {
+        return $this->rule($rule, $route, '*');
+    }
+
+    /**
+     * 注册GET路由
+     * @access public
+     * @param string $rule  路由规则
+     * @param mixed  $route 路由地址
+     * @return RuleItem
+     */
+    public function get(string $rule, $route): RuleItem
+    {
+        return $this->rule($rule, $route, 'GET');
+    }
+
+    /**
+     * 注册POST路由
+     * @access public
+     * @param string $rule  路由规则
+     * @param mixed  $route 路由地址
+     * @return RuleItem
+     */
+    public function post(string $rule, $route): RuleItem
+    {
+        return $this->rule($rule, $route, 'POST');
+    }
+
+    /**
+     * 注册PUT路由
+     * @access public
+     * @param string $rule  路由规则
+     * @param mixed  $route 路由地址
+     * @return RuleItem
+     */
+    public function put(string $rule, $route): RuleItem
+    {
+        return $this->rule($rule, $route, 'PUT');
+    }
+
+    /**
+     * 注册DELETE路由
+     * @access public
+     * @param string $rule  路由规则
+     * @param mixed  $route 路由地址
+     * @return RuleItem
+     */
+    public function delete(string $rule, $route): RuleItem
+    {
+        return $this->rule($rule, $route, 'DELETE');
+    }
+
+    /**
+     * 注册PATCH路由
+     * @access public
+     * @param string $rule  路由规则
+     * @param mixed  $route 路由地址
+     * @return RuleItem
+     */
+    public function patch(string $rule, $route): RuleItem
+    {
+        return $this->rule($rule, $route, 'PATCH');
+    }
+
+    /**
+     * 注册OPTIONS路由
+     * @access public
+     * @param string $rule  路由规则
+     * @param mixed  $route 路由地址
+     * @return RuleItem
+     */
+    public function options(string $rule, $route): RuleItem
+    {
+        return $this->rule($rule, $route, 'OPTIONS');
+    }
+
+    /**
+     * 注册资源路由
+     * @access public
+     * @param string $rule  路由规则
+     * @param string $route 路由地址
+     * @return Resource
+     */
+    public function resource(string $rule, string $route): Resource
+    {
+        return (new Resource($this, $this->group, $rule, $route, $this->rest))
+            ->lazy($this->lazy);
+    }
+
+    /**
+     * 注册视图路由
+     * @access public
+     * @param string $rule     路由规则
+     * @param string $template 路由模板地址
+     * @param array  $vars     模板变量
+     * @return RuleItem
+     */
+    public function view(string $rule, string $template = '', array $vars = []): RuleItem
+    {
+        return $this->rule($rule, function () use ($vars, $template) {
+            return Response::create($template, 'view')->assign($vars);
+        }, 'GET');
+    }
+
+    /**
+     * 注册重定向路由
+     * @access public
+     * @param string $rule   路由规则
+     * @param string $route  路由地址
+     * @param int    $status 状态码
+     * @return RuleItem
+     */
+    public function redirect(string $rule, string $route = '', int $status = 301): RuleItem
+    {
+        return $this->rule($rule, function (Request $request) use ($status, $route) {
+            $search  = $replace  = [];
+            $matches = $request->rule()->getVars();
+
+            foreach ($matches as $key => $value) {
+                $search[]  = '<' . $key . '>';
+                $replace[] = $value;
+
+                $search[]  = ':' . $key;
+                $replace[] = $value;
+            }
+
+            $route = str_replace($search, $replace, $route);
+            return Response::create($route, 'redirect')->code($status);
+        }, '*');
+    }
+
+    /**
+     * rest方法定义和修改
+     * @access public
+     * @param string|array $name     方法名称
+     * @param array|bool   $resource 资源
+     * @return $this
+     */
+    public function rest($name, $resource = [])
+    {
+        if (is_array($name)) {
+            $this->rest = $resource ? $name : array_merge($this->rest, $name);
+        } else {
+            $this->rest[$name] = $resource;
+        }
+
+        return $this;
+    }
+
+    /**
+     * 获取rest方法定义的参数
+     * @access public
+     * @param string $name 方法名称
+     * @return array|null
+     */
+    public function getRest(string $name = null)
+    {
+        if (is_null($name)) {
+            return $this->rest;
+        }
+
+        return $this->rest[$name] ?? null;
+    }
+
+    /**
+     * 注册未匹配路由规则后的处理
+     * @access public
+     * @param string|Closure $route  路由地址
+     * @param string         $method 请求类型
+     * @return RuleItem
+     */
+    public function miss($route, string $method = '*'): RuleItem
+    {
+        return $this->group->miss($route, $method);
+    }
+
+    /**
+     * 路由调度
+     * @param Request $request
+     * @param Closure|bool $withRoute
+     * @return Response
+     */
+    public function dispatch(Request $request, $withRoute = true)
+    {
+        $this->request = $request;
+        $this->host    = $this->request->host(true);
+        $this->init();
+
+        if ($withRoute) {
+            //加载路由
+            if ($withRoute instanceof Closure) {
+                $withRoute();
+            }
+            $dispatch = $this->check();
+        } else {
+            $dispatch = $this->url($this->path());
+        }
+
+        $dispatch->init($this->app);
+
+        return $this->app->middleware->pipeline('route')
+            ->send($request)
+            ->then(function () use ($dispatch) {
+                return $dispatch->run();
+            });
+    }
+
+    /**
+     * 检测URL路由
+     * @access public
+     * @return Dispatch|false
+     * @throws RouteNotFoundException
+     */
+    public function check()
+    {
+        // 自动检测域名路由
+        $url = str_replace($this->config['pathinfo_depr'], '|', $this->path());
+
+        $completeMatch = $this->config['route_complete_match'];
+
+        $result = $this->checkDomain()->check($this->request, $url, $completeMatch);
+
+        if (false === $result && !empty($this->cross)) {
+            // 检测跨域路由
+            $result = $this->cross->check($this->request, $url, $completeMatch);
+        }
+
+        if (false !== $result) {
+            return $result;
+        } elseif ($this->config['url_route_must']) {
+            throw new RouteNotFoundException();
+        }
+
+        return $this->url($url);
+    }
+
+    /**
+     * 获取当前请求URL的pathinfo信息(不含URL后缀)
+     * @access protected
+     * @return string
+     */
+    protected function path(): string
+    {
+        $suffix   = $this->config['url_html_suffix'];
+        $pathinfo = $this->request->pathinfo();
+
+        if (false === $suffix) {
+            // 禁止伪静态访问
+            $path = $pathinfo;
+        } elseif ($suffix) {
+            // 去除正常的URL后缀
+            $path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo);
+        } else {
+            // 允许任何后缀访问
+            $path = preg_replace('/\.' . $this->request->ext() . '$/i', '', $pathinfo);
+        }
+
+        return $path;
+    }
+
+    /**
+     * 默认URL解析
+     * @access public
+     * @param string $url URL地址
+     * @return Dispatch
+     */
+    public function url(string $url): Dispatch
+    {
+        if ($this->request->method() == 'OPTIONS') {
+            // 自动响应options请求
+            return new Callback($this->request, $this->group, function () {
+                return Response::create('', 'html', 204)->header(['Allow' => 'GET, POST, PUT, DELETE']);
+            });
+        }
+
+        return new UrlDispatch($this->request, $this->group, $url);
+    }
+
+    /**
+     * 检测域名的路由规则
+     * @access protected
+     * @return Domain
+     */
+    protected function checkDomain(): Domain
+    {
+        $item = false;
+
+        if (count($this->domains) > 1) {
+            // 获取当前子域名
+            $subDomain = $this->request->subDomain();
+
+            $domain  = $subDomain ? explode('.', $subDomain) : [];
+            $domain2 = $domain ? array_pop($domain) : '';
+
+            if ($domain) {
+                // 存在三级域名
+                $domain3 = array_pop($domain);
+            }
+
+            if (isset($this->domains[$this->host])) {
+                // 子域名配置
+                $item = $this->domains[$this->host];
+            } elseif (isset($this->domains[$subDomain])) {
+                $item = $this->domains[$subDomain];
+            } elseif (isset($this->domains['*.' . $domain2]) && !empty($domain3)) {
+                // 泛三级域名
+                $item      = $this->domains['*.' . $domain2];
+                $panDomain = $domain3;
+            } elseif (isset($this->domains['*']) && !empty($domain2)) {
+                // 泛二级域名
+                if ('www' != $domain2) {
+                    $item      = $this->domains['*'];
+                    $panDomain = $domain2;
+                }
+            }
+
+            if (isset($panDomain)) {
+                // 保存当前泛域名
+                $this->request->setPanDomain($panDomain);
+            }
+        }
+
+        if (false === $item) {
+            // 检测全局域名规则
+            $item = $this->domains['-'];
+        }
+
+        if (is_string($item)) {
+            $item = $this->domains[$item];
+        }
+
+        return $item;
+    }
+
+    /**
+     * URL生成 支持路由反射
+     * @access public
+     * @param string $url  路由地址
+     * @param array  $vars 参数 ['a'=>'val1', 'b'=>'val2']
+     * @return UrlBuild
+     */
+    public function buildUrl(string $url = '', array $vars = []): UrlBuild
+    {
+        return $this->app->make(UrlBuild::class, [$this, $this->app, $url, $vars], true);
+    }
+
+    /**
+     * 设置全局的路由分组参数
+     * @access public
+     * @param string $method 方法名
+     * @param array  $args   调用参数
+     * @return RuleGroup
+     */
+    public function __call($method, $args)
+    {
+        return call_user_func_array([$this->group, $method], $args);
+    }
+}

+ 66 - 0
vendor/topthink/framework/src/think/Service.php

@@ -0,0 +1,66 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use Closure;
+use think\event\RouteLoaded;
+
+/**
+ * 系统服务基础类
+ * @method void register()
+ * @method void boot()
+ */
+abstract class Service
+{
+    protected $app;
+
+    public function __construct(App $app)
+    {
+        $this->app = $app;
+    }
+
+    /**
+     * 加载路由
+     * @access protected
+     * @param string $path 路由路径
+     */
+    protected function loadRoutesFrom($path)
+    {
+        $this->registerRoutes(function () use ($path) {
+            include $path;
+        });
+    }
+
+    /**
+     * 注册路由
+     * @param Closure $closure
+     */
+    protected function registerRoutes(Closure $closure)
+    {
+        $this->app->event->listen(RouteLoaded::class, $closure);
+    }
+
+    /**
+     * 添加指令
+     * @access protected
+     * @param array|string $commands 指令
+     */
+    protected function commands($commands)
+    {
+        $commands = is_array($commands) ? $commands : func_get_args();
+
+        Console::starting(function (Console $console) use ($commands) {
+            $console->addCommands($commands);
+        });
+    }
+}

+ 65 - 0
vendor/topthink/framework/src/think/Session.php

@@ -0,0 +1,65 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use think\helper\Arr;
+use think\session\Store;
+
+/**
+ * Session管理类
+ * @package think
+ * @mixin Store
+ */
+class Session extends Manager
+{
+    protected $namespace = '\\think\\session\\driver\\';
+
+    protected function createDriver(string $name)
+    {
+        $handler = parent::createDriver($name);
+
+        return new Store($this->getConfig('name') ?: 'PHPSESSID', $handler, $this->getConfig('serialize'));
+    }
+
+    /**
+     * 获取Session配置
+     * @access public
+     * @param null|string $name    名称
+     * @param mixed       $default 默认值
+     * @return mixed
+     */
+    public function getConfig(string $name = null, $default = null)
+    {
+        if (!is_null($name)) {
+            return $this->app->config->get('session.' . $name, $default);
+        }
+
+        return $this->app->config->get('session');
+    }
+
+    protected function resolveConfig(string $name)
+    {
+        $config = $this->app->config->get('session', []);
+        Arr::forget($config, 'type');
+        return $config;
+    }
+
+    /**
+     * 默认驱动
+     * @return string|null
+     */
+    public function getDefaultDriver()
+    {
+        return $this->app->config->get('session.type', 'file');
+    }
+}

+ 1688 - 0
vendor/topthink/framework/src/think/Validate.php

@@ -0,0 +1,1688 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use Closure;
+use think\exception\ValidateException;
+use think\helper\Str;
+use think\validate\ValidateRule;
+
+/**
+ * 数据验证类
+ * @package think
+ */
+class Validate
+{
+    /**
+     * 自定义验证类型
+     * @var array
+     */
+    protected $type = [];
+
+    /**
+     * 验证类型别名
+     * @var array
+     */
+    protected $alias = [
+        '>' => 'gt', '>=' => 'egt', '<' => 'lt', '<=' => 'elt', '=' => 'eq', 'same' => 'eq',
+    ];
+
+    /**
+     * 当前验证规则
+     * @var array
+     */
+    protected $rule = [];
+
+    /**
+     * 验证提示信息
+     * @var array
+     */
+    protected $message = [];
+
+    /**
+     * 验证字段描述
+     * @var array
+     */
+    protected $field = [];
+
+    /**
+     * 默认规则提示
+     * @var array
+     */
+    protected $typeMsg = [
+        'require'     => ':attribute require',
+        'must'        => ':attribute must',
+        'number'      => ':attribute must be numeric',
+        'integer'     => ':attribute must be integer',
+        'float'       => ':attribute must be float',
+        'boolean'     => ':attribute must be bool',
+        'email'       => ':attribute not a valid email address',
+        'mobile'      => ':attribute not a valid mobile',
+        'array'       => ':attribute must be a array',
+        'accepted'    => ':attribute must be yes,on or 1',
+        'date'        => ':attribute not a valid datetime',
+        'file'        => ':attribute not a valid file',
+        'image'       => ':attribute not a valid image',
+        'alpha'       => ':attribute must be alpha',
+        'alphaNum'    => ':attribute must be alpha-numeric',
+        'alphaDash'   => ':attribute must be alpha-numeric, dash, underscore',
+        'activeUrl'   => ':attribute not a valid domain or ip',
+        'chs'         => ':attribute must be chinese',
+        'chsAlpha'    => ':attribute must be chinese or alpha',
+        'chsAlphaNum' => ':attribute must be chinese,alpha-numeric',
+        'chsDash'     => ':attribute must be chinese,alpha-numeric,underscore, dash',
+        'url'         => ':attribute not a valid url',
+        'ip'          => ':attribute not a valid ip',
+        'dateFormat'  => ':attribute must be dateFormat of :rule',
+        'in'          => ':attribute must be in :rule',
+        'notIn'       => ':attribute be notin :rule',
+        'between'     => ':attribute must between :1 - :2',
+        'notBetween'  => ':attribute not between :1 - :2',
+        'length'      => 'size of :attribute must be :rule',
+        'max'         => 'max size of :attribute must be :rule',
+        'min'         => 'min size of :attribute must be :rule',
+        'after'       => ':attribute cannot be less than :rule',
+        'before'      => ':attribute cannot exceed :rule',
+        'expire'      => ':attribute not within :rule',
+        'allowIp'     => 'access IP is not allowed',
+        'denyIp'      => 'access IP denied',
+        'confirm'     => ':attribute out of accord with :2',
+        'different'   => ':attribute cannot be same with :2',
+        'egt'         => ':attribute must greater than or equal :rule',
+        'gt'          => ':attribute must greater than :rule',
+        'elt'         => ':attribute must less than or equal :rule',
+        'lt'          => ':attribute must less than :rule',
+        'eq'          => ':attribute must equal :rule',
+        'unique'      => ':attribute has exists',
+        'regex'       => ':attribute not conform to the rules',
+        'method'      => 'invalid Request method',
+        'token'       => 'invalid token',
+        'fileSize'    => 'filesize not match',
+        'fileExt'     => 'extensions to upload is not allowed',
+        'fileMime'    => 'mimetype to upload is not allowed',
+    ];
+
+    /**
+     * 当前验证场景
+     * @var string
+     */
+    protected $currentScene;
+
+    /**
+     * 内置正则验证规则
+     * @var array
+     */
+    protected $defaultRegex = [
+        'alpha'       => '/^[A-Za-z]+$/',
+        'alphaNum'    => '/^[A-Za-z0-9]+$/',
+        'alphaDash'   => '/^[A-Za-z0-9\-\_]+$/',
+        'chs'         => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}]+$/u',
+        'chsAlpha'    => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}a-zA-Z]+$/u',
+        'chsAlphaNum' => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}a-zA-Z0-9]+$/u',
+        'chsDash'     => '/^[\x{4e00}-\x{9fa5}\x{9fa6}-\x{9fef}\x{3400}-\x{4db5}\x{20000}-\x{2ebe0}a-zA-Z0-9\_\-]+$/u',
+        'mobile'      => '/^1[3-9]\d{9}$/',
+        'idCard'      => '/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$)/',
+        'zip'         => '/\d{6}/',
+    ];
+
+    /**
+     * Filter_var 规则
+     * @var array
+     */
+    protected $filter = [
+        'email'   => FILTER_VALIDATE_EMAIL,
+        'ip'      => [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6],
+        'integer' => FILTER_VALIDATE_INT,
+        'url'     => FILTER_VALIDATE_URL,
+        'macAddr' => FILTER_VALIDATE_MAC,
+        'float'   => FILTER_VALIDATE_FLOAT,
+    ];
+
+    /**
+     * 验证场景定义
+     * @var array
+     */
+    protected $scene = [];
+
+    /**
+     * 验证失败错误信息
+     * @var string|array
+     */
+    protected $error = [];
+
+    /**
+     * 是否批量验证
+     * @var bool
+     */
+    protected $batch = false;
+
+    /**
+     * 验证失败是否抛出异常
+     * @var bool
+     */
+    protected $failException = false;
+
+    /**
+     * 场景需要验证的规则
+     * @var array
+     */
+    protected $only = [];
+
+    /**
+     * 场景需要移除的验证规则
+     * @var array
+     */
+    protected $remove = [];
+
+    /**
+     * 场景需要追加的验证规则
+     * @var array
+     */
+    protected $append = [];
+
+    /**
+     * 验证正则定义
+     * @var array
+     */
+    protected $regex = [];
+
+    /**
+     * Db对象
+     * @var Db
+     */
+    protected $db;
+
+    /**
+     * 语言对象
+     * @var Lang
+     */
+    protected $lang;
+
+    /**
+     * 请求对象
+     * @var Request
+     */
+    protected $request;
+
+    /**
+     * @var Closure[]
+     */
+    protected static $maker = [];
+
+    /**
+     * 构造方法
+     * @access public
+     */
+    public function __construct()
+    {
+        if (!empty(static::$maker)) {
+            foreach (static::$maker as $maker) {
+                call_user_func($maker, $this);
+            }
+        }
+    }
+
+    /**
+     * 设置服务注入
+     * @access public
+     * @param Closure $maker
+     * @return void
+     */
+    public static function maker(Closure $maker)
+    {
+        static::$maker[] = $maker;
+    }
+
+    /**
+     * 设置Lang对象
+     * @access public
+     * @param Lang $lang Lang对象
+     * @return void
+     */
+    public function setLang(Lang $lang)
+    {
+        $this->lang = $lang;
+    }
+
+    /**
+     * 设置Db对象
+     * @access public
+     * @param Db $db Db对象
+     * @return void
+     */
+    public function setDb(Db $db)
+    {
+        $this->db = $db;
+    }
+
+    /**
+     * 设置Request对象
+     * @access public
+     * @param Request $request Request对象
+     * @return void
+     */
+    public function setRequest(Request $request)
+    {
+        $this->request = $request;
+    }
+
+    /**
+     * 添加字段验证规则
+     * @access protected
+     * @param string|array $name 字段名称或者规则数组
+     * @param mixed        $rule 验证规则或者字段描述信息
+     * @return $this
+     */
+    public function rule($name, $rule = '')
+    {
+        if (is_array($name)) {
+            $this->rule = $name + $this->rule;
+            if (is_array($rule)) {
+                $this->field = array_merge($this->field, $rule);
+            }
+        } else {
+            $this->rule[$name] = $rule;
+        }
+
+        return $this;
+    }
+
+    /**
+     * 注册验证(类型)规则
+     * @access public
+     * @param string   $type     验证规则类型
+     * @param callable $callback callback方法(或闭包)
+     * @param string   $message  验证失败提示信息
+     * @return $this
+     */
+    public function extend(string $type, callable $callback = null, string $message = null)
+    {
+        $this->type[$type] = $callback;
+
+        if ($message) {
+            $this->typeMsg[$type] = $message;
+        }
+
+        return $this;
+    }
+
+    /**
+     * 设置验证规则的默认提示信息
+     * @access public
+     * @param string|array $type 验证规则类型名称或者数组
+     * @param string       $msg  验证提示信息
+     * @return void
+     */
+    public function setTypeMsg($type, string $msg = null): void
+    {
+        if (is_array($type)) {
+            $this->typeMsg = array_merge($this->typeMsg, $type);
+        } else {
+            $this->typeMsg[$type] = $msg;
+        }
+    }
+
+    /**
+     * 设置提示信息
+     * @access public
+     * @param array $message 错误信息
+     * @return Validate
+     */
+    public function message(array $message)
+    {
+        $this->message = array_merge($this->message, $message);
+
+        return $this;
+    }
+
+    /**
+     * 设置验证场景
+     * @access public
+     * @param string $name 场景名
+     * @return $this
+     */
+    public function scene(string $name)
+    {
+        // 设置当前场景
+        $this->currentScene = $name;
+
+        return $this;
+    }
+
+    /**
+     * 判断是否存在某个验证场景
+     * @access public
+     * @param string $name 场景名
+     * @return bool
+     */
+    public function hasScene(string $name): bool
+    {
+        return isset($this->scene[$name]) || method_exists($this, 'scene' . $name);
+    }
+
+    /**
+     * 设置批量验证
+     * @access public
+     * @param bool $batch 是否批量验证
+     * @return $this
+     */
+    public function batch(bool $batch = true)
+    {
+        $this->batch = $batch;
+
+        return $this;
+    }
+
+    /**
+     * 设置验证失败后是否抛出异常
+     * @access protected
+     * @param bool $fail 是否抛出异常
+     * @return $this
+     */
+    public function failException(bool $fail = true)
+    {
+        $this->failException = $fail;
+
+        return $this;
+    }
+
+    /**
+     * 指定需要验证的字段列表
+     * @access public
+     * @param array $fields 字段名
+     * @return $this
+     */
+    public function only(array $fields)
+    {
+        $this->only = $fields;
+
+        return $this;
+    }
+
+    /**
+     * 移除某个字段的验证规则
+     * @access public
+     * @param string|array $field 字段名
+     * @param mixed        $rule  验证规则 true 移除所有规则
+     * @return $this
+     */
+    public function remove($field, $rule = null)
+    {
+        if (is_array($field)) {
+            foreach ($field as $key => $rule) {
+                if (is_int($key)) {
+                    $this->remove($rule);
+                } else {
+                    $this->remove($key, $rule);
+                }
+            }
+        } else {
+            if (is_string($rule)) {
+                $rule = explode('|', $rule);
+            }
+
+            $this->remove[$field] = $rule;
+        }
+
+        return $this;
+    }
+
+    /**
+     * 追加某个字段的验证规则
+     * @access public
+     * @param string|array $field 字段名
+     * @param mixed        $rule  验证规则
+     * @return $this
+     */
+    public function append($field, $rule = null)
+    {
+        if (is_array($field)) {
+            foreach ($field as $key => $rule) {
+                $this->append($key, $rule);
+            }
+        } else {
+            if (is_string($rule)) {
+                $rule = explode('|', $rule);
+            }
+
+            $this->append[$field] = $rule;
+        }
+
+        return $this;
+    }
+
+    /**
+     * 数据自动验证
+     * @access public
+     * @param array $data  数据
+     * @param array $rules 验证规则
+     * @return bool
+     */
+    public function check(array $data, array $rules = []): bool
+    {
+        $this->error = [];
+
+        if ($this->currentScene) {
+            $this->getScene($this->currentScene);
+        }
+
+        if (empty($rules)) {
+            // 读取验证规则
+            $rules = $this->rule;
+        }
+
+        foreach ($this->append as $key => $rule) {
+            if (!isset($rules[$key])) {
+                $rules[$key] = $rule;
+                unset($this->append[$key]);
+            }
+        }
+
+        foreach ($rules as $key => $rule) {
+            // field => 'rule1|rule2...' field => ['rule1','rule2',...]
+            if (strpos($key, '|')) {
+                // 字段|描述 用于指定属性名称
+                [$key, $title] = explode('|', $key);
+            } else {
+                $title = $this->field[$key] ?? $key;
+            }
+
+            // 场景检测
+            if (!empty($this->only) && !in_array($key, $this->only)) {
+                continue;
+            }
+
+            // 获取数据 支持二维数组
+            $value = $this->getDataValue($data, $key);
+
+            // 字段验证
+            if ($rule instanceof Closure) {
+                $result = call_user_func_array($rule, [$value, $data]);
+            } elseif ($rule instanceof ValidateRule) {
+                //  验证因子
+                $result = $this->checkItem($key, $value, $rule->getRule(), $data, $rule->getTitle() ?: $title, $rule->getMsg());
+            } else {
+                $result = $this->checkItem($key, $value, $rule, $data, $title);
+            }
+
+            if (true !== $result) {
+                // 没有返回true 则表示验证失败
+                if (!empty($this->batch)) {
+                    // 批量验证
+                    $this->error[$key] = $result;
+                } elseif ($this->failException) {
+                    throw new ValidateException($result);
+                } else {
+                    $this->error = $result;
+                    return false;
+                }
+            }
+        }
+
+        if (!empty($this->error)) {
+            if ($this->failException) {
+                throw new ValidateException($this->error);
+            }
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * 根据验证规则验证数据
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rules 验证规则
+     * @return bool
+     */
+    public function checkRule($value, $rules): bool
+    {
+        if ($rules instanceof Closure) {
+            return call_user_func_array($rules, [$value]);
+        } elseif ($rules instanceof ValidateRule) {
+            $rules = $rules->getRule();
+        } elseif (is_string($rules)) {
+            $rules = explode('|', $rules);
+        }
+
+        foreach ($rules as $key => $rule) {
+            if ($rule instanceof Closure) {
+                $result = call_user_func_array($rule, [$value]);
+            } else {
+                // 判断验证类型
+                [$type, $rule] = $this->getValidateType($key, $rule);
+
+                $callback = $this->type[$type] ?? [$this, $type];
+
+                $result = call_user_func_array($callback, [$value, $rule]);
+            }
+
+            if (true !== $result) {
+                if ($this->failException) {
+                    throw new ValidateException($result);
+                }
+
+                return $result;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * 验证单个字段规则
+     * @access protected
+     * @param string $field 字段名
+     * @param mixed  $value 字段值
+     * @param mixed  $rules 验证规则
+     * @param array  $data  数据
+     * @param string $title 字段描述
+     * @param array  $msg   提示信息
+     * @return mixed
+     */
+    protected function checkItem(string $field, $value, $rules, $data, string $title = '', array $msg = [])
+    {
+        if (isset($this->remove[$field]) && true === $this->remove[$field] && empty($this->append[$field])) {
+            // 字段已经移除 无需验证
+            return true;
+        }
+
+        // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...]
+        if (is_string($rules)) {
+            $rules = explode('|', $rules);
+        }
+
+        if (isset($this->append[$field])) {
+            // 追加额外的验证规则
+            $rules = array_unique(array_merge($rules, $this->append[$field]), SORT_REGULAR);
+            unset($this->append[$field]);
+        }
+
+        if (empty($rules)) {
+            return true;
+        }
+
+        $i = 0;
+        foreach ($rules as $key => $rule) {
+            if ($rule instanceof Closure) {
+                $result = call_user_func_array($rule, [$value, $data]);
+                $info   = is_numeric($key) ? '' : $key;
+            } else {
+                // 判断验证类型
+                [$type, $rule, $info] = $this->getValidateType($key, $rule);
+
+                if (isset($this->append[$field]) && in_array($info, $this->append[$field])) {
+                } elseif (isset($this->remove[$field]) && in_array($info, $this->remove[$field])) {
+                    // 规则已经移除
+                    $i++;
+                    continue;
+                }
+
+                if (isset($this->type[$type])) {
+                    $result = call_user_func_array($this->type[$type], [$value, $rule, $data, $field, $title]);
+                } elseif ('must' == $info || 0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) {
+                    $result = call_user_func_array([$this, $type], [$value, $rule, $data, $field, $title]);
+                } else {
+                    $result = true;
+                }
+            }
+
+            if (false === $result) {
+                // 验证失败 返回错误信息
+                if (!empty($msg[$i])) {
+                    $message = $msg[$i];
+                    if (is_string($message) && strpos($message, '{%') === 0) {
+                        $message = $this->lang->get(substr($message, 2, -1));
+                    }
+                } else {
+                    $message = $this->getRuleMsg($field, $title, $info, $rule);
+                }
+
+                return $message;
+            } elseif (true !== $result) {
+                // 返回自定义错误信息
+                if (is_string($result) && false !== strpos($result, ':')) {
+                    $result = str_replace(':attribute', $title, $result);
+
+                    if (strpos($result, ':rule') && is_scalar($rule)) {
+                        $result = str_replace(':rule', (string) $rule, $result);
+                    }
+                }
+
+                return $result;
+            }
+            $i++;
+        }
+
+        return $result ?? true;
+    }
+
+    /**
+     * 获取当前验证类型及规则
+     * @access public
+     * @param mixed $key
+     * @param mixed $rule
+     * @return array
+     */
+    protected function getValidateType($key, $rule): array
+    {
+        // 判断验证类型
+        if (!is_numeric($key)) {
+            if (isset($this->alias[$key])) {
+                // 判断别名
+                $key = $this->alias[$key];
+            }
+            return [$key, $rule, $key];
+        }
+
+        if (strpos($rule, ':')) {
+            [$type, $rule] = explode(':', $rule, 2);
+            if (isset($this->alias[$type])) {
+                // 判断别名
+                $type = $this->alias[$type];
+            }
+            $info = $type;
+        } elseif (method_exists($this, $rule)) {
+            $type = $rule;
+            $info = $rule;
+            $rule = '';
+        } else {
+            $type = 'is';
+            $info = $rule;
+        }
+
+        return [$type, $rule, $info];
+    }
+
+    /**
+     * 验证是否和某个字段的值一致
+     * @access public
+     * @param mixed  $value 字段值
+     * @param mixed  $rule  验证规则
+     * @param array  $data  数据
+     * @param string $field 字段名
+     * @return bool
+     */
+    public function confirm($value, $rule, array $data = [], string $field = ''): bool
+    {
+        if ('' == $rule) {
+            if (strpos($field, '_confirm')) {
+                $rule = strstr($field, '_confirm', true);
+            } else {
+                $rule = $field . '_confirm';
+            }
+        }
+
+        return $this->getDataValue($data, $rule) === $value;
+    }
+
+    /**
+     * 验证是否和某个字段的值是否不同
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @param array $data  数据
+     * @return bool
+     */
+    public function different($value, $rule, array $data = []): bool
+    {
+        return $this->getDataValue($data, $rule) != $value;
+    }
+
+    /**
+     * 验证是否大于等于某个值
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @param array $data  数据
+     * @return bool
+     */
+    public function egt($value, $rule, array $data = []): bool
+    {
+        return $value >= $this->getDataValue($data, $rule);
+    }
+
+    /**
+     * 验证是否大于某个值
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @param array $data  数据
+     * @return bool
+     */
+    public function gt($value, $rule, array $data = []): bool
+    {
+        return $value > $this->getDataValue($data, $rule);
+    }
+
+    /**
+     * 验证是否小于等于某个值
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @param array $data  数据
+     * @return bool
+     */
+    public function elt($value, $rule, array $data = []): bool
+    {
+        return $value <= $this->getDataValue($data, $rule);
+    }
+
+    /**
+     * 验证是否小于某个值
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @param array $data  数据
+     * @return bool
+     */
+    public function lt($value, $rule, array $data = []): bool
+    {
+        return $value < $this->getDataValue($data, $rule);
+    }
+
+    /**
+     * 验证是否等于某个值
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @return bool
+     */
+    public function eq($value, $rule): bool
+    {
+        return $value == $rule;
+    }
+
+    /**
+     * 必须验证
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @return bool
+     */
+    public function must($value, $rule = null): bool
+    {
+        return !empty($value) || '0' == $value;
+    }
+
+    /**
+     * 验证字段值是否为有效格式
+     * @access public
+     * @param mixed  $value 字段值
+     * @param string $rule  验证规则
+     * @param array  $data  数据
+     * @return bool
+     */
+    public function is($value, string $rule, array $data = []): bool
+    {
+        switch (Str::camel($rule)) {
+            case 'require':
+                // 必须
+                $result = !empty($value) || '0' == $value;
+                break;
+            case 'accepted':
+                // 接受
+                $result = in_array($value, ['1', 'on', 'yes']);
+                break;
+            case 'date':
+                // 是否是一个有效日期
+                $result = false !== strtotime($value);
+                break;
+            case 'activeUrl':
+                // 是否为有效的网址
+                $result = checkdnsrr($value);
+                break;
+            case 'boolean':
+            case 'bool':
+                // 是否为布尔值
+                $result = in_array($value, [true, false, 0, 1, '0', '1'], true);
+                break;
+            case 'number':
+                $result = ctype_digit((string) $value);
+                break;
+            case 'alphaNum':
+                $result = ctype_alnum($value);
+                break;
+            case 'array':
+                // 是否为数组
+                $result = is_array($value);
+                break;
+            case 'file':
+                $result = $value instanceof File;
+                break;
+            case 'image':
+                $result = $value instanceof File && in_array($this->getImageType($value->getRealPath()), [1, 2, 3, 6]);
+                break;
+            case 'token':
+                $result = $this->token($value, '__token__', $data);
+                break;
+            default:
+                if (isset($this->type[$rule])) {
+                    // 注册的验证规则
+                    $result = call_user_func_array($this->type[$rule], [$value]);
+                } elseif (function_exists('ctype_' . $rule)) {
+                    // ctype验证规则
+                    $ctypeFun = 'ctype_' . $rule;
+                    $result   = $ctypeFun($value);
+                } elseif (isset($this->filter[$rule])) {
+                    // Filter_var验证规则
+                    $result = $this->filter($value, $this->filter[$rule]);
+                } else {
+                    // 正则验证
+                    $result = $this->regex($value, $rule);
+                }
+        }
+
+        return $result;
+    }
+
+    // 判断图像类型
+    protected function getImageType($image)
+    {
+        if (function_exists('exif_imagetype')) {
+            return exif_imagetype($image);
+        }
+
+        try {
+            $info = getimagesize($image);
+            return $info ? $info[2] : false;
+        } catch (\Exception $e) {
+            return false;
+        }
+    }
+
+    /**
+     * 验证表单令牌
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @param array $data  数据
+     * @return bool
+     */
+    public function token($value, string $rule, array $data): bool
+    {
+        $rule = !empty($rule) ? $rule : '__token__';
+        return $this->request->checkToken($rule, $data);
+    }
+
+    /**
+     * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @return bool
+     */
+    public function activeUrl(string $value, string $rule = 'MX'): bool
+    {
+        if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) {
+            $rule = 'MX';
+        }
+
+        return checkdnsrr($value, $rule);
+    }
+
+    /**
+     * 验证是否有效IP
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则 ipv4 ipv6
+     * @return bool
+     */
+    public function ip($value, string $rule = 'ipv4'): bool
+    {
+        if (!in_array($rule, ['ipv4', 'ipv6'])) {
+            $rule = 'ipv4';
+        }
+
+        return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]);
+    }
+
+    /**
+     * 检测上传文件后缀
+     * @access public
+     * @param File         $file
+     * @param array|string $ext 允许后缀
+     * @return bool
+     */
+    protected function checkExt(File $file, $ext): bool
+    {
+        if (is_string($ext)) {
+            $ext = explode(',', $ext);
+        }
+
+        return in_array(strtolower($file->extension()), $ext);
+    }
+
+    /**
+     * 检测上传文件大小
+     * @access public
+     * @param File    $file
+     * @param integer $size 最大大小
+     * @return bool
+     */
+    protected function checkSize(File $file, $size): bool
+    {
+        return $file->getSize() <= (int) $size;
+    }
+
+    /**
+     * 检测上传文件类型
+     * @access public
+     * @param File         $file
+     * @param array|string $mime 允许类型
+     * @return bool
+     */
+    protected function checkMime(File $file, $mime): bool
+    {
+        if (is_string($mime)) {
+            $mime = explode(',', $mime);
+        }
+
+        return in_array(strtolower($file->getMime()), $mime);
+    }
+
+    /**
+     * 验证上传文件后缀
+     * @access public
+     * @param mixed $file 上传文件
+     * @param mixed $rule 验证规则
+     * @return bool
+     */
+    public function fileExt($file, $rule): bool
+    {
+        if (is_array($file)) {
+            foreach ($file as $item) {
+                if (!($item instanceof File) || !$this->checkExt($item, $rule)) {
+                    return false;
+                }
+            }
+            return true;
+        } elseif ($file instanceof File) {
+            return $this->checkExt($file, $rule);
+        }
+
+        return false;
+    }
+
+    /**
+     * 验证上传文件类型
+     * @access public
+     * @param mixed $file 上传文件
+     * @param mixed $rule 验证规则
+     * @return bool
+     */
+    public function fileMime($file, $rule): bool
+    {
+        if (is_array($file)) {
+            foreach ($file as $item) {
+                if (!($item instanceof File) || !$this->checkMime($item, $rule)) {
+                    return false;
+                }
+            }
+            return true;
+        } elseif ($file instanceof File) {
+            return $this->checkMime($file, $rule);
+        }
+
+        return false;
+    }
+
+    /**
+     * 验证上传文件大小
+     * @access public
+     * @param mixed $file 上传文件
+     * @param mixed $rule 验证规则
+     * @return bool
+     */
+    public function fileSize($file, $rule): bool
+    {
+        if (is_array($file)) {
+            foreach ($file as $item) {
+                if (!($item instanceof File) || !$this->checkSize($item, $rule)) {
+                    return false;
+                }
+            }
+            return true;
+        } elseif ($file instanceof File) {
+            return $this->checkSize($file, $rule);
+        }
+
+        return false;
+    }
+
+    /**
+     * 验证图片的宽高及类型
+     * @access public
+     * @param mixed $file 上传文件
+     * @param mixed $rule 验证规则
+     * @return bool
+     */
+    public function image($file, $rule): bool
+    {
+        if (!($file instanceof File)) {
+            return false;
+        }
+
+        if ($rule) {
+            $rule = explode(',', $rule);
+
+            [$width, $height, $type] = getimagesize($file->getRealPath());
+
+            if (isset($rule[2])) {
+                $imageType = strtolower($rule[2]);
+
+                if ('jpg' == $imageType) {
+                    $imageType = 'jpeg';
+                }
+
+                if (image_type_to_extension($type, false) != $imageType) {
+                    return false;
+                }
+            }
+
+            [$w, $h] = $rule;
+
+            return $w == $width && $h == $height;
+        }
+
+        return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]);
+    }
+
+    /**
+     * 验证时间和日期是否符合指定格式
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @return bool
+     */
+    public function dateFormat($value, $rule): bool
+    {
+        $info = date_parse_from_format($rule, $value);
+        return 0 == $info['warning_count'] && 0 == $info['error_count'];
+    }
+
+    /**
+     * 验证是否唯一
+     * @access public
+     * @param mixed  $value 字段值
+     * @param mixed  $rule  验证规则 格式:数据表,字段名,排除ID,主键名
+     * @param array  $data  数据
+     * @param string $field 验证字段名
+     * @return bool
+     */
+    public function unique($value, $rule, array $data = [], string $field = ''): bool
+    {
+        if (is_string($rule)) {
+            $rule = explode(',', $rule);
+        }
+
+        if (false !== strpos($rule[0], '\\')) {
+            // 指定模型类
+            $db = new $rule[0];
+        } else {
+            $db = $this->db->name($rule[0]);
+        }
+
+        $key = $rule[1] ?? $field;
+        $map = [];
+
+        if (strpos($key, '^')) {
+            // 支持多个字段验证
+            $fields = explode('^', $key);
+            foreach ($fields as $key) {
+                if (isset($data[$key])) {
+                    $map[] = [$key, '=', $data[$key]];
+                }
+            }
+        } elseif (isset($data[$field])) {
+            $map[] = [$key, '=', $data[$field]];
+        } else {
+            $map = [];
+        }
+
+        $pk = !empty($rule[3]) ? $rule[3] : $db->getPk();
+
+        if (is_string($pk)) {
+            if (isset($rule[2])) {
+                $map[] = [$pk, '<>', $rule[2]];
+            } elseif (isset($data[$pk])) {
+                $map[] = [$pk, '<>', $data[$pk]];
+            }
+        }
+
+        if ($db->where($map)->field($pk)->find()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * 使用filter_var方式验证
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @return bool
+     */
+    public function filter($value, $rule): bool
+    {
+        if (is_string($rule) && strpos($rule, ',')) {
+            [$rule, $param] = explode(',', $rule);
+        } elseif (is_array($rule)) {
+            $param = $rule[1] ?? 0;
+            $rule  = $rule[0];
+        } else {
+            $param = 0;
+        }
+
+        return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param);
+    }
+
+    /**
+     * 验证某个字段等于某个值的时候必须
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @param array $data  数据
+     * @return bool
+     */
+    public function requireIf($value, $rule, array $data = []): bool
+    {
+        [$field, $val] = explode(',', $rule);
+
+        if ($this->getDataValue($data, $field) == $val) {
+            return !empty($value) || '0' == $value;
+        }
+
+        return true;
+    }
+
+    /**
+     * 通过回调方法验证某个字段是否必须
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @param array $data  数据
+     * @return bool
+     */
+    public function requireCallback($value, $rule, array $data = []): bool
+    {
+        $result = call_user_func_array([$this, $rule], [$value, $data]);
+
+        if ($result) {
+            return !empty($value) || '0' == $value;
+        }
+
+        return true;
+    }
+
+    /**
+     * 验证某个字段有值的情况下必须
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @param array $data  数据
+     * @return bool
+     */
+    public function requireWith($value, $rule, array $data = []): bool
+    {
+        $val = $this->getDataValue($data, $rule);
+
+        if (!empty($val)) {
+            return !empty($value) || '0' == $value;
+        }
+
+        return true;
+    }
+
+    /**
+     * 验证某个字段没有值的情况下必须
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @param array $data  数据
+     * @return bool
+     */
+    public function requireWithout($value, $rule, array $data = []): bool
+    {
+        $val = $this->getDataValue($data, $rule);
+
+        if (empty($val)) {
+            return !empty($value) || '0' == $value;
+        }
+
+        return true;
+    }
+
+    /**
+     * 验证是否在范围内
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @return bool
+     */
+    public function in($value, $rule): bool
+    {
+        return in_array($value, is_array($rule) ? $rule : explode(',', $rule));
+    }
+
+    /**
+     * 验证是否不在某个范围
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @return bool
+     */
+    public function notIn($value, $rule): bool
+    {
+        return !in_array($value, is_array($rule) ? $rule : explode(',', $rule));
+    }
+
+    /**
+     * between验证数据
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @return bool
+     */
+    public function between($value, $rule): bool
+    {
+        if (is_string($rule)) {
+            $rule = explode(',', $rule);
+        }
+        [$min, $max] = $rule;
+
+        return $value >= $min && $value <= $max;
+    }
+
+    /**
+     * 使用notbetween验证数据
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @return bool
+     */
+    public function notBetween($value, $rule): bool
+    {
+        if (is_string($rule)) {
+            $rule = explode(',', $rule);
+        }
+        [$min, $max] = $rule;
+
+        return $value < $min || $value > $max;
+    }
+
+    /**
+     * 验证数据长度
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @return bool
+     */
+    public function length($value, $rule): bool
+    {
+        if (is_array($value)) {
+            $length = count($value);
+        } elseif ($value instanceof File) {
+            $length = $value->getSize();
+        } else {
+            $length = mb_strlen((string) $value);
+        }
+
+        if (is_string($rule) && strpos($rule, ',')) {
+            // 长度区间
+            [$min, $max] = explode(',', $rule);
+            return $length >= $min && $length <= $max;
+        }
+
+        // 指定长度
+        return $length == $rule;
+    }
+
+    /**
+     * 验证数据最大长度
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @return bool
+     */
+    public function max($value, $rule): bool
+    {
+        if (is_array($value)) {
+            $length = count($value);
+        } elseif ($value instanceof File) {
+            $length = $value->getSize();
+        } else {
+            $length = mb_strlen((string) $value);
+        }
+
+        return $length <= $rule;
+    }
+
+    /**
+     * 验证数据最小长度
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @return bool
+     */
+    public function min($value, $rule): bool
+    {
+        if (is_array($value)) {
+            $length = count($value);
+        } elseif ($value instanceof File) {
+            $length = $value->getSize();
+        } else {
+            $length = mb_strlen((string) $value);
+        }
+
+        return $length >= $rule;
+    }
+
+    /**
+     * 验证日期
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @param array $data  数据
+     * @return bool
+     */
+    public function after($value, $rule, array $data = []): bool
+    {
+        return strtotime($value) >= strtotime($rule);
+    }
+
+    /**
+     * 验证日期
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @param array $data  数据
+     * @return bool
+     */
+    public function before($value, $rule, array $data = []): bool
+    {
+        return strtotime($value) <= strtotime($rule);
+    }
+
+    /**
+     * 验证日期
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @param array $data  数据
+     * @return bool
+     */
+    public function afterWith($value, $rule, array $data = []): bool
+    {
+        $rule = $this->getDataValue($data, $rule);
+        return !is_null($rule) && strtotime($value) >= strtotime($rule);
+    }
+
+    /**
+     * 验证日期
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @param array $data  数据
+     * @return bool
+     */
+    public function beforeWith($value, $rule, array $data = []): bool
+    {
+        $rule = $this->getDataValue($data, $rule);
+        return !is_null($rule) && strtotime($value) <= strtotime($rule);
+    }
+
+    /**
+     * 验证有效期
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @return bool
+     */
+    public function expire($value, $rule): bool
+    {
+        if (is_string($rule)) {
+            $rule = explode(',', $rule);
+        }
+
+        [$start, $end] = $rule;
+
+        if (!is_numeric($start)) {
+            $start = strtotime($start);
+        }
+
+        if (!is_numeric($end)) {
+            $end = strtotime($end);
+        }
+
+        return time() >= $start && time() <= $end;
+    }
+
+    /**
+     * 验证IP许可
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @return bool
+     */
+    public function allowIp($value, $rule): bool
+    {
+        return in_array($value, is_array($rule) ? $rule : explode(',', $rule));
+    }
+
+    /**
+     * 验证IP禁用
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @return bool
+     */
+    public function denyIp($value, $rule): bool
+    {
+        return !in_array($value, is_array($rule) ? $rule : explode(',', $rule));
+    }
+
+    /**
+     * 使用正则验证数据
+     * @access public
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则 正则规则或者预定义正则名
+     * @return bool
+     */
+    public function regex($value, $rule): bool
+    {
+        if (isset($this->regex[$rule])) {
+            $rule = $this->regex[$rule];
+        } elseif (isset($this->defaultRegex[$rule])) {
+            $rule = $this->defaultRegex[$rule];
+        }
+
+        if (is_string($rule) && 0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) {
+            // 不是正则表达式则两端补上/
+            $rule = '/^' . $rule . '$/';
+        }
+
+        return is_scalar($value) && 1 === preg_match($rule, (string) $value);
+    }
+
+    /**
+     * 获取错误信息
+     * @return array|string
+     */
+    public function getError()
+    {
+        return $this->error;
+    }
+
+    /**
+     * 获取数据值
+     * @access protected
+     * @param array  $data 数据
+     * @param string $key  数据标识 支持二维
+     * @return mixed
+     */
+    protected function getDataValue(array $data, $key)
+    {
+        if (is_numeric($key)) {
+            $value = $key;
+        } elseif (is_string($key) && strpos($key, '.')) {
+            // 支持多维数组验证
+            foreach (explode('.', $key) as $key) {
+                if (!isset($data[$key])) {
+                    $value = null;
+                    break;
+                }
+                $value = $data = $data[$key];
+            }
+        } else {
+            $value = $data[$key] ?? null;
+        }
+
+        return $value;
+    }
+
+    /**
+     * 获取验证规则的错误提示信息
+     * @access protected
+     * @param string $attribute 字段英文名
+     * @param string $title     字段描述名
+     * @param string $type      验证规则名称
+     * @param mixed  $rule      验证规则数据
+     * @return string|array
+     */
+    protected function getRuleMsg(string $attribute, string $title, string $type, $rule)
+    {
+        if (isset($this->message[$attribute . '.' . $type])) {
+            $msg = $this->message[$attribute . '.' . $type];
+        } elseif (isset($this->message[$attribute][$type])) {
+            $msg = $this->message[$attribute][$type];
+        } elseif (isset($this->message[$attribute])) {
+            $msg = $this->message[$attribute];
+        } elseif (isset($this->typeMsg[$type])) {
+            $msg = $this->typeMsg[$type];
+        } elseif (0 === strpos($type, 'require')) {
+            $msg = $this->typeMsg['require'];
+        } else {
+            $msg = $title . $this->lang->get('not conform to the rules');
+        }
+
+        if (is_array($msg)) {
+            return $this->errorMsgIsArray($msg, $rule, $title);
+        }
+
+        return $this->parseErrorMsg($msg, $rule, $title);
+    }
+
+    /**
+     * 获取验证规则的错误提示信息
+     * @access protected
+     * @param string $msg   错误信息
+     * @param mixed  $rule  验证规则数据
+     * @param string $title 字段描述名
+     * @return string|array
+     */
+    protected function parseErrorMsg(string $msg, $rule, string $title)
+    {
+        if (0 === strpos($msg, '{%')) {
+            $msg = $this->lang->get(substr($msg, 2, -1));
+        } elseif ($this->lang->has($msg)) {
+            $msg = $this->lang->get($msg);
+        }
+
+        if (is_array($msg)) {
+            return $this->errorMsgIsArray($msg, $rule, $title);
+        }
+
+        // rule若是数组则转为字符串
+        if (is_array($rule)) {
+            $rule = implode(',', $rule);
+        }
+
+        if (is_scalar($rule) && false !== strpos($msg, ':')) {
+            // 变量替换
+            if (is_string($rule) && strpos($rule, ',')) {
+                $array = array_pad(explode(',', $rule), 3, '');
+            } else {
+                $array = array_pad([], 3, '');
+            }
+
+            $msg = str_replace(
+                [':attribute', ':1', ':2', ':3'],
+                [$title, $array[0], $array[1], $array[2]],
+                $msg
+            );
+
+            if (strpos($msg, ':rule')) {
+                $msg = str_replace(':rule', (string) $rule, $msg);
+            }
+        }
+
+        return $msg;
+    }
+
+    /**
+     * 错误信息数组处理
+     * @access protected
+     * @param array $msg   错误信息
+     * @param mixed  $rule  验证规则数据
+     * @param string $title 字段描述名
+     * @return array
+     */
+    protected function errorMsgIsArray(array $msg, $rule, string $title)
+    {
+        foreach ($msg as $key => $val) {
+            if (is_string($val)) {
+                $msg[$key] = $this->parseErrorMsg($val, $rule, $title);
+            }
+        }
+        return $msg;
+    }
+
+    /**
+     * 获取数据验证的场景
+     * @access protected
+     * @param string $scene 验证场景
+     * @return void
+     */
+    protected function getScene(string $scene): void
+    {
+        $this->only = $this->append = $this->remove = [];
+
+        if (method_exists($this, 'scene' . $scene)) {
+            call_user_func([$this, 'scene' . $scene]);
+        } elseif (isset($this->scene[$scene])) {
+            // 如果设置了验证适用场景
+            $this->only = $this->scene[$scene];
+        }
+    }
+
+    /**
+     * 动态方法 直接调用is方法进行验证
+     * @access public
+     * @param string $method 方法名
+     * @param array  $args   调用参数
+     * @return bool
+     */
+    public function __call($method, $args)
+    {
+        if ('is' == strtolower(substr($method, 0, 2))) {
+            $method = substr($method, 2);
+        }
+
+        array_push($args, lcfirst($method));
+
+        return call_user_func_array([$this, 'is'], $args);
+    }
+}

+ 191 - 0
vendor/topthink/framework/src/think/View.php

@@ -0,0 +1,191 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think;
+
+use think\helper\Arr;
+
+/**
+ * 视图类
+ * @package think
+ */
+class View extends Manager
+{
+
+    protected $namespace = '\\think\\view\\driver\\';
+
+    /**
+     * 模板变量
+     * @var array
+     */
+    protected $data = [];
+
+    /**
+     * 内容过滤
+     * @var mixed
+     */
+    protected $filter;
+
+    /**
+     * 获取模板引擎
+     * @access public
+     * @param string $type 模板引擎类型
+     * @return $this
+     */
+    public function engine(string $type = null)
+    {
+        return $this->driver($type);
+    }
+
+    /**
+     * 模板变量赋值
+     * @access public
+     * @param string|array $name  模板变量
+     * @param mixed        $value 变量值
+     * @return $this
+     */
+    public function assign($name, $value = null)
+    {
+        if (is_array($name)) {
+            $this->data = array_merge($this->data, $name);
+        } else {
+            $this->data[$name] = $value;
+        }
+
+        return $this;
+    }
+
+    /**
+     * 视图过滤
+     * @access public
+     * @param Callable $filter 过滤方法或闭包
+     * @return $this
+     */
+    public function filter(callable $filter = null)
+    {
+        $this->filter = $filter;
+        return $this;
+    }
+
+    /**
+     * 解析和获取模板内容 用于输出
+     * @access public
+     * @param string $template 模板文件名或者内容
+     * @param array  $vars     模板变量
+     * @return string
+     * @throws \Exception
+     */
+    public function fetch(string $template = '', array $vars = []): string
+    {
+        return $this->getContent(function () use ($vars, $template) {
+            $this->engine()->fetch($template, array_merge($this->data, $vars));
+        });
+    }
+
+    /**
+     * 渲染内容输出
+     * @access public
+     * @param string $content 内容
+     * @param array  $vars    模板变量
+     * @return string
+     */
+    public function display(string $content, array $vars = []): string
+    {
+        return $this->getContent(function () use ($vars, $content) {
+            $this->engine()->display($content, array_merge($this->data, $vars));
+        });
+    }
+
+    /**
+     * 获取模板引擎渲染内容
+     * @param $callback
+     * @return string
+     * @throws \Exception
+     */
+    protected function getContent($callback): string
+    {
+        // 页面缓存
+        ob_start();
+        if (PHP_VERSION > 8.0) {
+            ob_implicit_flush(false);
+        } else {
+            ob_implicit_flush(0);
+        }
+
+        // 渲染输出
+        try {
+            $callback();
+        } catch (\Exception $e) {
+            ob_end_clean();
+            throw $e;
+        }
+
+        // 获取并清空缓存
+        $content = ob_get_clean();
+
+        if ($this->filter) {
+            $content = call_user_func_array($this->filter, [$content]);
+        }
+
+        return $content;
+    }
+
+    /**
+     * 模板变量赋值
+     * @access public
+     * @param string $name  变量名
+     * @param mixed  $value 变量值
+     */
+    public function __set($name, $value)
+    {
+        $this->data[$name] = $value;
+    }
+
+    /**
+     * 取得模板显示变量的值
+     * @access protected
+     * @param string $name 模板变量
+     * @return mixed
+     */
+    public function __get($name)
+    {
+        return $this->data[$name];
+    }
+
+    /**
+     * 检测模板变量是否设置
+     * @access public
+     * @param string $name 模板变量名
+     * @return bool
+     */
+    public function __isset($name)
+    {
+        return isset($this->data[$name]);
+    }
+
+    protected function resolveConfig(string $name)
+    {
+        $config = $this->app->config->get('view', []);
+        Arr::forget($config, 'type');
+        return $config;
+    }
+
+    /**
+     * 默认驱动
+     * @return string|null
+     */
+    public function getDefaultDriver()
+    {
+        return $this->app->config->get('view.type', 'php');
+    }
+
+}

+ 357 - 0
vendor/topthink/framework/src/think/cache/Driver.php

@@ -0,0 +1,357 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\cache;
+
+use Closure;
+use DateInterval;
+use DateTime;
+use DateTimeInterface;
+use Exception;
+use Psr\SimpleCache\CacheInterface;
+use think\Container;
+use think\contract\CacheHandlerInterface;
+use think\exception\InvalidArgumentException;
+use throwable;
+
+/**
+ * 缓存基础类
+ */
+abstract class Driver implements CacheInterface, CacheHandlerInterface
+{
+    /**
+     * 驱动句柄
+     * @var object
+     */
+    protected $handler = null;
+
+    /**
+     * 缓存读取次数
+     * @var integer
+     */
+    protected $readTimes = 0;
+
+    /**
+     * 缓存写入次数
+     * @var integer
+     */
+    protected $writeTimes = 0;
+
+    /**
+     * 缓存参数
+     * @var array
+     */
+    protected $options = [];
+
+    /**
+     * 缓存标签
+     * @var array
+     */
+    protected $tag = [];
+
+    /**
+     * 获取有效期
+     * @access protected
+     * @param integer|DateTimeInterface|DateInterval $expire 有效期
+     * @return int
+     */
+    protected function getExpireTime($expire): int
+    {
+        if ($expire instanceof DateTimeInterface) {
+            $expire = $expire->getTimestamp() - time();
+        } elseif ($expire instanceof DateInterval) {
+            $expire = DateTime::createFromFormat('U', (string) time())
+                ->add($expire)
+                ->format('U') - time();
+        }
+
+        return (int) $expire;
+    }
+
+    /**
+     * 获取实际的缓存标识
+     * @access public
+     * @param string $name 缓存名
+     * @return string
+     */
+    public function getCacheKey(string $name): string
+    {
+        return $this->options['prefix'] . $name;
+    }
+
+    /**
+     * 读取缓存并删除
+     * @access public
+     * @param string $name 缓存变量名
+     * @return mixed
+     */
+    public function pull(string $name)
+    {
+        $result = $this->get($name, false);
+
+        if ($result) {
+            $this->delete($name);
+            return $result;
+        }
+    }
+
+    /**
+     * 追加(数组)缓存
+     * @access public
+     * @param string $name  缓存变量名
+     * @param mixed  $value 存储数据
+     * @return void
+     */
+    public function push(string $name, $value): void
+    {
+        $item = $this->get($name, []);
+
+        if (!is_array($item)) {
+            throw new InvalidArgumentException('only array cache can be push');
+        }
+
+        $item[] = $value;
+
+        if (count($item) > 1000) {
+            array_shift($item);
+        }
+
+        $item = array_unique($item);
+
+        $this->set($name, $item);
+    }
+
+    /**
+     * 追加TagSet数据
+     * @access public
+     * @param string $name  缓存变量名
+     * @param mixed  $value 存储数据
+     * @return void
+     */
+    public function append(string $name, $value): void
+    {
+        $this->push($name, $value);
+    }
+
+    /**
+     * 如果不存在则写入缓存
+     * @access public
+     * @param string $name   缓存变量名
+     * @param mixed  $value  存储数据
+     * @param int    $expire 有效时间 0为永久
+     * @return mixed
+     */
+    public function remember(string $name, $value, $expire = null)
+    {
+        if ($this->has($name)) {
+            return $this->get($name);
+        }
+
+        $time = time();
+
+        while ($time + 5 > time() && $this->has($name . '_lock')) {
+            // 存在锁定则等待
+            usleep(200000);
+        }
+
+        try {
+            // 锁定
+            $this->set($name . '_lock', true);
+
+            if ($value instanceof Closure) {
+                // 获取缓存数据
+                $value = Container::getInstance()->invokeFunction($value);
+            }
+
+            // 缓存数据
+            $this->set($name, $value, $expire);
+
+            // 解锁
+            $this->delete($name . '_lock');
+        } catch (Exception | throwable $e) {
+            $this->delete($name . '_lock');
+            throw $e;
+        }
+
+        return $value;
+    }
+
+    /**
+     * 缓存标签
+     * @access public
+     * @param string|array $name 标签名
+     * @return TagSet
+     */
+    public function tag($name): TagSet
+    {
+        $name = (array) $name;
+        $key  = implode('-', $name);
+
+        if (!isset($this->tag[$key])) {
+            $this->tag[$key] = new TagSet($name, $this);
+        }
+
+        return $this->tag[$key];
+    }
+
+    /**
+     * 获取标签包含的缓存标识
+     * @access public
+     * @param string $tag 标签标识
+     * @return array
+     */
+    public function getTagItems(string $tag): array
+    {
+        $name = $this->getTagKey($tag);
+        return $this->get($name, []);
+    }
+
+    /**
+     * 获取实际标签名
+     * @access public
+     * @param string $tag 标签名
+     * @return string
+     */
+    public function getTagKey(string $tag): string
+    {
+        return $this->options['tag_prefix'] . md5($tag);
+    }
+
+    /**
+     * 序列化数据
+     * @access protected
+     * @param mixed $data 缓存数据
+     * @return string
+     */
+    protected function serialize($data): string
+    {
+        if (is_numeric($data)) {
+            return (string) $data;
+        }
+
+        $serialize = $this->options['serialize'][0] ?? "serialize";
+
+        return $serialize($data);
+    }
+
+    /**
+     * 反序列化数据
+     * @access protected
+     * @param string $data 缓存数据
+     * @return mixed
+     */
+    protected function unserialize(string $data)
+    {
+        if (is_numeric($data)) {
+            return $data;
+        }
+
+        $unserialize = $this->options['serialize'][1] ?? "unserialize";
+
+        return $unserialize($data);
+    }
+
+    /**
+     * 返回句柄对象,可执行其它高级方法
+     *
+     * @access public
+     * @return object
+     */
+    public function handler()
+    {
+        return $this->handler;
+    }
+
+    /**
+     * 返回缓存读取次数
+     * @access public
+     * @return int
+     */
+    public function getReadTimes(): int
+    {
+        return $this->readTimes;
+    }
+
+    /**
+     * 返回缓存写入次数
+     * @access public
+     * @return int
+     */
+    public function getWriteTimes(): int
+    {
+        return $this->writeTimes;
+    }
+
+    /**
+     * 读取缓存
+     * @access public
+     * @param iterable $keys    缓存变量名
+     * @param mixed    $default 默认值
+     * @return iterable
+     * @throws InvalidArgumentException
+     */
+    public function getMultiple($keys, $default = null): iterable
+    {
+        $result = [];
+
+        foreach ($keys as $key) {
+            $result[$key] = $this->get($key, $default);
+        }
+
+        return $result;
+    }
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param iterable               $values 缓存数据
+     * @param null|int|\DateInterval $ttl    有效时间 0为永久
+     * @return bool
+     */
+    public function setMultiple($values, $ttl = null): bool
+    {
+        foreach ($values as $key => $val) {
+            $result = $this->set($key, $val, $ttl);
+
+            if (false === $result) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * 删除缓存
+     * @access public
+     * @param iterable $keys 缓存变量名
+     * @return bool
+     * @throws InvalidArgumentException
+     */
+    public function deleteMultiple($keys): bool
+    {
+        foreach ($keys as $key) {
+            $result = $this->delete($key);
+
+            if (false === $result) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public function __call($method, $args)
+    {
+        return call_user_func_array([$this->handler, $method], $args);
+    }
+}

+ 132 - 0
vendor/topthink/framework/src/think/cache/TagSet.php

@@ -0,0 +1,132 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\cache;
+
+/**
+ * 标签集合
+ */
+class TagSet
+{
+    /**
+     * 标签的缓存Key
+     * @var array
+     */
+    protected $tag;
+
+    /**
+     * 缓存句柄
+     * @var Driver
+     */
+    protected $handler;
+
+    /**
+     * 架构函数
+     * @access public
+     * @param array  $tag   缓存标签
+     * @param Driver $cache 缓存对象
+     */
+    public function __construct(array $tag, Driver $cache)
+    {
+        $this->tag     = $tag;
+        $this->handler = $cache;
+    }
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param string            $name   缓存变量名
+     * @param mixed             $value  存储数据
+     * @param integer|\DateTime $expire 有效时间(秒)
+     * @return bool
+     */
+    public function set(string $name, $value, $expire = null): bool
+    {
+        $this->handler->set($name, $value, $expire);
+
+        $this->append($name);
+
+        return true;
+    }
+
+    /**
+     * 追加缓存标识到标签
+     * @access public
+     * @param string $name 缓存变量名
+     * @return void
+     */
+    public function append(string $name): void
+    {
+        $name = $this->handler->getCacheKey($name);
+
+        foreach ($this->tag as $tag) {
+            $key = $this->handler->getTagKey($tag);
+            $this->handler->append($key, $name);
+        }
+    }
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param iterable               $values 缓存数据
+     * @param null|int|\DateInterval $ttl    有效时间 0为永久
+     * @return bool
+     */
+    public function setMultiple($values, $ttl = null): bool
+    {
+        foreach ($values as $key => $val) {
+            $result = $this->set($key, $val, $ttl);
+
+            if (false === $result) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * 如果不存在则写入缓存
+     * @access public
+     * @param string $name   缓存变量名
+     * @param mixed  $value  存储数据
+     * @param int    $expire 有效时间 0为永久
+     * @return mixed
+     */
+    public function remember(string $name, $value, $expire = null)
+    {
+        $result = $this->handler->remember($name, $value, $expire);
+
+        $this->append($name);
+
+        return $result;
+    }
+
+    /**
+     * 清除缓存
+     * @access public
+     * @return bool
+     */
+    public function clear(): bool
+    {
+        // 指定标签清除
+        foreach ($this->tag as $tag) {
+            $names = $this->handler->getTagItems($tag);
+            $this->handler->clearTag($names);
+
+            $key = $this->handler->getTagKey($tag);
+            $this->handler->delete($key);
+        }
+
+        return true;
+    }
+}

+ 304 - 0
vendor/topthink/framework/src/think/cache/driver/File.php

@@ -0,0 +1,304 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\cache\driver;
+
+use FilesystemIterator;
+use think\App;
+use think\cache\Driver;
+
+/**
+ * 文件缓存类
+ */
+class File extends Driver
+{
+    /**
+     * 配置参数
+     * @var array
+     */
+    protected $options = [
+        'expire'        => 0,
+        'cache_subdir'  => true,
+        'prefix'        => '',
+        'path'          => '',
+        'hash_type'     => 'md5',
+        'data_compress' => false,
+        'tag_prefix'    => 'tag:',
+        'serialize'     => [],
+    ];
+
+    /**
+     * 架构函数
+     * @param App   $app
+     * @param array $options 参数
+     */
+    public function __construct(App $app, array $options = [])
+    {
+        if (!empty($options)) {
+            $this->options = array_merge($this->options, $options);
+        }
+
+        if (empty($this->options['path'])) {
+            $this->options['path'] = $app->getRuntimePath() . 'cache';
+        }
+
+        if (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) {
+            $this->options['path'] .= DIRECTORY_SEPARATOR;
+        }
+    }
+
+    /**
+     * 取得变量的存储文件名
+     * @access public
+     * @param string $name 缓存变量名
+     * @return string
+     */
+    public function getCacheKey(string $name): string
+    {
+        $name = hash($this->options['hash_type'], $name);
+
+        if ($this->options['cache_subdir']) {
+            // 使用子目录
+            $name = substr($name, 0, 2) . DIRECTORY_SEPARATOR . substr($name, 2);
+        }
+
+        if ($this->options['prefix']) {
+            $name = $this->options['prefix'] . DIRECTORY_SEPARATOR . $name;
+        }
+
+        return $this->options['path'] . $name . '.php';
+    }
+
+    /**
+     * 获取缓存数据
+     * @param string $name 缓存标识名
+     * @return array|null
+     */
+    protected function getRaw(string $name)
+    {
+        $filename = $this->getCacheKey($name);
+
+        if (!is_file($filename)) {
+            return;
+        }
+
+        $content = @file_get_contents($filename);
+
+        if (false !== $content) {
+            $expire = (int) substr($content, 8, 12);
+            if (0 != $expire && time() - $expire > filemtime($filename)) {
+                //缓存过期删除缓存文件
+                $this->unlink($filename);
+                return;
+            }
+
+            $content = substr($content, 32);
+
+            if ($this->options['data_compress'] && function_exists('gzcompress')) {
+                //启用数据压缩
+                $content = gzuncompress($content);
+            }
+
+            return is_string($content) ? ['content' => $content, 'expire' => $expire] : null;
+        }
+    }
+
+    /**
+     * 判断缓存是否存在
+     * @access public
+     * @param string $name 缓存变量名
+     * @return bool
+     */
+    public function has($name): bool
+    {
+        return $this->getRaw($name) !== null;
+    }
+
+    /**
+     * 读取缓存
+     * @access public
+     * @param string $name    缓存变量名
+     * @param mixed  $default 默认值
+     * @return mixed
+     */
+    public function get($name, $default = null)
+    {
+        $this->readTimes++;
+
+        $raw = $this->getRaw($name);
+
+        return is_null($raw) ? $default : $this->unserialize($raw['content']);
+    }
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param string        $name   缓存变量名
+     * @param mixed         $value  存储数据
+     * @param int|\DateTime $expire 有效时间 0为永久
+     * @return bool
+     */
+    public function set($name, $value, $expire = null): bool
+    {
+        $this->writeTimes++;
+
+        if (is_null($expire)) {
+            $expire = $this->options['expire'];
+        }
+
+        $expire   = $this->getExpireTime($expire);
+        $filename = $this->getCacheKey($name);
+
+        $dir = dirname($filename);
+
+        if (!is_dir($dir)) {
+            try {
+                mkdir($dir, 0755, true);
+            } catch (\Exception $e) {
+                // 创建失败
+            }
+        }
+
+        $data = $this->serialize($value);
+
+        if ($this->options['data_compress'] && function_exists('gzcompress')) {
+            //数据压缩
+            $data = gzcompress($data, 3);
+        }
+
+        $data   = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
+        $result = file_put_contents($filename, $data);
+
+        if ($result) {
+            clearstatcache();
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * 自增缓存(针对数值缓存)
+     * @access public
+     * @param string $name 缓存变量名
+     * @param int    $step 步长
+     * @return false|int
+     */
+    public function inc(string $name, int $step = 1)
+    {
+        if ($raw = $this->getRaw($name)) {
+            $value  = $this->unserialize($raw['content']) + $step;
+            $expire = $raw['expire'];
+        } else {
+            $value  = $step;
+            $expire = 0;
+        }
+
+        return $this->set($name, $value, $expire) ? $value : false;
+    }
+
+    /**
+     * 自减缓存(针对数值缓存)
+     * @access public
+     * @param string $name 缓存变量名
+     * @param int    $step 步长
+     * @return false|int
+     */
+    public function dec(string $name, int $step = 1)
+    {
+        return $this->inc($name, -$step);
+    }
+
+    /**
+     * 删除缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @return bool
+     */
+    public function delete($name): bool
+    {
+        $this->writeTimes++;
+
+        return $this->unlink($this->getCacheKey($name));
+    }
+
+    /**
+     * 清除缓存
+     * @access public
+     * @return bool
+     */
+    public function clear(): bool
+    {
+        $this->writeTimes++;
+
+        $dirname = $this->options['path'] . $this->options['prefix'];
+
+        $this->rmdir($dirname);
+
+        return true;
+    }
+
+    /**
+     * 删除缓存标签
+     * @access public
+     * @param array $keys 缓存标识列表
+     * @return void
+     */
+    public function clearTag(array $keys): void
+    {
+        foreach ($keys as $key) {
+            $this->unlink($key);
+        }
+    }
+
+    /**
+     * 判断文件是否存在后,删除
+     * @access private
+     * @param string $path
+     * @return bool
+     */
+    private function unlink(string $path): bool
+    {
+        try {
+            return is_file($path) && unlink($path);
+        } catch (\Exception $e) {
+            return false;
+        }
+    }
+
+    /**
+     * 删除文件夹
+     * @param $dirname
+     * @return bool
+     */
+    private function rmdir($dirname)
+    {
+        if (!is_dir($dirname)) {
+            return false;
+        }
+
+        $items = new FilesystemIterator($dirname);
+
+        foreach ($items as $item) {
+            if ($item->isDir() && !$item->isLink()) {
+                $this->rmdir($item->getPathname());
+            } else {
+                $this->unlink($item->getPathname());
+            }
+        }
+
+        @rmdir($dirname);
+
+        return true;
+    }
+
+}

+ 209 - 0
vendor/topthink/framework/src/think/cache/driver/Memcache.php

@@ -0,0 +1,209 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\cache\driver;
+
+use think\cache\Driver;
+
+/**
+ * Memcache缓存类
+ */
+class Memcache extends Driver
+{
+    /**
+     * 配置参数
+     * @var array
+     */
+    protected $options = [
+        'host'       => '127.0.0.1',
+        'port'       => 11211,
+        'expire'     => 0,
+        'timeout'    => 0, // 超时时间(单位:毫秒)
+        'persistent' => true,
+        'prefix'     => '',
+        'tag_prefix' => 'tag:',
+        'serialize'  => [],
+    ];
+
+    /**
+     * 架构函数
+     * @access public
+     * @param array $options 缓存参数
+     * @throws \BadFunctionCallException
+     */
+    public function __construct(array $options = [])
+    {
+        if (!extension_loaded('memcache')) {
+            throw new \BadFunctionCallException('not support: memcache');
+        }
+
+        if (!empty($options)) {
+            $this->options = array_merge($this->options, $options);
+        }
+
+        $this->handler = new \Memcache;
+
+        // 支持集群
+        $hosts = (array) $this->options['host'];
+        $ports = (array) $this->options['port'];
+
+        if (empty($ports[0])) {
+            $ports[0] = 11211;
+        }
+
+        // 建立连接
+        foreach ($hosts as $i => $host) {
+            $port = $ports[$i] ?? $ports[0];
+            $this->options['timeout'] > 0 ?
+            $this->handler->addServer($host, (int) $port, $this->options['persistent'], 1, (int) $this->options['timeout']) :
+            $this->handler->addServer($host, (int) $port, $this->options['persistent'], 1);
+        }
+    }
+
+    /**
+     * 判断缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @return bool
+     */
+    public function has($name): bool
+    {
+        $key = $this->getCacheKey($name);
+
+        return false !== $this->handler->get($key);
+    }
+
+    /**
+     * 读取缓存
+     * @access public
+     * @param string $name    缓存变量名
+     * @param mixed  $default 默认值
+     * @return mixed
+     */
+    public function get($name, $default = null)
+    {
+        $this->readTimes++;
+
+        $result = $this->handler->get($this->getCacheKey($name));
+
+        return false !== $result ? $this->unserialize($result) : $default;
+    }
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param string        $name   缓存变量名
+     * @param mixed         $value  存储数据
+     * @param int|\DateTime $expire 有效时间(秒)
+     * @return bool
+     */
+    public function set($name, $value, $expire = null): bool
+    {
+        $this->writeTimes++;
+
+        if (is_null($expire)) {
+            $expire = $this->options['expire'];
+        }
+
+        $key    = $this->getCacheKey($name);
+        $expire = $this->getExpireTime($expire);
+        $value  = $this->serialize($value);
+
+        if ($this->handler->set($key, $value, 0, $expire)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * 自增缓存(针对数值缓存)
+     * @access public
+     * @param string $name 缓存变量名
+     * @param int    $step 步长
+     * @return false|int
+     */
+    public function inc(string $name, int $step = 1)
+    {
+        $this->writeTimes++;
+
+        $key = $this->getCacheKey($name);
+
+        if ($this->handler->get($key)) {
+            return $this->handler->increment($key, $step);
+        }
+
+        return $this->handler->set($key, $step);
+    }
+
+    /**
+     * 自减缓存(针对数值缓存)
+     * @access public
+     * @param string $name 缓存变量名
+     * @param int    $step 步长
+     * @return false|int
+     */
+    public function dec(string $name, int $step = 1)
+    {
+        $this->writeTimes++;
+
+        $key   = $this->getCacheKey($name);
+        $value = $this->handler->get($key) - $step;
+        $res   = $this->handler->set($key, $value);
+
+        return !$res ? false : $value;
+    }
+
+    /**
+     * 删除缓存
+     * @access public
+     * @param string     $name 缓存变量名
+     * @param bool|false $ttl
+     * @return bool
+     */
+    public function delete($name, $ttl = false): bool
+    {
+        $this->writeTimes++;
+
+        $key = $this->getCacheKey($name);
+
+        return false === $ttl ?
+        $this->handler->delete($key) :
+        $this->handler->delete($key, $ttl);
+    }
+
+    /**
+     * 清除缓存
+     * @access public
+     * @return bool
+     */
+    public function clear(): bool
+    {
+        $this->writeTimes++;
+
+        return $this->handler->flush();
+    }
+
+    /**
+     * 删除缓存标签
+     * @access public
+     * @param array $keys 缓存标识列表
+     * @return void
+     */
+    public function clearTag(array $keys): void
+    {
+        foreach ($keys as $key) {
+            $this->handler->delete($key);
+        }
+    }
+
+}

+ 221 - 0
vendor/topthink/framework/src/think/cache/driver/Memcached.php

@@ -0,0 +1,221 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\cache\driver;
+
+use think\cache\Driver;
+
+/**
+ * Memcached缓存类
+ */
+class Memcached extends Driver
+{
+    /**
+     * 配置参数
+     * @var array
+     */
+    protected $options = [
+        'host'       => '127.0.0.1',
+        'port'       => 11211,
+        'expire'     => 0,
+        'timeout'    => 0, // 超时时间(单位:毫秒)
+        'prefix'     => '',
+        'username'   => '', //账号
+        'password'   => '', //密码
+        'option'     => [],
+        'tag_prefix' => 'tag:',
+        'serialize'  => [],
+    ];
+
+    /**
+     * 架构函数
+     * @access public
+     * @param array $options 缓存参数
+     */
+    public function __construct(array $options = [])
+    {
+        if (!extension_loaded('memcached')) {
+            throw new \BadFunctionCallException('not support: memcached');
+        }
+
+        if (!empty($options)) {
+            $this->options = array_merge($this->options, $options);
+        }
+
+        $this->handler = new \Memcached;
+
+        if (!empty($this->options['option'])) {
+            $this->handler->setOptions($this->options['option']);
+        }
+
+        // 设置连接超时时间(单位:毫秒)
+        if ($this->options['timeout'] > 0) {
+            $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->options['timeout']);
+        }
+
+        // 支持集群
+        $hosts = (array) $this->options['host'];
+        $ports = (array) $this->options['port'];
+        if (empty($ports[0])) {
+            $ports[0] = 11211;
+        }
+
+        // 建立连接
+        $servers = [];
+        foreach ($hosts as $i => $host) {
+            $servers[] = [$host, $ports[$i] ?? $ports[0], 1];
+        }
+
+        $this->handler->addServers($servers);
+
+        if ('' != $this->options['username']) {
+            $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true);
+            $this->handler->setSaslAuthData($this->options['username'], $this->options['password']);
+        }
+    }
+
+    /**
+     * 判断缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @return bool
+     */
+    public function has($name): bool
+    {
+        $key = $this->getCacheKey($name);
+
+        return $this->handler->get($key) ? true : false;
+    }
+
+    /**
+     * 读取缓存
+     * @access public
+     * @param string $name    缓存变量名
+     * @param mixed  $default 默认值
+     * @return mixed
+     */
+    public function get($name, $default = null)
+    {
+        $this->readTimes++;
+
+        $result = $this->handler->get($this->getCacheKey($name));
+
+        return false !== $result ? $this->unserialize($result) : $default;
+    }
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param string            $name   缓存变量名
+     * @param mixed             $value  存储数据
+     * @param integer|\DateTime $expire 有效时间(秒)
+     * @return bool
+     */
+    public function set($name, $value, $expire = null): bool
+    {
+        $this->writeTimes++;
+
+        if (is_null($expire)) {
+            $expire = $this->options['expire'];
+        }
+
+        $key    = $this->getCacheKey($name);
+        $expire = $this->getExpireTime($expire);
+        $value  = $this->serialize($value);
+
+        if ($this->handler->set($key, $value, $expire)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * 自增缓存(针对数值缓存)
+     * @access public
+     * @param string $name 缓存变量名
+     * @param int    $step 步长
+     * @return false|int
+     */
+    public function inc(string $name, int $step = 1)
+    {
+        $this->writeTimes++;
+
+        $key = $this->getCacheKey($name);
+
+        if ($this->handler->get($key)) {
+            return $this->handler->increment($key, $step);
+        }
+
+        return $this->handler->set($key, $step);
+    }
+
+    /**
+     * 自减缓存(针对数值缓存)
+     * @access public
+     * @param string $name 缓存变量名
+     * @param int    $step 步长
+     * @return false|int
+     */
+    public function dec(string $name, int $step = 1)
+    {
+        $this->writeTimes++;
+
+        $key   = $this->getCacheKey($name);
+        $value = $this->handler->get($key) - $step;
+        $res   = $this->handler->set($key, $value);
+
+        return !$res ? false : $value;
+    }
+
+    /**
+     * 删除缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @param bool|false $ttl
+     * @return bool
+     */
+    public function delete($name, $ttl = false): bool
+    {
+        $this->writeTimes++;
+
+        $key = $this->getCacheKey($name);
+
+        return false === $ttl ?
+        $this->handler->delete($key) :
+        $this->handler->delete($key, $ttl);
+    }
+
+    /**
+     * 清除缓存
+     * @access public
+     * @return bool
+     */
+    public function clear(): bool
+    {
+        $this->writeTimes++;
+
+        return $this->handler->flush();
+    }
+
+    /**
+     * 删除缓存标签
+     * @access public
+     * @param array $keys 缓存标识列表
+     * @return void
+     */
+    public function clearTag(array $keys): void
+    {
+        $this->handler->deleteMulti($keys);
+    }
+
+}

+ 249 - 0
vendor/topthink/framework/src/think/cache/driver/Redis.php

@@ -0,0 +1,249 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\cache\driver;
+
+use think\cache\Driver;
+
+/**
+ * Redis缓存驱动,适合单机部署、有前端代理实现高可用的场景,性能最好
+ * 有需要在业务层实现读写分离、或者使用RedisCluster的需求,请使用Redisd驱动
+ *
+ * 要求安装phpredis扩展:https://github.com/nicolasff/phpredis
+ * @author    尘缘 <130775@qq.com>
+ */
+class Redis extends Driver
+{
+    /** @var \Predis\Client|\Redis */
+    protected $handler;
+
+    /**
+     * 配置参数
+     * @var array
+     */
+    protected $options = [
+        'host'       => '127.0.0.1',
+        'port'       => 6379,
+        'password'   => '',
+        'select'     => 0,
+        'timeout'    => 0,
+        'expire'     => 0,
+        'persistent' => false,
+        'prefix'     => '',
+        'tag_prefix' => 'tag:',
+        'serialize'  => [],
+    ];
+
+    /**
+     * 架构函数
+     * @access public
+     * @param array $options 缓存参数
+     */
+    public function __construct(array $options = [])
+    {
+        if (!empty($options)) {
+            $this->options = array_merge($this->options, $options);
+        }
+
+        if (extension_loaded('redis')) {
+            $this->handler = new \Redis;
+
+            if ($this->options['persistent']) {
+                $this->handler->pconnect($this->options['host'], (int) $this->options['port'], (int) $this->options['timeout'], 'persistent_id_' . $this->options['select']);
+            } else {
+                $this->handler->connect($this->options['host'], (int) $this->options['port'], (int) $this->options['timeout']);
+            }
+
+            if ('' != $this->options['password']) {
+                $this->handler->auth($this->options['password']);
+            }
+        } elseif (class_exists('\Predis\Client')) {
+            $params = [];
+            foreach ($this->options as $key => $val) {
+                if (in_array($key, ['aggregate', 'cluster', 'connections', 'exceptions', 'prefix', 'profile', 'replication', 'parameters'])) {
+                    $params[$key] = $val;
+                    unset($this->options[$key]);
+                }
+            }
+
+            if ('' == $this->options['password']) {
+                unset($this->options['password']);
+            }
+
+            $this->handler = new \Predis\Client($this->options, $params);
+
+            $this->options['prefix'] = '';
+        } else {
+            throw new \BadFunctionCallException('not support: redis');
+        }
+
+        if (0 != $this->options['select']) {
+            $this->handler->select((int) $this->options['select']);
+        }
+    }
+
+    /**
+     * 判断缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @return bool
+     */
+    public function has($name): bool
+    {
+        return $this->handler->exists($this->getCacheKey($name)) ? true : false;
+    }
+
+    /**
+     * 读取缓存
+     * @access public
+     * @param string $name    缓存变量名
+     * @param mixed  $default 默认值
+     * @return mixed
+     */
+    public function get($name, $default = null)
+    {
+        $this->readTimes++;
+        $key   = $this->getCacheKey($name);
+        $value = $this->handler->get($key);
+
+        if (false === $value || is_null($value)) {
+            return $default;
+        }
+
+        return $this->unserialize($value);
+    }
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param string            $name   缓存变量名
+     * @param mixed             $value  存储数据
+     * @param integer|\DateTime $expire 有效时间(秒)
+     * @return bool
+     */
+    public function set($name, $value, $expire = null): bool
+    {
+        $this->writeTimes++;
+
+        if (is_null($expire)) {
+            $expire = $this->options['expire'];
+        }
+
+        $key    = $this->getCacheKey($name);
+        $expire = $this->getExpireTime($expire);
+        $value  = $this->serialize($value);
+
+        if ($expire) {
+            $this->handler->setex($key, $expire, $value);
+        } else {
+            $this->handler->set($key, $value);
+        }
+
+        return true;
+    }
+
+    /**
+     * 自增缓存(针对数值缓存)
+     * @access public
+     * @param string $name 缓存变量名
+     * @param int    $step 步长
+     * @return false|int
+     */
+    public function inc(string $name, int $step = 1)
+    {
+        $this->writeTimes++;
+        $key = $this->getCacheKey($name);
+
+        return $this->handler->incrby($key, $step);
+    }
+
+    /**
+     * 自减缓存(针对数值缓存)
+     * @access public
+     * @param string $name 缓存变量名
+     * @param int    $step 步长
+     * @return false|int
+     */
+    public function dec(string $name, int $step = 1)
+    {
+        $this->writeTimes++;
+        $key = $this->getCacheKey($name);
+
+        return $this->handler->decrby($key, $step);
+    }
+
+    /**
+     * 删除缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @return bool
+     */
+    public function delete($name): bool
+    {
+        $this->writeTimes++;
+
+        $key    = $this->getCacheKey($name);
+        $result = $this->handler->del($key);
+        return $result > 0;
+    }
+
+    /**
+     * 清除缓存
+     * @access public
+     * @return bool
+     */
+    public function clear(): bool
+    {
+        $this->writeTimes++;
+        $this->handler->flushDB();
+        return true;
+    }
+
+    /**
+     * 删除缓存标签
+     * @access public
+     * @param array $keys 缓存标识列表
+     * @return void
+     */
+    public function clearTag(array $keys): void
+    {
+        // 指定标签清除
+        $this->handler->del($keys);
+    }
+
+    /**
+     * 追加TagSet数据
+     * @access public
+     * @param string $name  缓存标识
+     * @param mixed  $value 数据
+     * @return void
+     */
+    public function append(string $name, $value): void
+    {
+        $key = $this->getCacheKey($name);
+        $this->handler->sAdd($key, $value);
+    }
+
+    /**
+     * 获取标签包含的缓存标识
+     * @access public
+     * @param string $tag 缓存标签
+     * @return array
+     */
+    public function getTagItems(string $tag): array
+    {
+        $name = $this->getTagKey($tag);
+        $key  = $this->getCacheKey($name);
+        return $this->handler->sMembers($key);
+    }
+
+}

+ 175 - 0
vendor/topthink/framework/src/think/cache/driver/Wincache.php

@@ -0,0 +1,175 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\cache\driver;
+
+use think\cache\Driver;
+
+/**
+ * Wincache缓存驱动
+ */
+class Wincache extends Driver
+{
+    /**
+     * 配置参数
+     * @var array
+     */
+    protected $options = [
+        'prefix'     => '',
+        'expire'     => 0,
+        'tag_prefix' => 'tag:',
+        'serialize'  => [],
+    ];
+
+    /**
+     * 架构函数
+     * @access public
+     * @param array $options 缓存参数
+     * @throws \BadFunctionCallException
+     */
+    public function __construct(array $options = [])
+    {
+        if (!function_exists('wincache_ucache_info')) {
+            throw new \BadFunctionCallException('not support: WinCache');
+        }
+
+        if (!empty($options)) {
+            $this->options = array_merge($this->options, $options);
+        }
+    }
+
+    /**
+     * 判断缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @return bool
+     */
+    public function has($name): bool
+    {
+        $this->readTimes++;
+
+        $key = $this->getCacheKey($name);
+
+        return wincache_ucache_exists($key);
+    }
+
+    /**
+     * 读取缓存
+     * @access public
+     * @param string $name    缓存变量名
+     * @param mixed  $default 默认值
+     * @return mixed
+     */
+    public function get($name, $default = null)
+    {
+        $this->readTimes++;
+
+        $key = $this->getCacheKey($name);
+
+        return wincache_ucache_exists($key) ? $this->unserialize(wincache_ucache_get($key)) : $default;
+    }
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param string            $name   缓存变量名
+     * @param mixed             $value  存储数据
+     * @param integer|\DateTime $expire 有效时间(秒)
+     * @return bool
+     */
+    public function set($name, $value, $expire = null): bool
+    {
+        $this->writeTimes++;
+
+        if (is_null($expire)) {
+            $expire = $this->options['expire'];
+        }
+
+        $key    = $this->getCacheKey($name);
+        $expire = $this->getExpireTime($expire);
+        $value  = $this->serialize($value);
+
+        if (wincache_ucache_set($key, $value, $expire)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * 自增缓存(针对数值缓存)
+     * @access public
+     * @param string $name 缓存变量名
+     * @param int    $step 步长
+     * @return false|int
+     */
+    public function inc(string $name, int $step = 1)
+    {
+        $this->writeTimes++;
+
+        $key = $this->getCacheKey($name);
+
+        return wincache_ucache_inc($key, $step);
+    }
+
+    /**
+     * 自减缓存(针对数值缓存)
+     * @access public
+     * @param string $name 缓存变量名
+     * @param int    $step 步长
+     * @return false|int
+     */
+    public function dec(string $name, int $step = 1)
+    {
+        $this->writeTimes++;
+
+        $key = $this->getCacheKey($name);
+
+        return wincache_ucache_dec($key, $step);
+    }
+
+    /**
+     * 删除缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @return bool
+     */
+    public function delete($name): bool
+    {
+        $this->writeTimes++;
+
+        return wincache_ucache_delete($this->getCacheKey($name));
+    }
+
+    /**
+     * 清除缓存
+     * @access public
+     * @return bool
+     */
+    public function clear(): bool
+    {
+        $this->writeTimes++;
+        return wincache_ucache_clear();
+    }
+
+    /**
+     * 删除缓存标签
+     * @access public
+     * @param array $keys 缓存标识列表
+     * @return void
+     */
+    public function clearTag(array $keys): void
+    {
+        wincache_ucache_delete($keys);
+    }
+
+}

+ 504 - 0
vendor/topthink/framework/src/think/console/Command.php

@@ -0,0 +1,504 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\console;
+
+use Exception;
+use InvalidArgumentException;
+use LogicException;
+use think\App;
+use think\Console;
+use think\console\input\Argument;
+use think\console\input\Definition;
+use think\console\input\Option;
+
+abstract class Command
+{
+
+    /** @var  Console */
+    private $console;
+    private $name;
+    private $processTitle;
+    private $aliases = [];
+    private $definition;
+    private $help;
+    private $description;
+    private $ignoreValidationErrors          = false;
+    private $consoleDefinitionMerged         = false;
+    private $consoleDefinitionMergedWithArgs = false;
+    private $synopsis                        = [];
+    private $usages                          = [];
+
+    /** @var  Input */
+    protected $input;
+
+    /** @var  Output */
+    protected $output;
+
+    /** @var App */
+    protected $app;
+
+    /**
+     * 构造方法
+     * @throws LogicException
+     * @api
+     */
+    public function __construct()
+    {
+        $this->definition = new Definition();
+
+        $this->configure();
+
+        if (!$this->name) {
+            throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this)));
+        }
+    }
+
+    /**
+     * 忽略验证错误
+     */
+    public function ignoreValidationErrors(): void
+    {
+        $this->ignoreValidationErrors = true;
+    }
+
+    /**
+     * 设置控制台
+     * @param Console $console
+     */
+    public function setConsole(Console $console = null): void
+    {
+        $this->console = $console;
+    }
+
+    /**
+     * 获取控制台
+     * @return Console
+     * @api
+     */
+    public function getConsole(): Console
+    {
+        return $this->console;
+    }
+
+    /**
+     * 设置app
+     * @param App $app
+     */
+    public function setApp(App $app)
+    {
+        $this->app = $app;
+    }
+
+    /**
+     * 获取app
+     * @return App
+     */
+    public function getApp()
+    {
+        return $this->app;
+    }
+
+    /**
+     * 是否有效
+     * @return bool
+     */
+    public function isEnabled(): bool
+    {
+        return true;
+    }
+
+    /**
+     * 配置指令
+     */
+    protected function configure()
+    {
+    }
+
+    /**
+     * 执行指令
+     * @param Input  $input
+     * @param Output $output
+     * @return null|int
+     * @throws LogicException
+     * @see setCode()
+     */
+    protected function execute(Input $input, Output $output)
+    {
+        return $this->app->invoke([$this, 'handle']);
+    }
+
+    /**
+     * 用户验证
+     * @param Input  $input
+     * @param Output $output
+     */
+    protected function interact(Input $input, Output $output)
+    {
+    }
+
+    /**
+     * 初始化
+     * @param Input  $input  An InputInterface instance
+     * @param Output $output An OutputInterface instance
+     */
+    protected function initialize(Input $input, Output $output)
+    {
+    }
+
+    /**
+     * 执行
+     * @param Input  $input
+     * @param Output $output
+     * @return int
+     * @throws Exception
+     * @see setCode()
+     * @see execute()
+     */
+    public function run(Input $input, Output $output): int
+    {
+        $this->input  = $input;
+        $this->output = $output;
+
+        $this->getSynopsis(true);
+        $this->getSynopsis(false);
+
+        $this->mergeConsoleDefinition();
+
+        try {
+            $input->bind($this->definition);
+        } catch (Exception $e) {
+            if (!$this->ignoreValidationErrors) {
+                throw $e;
+            }
+        }
+
+        $this->initialize($input, $output);
+
+        if (null !== $this->processTitle) {
+            if (function_exists('cli_set_process_title')) {
+                if (false === @cli_set_process_title($this->processTitle)) {
+                    if ('Darwin' === PHP_OS) {
+                        $output->writeln('<comment>Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.</comment>');
+                    } else {
+                        $error = error_get_last();
+                        trigger_error($error['message'], E_USER_WARNING);
+                    }
+                }
+            } elseif (function_exists('setproctitle')) {
+                setproctitle($this->processTitle);
+            } elseif (Output::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) {
+                $output->writeln('<comment>Install the proctitle PECL to be able to change the process title.</comment>');
+            }
+        }
+
+        if ($input->isInteractive()) {
+            $this->interact($input, $output);
+        }
+
+        $input->validate();
+
+        $statusCode = $this->execute($input, $output);
+
+        return is_numeric($statusCode) ? (int) $statusCode : 0;
+    }
+
+    /**
+     * 合并参数定义
+     * @param bool $mergeArgs
+     */
+    public function mergeConsoleDefinition(bool $mergeArgs = true)
+    {
+        if (null === $this->console
+            || (true === $this->consoleDefinitionMerged
+                && ($this->consoleDefinitionMergedWithArgs || !$mergeArgs))
+        ) {
+            return;
+        }
+
+        if ($mergeArgs) {
+            $currentArguments = $this->definition->getArguments();
+            $this->definition->setArguments($this->console->getDefinition()->getArguments());
+            $this->definition->addArguments($currentArguments);
+        }
+
+        $this->definition->addOptions($this->console->getDefinition()->getOptions());
+
+        $this->consoleDefinitionMerged = true;
+        if ($mergeArgs) {
+            $this->consoleDefinitionMergedWithArgs = true;
+        }
+    }
+
+    /**
+     * 设置参数定义
+     * @param array|Definition $definition
+     * @return Command
+     * @api
+     */
+    public function setDefinition($definition)
+    {
+        if ($definition instanceof Definition) {
+            $this->definition = $definition;
+        } else {
+            $this->definition->setDefinition($definition);
+        }
+
+        $this->consoleDefinitionMerged = false;
+
+        return $this;
+    }
+
+    /**
+     * 获取参数定义
+     * @return Definition
+     * @api
+     */
+    public function getDefinition(): Definition
+    {
+        return $this->definition;
+    }
+
+    /**
+     * 获取当前指令的参数定义
+     * @return Definition
+     */
+    public function getNativeDefinition(): Definition
+    {
+        return $this->getDefinition();
+    }
+
+    /**
+     * 添加参数
+     * @param string $name        名称
+     * @param int    $mode        类型
+     * @param string $description 描述
+     * @param mixed  $default     默认值
+     * @return Command
+     */
+    public function addArgument(string $name, int $mode = null, string $description = '', $default = null)
+    {
+        $this->definition->addArgument(new Argument($name, $mode, $description, $default));
+
+        return $this;
+    }
+
+    /**
+     * 添加选项
+     * @param string $name        选项名称
+     * @param string $shortcut    别名
+     * @param int    $mode        类型
+     * @param string $description 描述
+     * @param mixed  $default     默认值
+     * @return Command
+     */
+    public function addOption(string $name, string $shortcut = null, int $mode = null, string $description = '', $default = null)
+    {
+        $this->definition->addOption(new Option($name, $shortcut, $mode, $description, $default));
+
+        return $this;
+    }
+
+    /**
+     * 设置指令名称
+     * @param string $name
+     * @return Command
+     * @throws InvalidArgumentException
+     */
+    public function setName(string $name)
+    {
+        $this->validateName($name);
+
+        $this->name = $name;
+
+        return $this;
+    }
+
+    /**
+     * 设置进程名称
+     *
+     * PHP 5.5+ or the proctitle PECL library is required
+     *
+     * @param string $title The process title
+     *
+     * @return $this
+     */
+    public function setProcessTitle($title)
+    {
+        $this->processTitle = $title;
+
+        return $this;
+    }
+
+    /**
+     * 获取指令名称
+     * @return string
+     */
+    public function getName(): string
+    {
+        return $this->name ?: '';
+    }
+
+    /**
+     * 设置描述
+     * @param string $description
+     * @return Command
+     */
+    public function setDescription(string $description)
+    {
+        $this->description = $description;
+
+        return $this;
+    }
+
+    /**
+     *  获取描述
+     * @return string
+     */
+    public function getDescription(): string
+    {
+        return $this->description ?: '';
+    }
+
+    /**
+     * 设置帮助信息
+     * @param string $help
+     * @return Command
+     */
+    public function setHelp(string $help)
+    {
+        $this->help = $help;
+
+        return $this;
+    }
+
+    /**
+     * 获取帮助信息
+     * @return string
+     */
+    public function getHelp(): string
+    {
+        return $this->help ?: '';
+    }
+
+    /**
+     * 描述信息
+     * @return string
+     */
+    public function getProcessedHelp(): string
+    {
+        $name = $this->name;
+
+        $placeholders = [
+            '%command.name%',
+            '%command.full_name%',
+        ];
+        $replacements = [
+            $name,
+            $_SERVER['PHP_SELF'] . ' ' . $name,
+        ];
+
+        return str_replace($placeholders, $replacements, $this->getHelp());
+    }
+
+    /**
+     * 设置别名
+     * @param string[] $aliases
+     * @return Command
+     * @throws InvalidArgumentException
+     */
+    public function setAliases(iterable $aliases)
+    {
+        foreach ($aliases as $alias) {
+            $this->validateName($alias);
+        }
+
+        $this->aliases = $aliases;
+
+        return $this;
+    }
+
+    /**
+     * 获取别名
+     * @return array
+     */
+    public function getAliases(): array
+    {
+        return $this->aliases;
+    }
+
+    /**
+     * 获取简介
+     * @param bool $short 是否简单的
+     * @return string
+     */
+    public function getSynopsis(bool $short = false): string
+    {
+        $key = $short ? 'short' : 'long';
+
+        if (!isset($this->synopsis[$key])) {
+            $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short)));
+        }
+
+        return $this->synopsis[$key];
+    }
+
+    /**
+     * 添加用法介绍
+     * @param string $usage
+     * @return $this
+     */
+    public function addUsage(string $usage)
+    {
+        if (0 !== strpos($usage, $this->name)) {
+            $usage = sprintf('%s %s', $this->name, $usage);
+        }
+
+        $this->usages[] = $usage;
+
+        return $this;
+    }
+
+    /**
+     * 获取用法介绍
+     * @return array
+     */
+    public function getUsages(): array
+    {
+        return $this->usages;
+    }
+
+    /**
+     * 验证指令名称
+     * @param string $name
+     * @throws InvalidArgumentException
+     */
+    private function validateName(string $name)
+    {
+        if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
+            throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
+        }
+    }
+
+    /**
+     * 输出表格
+     * @param Table $table
+     * @return string
+     */
+    protected function table(Table $table): string
+    {
+        $content = $table->render();
+        $this->output->writeln($content);
+        return $content;
+    }
+
+}

+ 465 - 0
vendor/topthink/framework/src/think/console/Input.php

@@ -0,0 +1,465 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\console;
+
+use think\console\input\Argument;
+use think\console\input\Definition;
+use think\console\input\Option;
+
+class Input
+{
+
+    /**
+     * @var Definition
+     */
+    protected $definition;
+
+    /**
+     * @var Option[]
+     */
+    protected $options = [];
+
+    /**
+     * @var Argument[]
+     */
+    protected $arguments = [];
+
+    protected $interactive = true;
+
+    private $tokens;
+    private $parsed;
+
+    public function __construct($argv = null)
+    {
+        if (null === $argv) {
+            $argv = $_SERVER['argv'];
+            // 去除命令名
+            array_shift($argv);
+        }
+
+        $this->tokens = $argv;
+
+        $this->definition = new Definition();
+    }
+
+    protected function setTokens(array $tokens)
+    {
+        $this->tokens = $tokens;
+    }
+
+    /**
+     * 绑定实例
+     * @param Definition $definition A InputDefinition instance
+     */
+    public function bind(Definition $definition): void
+    {
+        $this->arguments  = [];
+        $this->options    = [];
+        $this->definition = $definition;
+
+        $this->parse();
+    }
+
+    /**
+     * 解析参数
+     */
+    protected function parse(): void
+    {
+        $parseOptions = true;
+        $this->parsed = $this->tokens;
+        while (null !== $token = array_shift($this->parsed)) {
+            if ($parseOptions && '' == $token) {
+                $this->parseArgument($token);
+            } elseif ($parseOptions && '--' == $token) {
+                $parseOptions = false;
+            } elseif ($parseOptions && 0 === strpos($token, '--')) {
+                $this->parseLongOption($token);
+            } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
+                $this->parseShortOption($token);
+            } else {
+                $this->parseArgument($token);
+            }
+        }
+    }
+
+    /**
+     * 解析短选项
+     * @param string $token 当前的指令.
+     */
+    private function parseShortOption(string $token): void
+    {
+        $name = substr($token, 1);
+
+        if (strlen($name) > 1) {
+            if ($this->definition->hasShortcut($name[0])
+                && $this->definition->getOptionForShortcut($name[0])->acceptValue()
+            ) {
+                $this->addShortOption($name[0], substr($name, 1));
+            } else {
+                $this->parseShortOptionSet($name);
+            }
+        } else {
+            $this->addShortOption($name, null);
+        }
+    }
+
+    /**
+     * 解析短选项
+     * @param string $name 当前指令
+     * @throws \RuntimeException
+     */
+    private function parseShortOptionSet(string $name): void
+    {
+        $len = strlen($name);
+        for ($i = 0; $i < $len; ++$i) {
+            if (!$this->definition->hasShortcut($name[$i])) {
+                throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i]));
+            }
+
+            $option = $this->definition->getOptionForShortcut($name[$i]);
+            if ($option->acceptValue()) {
+                $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));
+
+                break;
+            } else {
+                $this->addLongOption($option->getName(), null);
+            }
+        }
+    }
+
+    /**
+     * 解析完整选项
+     * @param string $token 当前指令
+     */
+    private function parseLongOption(string $token): void
+    {
+        $name = substr($token, 2);
+
+        if (false !== $pos = strpos($name, '=')) {
+            $this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1));
+        } else {
+            $this->addLongOption($name, null);
+        }
+    }
+
+    /**
+     * 解析参数
+     * @param string $token 当前指令
+     * @throws \RuntimeException
+     */
+    private function parseArgument(string $token): void
+    {
+        $c = count($this->arguments);
+
+        if ($this->definition->hasArgument($c)) {
+            $arg = $this->definition->getArgument($c);
+
+            $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token;
+
+        } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) {
+            $arg = $this->definition->getArgument($c - 1);
+
+            $this->arguments[$arg->getName()][] = $token;
+        } else {
+            throw new \RuntimeException('Too many arguments.');
+        }
+    }
+
+    /**
+     * 添加一个短选项的值
+     * @param string $shortcut 短名称
+     * @param mixed  $value    值
+     * @throws \RuntimeException
+     */
+    private function addShortOption(string $shortcut, $value): void
+    {
+        if (!$this->definition->hasShortcut($shortcut)) {
+            throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
+        }
+
+        $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
+    }
+
+    /**
+     * 添加一个完整选项的值
+     * @param string $name  选项名
+     * @param mixed  $value 值
+     * @throws \RuntimeException
+     */
+    private function addLongOption(string $name, $value): void
+    {
+        if (!$this->definition->hasOption($name)) {
+            throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name));
+        }
+
+        $option = $this->definition->getOption($name);
+
+        if (false === $value) {
+            $value = null;
+        }
+
+        if (null !== $value && !$option->acceptValue()) {
+            throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name, $value));
+        }
+
+        if (null === $value && $option->acceptValue() && count($this->parsed)) {
+            $next = array_shift($this->parsed);
+            if (isset($next[0]) && '-' !== $next[0]) {
+                $value = $next;
+            } elseif (empty($next)) {
+                $value = '';
+            } else {
+                array_unshift($this->parsed, $next);
+            }
+        }
+
+        if (null === $value) {
+            if ($option->isValueRequired()) {
+                throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name));
+            }
+
+            if (!$option->isArray()) {
+                $value = $option->isValueOptional() ? $option->getDefault() : true;
+            }
+        }
+
+        if ($option->isArray()) {
+            $this->options[$name][] = $value;
+        } else {
+            $this->options[$name] = $value;
+        }
+    }
+
+    /**
+     * 获取第一个参数
+     * @return string|null
+     */
+    public function getFirstArgument()
+    {
+        foreach ($this->tokens as $token) {
+            if ($token && '-' === $token[0]) {
+                continue;
+            }
+
+            return $token;
+        }
+        return;
+    }
+
+    /**
+     * 检查原始参数是否包含某个值
+     * @param string|array $values 需要检查的值
+     * @return bool
+     */
+    public function hasParameterOption($values): bool
+    {
+        $values = (array) $values;
+
+        foreach ($this->tokens as $token) {
+            foreach ($values as $value) {
+                if ($token === $value || 0 === strpos($token, $value . '=')) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 获取原始选项的值
+     * @param string|array $values  需要检查的值
+     * @param mixed        $default 默认值
+     * @return mixed The option value
+     */
+    public function getParameterOption($values, $default = false)
+    {
+        $values = (array) $values;
+        $tokens = $this->tokens;
+
+        while (0 < count($tokens)) {
+            $token = array_shift($tokens);
+
+            foreach ($values as $value) {
+                if ($token === $value || 0 === strpos($token, $value . '=')) {
+                    if (false !== $pos = strpos($token, '=')) {
+                        return substr($token, $pos + 1);
+                    }
+
+                    return array_shift($tokens);
+                }
+            }
+        }
+
+        return $default;
+    }
+
+    /**
+     * 验证输入
+     * @throws \RuntimeException
+     */
+    public function validate()
+    {
+        if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) {
+            throw new \RuntimeException('Not enough arguments.');
+        }
+    }
+
+    /**
+     * 检查输入是否是交互的
+     * @return bool
+     */
+    public function isInteractive(): bool
+    {
+        return $this->interactive;
+    }
+
+    /**
+     * 设置输入的交互
+     * @param bool
+     */
+    public function setInteractive(bool $interactive): void
+    {
+        $this->interactive = $interactive;
+    }
+
+    /**
+     * 获取所有的参数
+     * @return Argument[]
+     */
+    public function getArguments(): array
+    {
+        return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
+    }
+
+    /**
+     * 根据名称获取参数
+     * @param string $name 参数名
+     * @return mixed
+     * @throws \InvalidArgumentException
+     */
+    public function getArgument(string $name)
+    {
+        if (!$this->definition->hasArgument($name)) {
+            throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
+        }
+
+        return $this->arguments[$name] ?? $this->definition->getArgument($name)
+            ->getDefault();
+    }
+
+    /**
+     * 设置参数的值
+     * @param string $name  参数名
+     * @param string $value 值
+     * @throws \InvalidArgumentException
+     */
+    public function setArgument(string $name, $value)
+    {
+        if (!$this->definition->hasArgument($name)) {
+            throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
+        }
+
+        $this->arguments[$name] = $value;
+    }
+
+    /**
+     * 检查是否存在某个参数
+     * @param string|int $name 参数名或位置
+     * @return bool
+     */
+    public function hasArgument($name): bool
+    {
+        return $this->definition->hasArgument($name);
+    }
+
+    /**
+     * 获取所有的选项
+     * @return Option[]
+     */
+    public function getOptions(): array
+    {
+        return array_merge($this->definition->getOptionDefaults(), $this->options);
+    }
+
+    /**
+     * 获取选项值
+     * @param string $name 选项名称
+     * @return mixed
+     * @throws \InvalidArgumentException
+     */
+    public function getOption(string $name)
+    {
+        if (!$this->definition->hasOption($name)) {
+            throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
+        }
+
+        return $this->options[$name] ?? $this->definition->getOption($name)->getDefault();
+    }
+
+    /**
+     * 设置选项值
+     * @param string      $name  选项名
+     * @param string|bool $value 值
+     * @throws \InvalidArgumentException
+     */
+    public function setOption(string $name, $value): void
+    {
+        if (!$this->definition->hasOption($name)) {
+            throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
+        }
+
+        $this->options[$name] = $value;
+    }
+
+    /**
+     * 是否有某个选项
+     * @param string $name 选项名
+     * @return bool
+     */
+    public function hasOption(string $name): bool
+    {
+        return $this->definition->hasOption($name) && isset($this->options[$name]);
+    }
+
+    /**
+     * 转义指令
+     * @param string $token
+     * @return string
+     */
+    public function escapeToken(string $token): string
+    {
+        return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
+    }
+
+    /**
+     * 返回传递给命令的参数的字符串
+     * @return string
+     */
+    public function __toString()
+    {
+        $tokens = array_map(function ($token) {
+            if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
+                return $match[1] . $this->escapeToken($match[2]);
+            }
+
+            if ($token && '-' !== $token[0]) {
+                return $this->escapeToken($token);
+            }
+
+            return $token;
+        }, $this->tokens);
+
+        return implode(' ', $tokens);
+    }
+}

+ 19 - 0
vendor/topthink/framework/src/think/console/LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2004-2016 Fabien Potencier
+
+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.

+ 231 - 0
vendor/topthink/framework/src/think/console/Output.php

@@ -0,0 +1,231 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2020 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\console;
+
+use Exception;
+use think\console\output\Ask;
+use think\console\output\Descriptor;
+use think\console\output\driver\Buffer;
+use think\console\output\driver\Console;
+use think\console\output\driver\Nothing;
+use think\console\output\Question;
+use think\console\output\question\Choice;
+use think\console\output\question\Confirmation;
+use Throwable;
+
+/**
+ * Class Output
+ * @package think\console
+ *
+ * @see     \think\console\output\driver\Console::setDecorated
+ * @method void setDecorated($decorated)
+ *
+ * @see     \think\console\output\driver\Buffer::fetch
+ * @method string fetch()
+ *
+ * @method void info($message)
+ * @method void error($message)
+ * @method void comment($message)
+ * @method void warning($message)
+ * @method void highlight($message)
+ * @method void question($message)
+ */
+class Output
+{
+    // 不显示信息(静默)
+    const VERBOSITY_QUIET        = 0;
+    // 正常信息
+    const VERBOSITY_NORMAL       = 1;
+    // 详细信息
+    const VERBOSITY_VERBOSE      = 2;
+    // 非常详细的信息
+    const VERBOSITY_VERY_VERBOSE = 3;
+    // 调试信息
+    const VERBOSITY_DEBUG        = 4;
+
+    const OUTPUT_NORMAL = 0;
+    const OUTPUT_RAW    = 1;
+    const OUTPUT_PLAIN  = 2;
+
+    // 输出信息级别
+    private $verbosity = self::VERBOSITY_NORMAL;
+
+    /** @var Buffer|Console|Nothing */
+    private $handle = null;
+
+    protected $styles = [
+        'info',
+        'error',
+        'comment',
+        'question',
+        'highlight',
+        'warning',
+    ];
+
+    public function __construct($driver = 'console')
+    {
+        $class = '\\think\\console\\output\\driver\\' . ucwords($driver);
+
+        $this->handle = new $class($this);
+    }
+
+    public function ask(Input $input, $question, $default = null, $validator = null)
+    {
+        $question = new Question($question, $default);
+        $question->setValidator($validator);
+
+        return $this->askQuestion($input, $question);
+    }
+
+    public function askHidden(Input $input, $question, $validator = null)
+    {
+        $question = new Question($question);
+
+        $question->setHidden(true);
+        $question->setValidator($validator);
+
+        return $this->askQuestion($input, $question);
+    }
+
+    public function confirm(Input $input, $question, $default = true)
+    {
+        return $this->askQuestion($input, new Confirmation($question, $default));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function choice(Input $input, $question, array $choices, $default = null)
+    {
+        if (null !== $default) {
+            $values  = array_flip($choices);
+            $default = $values[$default];
+        }
+
+        return $this->askQuestion($input, new Choice($question, $choices, $default));
+    }
+
+    protected function askQuestion(Input $input, Question $question)
+    {
+        $ask    = new Ask($input, $this, $question);
+        $answer = $ask->run();
+
+        if ($input->isInteractive()) {
+            $this->newLine();
+        }
+
+        return $answer;
+    }
+
+    protected function block(string $style, string $message): void
+    {
+        $this->writeln("<{$style}>{$message}</$style>");
+    }
+
+    /**
+     * 输出空行
+     * @param int $count
+     */
+    public function newLine(int $count = 1): void
+    {
+        $this->write(str_repeat(PHP_EOL, $count));
+    }
+
+    /**
+     * 输出信息并换行
+     * @param string $messages
+     * @param int    $type
+     */
+    public function writeln(string $messages, int $type = 0): void
+    {
+        $this->write($messages, true, $type);
+    }
+
+    /**
+     * 输出信息
+     * @param string $messages
+     * @param bool   $newline
+     * @param int    $type
+     */
+    public function write(string $messages, bool $newline = false, int $type = 0): void
+    {
+        $this->handle->write($messages, $newline, $type);
+    }
+
+    public function renderException(Throwable $e): void
+    {
+        $this->handle->renderException($e);
+    }
+
+    /**
+     * 设置输出信息级别
+     * @param int $level 输出信息级别
+     */
+    public function setVerbosity(int $level)
+    {
+        $this->verbosity = $level;
+    }
+
+    /**
+     * 获取输出信息级别
+     * @return int
+     */
+    public function getVerbosity(): int
+    {
+        return $this->verbosity;
+    }
+
+    public function isQuiet(): bool
+    {
+        return self::VERBOSITY_QUIET === $this->verbosity;
+    }
+
+    public function isVerbose(): bool
+    {
+        return self::VERBOSITY_VERBOSE <= $this->verbosity;
+    }
+
+    public function isVeryVerbose(): bool
+    {
+        return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
+    }
+
+    public function isDebug(): bool
+    {
+        return self::VERBOSITY_DEBUG <= $this->verbosity;
+    }
+
+    public function describe($object, array $options = []): void
+    {
+        $descriptor = new Descriptor();
+        $options    = array_merge([
+            'raw_text' => false,
+        ], $options);
+
+        $descriptor->describe($this, $object, $options);
+    }
+
+    public function __call($method, $args)
+    {
+        if (in_array($method, $this->styles)) {
+            array_unshift($args, $method);
+            return call_user_func_array([$this, 'block'], $args);
+        }
+
+        if ($this->handle && method_exists($this->handle, $method)) {
+            return call_user_func_array([$this->handle, $method], $args);
+        } else {
+            throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
+        }
+    }
+}

+ 300 - 0
vendor/topthink/framework/src/think/console/Table.php

@@ -0,0 +1,300 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\console;
+
+class Table
+{
+    const ALIGN_LEFT   = 1;
+    const ALIGN_RIGHT  = 0;
+    const ALIGN_CENTER = 2;
+
+    /**
+     * 头信息数据
+     * @var array
+     */
+    protected $header = [];
+
+    /**
+     * 头部对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
+     * @var int
+     */
+    protected $headerAlign = 1;
+
+    /**
+     * 表格数据(二维数组)
+     * @var array
+     */
+    protected $rows = [];
+
+    /**
+     * 单元格对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
+     * @var int
+     */
+    protected $cellAlign = 1;
+
+    /**
+     * 单元格宽度信息
+     * @var array
+     */
+    protected $colWidth = [];
+
+    /**
+     * 表格输出样式
+     * @var string
+     */
+    protected $style = 'default';
+
+    /**
+     * 表格样式定义
+     * @var array
+     */
+    protected $format = [
+        'compact'    => [],
+        'default'    => [
+            'top'          => ['+', '-', '+', '+'],
+            'cell'         => ['|', ' ', '|', '|'],
+            'middle'       => ['+', '-', '+', '+'],
+            'bottom'       => ['+', '-', '+', '+'],
+            'cross-top'    => ['+', '-', '-', '+'],
+            'cross-bottom' => ['+', '-', '-', '+'],
+        ],
+        'markdown'   => [
+            'top'          => [' ', ' ', ' ', ' '],
+            'cell'         => ['|', ' ', '|', '|'],
+            'middle'       => ['|', '-', '|', '|'],
+            'bottom'       => [' ', ' ', ' ', ' '],
+            'cross-top'    => ['|', ' ', ' ', '|'],
+            'cross-bottom' => ['|', ' ', ' ', '|'],
+        ],
+        'borderless' => [
+            'top'          => ['=', '=', ' ', '='],
+            'cell'         => [' ', ' ', ' ', ' '],
+            'middle'       => ['=', '=', ' ', '='],
+            'bottom'       => ['=', '=', ' ', '='],
+            'cross-top'    => ['=', '=', ' ', '='],
+            'cross-bottom' => ['=', '=', ' ', '='],
+        ],
+        'box'        => [
+            'top'          => ['┌', '─', '┬', '┐'],
+            'cell'         => ['│', ' ', '│', '│'],
+            'middle'       => ['├', '─', '┼', '┤'],
+            'bottom'       => ['└', '─', '┴', '┘'],
+            'cross-top'    => ['├', '─', '┴', '┤'],
+            'cross-bottom' => ['├', '─', '┬', '┤'],
+        ],
+        'box-double' => [
+            'top'          => ['╔', '═', '╤', '╗'],
+            'cell'         => ['║', ' ', '│', '║'],
+            'middle'       => ['╠', '─', '╪', '╣'],
+            'bottom'       => ['╚', '═', '╧', '╝'],
+            'cross-top'    => ['╠', '═', '╧', '╣'],
+            'cross-bottom' => ['╠', '═', '╤', '╣'],
+        ],
+    ];
+
+    /**
+     * 设置表格头信息 以及对齐方式
+     * @access public
+     * @param array $header     要输出的Header信息
+     * @param int   $align      对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
+     * @return void
+     */
+    public function setHeader(array $header, int $align = 1): void
+    {
+        $this->header      = $header;
+        $this->headerAlign = $align;
+
+        $this->checkColWidth($header);
+    }
+
+    /**
+     * 设置输出表格数据 及对齐方式
+     * @access public
+     * @param array $rows       要输出的表格数据(二维数组)
+     * @param int   $align      对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
+     * @return void
+     */
+    public function setRows(array $rows, int $align = 1): void
+    {
+        $this->rows      = $rows;
+        $this->cellAlign = $align;
+
+        foreach ($rows as $row) {
+            $this->checkColWidth($row);
+        }
+    }
+
+    /**
+     * 设置全局单元格对齐方式
+     * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER
+     * @return $this
+     */
+    public function setCellAlign(int $align = 1)
+    {
+        $this->cellAlign = $align;
+        return $this;
+    }
+
+    /**
+     * 检查列数据的显示宽度
+     * @access public
+     * @param  mixed $row       行数据
+     * @return void
+     */
+    protected function checkColWidth($row): void
+    {
+        if (is_array($row)) {
+            foreach ($row as $key => $cell) {
+                $width = mb_strwidth((string) $cell);
+                if (!isset($this->colWidth[$key]) || $width > $this->colWidth[$key]) {
+                    $this->colWidth[$key] = $width;
+                }
+            }
+        }
+    }
+
+    /**
+     * 增加一行表格数据
+     * @access public
+     * @param  mixed $row       行数据
+     * @param  bool  $first     是否在开头插入
+     * @return void
+     */
+    public function addRow($row, bool $first = false): void
+    {
+        if ($first) {
+            array_unshift($this->rows, $row);
+        } else {
+            $this->rows[] = $row;
+        }
+
+        $this->checkColWidth($row);
+    }
+
+    /**
+     * 设置输出表格的样式
+     * @access public
+     * @param  string $style       样式名
+     * @return void
+     */
+    public function setStyle(string $style): void
+    {
+        $this->style = isset($this->format[$style]) ? $style : 'default';
+    }
+
+    /**
+     * 输出分隔行
+     * @access public
+     * @param  string $pos       位置
+     * @return string
+     */
+    protected function renderSeparator(string $pos): string
+    {
+        $style = $this->getStyle($pos);
+        $array = [];
+
+        foreach ($this->colWidth as $width) {
+            $array[] = str_repeat($style[1], $width + 2);
+        }
+
+        return $style[0] . implode($style[2], $array) . $style[3] . PHP_EOL;
+    }
+
+    /**
+     * 输出表格头部
+     * @access public
+     * @return string
+     */
+    protected function renderHeader(): string
+    {
+        $style   = $this->getStyle('cell');
+        $content = $this->renderSeparator('top');
+
+        foreach ($this->header as $key => $header) {
+            $array[] = ' ' . str_pad($header, $this->colWidth[$key], $style[1], $this->headerAlign);
+        }
+
+        if (!empty($array)) {
+            $content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL;
+
+            if (!empty($this->rows)) {
+                $content .= $this->renderSeparator('middle');
+            }
+        }
+
+        return $content;
+    }
+
+    protected function getStyle(string $style): array
+    {
+        if ($this->format[$this->style]) {
+            $style = $this->format[$this->style][$style];
+        } else {
+            $style = [' ', ' ', ' ', ' '];
+        }
+
+        return $style;
+    }
+
+    /**
+     * 输出表格
+     * @access public
+     * @param  array $dataList       表格数据
+     * @return string
+     */
+    public function render(array $dataList = []): string
+    {
+        if (!empty($dataList)) {
+            $this->setRows($dataList);
+        }
+
+        // 输出头部
+        $content = $this->renderHeader();
+        $style   = $this->getStyle('cell');
+
+        if (!empty($this->rows)) {
+            foreach ($this->rows as $row) {
+                if (is_string($row) && '-' === $row) {
+                    $content .= $this->renderSeparator('middle');
+                } elseif (is_scalar($row)) {
+                    $content .= $this->renderSeparator('cross-top');
+                    $width = 3 * (count($this->colWidth) - 1) + array_reduce($this->colWidth, function ($a, $b) {
+                        return $a + $b;
+                    });
+                    $array = str_pad($row, $width);
+
+                    $content .= $style[0] . ' ' . $array . ' ' . $style[3] . PHP_EOL;
+                    $content .= $this->renderSeparator('cross-bottom');
+                } else {
+                    $array = [];
+
+                    foreach ($row as $key => $val) {
+                        $width = $this->colWidth[$key];
+                        // form https://github.com/symfony/console/blob/20c9821c8d1c2189f287dcee709b2f86353ea08f/Helper/Table.php#L467
+                        // str_pad won't work properly with multi-byte strings, we need to fix the padding
+                        if (false !== $encoding = mb_detect_encoding((string) $val, null, true)) {
+                            $width += strlen((string) $val) - mb_strwidth((string) $val, $encoding);
+                        }
+                        $array[] = ' ' . str_pad((string) $val, $width, ' ', $this->cellAlign);
+                    }
+
+                    $content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL;
+                }
+            }
+        }
+
+        $content .= $this->renderSeparator('bottom');
+
+        return $content;
+    }
+}

+ 1 - 0
vendor/topthink/framework/src/think/console/bin/README.md

@@ -0,0 +1 @@
+console 工具使用 hiddeninput.exe 在 windows 上隐藏密码输入,该二进制文件由第三方提供,相关源码和其他细节可以在 [Hidden Input](https://github.com/Seldaek/hidden-input) 找到。

BIN
vendor/topthink/framework/src/think/console/bin/hiddeninput.exe


+ 85 - 0
vendor/topthink/framework/src/think/console/command/Clear.php

@@ -0,0 +1,85 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Option;
+use think\console\Output;
+
+class Clear extends Command
+{
+    protected function configure()
+    {
+        // 指令配置
+        $this->setName('clear')
+            ->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null)
+            ->addOption('cache', 'c', Option::VALUE_NONE, 'clear cache file')
+            ->addOption('log', 'l', Option::VALUE_NONE, 'clear log file')
+            ->addOption('dir', 'r', Option::VALUE_NONE, 'clear empty dir')
+            ->addOption('expire', 'e', Option::VALUE_NONE, 'clear cache file if cache has expired')
+            ->setDescription('Clear runtime file');
+    }
+
+    protected function execute(Input $input, Output $output)
+    {
+        $runtimePath = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR;
+
+        if ($input->getOption('cache')) {
+            $path = $runtimePath . 'cache';
+        } elseif ($input->getOption('log')) {
+            $path = $runtimePath . 'log';
+        } else {
+            $path = $input->getOption('path') ?: $runtimePath;
+        }
+
+        $rmdir = $input->getOption('dir') ? true : false;
+        // --expire 仅当 --cache 时生效
+        $cache_expire = $input->getOption('expire') && $input->getOption('cache') ? true : false;
+        $this->clear(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir, $cache_expire);
+
+        $output->writeln("<info>Clear Successed</info>");
+    }
+
+    protected function clear(string $path, bool $rmdir, bool $cache_expire): void
+    {
+        $files = is_dir($path) ? scandir($path) : [];
+
+        foreach ($files as $file) {
+            if ('.' != $file && '..' != $file && is_dir($path . $file)) {
+                $this->clear($path . $file . DIRECTORY_SEPARATOR, $rmdir, $cache_expire);
+                if ($rmdir) {
+                    @rmdir($path . $file);
+                }
+            } elseif ('.gitignore' != $file && is_file($path . $file)) {
+                if ($cache_expire) {
+                    if ($this->cacheHasExpired($path . $file)) {
+                        unlink($path . $file);
+                    }
+                } else {
+                    unlink($path . $file);
+                }
+            }
+        }
+    }
+
+    /**
+     * 缓存文件是否已过期
+     * @param $filename string 文件路径
+     * @return bool
+     */
+    protected function cacheHasExpired($filename) {
+        $content = file_get_contents($filename);
+        $expire = (int) substr($content, 8, 12);
+        return 0 != $expire && time() - $expire > filemtime($filename);
+    }
+
+}

+ 70 - 0
vendor/topthink/framework/src/think/console/command/Help.php

@@ -0,0 +1,70 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument as InputArgument;
+use think\console\input\Option as InputOption;
+use think\console\Output;
+
+class Help extends Command
+{
+
+    private $command;
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configure()
+    {
+        $this->ignoreValidationErrors();
+
+        $this->setName('help')->setDefinition([
+            new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
+            new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
+        ])->setDescription('Displays help for a command')->setHelp(
+            <<<EOF
+The <info>%command.name%</info> command displays help for a given command:
+
+  <info>php %command.full_name% list</info>
+
+To display the list of available commands, please use the <info>list</info> command.
+EOF
+        );
+    }
+
+    /**
+     * Sets the command.
+     * @param Command $command The command to set
+     */
+    public function setCommand(Command $command): void
+    {
+        $this->command = $command;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function execute(Input $input, Output $output)
+    {
+        if (null === $this->command) {
+            $this->command = $this->getConsole()->find($input->getArgument('command_name'));
+        }
+
+        $output->describe($this->command, [
+            'raw_text' => $input->getOption('raw'),
+        ]);
+
+        $this->command = null;
+    }
+}

+ 74 - 0
vendor/topthink/framework/src/think/console/command/Lists.php

@@ -0,0 +1,74 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument as InputArgument;
+use think\console\input\Definition as InputDefinition;
+use think\console\input\Option as InputOption;
+use think\console\Output;
+
+class Lists extends Command
+{
+    /**
+     * {@inheritdoc}
+     */
+    protected function configure()
+    {
+        $this->setName('list')->setDefinition($this->createDefinition())->setDescription('Lists commands')->setHelp(
+            <<<EOF
+The <info>%command.name%</info> command lists all commands:
+
+  <info>php %command.full_name%</info>
+
+You can also display the commands for a specific namespace:
+
+  <info>php %command.full_name% test</info>
+
+It's also possible to get raw list of commands (useful for embedding command runner):
+
+  <info>php %command.full_name% --raw</info>
+EOF
+        );
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getNativeDefinition(): InputDefinition
+    {
+        return $this->createDefinition();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function execute(Input $input, Output $output)
+    {
+        $output->describe($this->getConsole(), [
+            'raw_text'  => $input->getOption('raw'),
+            'namespace' => $input->getArgument('namespace'),
+        ]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    private function createDefinition(): InputDefinition
+    {
+        return new InputDefinition([
+            new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
+            new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
+        ]);
+    }
+}

+ 99 - 0
vendor/topthink/framework/src/think/console/command/Make.php

@@ -0,0 +1,99 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 刘志淳 <chun@engineer.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\Output;
+
+abstract class Make extends Command
+{
+    protected $type;
+
+    abstract protected function getStub();
+
+    protected function configure()
+    {
+        $this->addArgument('name', Argument::REQUIRED, "The name of the class");
+    }
+
+    protected function execute(Input $input, Output $output)
+    {
+        $name = trim($input->getArgument('name'));
+
+        $classname = $this->getClassName($name);
+
+        $pathname = $this->getPathName($classname);
+
+        if (is_file($pathname)) {
+            $output->writeln('<error>' . $this->type . ':' . $classname . ' already exists!</error>');
+            return false;
+        }
+
+        if (!is_dir(dirname($pathname))) {
+            mkdir(dirname($pathname), 0755, true);
+        }
+
+        file_put_contents($pathname, $this->buildClass($classname));
+
+        $output->writeln('<info>' . $this->type . ':' . $classname . ' created successfully.</info>');
+    }
+
+    protected function buildClass(string $name)
+    {
+        $stub = file_get_contents($this->getStub());
+
+        $namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\');
+
+        $class = str_replace($namespace . '\\', '', $name);
+
+        return str_replace(['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'], [
+            $class,
+            $this->app->config->get('route.action_suffix'),
+            $namespace,
+            $this->app->getNamespace(),
+        ], $stub);
+    }
+
+    protected function getPathName(string $name): string
+    {
+        $name = str_replace('app\\', '', $name);
+
+        return $this->app->getBasePath() . ltrim(str_replace('\\', '/', $name), '/') . '.php';
+    }
+
+    protected function getClassName(string $name): string
+    {
+        if (strpos($name, '\\') !== false) {
+            return $name;
+        }
+
+        if (strpos($name, '@')) {
+            [$app, $name] = explode('@', $name);
+        } else {
+            $app = '';
+        }
+
+        if (strpos($name, '/') !== false) {
+            $name = str_replace('/', '\\', $name);
+        }
+
+        return $this->getNamespace($app) . '\\' . $name;
+    }
+
+    protected function getNamespace(string $app): string
+    {
+        return 'app' . ($app ? '\\' . $app : '');
+    }
+
+}

+ 129 - 0
vendor/topthink/framework/src/think/console/command/RouteList.php

@@ -0,0 +1,129 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\input\Option;
+use think\console\Output;
+use think\console\Table;
+use think\event\RouteLoaded;
+
+class RouteList extends Command
+{
+    protected $sortBy = [
+        'rule'   => 0,
+        'route'  => 1,
+        'method' => 2,
+        'name'   => 3,
+        'domain' => 4,
+    ];
+
+    protected function configure()
+    {
+        $this->setName('route:list')
+            ->addArgument('dir', Argument::OPTIONAL, 'dir name .')
+            ->addArgument('style', Argument::OPTIONAL, "the style of the table.", 'default')
+            ->addOption('sort', 's', Option::VALUE_OPTIONAL, 'order by rule name.', 0)
+            ->addOption('more', 'm', Option::VALUE_NONE, 'show route options.')
+            ->setDescription('show route list.');
+    }
+
+    protected function execute(Input $input, Output $output)
+    {
+        $dir = $input->getArgument('dir') ?: '';
+
+        $filename = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : '') . 'route_list.php';
+
+        if (is_file($filename)) {
+            unlink($filename);
+        } elseif (!is_dir(dirname($filename))) {
+            mkdir(dirname($filename), 0755);
+        }
+
+        $content = $this->getRouteList($dir);
+        file_put_contents($filename, 'Route List' . PHP_EOL . $content);
+    }
+
+    protected function getRouteList(string $dir = null): string
+    {
+        $this->app->route->setTestMode(true);
+        $this->app->route->clear();
+
+        if ($dir) {
+            $path = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR;
+        } else {
+            $path = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR;
+        }
+
+        $files = is_dir($path) ? scandir($path) : [];
+
+        foreach ($files as $file) {
+            if (strpos($file, '.php')) {
+                include $path . $file;
+            }
+        }
+
+        //触发路由载入完成事件
+        $this->app->event->trigger(RouteLoaded::class);
+
+        $table = new Table();
+
+        if ($this->input->hasOption('more')) {
+            $header = ['Rule', 'Route', 'Method', 'Name', 'Domain', 'Option', 'Pattern'];
+        } else {
+            $header = ['Rule', 'Route', 'Method', 'Name'];
+        }
+
+        $table->setHeader($header);
+
+        $routeList = $this->app->route->getRuleList();
+        $rows      = [];
+
+        foreach ($routeList as $item) {
+            $item['route'] = $item['route'] instanceof \Closure ? '<Closure>' : $item['route'];
+
+            if ($this->input->hasOption('more')) {
+                $item = [$item['rule'], $item['route'], $item['method'], $item['name'], $item['domain'], json_encode($item['option']), json_encode($item['pattern'])];
+            } else {
+                $item = [$item['rule'], $item['route'], $item['method'], $item['name']];
+            }
+
+            $rows[] = $item;
+        }
+
+        if ($this->input->getOption('sort')) {
+            $sort = strtolower($this->input->getOption('sort'));
+
+            if (isset($this->sortBy[$sort])) {
+                $sort = $this->sortBy[$sort];
+            }
+
+            uasort($rows, function ($a, $b) use ($sort) {
+                $itemA = $a[$sort] ?? null;
+                $itemB = $b[$sort] ?? null;
+
+                return strcasecmp($itemA, $itemB);
+            });
+        }
+
+        $table->setRows($rows);
+
+        if ($this->input->getArgument('style')) {
+            $style = $this->input->getArgument('style');
+            $table->setStyle($style);
+        }
+
+        return $this->table($table);
+    }
+
+}

+ 72 - 0
vendor/topthink/framework/src/think/console/command/RunServer.php

@@ -0,0 +1,72 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: Slince <taosikai@yeah.net>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Option;
+use think\console\Output;
+
+class RunServer extends Command
+{
+    public function configure()
+    {
+        $this->setName('run')
+            ->addOption(
+                'host',
+                'H',
+                Option::VALUE_OPTIONAL,
+                'The host to server the application on',
+                '0.0.0.0'
+            )
+            ->addOption(
+                'port',
+                'p',
+                Option::VALUE_OPTIONAL,
+                'The port to server the application on',
+                8000
+            )
+            ->addOption(
+                'root',
+                'r',
+                Option::VALUE_OPTIONAL,
+                'The document root of the application',
+                ''
+            )
+            ->setDescription('PHP Built-in Server for ThinkPHP');
+    }
+
+    public function execute(Input $input, Output $output)
+    {
+        $host = $input->getOption('host');
+        $port = $input->getOption('port');
+        $root = $input->getOption('root');
+        if (empty($root)) {
+            $root = $this->app->getRootPath() . 'public';
+        }
+
+        $command = sprintf(
+            'php -S %s:%d -t %s %s',
+            $host,
+            $port,
+            escapeshellarg($root),
+            escapeshellarg($root . DIRECTORY_SEPARATOR . 'router.php')
+        );
+
+        $output->writeln(sprintf('ThinkPHP Development server is started On <http://%s:%s/>', $host, $port));
+        $output->writeln(sprintf('You can exit with <info>`CTRL-C`</info>'));
+        $output->writeln(sprintf('Document root is: %s', $root));
+        passthru($command);
+    }
+
+}

+ 52 - 0
vendor/topthink/framework/src/think/console/command/ServiceDiscover.php

@@ -0,0 +1,52 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\Output;
+
+class ServiceDiscover extends Command
+{
+    public function configure()
+    {
+        $this->setName('service:discover')
+            ->setDescription('Discover Services for ThinkPHP');
+    }
+
+    public function execute(Input $input, Output $output)
+    {
+        if (is_file($path = $this->app->getRootPath() . 'vendor/composer/installed.json')) {
+            $packages = json_decode(@file_get_contents($path), true);
+            // Compatibility with Composer 2.0
+            if (isset($packages['packages'])) {
+                $packages = $packages['packages'];
+            }
+
+            $services = [];
+            foreach ($packages as $package) {
+                if (!empty($package['extra']['think']['services'])) {
+                    $services = array_merge($services, (array) $package['extra']['think']['services']);
+                }
+            }
+
+            $header = '// This file is automatically generated at:' . date('Y-m-d H:i:s') . PHP_EOL . 'declare (strict_types = 1);' . PHP_EOL;
+
+            $content = '<?php ' . PHP_EOL . $header . "return " . var_export($services, true) . ';';
+
+            file_put_contents($this->app->getRootPath() . 'vendor/services.php', $content);
+
+            $output->writeln('<info>Succeed!</info>');
+        }
+    }
+}

+ 69 - 0
vendor/topthink/framework/src/think/console/command/VendorPublish.php

@@ -0,0 +1,69 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\input\Option;
+
+class VendorPublish extends Command
+{
+    public function configure()
+    {
+        $this->setName('vendor:publish')
+            ->addOption('force', 'f', Option::VALUE_NONE, 'Overwrite any existing files')
+            ->setDescription('Publish any publishable assets from vendor packages');
+    }
+
+    public function handle()
+    {
+
+        $force = $this->input->getOption('force');
+
+        if (is_file($path = $this->app->getRootPath() . 'vendor/composer/installed.json')) {
+            $packages = json_decode(@file_get_contents($path), true);
+            // Compatibility with Composer 2.0
+            if (isset($packages['packages'])) {
+                $packages = $packages['packages'];
+            }
+            foreach ($packages as $package) {
+                //配置
+                $configDir = $this->app->getConfigPath();
+
+                if (!empty($package['extra']['think']['config'])) {
+
+                    $installPath = $this->app->getRootPath() . 'vendor/' . $package['name'] . DIRECTORY_SEPARATOR;
+
+                    foreach ((array) $package['extra']['think']['config'] as $name => $file) {
+
+                        $target = $configDir . $name . '.php';
+                        $source = $installPath . $file;
+
+                        if (is_file($target) && !$force) {
+                            $this->output->info("File {$target} exist!");
+                            continue;
+                        }
+
+                        if (!is_file($source)) {
+                            $this->output->info("File {$source} not exist!");
+                            continue;
+                        }
+
+                        copy($source, $target);
+                    }
+                }
+            }
+
+            $this->output->writeln('<info>Succeed!</info>');
+        }
+    }
+}

+ 33 - 0
vendor/topthink/framework/src/think/console/command/Version.php

@@ -0,0 +1,33 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+declare (strict_types = 1);
+
+namespace think\console\command;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\Output;
+
+class Version extends Command
+{
+    protected function configure()
+    {
+        // 指令配置
+        $this->setName('version')
+            ->setDescription('show thinkphp framework version');
+    }
+
+    protected function execute(Input $input, Output $output)
+    {
+        $output->writeln('v' . $this->app->version());
+    }
+
+}

+ 55 - 0
vendor/topthink/framework/src/think/console/command/make/Command.php

@@ -0,0 +1,55 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\command\make;
+
+use think\console\command\Make;
+use think\console\input\Argument;
+
+class Command extends Make
+{
+    protected $type = "Command";
+
+    protected function configure()
+    {
+        parent::configure();
+        $this->setName('make:command')
+            ->addArgument('commandName', Argument::OPTIONAL, "The name of the command")
+            ->setDescription('Create a new command class');
+    }
+
+    protected function buildClass(string $name): string
+    {
+        $commandName = $this->input->getArgument('commandName') ?: strtolower(basename($name));
+        $namespace   = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\');
+
+        $class = str_replace($namespace . '\\', '', $name);
+        $stub  = file_get_contents($this->getStub());
+
+        return str_replace(['{%commandName%}', '{%className%}', '{%namespace%}', '{%app_namespace%}'], [
+            $commandName,
+            $class,
+            $namespace,
+            $this->app->getNamespace(),
+        ], $stub);
+    }
+
+    protected function getStub(): string
+    {
+        return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'command.stub';
+    }
+
+    protected function getNamespace(string $app): string
+    {
+        return parent::getNamespace($app) . '\\command';
+    }
+
+}

+ 56 - 0
vendor/topthink/framework/src/think/console/command/make/Controller.php

@@ -0,0 +1,56 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\command\make;
+
+use think\console\command\Make;
+use think\console\input\Option;
+
+class Controller extends Make
+{
+
+    protected $type = "Controller";
+
+    protected function configure()
+    {
+        parent::configure();
+        $this->setName('make:controller')
+            ->addOption('api', null, Option::VALUE_NONE, 'Generate an api controller class.')
+            ->addOption('plain', null, Option::VALUE_NONE, 'Generate an empty controller class.')
+            ->setDescription('Create a new resource controller class');
+    }
+
+    protected function getStub(): string
+    {
+        $stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR;
+
+        if ($this->input->getOption('api')) {
+            return $stubPath . 'controller.api.stub';
+        }
+
+        if ($this->input->getOption('plain')) {
+            return $stubPath . 'controller.plain.stub';
+        }
+
+        return $stubPath . 'controller.stub';
+    }
+
+    protected function getClassName(string $name): string
+    {
+        return parent::getClassName($name) . ($this->app->config->get('route.controller_suffix') ? 'Controller' : '');
+    }
+
+    protected function getNamespace(string $app): string
+    {
+        return parent::getNamespace($app) . '\\controller';
+    }
+
+}

+ 35 - 0
vendor/topthink/framework/src/think/console/command/make/Event.php

@@ -0,0 +1,35 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+namespace think\console\command\make;
+
+use think\console\command\Make;
+
+class Event extends Make
+{
+    protected $type = "Event";
+
+    protected function configure()
+    {
+        parent::configure();
+        $this->setName('make:event')
+            ->setDescription('Create a new event class');
+    }
+
+    protected function getStub(): string
+    {
+        return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'event.stub';
+    }
+
+    protected function getNamespace(string $app): string
+    {
+        return parent::getNamespace($app) . '\\event';
+    }
+}

+ 35 - 0
vendor/topthink/framework/src/think/console/command/make/Listener.php

@@ -0,0 +1,35 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+namespace think\console\command\make;
+
+use think\console\command\Make;
+
+class Listener extends Make
+{
+    protected $type = "Listener";
+
+    protected function configure()
+    {
+        parent::configure();
+        $this->setName('make:listener')
+            ->setDescription('Create a new listener class');
+    }
+
+    protected function getStub(): string
+    {
+        return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'listener.stub';
+    }
+
+    protected function getNamespace(string $app): string
+    {
+        return parent::getNamespace($app) . '\\listener';
+    }
+}

+ 36 - 0
vendor/topthink/framework/src/think/console/command/make/Middleware.php

@@ -0,0 +1,36 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\command\make;
+
+use think\console\command\Make;
+
+class Middleware extends Make
+{
+    protected $type = "Middleware";
+
+    protected function configure()
+    {
+        parent::configure();
+        $this->setName('make:middleware')
+            ->setDescription('Create a new middleware class');
+    }
+
+    protected function getStub(): string
+    {
+        return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'middleware.stub';
+    }
+
+    protected function getNamespace(string $app): string
+    {
+        return parent::getNamespace($app) . '\\middleware';
+    }
+}

+ 36 - 0
vendor/topthink/framework/src/think/console/command/make/Model.php

@@ -0,0 +1,36 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\command\make;
+
+use think\console\command\Make;
+
+class Model extends Make
+{
+    protected $type = "Model";
+
+    protected function configure()
+    {
+        parent::configure();
+        $this->setName('make:model')
+            ->setDescription('Create a new model class');
+    }
+
+    protected function getStub(): string
+    {
+        return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'model.stub';
+    }
+
+    protected function getNamespace(string $app): string
+    {
+        return parent::getNamespace($app) . '\\model';
+    }
+}

+ 36 - 0
vendor/topthink/framework/src/think/console/command/make/Service.php

@@ -0,0 +1,36 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\command\make;
+
+use think\console\command\Make;
+
+class Service extends Make
+{
+    protected $type = "Service";
+
+    protected function configure()
+    {
+        parent::configure();
+        $this->setName('make:service')
+            ->setDescription('Create a new Service class');
+    }
+
+    protected function getStub(): string
+    {
+        return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'service.stub';
+    }
+
+    protected function getNamespace(string $app): string
+    {
+        return parent::getNamespace($app) . '\\service';
+    }
+}

+ 35 - 0
vendor/topthink/framework/src/think/console/command/make/Subscribe.php

@@ -0,0 +1,35 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+namespace think\console\command\make;
+
+use think\console\command\Make;
+
+class Subscribe extends Make
+{
+    protected $type = "Subscribe";
+
+    protected function configure()
+    {
+        parent::configure();
+        $this->setName('make:subscribe')
+            ->setDescription('Create a new subscribe class');
+    }
+
+    protected function getStub(): string
+    {
+        return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'subscribe.stub';
+    }
+
+    protected function getNamespace(string $app): string
+    {
+        return parent::getNamespace($app) . '\\subscribe';
+    }
+}

+ 39 - 0
vendor/topthink/framework/src/think/console/command/make/Validate.php

@@ -0,0 +1,39 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\command\make;
+
+use think\console\command\Make;
+
+class Validate extends Make
+{
+    protected $type = "Validate";
+
+    protected function configure()
+    {
+        parent::configure();
+        $this->setName('make:validate')
+            ->setDescription('Create a validate class');
+    }
+
+    protected function getStub(): string
+    {
+        $stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR;
+
+        return $stubPath . 'validate.stub';
+    }
+
+    protected function getNamespace(string $app): string
+    {
+        return parent::getNamespace($app) . '\\validate';
+    }
+
+}

+ 26 - 0
vendor/topthink/framework/src/think/console/command/make/stubs/command.stub

@@ -0,0 +1,26 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\input\Option;
+use think\console\Output;
+
+class {%className%} extends Command
+{
+    protected function configure()
+    {
+        // 指令配置
+        $this->setName('{%commandName%}')
+            ->setDescription('the {%commandName%} command');
+    }
+
+    protected function execute(Input $input, Output $output)
+    {
+        // 指令输出
+        $output->writeln('{%commandName%}');
+    }
+}

+ 64 - 0
vendor/topthink/framework/src/think/console/command/make/stubs/controller.api.stub

@@ -0,0 +1,64 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+use think\Request;
+
+class {%className%}
+{
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index{%actionSuffix%}()
+    {
+        //
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param  \think\Request  $request
+     * @return \think\Response
+     */
+    public function save{%actionSuffix%}(Request $request)
+    {
+        //
+    }
+
+    /**
+     * 显示指定的资源
+     *
+     * @param  int  $id
+     * @return \think\Response
+     */
+    public function read{%actionSuffix%}($id)
+    {
+        //
+    }
+
+    /**
+     * 保存更新的资源
+     *
+     * @param  \think\Request  $request
+     * @param  int  $id
+     * @return \think\Response
+     */
+    public function update{%actionSuffix%}(Request $request, $id)
+    {
+        //
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param  int  $id
+     * @return \think\Response
+     */
+    public function delete{%actionSuffix%}($id)
+    {
+        //
+    }
+}

+ 9 - 0
vendor/topthink/framework/src/think/console/command/make/stubs/controller.plain.stub

@@ -0,0 +1,9 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+class {%className%}
+{
+    //
+}

+ 85 - 0
vendor/topthink/framework/src/think/console/command/make/stubs/controller.stub

@@ -0,0 +1,85 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+use think\Request;
+
+class {%className%}
+{
+    /**
+     * 显示资源列表
+     *
+     * @return \think\Response
+     */
+    public function index{%actionSuffix%}()
+    {
+        //
+    }
+
+    /**
+     * 显示创建资源表单页.
+     *
+     * @return \think\Response
+     */
+    public function create{%actionSuffix%}()
+    {
+        //
+    }
+
+    /**
+     * 保存新建的资源
+     *
+     * @param  \think\Request  $request
+     * @return \think\Response
+     */
+    public function save{%actionSuffix%}(Request $request)
+    {
+        //
+    }
+
+    /**
+     * 显示指定的资源
+     *
+     * @param  int  $id
+     * @return \think\Response
+     */
+    public function read{%actionSuffix%}($id)
+    {
+        //
+    }
+
+    /**
+     * 显示编辑资源表单页.
+     *
+     * @param  int  $id
+     * @return \think\Response
+     */
+    public function edit{%actionSuffix%}($id)
+    {
+        //
+    }
+
+    /**
+     * 保存更新的资源
+     *
+     * @param  \think\Request  $request
+     * @param  int  $id
+     * @return \think\Response
+     */
+    public function update{%actionSuffix%}(Request $request, $id)
+    {
+        //
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param  int  $id
+     * @return \think\Response
+     */
+    public function delete{%actionSuffix%}($id)
+    {
+        //
+    }
+}

+ 8 - 0
vendor/topthink/framework/src/think/console/command/make/stubs/event.stub

@@ -0,0 +1,8 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+class {%className%}
+{
+}

+ 17 - 0
vendor/topthink/framework/src/think/console/command/make/stubs/listener.stub

@@ -0,0 +1,17 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+class {%className%}
+{
+    /**
+     * 事件监听处理
+     *
+     * @return mixed
+     */
+    public function handle($event)
+    {
+        //
+    }
+}

+ 19 - 0
vendor/topthink/framework/src/think/console/command/make/stubs/middleware.stub

@@ -0,0 +1,19 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+class {%className%}
+{
+    /**
+     * 处理请求
+     *
+     * @param \think\Request $request
+     * @param \Closure       $next
+     * @return Response
+     */
+    public function handle($request, \Closure $next)
+    {
+        //
+    }
+}

+ 14 - 0
vendor/topthink/framework/src/think/console/command/make/stubs/model.stub

@@ -0,0 +1,14 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+use think\Model;
+
+/**
+ * @mixin \think\Model
+ */
+class {%className%} extends Model
+{
+    //
+}

+ 27 - 0
vendor/topthink/framework/src/think/console/command/make/stubs/service.stub

@@ -0,0 +1,27 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+class {%className%} extends \think\Service
+{
+    /**
+     * 注册服务
+     *
+     * @return mixed
+     */
+    public function register()
+    {
+    	//
+    }
+
+    /**
+     * 执行服务
+     *
+     * @return mixed
+     */
+    public function boot()
+    {
+        //
+    }
+}

+ 8 - 0
vendor/topthink/framework/src/think/console/command/make/stubs/subscribe.stub

@@ -0,0 +1,8 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+class {%className%}
+{
+}

+ 25 - 0
vendor/topthink/framework/src/think/console/command/make/stubs/validate.stub

@@ -0,0 +1,25 @@
+<?php
+declare (strict_types = 1);
+
+namespace {%namespace%};
+
+use think\Validate;
+
+class {%className%} extends Validate
+{
+    /**
+     * 定义验证规则
+     * 格式:'字段名' =>  ['规则1','规则2'...]
+     *
+     * @var array
+     */
+    protected $rule = [];
+
+    /**
+     * 定义错误信息
+     * 格式:'字段名.规则名' =>  '错误信息'
+     *
+     * @var array
+     */
+    protected $message = [];
+}

+ 66 - 0
vendor/topthink/framework/src/think/console/command/optimize/Route.php

@@ -0,0 +1,66 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace think\console\command\optimize;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\Output;
+use think\event\RouteLoaded;
+
+class Route extends Command
+{
+    protected function configure()
+    {
+        $this->setName('optimize:route')
+            ->addArgument('dir', Argument::OPTIONAL, 'dir name .')
+            ->setDescription('Build app route cache.');
+    }
+
+    protected function execute(Input $input, Output $output)
+    {
+        $dir = $input->getArgument('dir') ?: '';
+
+        $path = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : '');
+
+        $filename = $path . 'route.php';
+        if (is_file($filename)) {
+            unlink($filename);
+        }
+
+        file_put_contents($filename, $this->buildRouteCache($dir));
+        $output->writeln('<info>Succeed!</info>');
+    }
+
+    protected function buildRouteCache(string $dir = null): string
+    {
+        $this->app->route->clear();
+        $this->app->route->lazy(false);
+
+        // 路由检测
+        $path = $this->app->getRootPath() . ($dir ? 'app' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR : '') . 'route' . DIRECTORY_SEPARATOR;
+
+        $files = is_dir($path) ? scandir($path) : [];
+
+        foreach ($files as $file) {
+            if (strpos($file, '.php')) {
+                include $path . $file;
+            }
+        }
+
+        //触发路由载入完成事件
+        $this->app->event->trigger(RouteLoaded::class);
+        $rules = $this->app->route->getName();
+
+        return '<?php ' . PHP_EOL . 'return unserialize(\'' . serialize($rules) . '\');';
+    }
+
+}

+ 104 - 0
vendor/topthink/framework/src/think/console/command/optimize/Schema.php

@@ -0,0 +1,104 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace think\console\command\optimize;
+
+use Exception;
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\input\Option;
+use think\console\Output;
+use think\db\PDOConnection;
+
+class Schema extends Command
+{
+    protected function configure()
+    {
+        $this->setName('optimize:schema')
+            ->addArgument('dir', Argument::OPTIONAL, 'dir name .')
+            ->addOption('connection', null, Option::VALUE_REQUIRED, 'connection name .')
+            ->addOption('table', null, Option::VALUE_REQUIRED, 'table name .')
+            ->setDescription('Build database schema cache.');
+    }
+
+    protected function execute(Input $input, Output $output)
+    {
+        $dir = $input->getArgument('dir') ?: '';
+
+        if ($input->hasOption('table')) {
+            $connection = $this->app->db->connect($input->getOption('connection'));
+            if (!$connection instanceof PDOConnection) {
+                $output->error("only PDO connection support schema cache!");
+                return;
+            }
+            $table = $input->getOption('table');
+            if (false === strpos($table, '.')) {
+                $dbName = $connection->getConfig('database');
+            } else {
+                [$dbName, $table] = explode('.', $table);
+            }
+
+            if ($table == '*') {
+                $table = $connection->getTables($dbName);
+            }
+
+            $this->buildDataBaseSchema($connection, (array) $table, $dbName);
+        } else {
+            if ($dir) {
+                $appPath   = $this->app->getBasePath() . $dir . DIRECTORY_SEPARATOR;
+                $namespace = 'app\\' . $dir;
+            } else {
+                $appPath   = $this->app->getBasePath();
+                $namespace = 'app';
+            }
+
+            $path = $appPath . 'model';
+            $list = is_dir($path) ? scandir($path) : [];
+
+            foreach ($list as $file) {
+                if (0 === strpos($file, '.')) {
+                    continue;
+                }
+                $class = '\\' . $namespace . '\\model\\' . pathinfo($file, PATHINFO_FILENAME);
+                $this->buildModelSchema($class);
+            }
+        }
+
+        $output->writeln('<info>Succeed!</info>');
+    }
+
+    protected function buildModelSchema(string $class): void
+    {
+        $reflect = new \ReflectionClass($class);
+        if (!$reflect->isAbstract() && $reflect->isSubclassOf('\think\Model')) {
+            try {
+                /** @var \think\Model $model */
+                $model      = new $class;
+                $connection = $model->db()->getConnection();
+                if ($connection instanceof PDOConnection) {
+                    $table = $model->getTable();
+                    //预读字段信息
+                    $connection->getSchemaInfo($table, true);
+                }
+            } catch (Exception $e) {
+
+            }
+        }
+    }
+
+    protected function buildDataBaseSchema(PDOConnection $connection, array $tables, string $dbName): void
+    {
+        foreach ($tables as $table) {
+            //预读字段信息
+            $connection->getSchemaInfo("{$dbName}.{$table}", true);
+        }
+    }
+}

+ 138 - 0
vendor/topthink/framework/src/think/console/input/Argument.php

@@ -0,0 +1,138 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\input;
+
+class Argument
+{
+    // 必传参数
+    const REQUIRED = 1;
+
+    // 可选参数
+    const OPTIONAL = 2;
+
+    // 数组参数
+    const IS_ARRAY = 4;
+
+    /**
+     * 参数名
+     * @var string
+     */
+    private $name;
+
+    /**
+     * 参数类型
+     * @var int
+     */
+    private $mode;
+
+    /**
+     * 参数默认值
+     * @var mixed
+     */
+    private $default;
+
+    /**
+     * 参数描述
+     * @var string
+     */
+    private $description;
+
+    /**
+     * 构造方法
+     * @param string $name        参数名
+     * @param int    $mode        参数类型: self::REQUIRED 或者 self::OPTIONAL
+     * @param string $description 描述
+     * @param mixed  $default     默认值 (仅 self::OPTIONAL 类型有效)
+     * @throws \InvalidArgumentException
+     */
+    public function __construct(string $name, int $mode = null, string $description = '', $default = null)
+    {
+        if (null === $mode) {
+            $mode = self::OPTIONAL;
+        } elseif (!is_int($mode) || $mode > 7 || $mode < 1) {
+            throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode));
+        }
+
+        $this->name        = $name;
+        $this->mode        = $mode;
+        $this->description = $description;
+
+        $this->setDefault($default);
+    }
+
+    /**
+     * 获取参数名
+     * @return string
+     */
+    public function getName(): string
+    {
+        return $this->name;
+    }
+
+    /**
+     * 是否必须
+     * @return bool
+     */
+    public function isRequired(): bool
+    {
+        return self::REQUIRED === (self::REQUIRED & $this->mode);
+    }
+
+    /**
+     * 该参数是否接受数组
+     * @return bool
+     */
+    public function isArray(): bool
+    {
+        return self::IS_ARRAY === (self::IS_ARRAY & $this->mode);
+    }
+
+    /**
+     * 设置默认值
+     * @param mixed $default 默认值
+     * @throws \LogicException
+     */
+    public function setDefault($default = null): void
+    {
+        if (self::REQUIRED === $this->mode && null !== $default) {
+            throw new \LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.');
+        }
+
+        if ($this->isArray()) {
+            if (null === $default) {
+                $default = [];
+            } elseif (!is_array($default)) {
+                throw new \LogicException('A default value for an array argument must be an array.');
+            }
+        }
+
+        $this->default = $default;
+    }
+
+    /**
+     * 获取默认值
+     * @return mixed
+     */
+    public function getDefault()
+    {
+        return $this->default;
+    }
+
+    /**
+     * 获取描述
+     * @return string
+     */
+    public function getDescription(): string
+    {
+        return $this->description;
+    }
+}

+ 375 - 0
vendor/topthink/framework/src/think/console/input/Definition.php

@@ -0,0 +1,375 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\input;
+
+class Definition
+{
+
+    /**
+     * @var Argument[]
+     */
+    private $arguments;
+
+    private $requiredCount;
+    private $hasAnArrayArgument = false;
+    private $hasOptional;
+
+    /**
+     * @var Option[]
+     */
+    private $options;
+    private $shortcuts;
+
+    /**
+     * 构造方法
+     * @param array $definition
+     * @api
+     */
+    public function __construct(array $definition = [])
+    {
+        $this->setDefinition($definition);
+    }
+
+    /**
+     * 设置指令的定义
+     * @param array $definition 定义的数组
+     */
+    public function setDefinition(array $definition): void
+    {
+        $arguments = [];
+        $options   = [];
+        foreach ($definition as $item) {
+            if ($item instanceof Option) {
+                $options[] = $item;
+            } else {
+                $arguments[] = $item;
+            }
+        }
+
+        $this->setArguments($arguments);
+        $this->setOptions($options);
+    }
+
+    /**
+     * 设置参数
+     * @param Argument[] $arguments 参数数组
+     */
+    public function setArguments(array $arguments = []): void
+    {
+        $this->arguments          = [];
+        $this->requiredCount      = 0;
+        $this->hasOptional        = false;
+        $this->hasAnArrayArgument = false;
+        $this->addArguments($arguments);
+    }
+
+    /**
+     * 添加参数
+     * @param Argument[] $arguments 参数数组
+     * @api
+     */
+    public function addArguments(array $arguments = []): void
+    {
+        if (null !== $arguments) {
+            foreach ($arguments as $argument) {
+                $this->addArgument($argument);
+            }
+        }
+    }
+
+    /**
+     * 添加一个参数
+     * @param Argument $argument 参数
+     * @throws \LogicException
+     */
+    public function addArgument(Argument $argument): void
+    {
+        if (isset($this->arguments[$argument->getName()])) {
+            throw new \LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName()));
+        }
+
+        if ($this->hasAnArrayArgument) {
+            throw new \LogicException('Cannot add an argument after an array argument.');
+        }
+
+        if ($argument->isRequired() && $this->hasOptional) {
+            throw new \LogicException('Cannot add a required argument after an optional one.');
+        }
+
+        if ($argument->isArray()) {
+            $this->hasAnArrayArgument = true;
+        }
+
+        if ($argument->isRequired()) {
+            ++$this->requiredCount;
+        } else {
+            $this->hasOptional = true;
+        }
+
+        $this->arguments[$argument->getName()] = $argument;
+    }
+
+    /**
+     * 根据名称或者位置获取参数
+     * @param string|int $name 参数名或者位置
+     * @return Argument 参数
+     * @throws \InvalidArgumentException
+     */
+    public function getArgument($name): Argument
+    {
+        if (!$this->hasArgument($name)) {
+            throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
+        }
+
+        $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;
+
+        return $arguments[$name];
+    }
+
+    /**
+     * 根据名称或位置检查是否具有某个参数
+     * @param string|int $name 参数名或者位置
+     * @return bool
+     * @api
+     */
+    public function hasArgument($name): bool
+    {
+        $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;
+
+        return isset($arguments[$name]);
+    }
+
+    /**
+     * 获取所有的参数
+     * @return Argument[] 参数数组
+     */
+    public function getArguments(): array
+    {
+        return $this->arguments;
+    }
+
+    /**
+     * 获取参数数量
+     * @return int
+     */
+    public function getArgumentCount(): int
+    {
+        return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments);
+    }
+
+    /**
+     * 获取必填的参数的数量
+     * @return int
+     */
+    public function getArgumentRequiredCount(): int
+    {
+        return $this->requiredCount;
+    }
+
+    /**
+     * 获取参数默认值
+     * @return array
+     */
+    public function getArgumentDefaults(): array
+    {
+        $values = [];
+        foreach ($this->arguments as $argument) {
+            $values[$argument->getName()] = $argument->getDefault();
+        }
+
+        return $values;
+    }
+
+    /**
+     * 设置选项
+     * @param Option[] $options 选项数组
+     */
+    public function setOptions(array $options = []): void
+    {
+        $this->options   = [];
+        $this->shortcuts = [];
+        $this->addOptions($options);
+    }
+
+    /**
+     * 添加选项
+     * @param Option[] $options 选项数组
+     * @api
+     */
+    public function addOptions(array $options = []): void
+    {
+        foreach ($options as $option) {
+            $this->addOption($option);
+        }
+    }
+
+    /**
+     * 添加一个选项
+     * @param Option $option 选项
+     * @throws \LogicException
+     * @api
+     */
+    public function addOption(Option $option): void
+    {
+        if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) {
+            throw new \LogicException(sprintf('An option named "%s" already exists.', $option->getName()));
+        }
+
+        if ($option->getShortcut()) {
+            foreach (explode('|', $option->getShortcut()) as $shortcut) {
+                if (isset($this->shortcuts[$shortcut])
+                    && !$option->equals($this->options[$this->shortcuts[$shortcut]])
+                ) {
+                    throw new \LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut));
+                }
+            }
+        }
+
+        $this->options[$option->getName()] = $option;
+        if ($option->getShortcut()) {
+            foreach (explode('|', $option->getShortcut()) as $shortcut) {
+                $this->shortcuts[$shortcut] = $option->getName();
+            }
+        }
+    }
+
+    /**
+     * 根据名称获取选项
+     * @param string $name 选项名
+     * @return Option
+     * @throws \InvalidArgumentException
+     * @api
+     */
+    public function getOption(string $name): Option
+    {
+        if (!$this->hasOption($name)) {
+            throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
+        }
+
+        return $this->options[$name];
+    }
+
+    /**
+     * 根据名称检查是否有这个选项
+     * @param string $name 选项名
+     * @return bool
+     * @api
+     */
+    public function hasOption(string $name): bool
+    {
+        return isset($this->options[$name]);
+    }
+
+    /**
+     * 获取所有选项
+     * @return Option[]
+     * @api
+     */
+    public function getOptions(): array
+    {
+        return $this->options;
+    }
+
+    /**
+     * 根据名称检查某个选项是否有短名称
+     * @param string $name 短名称
+     * @return bool
+     */
+    public function hasShortcut(string $name): bool
+    {
+        return isset($this->shortcuts[$name]);
+    }
+
+    /**
+     * 根据短名称获取选项
+     * @param string $shortcut 短名称
+     * @return Option
+     */
+    public function getOptionForShortcut(string $shortcut): Option
+    {
+        return $this->getOption($this->shortcutToName($shortcut));
+    }
+
+    /**
+     * 获取所有选项的默认值
+     * @return array
+     */
+    public function getOptionDefaults(): array
+    {
+        $values = [];
+        foreach ($this->options as $option) {
+            $values[$option->getName()] = $option->getDefault();
+        }
+
+        return $values;
+    }
+
+    /**
+     * 根据短名称获取选项名
+     * @param string $shortcut 短名称
+     * @return string
+     * @throws \InvalidArgumentException
+     */
+    private function shortcutToName(string $shortcut): string
+    {
+        if (!isset($this->shortcuts[$shortcut])) {
+            throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
+        }
+
+        return $this->shortcuts[$shortcut];
+    }
+
+    /**
+     * 获取该指令的介绍
+     * @param bool $short 是否简洁介绍
+     * @return string
+     */
+    public function getSynopsis(bool $short = false): string
+    {
+        $elements = [];
+
+        if ($short && $this->getOptions()) {
+            $elements[] = '[options]';
+        } elseif (!$short) {
+            foreach ($this->getOptions() as $option) {
+                $value = '';
+                if ($option->acceptValue()) {
+                    $value = sprintf(' %s%s%s', $option->isValueOptional() ? '[' : '', strtoupper($option->getName()), $option->isValueOptional() ? ']' : '');
+                }
+
+                $shortcut   = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : '';
+                $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value);
+            }
+        }
+
+        if (count($elements) && $this->getArguments()) {
+            $elements[] = '[--]';
+        }
+
+        foreach ($this->getArguments() as $argument) {
+            $element = '<' . $argument->getName() . '>';
+            if (!$argument->isRequired()) {
+                $element = '[' . $element . ']';
+            } elseif ($argument->isArray()) {
+                $element .= ' (' . $element . ')';
+            }
+
+            if ($argument->isArray()) {
+                $element .= '...';
+            }
+
+            $elements[] = $element;
+        }
+
+        return implode(' ', $elements);
+    }
+}

+ 221 - 0
vendor/topthink/framework/src/think/console/input/Option.php

@@ -0,0 +1,221 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\input;
+
+/**
+ * 命令行选项
+ * @package think\console\input
+ */
+class Option
+{
+    // 无需传值
+    const VALUE_NONE     = 1;
+    // 必须传值
+    const VALUE_REQUIRED = 2;
+    // 可选传值
+    const VALUE_OPTIONAL = 4;
+    // 传数组值
+    const VALUE_IS_ARRAY = 8;
+
+    /**
+     * 选项名
+     * @var string
+     */
+    private $name;
+
+    /**
+     * 选项短名称
+     * @var string
+     */
+    private $shortcut;
+
+    /**
+     * 选项类型
+     * @var int
+     */
+    private $mode;
+
+    /**
+     * 选项默认值
+     * @var mixed
+     */
+    private $default;
+
+    /**
+     * 选项描述
+     * @var string
+     */
+    private $description;
+
+    /**
+     * 构造方法
+     * @param string       $name        选项名
+     * @param string|array $shortcut    短名称,多个用|隔开或者使用数组
+     * @param int          $mode        选项类型(可选类型为 self::VALUE_*)
+     * @param string       $description 描述
+     * @param mixed        $default     默认值 (类型为 self::VALUE_REQUIRED 或者 self::VALUE_NONE 的时候必须为null)
+     * @throws \InvalidArgumentException
+     */
+    public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null)
+    {
+        if (0 === strpos($name, '--')) {
+            $name = substr($name, 2);
+        }
+
+        if (empty($name)) {
+            throw new \InvalidArgumentException('An option name cannot be empty.');
+        }
+
+        if (empty($shortcut)) {
+            $shortcut = null;
+        }
+
+        if (null !== $shortcut) {
+            if (is_array($shortcut)) {
+                $shortcut = implode('|', $shortcut);
+            }
+            $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-'));
+            $shortcuts = array_filter($shortcuts);
+            $shortcut  = implode('|', $shortcuts);
+
+            if (empty($shortcut)) {
+                throw new \InvalidArgumentException('An option shortcut cannot be empty.');
+            }
+        }
+
+        if (null === $mode) {
+            $mode = self::VALUE_NONE;
+        } elseif (!is_int($mode) || $mode > 15 || $mode < 1) {
+            throw new \InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode));
+        }
+
+        $this->name        = $name;
+        $this->shortcut    = $shortcut;
+        $this->mode        = $mode;
+        $this->description = $description;
+
+        if ($this->isArray() && !$this->acceptValue()) {
+            throw new \InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.');
+        }
+
+        $this->setDefault($default);
+    }
+
+    /**
+     * 获取短名称
+     * @return string
+     */
+    public function getShortcut()
+    {
+        return $this->shortcut;
+    }
+
+    /**
+     * 获取选项名
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * 是否可以设置值
+     * @return bool 类型不是 self::VALUE_NONE 的时候返回true,其他均返回false
+     */
+    public function acceptValue()
+    {
+        return $this->isValueRequired() || $this->isValueOptional();
+    }
+
+    /**
+     * 是否必须
+     * @return bool 类型是 self::VALUE_REQUIRED 的时候返回true,其他均返回false
+     */
+    public function isValueRequired()
+    {
+        return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode);
+    }
+
+    /**
+     * 是否可选
+     * @return bool 类型是 self::VALUE_OPTIONAL 的时候返回true,其他均返回false
+     */
+    public function isValueOptional()
+    {
+        return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode);
+    }
+
+    /**
+     * 选项值是否接受数组
+     * @return bool 类型是 self::VALUE_IS_ARRAY 的时候返回true,其他均返回false
+     */
+    public function isArray()
+    {
+        return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode);
+    }
+
+    /**
+     * 设置默认值
+     * @param mixed $default 默认值
+     * @throws \LogicException
+     */
+    public function setDefault($default = null)
+    {
+        if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) {
+            throw new \LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.');
+        }
+
+        if ($this->isArray()) {
+            if (null === $default) {
+                $default = [];
+            } elseif (!is_array($default)) {
+                throw new \LogicException('A default value for an array option must be an array.');
+            }
+        }
+
+        $this->default = $this->acceptValue() ? $default : false;
+    }
+
+    /**
+     * 获取默认值
+     * @return mixed
+     */
+    public function getDefault()
+    {
+        return $this->default;
+    }
+
+    /**
+     * 获取描述文字
+     * @return string
+     */
+    public function getDescription()
+    {
+        return $this->description;
+    }
+
+    /**
+     * 检查所给选项是否是当前这个
+     * @param Option $option
+     * @return bool
+     */
+    public function equals(Option $option)
+    {
+        return $option->getName() === $this->getName()
+        && $option->getShortcut() === $this->getShortcut()
+        && $option->getDefault() === $this->getDefault()
+        && $option->isArray() === $this->isArray()
+        && $option->isValueRequired() === $this->isValueRequired()
+        && $option->isValueOptional() === $this->isValueOptional();
+    }
+}

+ 336 - 0
vendor/topthink/framework/src/think/console/output/Ask.php

@@ -0,0 +1,336 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output;
+
+use think\console\Input;
+use think\console\Output;
+use think\console\output\question\Choice;
+use think\console\output\question\Confirmation;
+
+class Ask
+{
+    private static $stty;
+
+    private static $shell;
+
+    /** @var  Input */
+    protected $input;
+
+    /** @var  Output */
+    protected $output;
+
+    /** @var  Question */
+    protected $question;
+
+    public function __construct(Input $input, Output $output, Question $question)
+    {
+        $this->input    = $input;
+        $this->output   = $output;
+        $this->question = $question;
+    }
+
+    public function run()
+    {
+        if (!$this->input->isInteractive()) {
+            return $this->question->getDefault();
+        }
+
+        if (!$this->question->getValidator()) {
+            return $this->doAsk();
+        }
+
+        $that = $this;
+
+        $interviewer = function () use ($that) {
+            return $that->doAsk();
+        };
+
+        return $this->validateAttempts($interviewer);
+    }
+
+    protected function doAsk()
+    {
+        $this->writePrompt();
+
+        $inputStream  = STDIN;
+        $autocomplete = $this->question->getAutocompleterValues();
+
+        if (null === $autocomplete || !$this->hasSttyAvailable()) {
+            $ret = false;
+            if ($this->question->isHidden()) {
+                try {
+                    $ret = trim($this->getHiddenResponse($inputStream));
+                } catch (\RuntimeException $e) {
+                    if (!$this->question->isHiddenFallback()) {
+                        throw $e;
+                    }
+                }
+            }
+
+            if (false === $ret) {
+                $ret = fgets($inputStream, 4096);
+                if (false === $ret) {
+                    throw new \RuntimeException('Aborted');
+                }
+                $ret = trim($ret);
+            }
+        } else {
+            $ret = trim($this->autocomplete($inputStream));
+        }
+
+        $ret = strlen($ret) > 0 ? $ret : $this->question->getDefault();
+
+        if ($normalizer = $this->question->getNormalizer()) {
+            return $normalizer($ret);
+        }
+
+        return $ret;
+    }
+
+    private function autocomplete($inputStream)
+    {
+        $autocomplete = $this->question->getAutocompleterValues();
+        $ret          = '';
+
+        $i          = 0;
+        $ofs        = -1;
+        $matches    = $autocomplete;
+        $numMatches = count($matches);
+
+        $sttyMode = shell_exec('stty -g');
+
+        shell_exec('stty -icanon -echo');
+
+        while (!feof($inputStream)) {
+            $c = fread($inputStream, 1);
+
+            if ("\177" === $c) {
+                if (0 === $numMatches && 0 !== $i) {
+                    --$i;
+                    $this->output->write("\033[1D");
+                }
+
+                if ($i === 0) {
+                    $ofs        = -1;
+                    $matches    = $autocomplete;
+                    $numMatches = count($matches);
+                } else {
+                    $numMatches = 0;
+                }
+
+                $ret = substr($ret, 0, $i);
+            } elseif ("\033" === $c) {
+                $c .= fread($inputStream, 2);
+
+                if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
+                    if ('A' === $c[2] && -1 === $ofs) {
+                        $ofs = 0;
+                    }
+
+                    if (0 === $numMatches) {
+                        continue;
+                    }
+
+                    $ofs += ('A' === $c[2]) ? -1 : 1;
+                    $ofs = ($numMatches + $ofs) % $numMatches;
+                }
+            } elseif (ord($c) < 32) {
+                if ("\t" === $c || "\n" === $c) {
+                    if ($numMatches > 0 && -1 !== $ofs) {
+                        $ret = $matches[$ofs];
+                        $this->output->write(substr($ret, $i));
+                        $i = strlen($ret);
+                    }
+
+                    if ("\n" === $c) {
+                        $this->output->write($c);
+                        break;
+                    }
+
+                    $numMatches = 0;
+                }
+
+                continue;
+            } else {
+                $this->output->write($c);
+                $ret .= $c;
+                ++$i;
+
+                $numMatches = 0;
+                $ofs        = 0;
+
+                foreach ($autocomplete as $value) {
+                    if (0 === strpos($value, $ret) && $i !== strlen($value)) {
+                        $matches[$numMatches++] = $value;
+                    }
+                }
+            }
+
+            $this->output->write("\033[K");
+
+            if ($numMatches > 0 && -1 !== $ofs) {
+                $this->output->write("\0337");
+                $this->output->highlight(substr($matches[$ofs], $i));
+                $this->output->write("\0338");
+            }
+        }
+
+        shell_exec(sprintf('stty %s', $sttyMode));
+
+        return $ret;
+    }
+
+    protected function getHiddenResponse($inputStream)
+    {
+        if ('\\' === DIRECTORY_SEPARATOR) {
+            $exe = __DIR__ . '/../bin/hiddeninput.exe';
+
+            $value = rtrim(shell_exec($exe));
+            $this->output->writeln('');
+
+            return $value;
+        }
+
+        if ($this->hasSttyAvailable()) {
+            $sttyMode = shell_exec('stty -g');
+
+            shell_exec('stty -echo');
+            $value = fgets($inputStream, 4096);
+            shell_exec(sprintf('stty %s', $sttyMode));
+
+            if (false === $value) {
+                throw new \RuntimeException('Aborted');
+            }
+
+            $value = trim($value);
+            $this->output->writeln('');
+
+            return $value;
+        }
+
+        if (false !== $shell = $this->getShell()) {
+            $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword';
+            $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd);
+            $value   = rtrim(shell_exec($command));
+            $this->output->writeln('');
+
+            return $value;
+        }
+
+        throw new \RuntimeException('Unable to hide the response.');
+    }
+
+    protected function validateAttempts($interviewer)
+    {
+        /** @var \Exception $error */
+        $error    = null;
+        $attempts = $this->question->getMaxAttempts();
+        while (null === $attempts || $attempts--) {
+            if (null !== $error) {
+                $this->output->error($error->getMessage());
+            }
+
+            try {
+                return call_user_func($this->question->getValidator(), $interviewer());
+            } catch (\Exception $error) {
+            }
+        }
+
+        throw $error;
+    }
+
+    /**
+     * 显示问题的提示信息
+     */
+    protected function writePrompt()
+    {
+        $text    = $this->question->getQuestion();
+        $default = $this->question->getDefault();
+
+        switch (true) {
+            case null === $default:
+                $text = sprintf(' <info>%s</info>:', $text);
+
+                break;
+
+            case $this->question instanceof Confirmation:
+                $text = sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $text, $default ? 'yes' : 'no');
+
+                break;
+
+            case $this->question instanceof Choice && $this->question->isMultiselect():
+                $choices = $this->question->getChoices();
+                $default = explode(',', $default);
+
+                foreach ($default as $key => $value) {
+                    $default[$key] = $choices[trim($value)];
+                }
+
+                $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, implode(', ', $default));
+
+                break;
+
+            case $this->question instanceof Choice:
+                $choices = $this->question->getChoices();
+                $text    = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, $choices[$default]);
+
+                break;
+
+            default:
+                $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, $default);
+        }
+
+        $this->output->writeln($text);
+
+        if ($this->question instanceof Choice) {
+            $width = max(array_map('strlen', array_keys($this->question->getChoices())));
+
+            foreach ($this->question->getChoices() as $key => $value) {
+                $this->output->writeln(sprintf("  [<comment>%-${width}s</comment>] %s", $key, $value));
+            }
+        }
+
+        $this->output->write(' > ');
+    }
+
+    private function getShell()
+    {
+        if (null !== self::$shell) {
+            return self::$shell;
+        }
+
+        self::$shell = false;
+
+        if (file_exists('/usr/bin/env')) {
+            $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null";
+            foreach (['bash', 'zsh', 'ksh', 'csh'] as $sh) {
+                if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
+                    self::$shell = $sh;
+                    break;
+                }
+            }
+        }
+
+        return self::$shell;
+    }
+
+    private function hasSttyAvailable()
+    {
+        if (null !== self::$stty) {
+            return self::$stty;
+        }
+
+        exec('stty 2>&1', $output, $exitcode);
+
+        return self::$stty = $exitcode === 0;
+    }
+}

+ 323 - 0
vendor/topthink/framework/src/think/console/output/Descriptor.php

@@ -0,0 +1,323 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output;
+
+use think\Console;
+use think\console\Command;
+use think\console\input\Argument as InputArgument;
+use think\console\input\Definition as InputDefinition;
+use think\console\input\Option as InputOption;
+use think\console\Output;
+use think\console\output\descriptor\Console as ConsoleDescription;
+
+class Descriptor
+{
+
+    /**
+     * @var Output
+     */
+    protected $output;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function describe(Output $output, $object, array $options = [])
+    {
+        $this->output = $output;
+
+        switch (true) {
+            case $object instanceof InputArgument:
+                $this->describeInputArgument($object, $options);
+                break;
+            case $object instanceof InputOption:
+                $this->describeInputOption($object, $options);
+                break;
+            case $object instanceof InputDefinition:
+                $this->describeInputDefinition($object, $options);
+                break;
+            case $object instanceof Command:
+                $this->describeCommand($object, $options);
+                break;
+            case $object instanceof Console:
+                $this->describeConsole($object, $options);
+                break;
+            default:
+                throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object)));
+        }
+    }
+
+    /**
+     * 输出内容
+     * @param string $content
+     * @param bool   $decorated
+     */
+    protected function write($content, $decorated = false)
+    {
+        $this->output->write($content, false, $decorated ? Output::OUTPUT_NORMAL : Output::OUTPUT_RAW);
+    }
+
+    /**
+     * 描述参数
+     * @param InputArgument $argument
+     * @param array         $options
+     * @return string|mixed
+     */
+    protected function describeInputArgument(InputArgument $argument, array $options = [])
+    {
+        if (null !== $argument->getDefault()
+            && (!is_array($argument->getDefault())
+                || count($argument->getDefault()))
+        ) {
+            $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
+        } else {
+            $default = '';
+        }
+
+        $totalWidth   = $options['total_width'] ?? strlen($argument->getName());
+        $spacingWidth = $totalWidth - strlen($argument->getName()) + 2;
+
+        $this->writeText(sprintf("  <info>%s</info>%s%s%s", $argument->getName(), str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + <info> + </info> + 2 spaces
+            preg_replace('/\s*\R\s*/', PHP_EOL . str_repeat(' ', $totalWidth + 17), $argument->getDescription()), $default), $options);
+    }
+
+    /**
+     * 描述选项
+     * @param InputOption $option
+     * @param array       $options
+     * @return string|mixed
+     */
+    protected function describeInputOption(InputOption $option, array $options = [])
+    {
+        if ($option->acceptValue() && null !== $option->getDefault()
+            && (!is_array($option->getDefault())
+                || count($option->getDefault()))
+        ) {
+            $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
+        } else {
+            $default = '';
+        }
+
+        $value = '';
+        if ($option->acceptValue()) {
+            $value = '=' . strtoupper($option->getName());
+
+            if ($option->isValueOptional()) {
+                $value = '[' . $value . ']';
+            }
+        }
+
+        $totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]);
+        $synopsis   = sprintf('%s%s', $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : '    ', sprintf('--%s%s', $option->getName(), $value));
+
+        $spacingWidth = $totalWidth - strlen($synopsis) + 2;
+
+        $this->writeText(sprintf("  <info>%s</info>%s%s%s%s", $synopsis, str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + <info> + </info> + 2 spaces
+            preg_replace('/\s*\R\s*/', "\n" . str_repeat(' ', $totalWidth + 17), $option->getDescription()), $default, $option->isArray() ? '<comment> (multiple values allowed)</comment>' : ''), $options);
+    }
+
+    /**
+     * 描述输入
+     * @param InputDefinition $definition
+     * @param array           $options
+     * @return string|mixed
+     */
+    protected function describeInputDefinition(InputDefinition $definition, array $options = [])
+    {
+        $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
+        foreach ($definition->getArguments() as $argument) {
+            $totalWidth = max($totalWidth, strlen($argument->getName()));
+        }
+
+        if ($definition->getArguments()) {
+            $this->writeText('<comment>Arguments:</comment>', $options);
+            $this->writeText("\n");
+            foreach ($definition->getArguments() as $argument) {
+                $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth]));
+                $this->writeText("\n");
+            }
+        }
+
+        if ($definition->getArguments() && $definition->getOptions()) {
+            $this->writeText("\n");
+        }
+
+        if ($definition->getOptions()) {
+            $laterOptions = [];
+
+            $this->writeText('<comment>Options:</comment>', $options);
+            foreach ($definition->getOptions() as $option) {
+                if (strlen($option->getShortcut()) > 1) {
+                    $laterOptions[] = $option;
+                    continue;
+                }
+                $this->writeText("\n");
+                $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
+            }
+            foreach ($laterOptions as $option) {
+                $this->writeText("\n");
+                $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
+            }
+        }
+    }
+
+    /**
+     * 描述指令
+     * @param Command $command
+     * @param array   $options
+     * @return string|mixed
+     */
+    protected function describeCommand(Command $command, array $options = [])
+    {
+        $command->getSynopsis(true);
+        $command->getSynopsis(false);
+        $command->mergeConsoleDefinition(false);
+
+        $this->writeText('<comment>Usage:</comment>', $options);
+        foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) {
+            $this->writeText("\n");
+            $this->writeText('  ' . $usage, $options);
+        }
+        $this->writeText("\n");
+
+        $definition = $command->getNativeDefinition();
+        if ($definition->getOptions() || $definition->getArguments()) {
+            $this->writeText("\n");
+            $this->describeInputDefinition($definition, $options);
+            $this->writeText("\n");
+        }
+
+        if ($help = $command->getProcessedHelp()) {
+            $this->writeText("\n");
+            $this->writeText('<comment>Help:</comment>', $options);
+            $this->writeText("\n");
+            $this->writeText(' ' . str_replace("\n", "\n ", $help), $options);
+            $this->writeText("\n");
+        }
+    }
+
+    /**
+     * 描述控制台
+     * @param Console $console
+     * @param array   $options
+     * @return string|mixed
+     */
+    protected function describeConsole(Console $console, array $options = [])
+    {
+        $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
+        $description        = new ConsoleDescription($console, $describedNamespace);
+
+        if (isset($options['raw_text']) && $options['raw_text']) {
+            $width = $this->getColumnWidth($description->getNamespaces());
+
+            foreach ($description->getCommands() as $command) {
+                $this->writeText(sprintf("%-${width}s %s", $command->getName(), $command->getDescription()), $options);
+                $this->writeText("\n");
+            }
+        } else {
+            if ('' != $help = $console->getHelp()) {
+                $this->writeText("$help\n\n", $options);
+            }
+
+            $this->writeText("<comment>Usage:</comment>\n", $options);
+            $this->writeText("  command [options] [arguments]\n\n", $options);
+
+            $this->describeInputDefinition(new InputDefinition($console->getDefinition()->getOptions()), $options);
+
+            $this->writeText("\n");
+            $this->writeText("\n");
+
+            $width = $this->getColumnWidth($description->getNamespaces());
+
+            if ($describedNamespace) {
+                $this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
+            } else {
+                $this->writeText('<comment>Available commands:</comment>', $options);
+            }
+
+            // add commands by namespace
+            foreach ($description->getNamespaces() as $namespace) {
+                if (!$describedNamespace && ConsoleDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
+                    $this->writeText("\n");
+                    $this->writeText(' <comment>' . $namespace['id'] . '</comment>', $options);
+                }
+
+                foreach ($namespace['commands'] as $name) {
+                    $this->writeText("\n");
+                    $spacingWidth = $width - strlen($name);
+                    $this->writeText(sprintf("  <info>%s</info>%s%s", $name, str_repeat(' ', $spacingWidth), $description->getCommand($name)
+                            ->getDescription()), $options);
+                }
+            }
+
+            $this->writeText("\n");
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    private function writeText($content, array $options = [])
+    {
+        $this->write(isset($options['raw_text'])
+            && $options['raw_text'] ? strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : true);
+    }
+
+    /**
+     * 格式化
+     * @param mixed $default
+     * @return string
+     */
+    private function formatDefaultValue($default)
+    {
+        return json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
+    }
+
+    /**
+     * @param Namespaces[] $namespaces
+     * @return int
+     */
+    private function getColumnWidth(array $namespaces)
+    {
+        $width = 0;
+        foreach ($namespaces as $namespace) {
+            foreach ($namespace['commands'] as $name) {
+                if (strlen($name) > $width) {
+                    $width = strlen($name);
+                }
+            }
+        }
+
+        return $width + 2;
+    }
+
+    /**
+     * @param InputOption[] $options
+     * @return int
+     */
+    private function calculateTotalWidthForOptions($options)
+    {
+        $totalWidth = 0;
+        foreach ($options as $option) {
+            $nameLength = 4 + strlen($option->getName()) + 2; // - + shortcut + , + whitespace + name + --
+
+            if ($option->acceptValue()) {
+                $valueLength = 1 + strlen($option->getName()); // = + value
+                $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]
+
+                $nameLength += $valueLength;
+            }
+            $totalWidth = max($totalWidth, $nameLength);
+        }
+
+        return $totalWidth;
+    }
+}

+ 198 - 0
vendor/topthink/framework/src/think/console/output/Formatter.php

@@ -0,0 +1,198 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+namespace think\console\output;
+
+use think\console\output\formatter\Stack as StyleStack;
+use think\console\output\formatter\Style;
+
+class Formatter
+{
+
+    private $decorated = false;
+    private $styles    = [];
+    private $styleStack;
+
+    /**
+     * 转义
+     * @param string $text
+     * @return string
+     */
+    public static function escape($text)
+    {
+        return preg_replace('/([^\\\\]?)</is', '$1\\<', $text);
+    }
+
+    /**
+     * 初始化命令行输出格式
+     */
+    public function __construct()
+    {
+        $this->setStyle('error', new Style('white', 'red'));
+        $this->setStyle('info', new Style('green'));
+        $this->setStyle('comment', new Style('yellow'));
+        $this->setStyle('question', new Style('black', 'cyan'));
+        $this->setStyle('highlight', new Style('red'));
+        $this->setStyle('warning', new Style('black', 'yellow'));
+
+        $this->styleStack = new StyleStack();
+    }
+
+    /**
+     * 设置外观标识
+     * @param bool $decorated 是否美化文字
+     */
+    public function setDecorated($decorated)
+    {
+        $this->decorated = (bool) $decorated;
+    }
+
+    /**
+     * 获取外观标识
+     * @return bool
+     */
+    public function isDecorated()
+    {
+        return $this->decorated;
+    }
+
+    /**
+     * 添加一个新样式
+     * @param string $name  样式名
+     * @param Style  $style 样式实例
+     */
+    public function setStyle($name, Style $style)
+    {
+        $this->styles[strtolower($name)] = $style;
+    }
+
+    /**
+     * 是否有这个样式
+     * @param string $name
+     * @return bool
+     */
+    public function hasStyle($name)
+    {
+        return isset($this->styles[strtolower($name)]);
+    }
+
+    /**
+     * 获取样式
+     * @param string $name
+     * @return Style
+     * @throws \InvalidArgumentException
+     */
+    public function getStyle($name)
+    {
+        if (!$this->hasStyle($name)) {
+            throw new \InvalidArgumentException(sprintf('Undefined style: %s', $name));
+        }
+
+        return $this->styles[strtolower($name)];
+    }
+
+    /**
+     * 使用所给的样式格式化文字
+     * @param string $message 文字
+     * @return string
+     */
+    public function format($message)
+    {
+        $offset   = 0;
+        $output   = '';
+        $tagRegex = '[a-z][a-z0-9_=;-]*';
+        preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#isx", $message, $matches, PREG_OFFSET_CAPTURE);
+        foreach ($matches[0] as $i => $match) {
+            $pos  = $match[1];
+            $text = $match[0];
+
+            if (0 != $pos && '\\' == $message[$pos - 1]) {
+                continue;
+            }
+
+            $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset));
+            $offset = $pos + strlen($text);
+
+            if ($open = '/' != $text[1]) {
+                $tag = $matches[1][$i][0];
+            } else {
+                $tag = $matches[3][$i][0] ?? '';
+            }
+
+            if (!$open && !$tag) {
+                // </>
+                $this->styleStack->pop();
+            } elseif (false === $style = $this->createStyleFromString(strtolower($tag))) {
+                $output .= $this->applyCurrentStyle($text);
+            } elseif ($open) {
+                $this->styleStack->push($style);
+            } else {
+                $this->styleStack->pop($style);
+            }
+        }
+
+        $output .= $this->applyCurrentStyle(substr($message, $offset));
+
+        return str_replace('\\<', '<', $output);
+    }
+
+    /**
+     * @return StyleStack
+     */
+    public function getStyleStack()
+    {
+        return $this->styleStack;
+    }
+
+    /**
+     * 根据字符串创建新的样式实例
+     * @param string $string
+     * @return Style|bool
+     */
+    private function createStyleFromString($string)
+    {
+        if (isset($this->styles[$string])) {
+            return $this->styles[$string];
+        }
+
+        if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($string), $matches, PREG_SET_ORDER)) {
+            return false;
+        }
+
+        $style = new Style();
+        foreach ($matches as $match) {
+            array_shift($match);
+
+            if ('fg' == $match[0]) {
+                $style->setForeground($match[1]);
+            } elseif ('bg' == $match[0]) {
+                $style->setBackground($match[1]);
+            } else {
+                try {
+                    $style->setOption($match[1]);
+                } catch (\InvalidArgumentException $e) {
+                    return false;
+                }
+            }
+        }
+
+        return $style;
+    }
+
+    /**
+     * 从堆栈应用样式到文字
+     * @param string $text 文字
+     * @return string
+     */
+    private function applyCurrentStyle($text)
+    {
+        return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text;
+    }
+}

+ 211 - 0
vendor/topthink/framework/src/think/console/output/Question.php

@@ -0,0 +1,211 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output;
+
+class Question
+{
+
+    private $question;
+    private $attempts;
+    private $hidden         = false;
+    private $hiddenFallback = true;
+    private $autocompleterValues;
+    private $validator;
+    private $default;
+    private $normalizer;
+
+    /**
+     * 构造方法
+     * @param string $question 问题
+     * @param mixed  $default  默认答案
+     */
+    public function __construct($question, $default = null)
+    {
+        $this->question = $question;
+        $this->default  = $default;
+    }
+
+    /**
+     * 获取问题
+     * @return string
+     */
+    public function getQuestion()
+    {
+        return $this->question;
+    }
+
+    /**
+     * 获取默认答案
+     * @return mixed
+     */
+    public function getDefault()
+    {
+        return $this->default;
+    }
+
+    /**
+     * 是否隐藏答案
+     * @return bool
+     */
+    public function isHidden()
+    {
+        return $this->hidden;
+    }
+
+    /**
+     * 隐藏答案
+     * @param bool $hidden
+     * @return Question
+     */
+    public function setHidden($hidden)
+    {
+        if ($this->autocompleterValues) {
+            throw new \LogicException('A hidden question cannot use the autocompleter.');
+        }
+
+        $this->hidden = (bool) $hidden;
+
+        return $this;
+    }
+
+    /**
+     * 不能被隐藏是否撤销
+     * @return bool
+     */
+    public function isHiddenFallback()
+    {
+        return $this->hiddenFallback;
+    }
+
+    /**
+     * 设置不能被隐藏的时候的操作
+     * @param bool $fallback
+     * @return Question
+     */
+    public function setHiddenFallback($fallback)
+    {
+        $this->hiddenFallback = (bool) $fallback;
+
+        return $this;
+    }
+
+    /**
+     * 获取自动完成
+     * @return null|array|\Traversable
+     */
+    public function getAutocompleterValues()
+    {
+        return $this->autocompleterValues;
+    }
+
+    /**
+     * 设置自动完成的值
+     * @param null|array|\Traversable $values
+     * @return Question
+     * @throws \InvalidArgumentException
+     * @throws \LogicException
+     */
+    public function setAutocompleterValues($values)
+    {
+        if (is_array($values) && $this->isAssoc($values)) {
+            $values = array_merge(array_keys($values), array_values($values));
+        }
+
+        if (null !== $values && !is_array($values)) {
+            if (!$values instanceof \Traversable || $values instanceof \Countable) {
+                throw new \InvalidArgumentException('Autocompleter values can be either an array, `null` or an object implementing both `Countable` and `Traversable` interfaces.');
+            }
+        }
+
+        if ($this->hidden) {
+            throw new \LogicException('A hidden question cannot use the autocompleter.');
+        }
+
+        $this->autocompleterValues = $values;
+
+        return $this;
+    }
+
+    /**
+     * 设置答案的验证器
+     * @param null|callable $validator
+     * @return Question The current instance
+     */
+    public function setValidator($validator)
+    {
+        $this->validator = $validator;
+
+        return $this;
+    }
+
+    /**
+     * 获取验证器
+     * @return null|callable
+     */
+    public function getValidator()
+    {
+        return $this->validator;
+    }
+
+    /**
+     * 设置最大重试次数
+     * @param null|int $attempts
+     * @return Question
+     * @throws \InvalidArgumentException
+     */
+    public function setMaxAttempts($attempts)
+    {
+        if (null !== $attempts && $attempts < 1) {
+            throw new \InvalidArgumentException('Maximum number of attempts must be a positive value.');
+        }
+
+        $this->attempts = $attempts;
+
+        return $this;
+    }
+
+    /**
+     * 获取最大重试次数
+     * @return null|int
+     */
+    public function getMaxAttempts()
+    {
+        return $this->attempts;
+    }
+
+    /**
+     * 设置响应的回调
+     * @param string|\Closure $normalizer
+     * @return Question
+     */
+    public function setNormalizer($normalizer)
+    {
+        $this->normalizer = $normalizer;
+
+        return $this;
+    }
+
+    /**
+     * 获取响应回调
+     * The normalizer can ba a callable (a string), a closure or a class implementing __invoke.
+     * @return string|\Closure
+     */
+    public function getNormalizer()
+    {
+        return $this->normalizer;
+    }
+
+    protected function isAssoc($array)
+    {
+        return (bool) count(array_filter(array_keys($array), 'is_string'));
+    }
+}

+ 153 - 0
vendor/topthink/framework/src/think/console/output/descriptor/Console.php

@@ -0,0 +1,153 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output\descriptor;
+
+use think\Console as ThinkConsole;
+use think\console\Command;
+
+class Console
+{
+
+    const GLOBAL_NAMESPACE = '_global';
+
+    /**
+     * @var ThinkConsole
+     */
+    private $console;
+
+    /**
+     * @var null|string
+     */
+    private $namespace;
+
+    /**
+     * @var array
+     */
+    private $namespaces;
+
+    /**
+     * @var Command[]
+     */
+    private $commands;
+
+    /**
+     * @var Command[]
+     */
+    private $aliases;
+
+    /**
+     * 构造方法
+     * @param ThinkConsole $console
+     * @param string|null  $namespace
+     */
+    public function __construct(ThinkConsole $console, $namespace = null)
+    {
+        $this->console   = $console;
+        $this->namespace = $namespace;
+    }
+
+    /**
+     * @return array
+     */
+    public function getNamespaces(): array
+    {
+        if (null === $this->namespaces) {
+            $this->inspectConsole();
+        }
+
+        return $this->namespaces;
+    }
+
+    /**
+     * @return Command[]
+     */
+    public function getCommands(): array
+    {
+        if (null === $this->commands) {
+            $this->inspectConsole();
+        }
+
+        return $this->commands;
+    }
+
+    /**
+     * @param string $name
+     * @return Command
+     * @throws \InvalidArgumentException
+     */
+    public function getCommand(string $name): Command
+    {
+        if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
+            throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name));
+        }
+
+        return $this->commands[$name] ?? $this->aliases[$name];
+    }
+
+    private function inspectConsole(): void
+    {
+        $this->commands   = [];
+        $this->namespaces = [];
+
+        $all = $this->console->all($this->namespace ? $this->console->findNamespace($this->namespace) : null);
+        foreach ($this->sortCommands($all) as $namespace => $commands) {
+            $names = [];
+
+            /** @var Command $command */
+            foreach ($commands as $name => $command) {
+                if (is_string($command)) {
+                    $command = new $command();
+                }
+
+                if (!$command->getName()) {
+                    continue;
+                }
+
+                if ($command->getName() === $name) {
+                    $this->commands[$name] = $command;
+                } else {
+                    $this->aliases[$name] = $command;
+                }
+
+                $names[] = $name;
+            }
+
+            $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names];
+        }
+    }
+
+    /**
+     * @param array $commands
+     * @return array
+     */
+    private function sortCommands(array $commands): array
+    {
+        $namespacedCommands = [];
+        foreach ($commands as $name => $command) {
+            $key = $this->console->extractNamespace($name, 1);
+            if (!$key) {
+                $key = self::GLOBAL_NAMESPACE;
+            }
+
+            $namespacedCommands[$key][$name] = $command;
+        }
+        ksort($namespacedCommands);
+
+        foreach ($namespacedCommands as &$commandsSet) {
+            ksort($commandsSet);
+        }
+        // unset reference to keep scope clear
+        unset($commandsSet);
+
+        return $namespacedCommands;
+    }
+}

+ 52 - 0
vendor/topthink/framework/src/think/console/output/driver/Buffer.php

@@ -0,0 +1,52 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output\driver;
+
+use think\console\Output;
+
+class Buffer
+{
+    /**
+     * @var string
+     */
+    private $buffer = '';
+
+    public function __construct(Output $output)
+    {
+        // do nothing
+    }
+
+    public function fetch()
+    {
+        $content      = $this->buffer;
+        $this->buffer = '';
+        return $content;
+    }
+
+    public function write($messages, bool $newline = false, int $options = 0)
+    {
+        $messages = (array) $messages;
+
+        foreach ($messages as $message) {
+            $this->buffer .= $message;
+        }
+        if ($newline) {
+            $this->buffer .= "\n";
+        }
+    }
+
+    public function renderException(\Throwable $e)
+    {
+        // do nothing
+    }
+
+}

+ 368 - 0
vendor/topthink/framework/src/think/console/output/driver/Console.php

@@ -0,0 +1,368 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output\driver;
+
+use think\console\Output;
+use think\console\output\Formatter;
+
+class Console
+{
+
+    /** @var  Resource */
+    private $stdout;
+
+    /** @var  Formatter */
+    private $formatter;
+
+    private $terminalDimensions;
+
+    /** @var  Output */
+    private $output;
+
+    public function __construct(Output $output)
+    {
+        $this->output    = $output;
+        $this->formatter = new Formatter();
+        $this->stdout    = $this->openOutputStream();
+        $decorated       = $this->hasColorSupport($this->stdout);
+        $this->formatter->setDecorated($decorated);
+    }
+
+    public function setDecorated($decorated)
+    {
+        $this->formatter->setDecorated($decorated);
+    }
+
+    public function write($messages, bool $newline = false, int $type = 0, $stream = null)
+    {
+        if (Output::VERBOSITY_QUIET === $this->output->getVerbosity()) {
+            return;
+        }
+
+        $messages = (array) $messages;
+
+        foreach ($messages as $message) {
+            switch ($type) {
+                case Output::OUTPUT_NORMAL:
+                    $message = $this->formatter->format($message);
+                    break;
+                case Output::OUTPUT_RAW:
+                    break;
+                case Output::OUTPUT_PLAIN:
+                    $message = strip_tags($this->formatter->format($message));
+                    break;
+                default:
+                    throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type));
+            }
+
+            $this->doWrite($message, $newline, $stream);
+        }
+    }
+
+    public function renderException(\Throwable $e)
+    {
+        $stderr    = $this->openErrorStream();
+        $decorated = $this->hasColorSupport($stderr);
+        $this->formatter->setDecorated($decorated);
+
+        do {
+            $title = sprintf('  [%s]  ', get_class($e));
+
+            $len = $this->stringWidth($title);
+
+            $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX;
+
+            if (defined('HHVM_VERSION') && $width > 1 << 31) {
+                $width = 1 << 31;
+            }
+            $lines = [];
+            foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) {
+                foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
+
+                    $lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $line)) + 4;
+                    $lines[]    = [$line, $lineLength];
+
+                    $len = max($lineLength, $len);
+                }
+            }
+
+            $messages   = ['', ''];
+            $messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len));
+            $messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title))));
+            foreach ($lines as $line) {
+                $messages[] = sprintf('<error>  %s  %s</error>', $line[0], str_repeat(' ', $len - $line[1]));
+            }
+            $messages[] = $emptyLine;
+            $messages[] = '';
+            $messages[] = '';
+
+            $this->write($messages, true, Output::OUTPUT_NORMAL, $stderr);
+
+            if (Output::VERBOSITY_VERBOSE <= $this->output->getVerbosity()) {
+                $this->write('<comment>Exception trace:</comment>', true, Output::OUTPUT_NORMAL, $stderr);
+
+                // exception related properties
+                $trace = $e->getTrace();
+                array_unshift($trace, [
+                    'function' => '',
+                    'file'     => $e->getFile() !== null ? $e->getFile() : 'n/a',
+                    'line'     => $e->getLine() !== null ? $e->getLine() : 'n/a',
+                    'args'     => [],
+                ]);
+
+                for ($i = 0, $count = count($trace); $i < $count; ++$i) {
+                    $class    = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
+                    $type     = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
+                    $function = $trace[$i]['function'];
+                    $file     = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
+                    $line     = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
+
+                    $this->write(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line), true, Output::OUTPUT_NORMAL, $stderr);
+                }
+
+                $this->write('', true, Output::OUTPUT_NORMAL, $stderr);
+                $this->write('', true, Output::OUTPUT_NORMAL, $stderr);
+            }
+        } while ($e = $e->getPrevious());
+
+    }
+
+    /**
+     * 获取终端宽度
+     * @return int|null
+     */
+    protected function getTerminalWidth()
+    {
+        $dimensions = $this->getTerminalDimensions();
+
+        return $dimensions[0];
+    }
+
+    /**
+     * 获取终端高度
+     * @return int|null
+     */
+    protected function getTerminalHeight()
+    {
+        $dimensions = $this->getTerminalDimensions();
+
+        return $dimensions[1];
+    }
+
+    /**
+     * 获取当前终端的尺寸
+     * @return array
+     */
+    public function getTerminalDimensions(): array
+    {
+        if ($this->terminalDimensions) {
+            return $this->terminalDimensions;
+        }
+
+        if ('\\' === DIRECTORY_SEPARATOR) {
+            if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) {
+                return [(int) $matches[1], (int) $matches[2]];
+            }
+            if (preg_match('/^(\d+)x(\d+)$/', $this->getMode(), $matches)) {
+                return [(int) $matches[1], (int) $matches[2]];
+            }
+        }
+
+        if ($sttyString = $this->getSttyColumns()) {
+            if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
+                return [(int) $matches[2], (int) $matches[1]];
+            }
+            if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
+                return [(int) $matches[2], (int) $matches[1]];
+            }
+        }
+
+        return [null, null];
+    }
+
+    /**
+     * 获取stty列数
+     * @return string
+     */
+    private function getSttyColumns()
+    {
+        if (!function_exists('proc_open')) {
+            return;
+        }
+
+        $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
+        $process        = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]);
+        if (is_resource($process)) {
+            $info = stream_get_contents($pipes[1]);
+            fclose($pipes[1]);
+            fclose($pipes[2]);
+            proc_close($process);
+
+            return $info;
+        }
+        return;
+    }
+
+    /**
+     * 获取终端模式
+     * @return string <width>x<height> 或 null
+     */
+    private function getMode()
+    {
+        if (!function_exists('proc_open')) {
+            return;
+        }
+
+        $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
+        $process        = proc_open('mode CON', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]);
+        if (is_resource($process)) {
+            $info = stream_get_contents($pipes[1]);
+            fclose($pipes[1]);
+            fclose($pipes[2]);
+            proc_close($process);
+
+            if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
+                return $matches[2] . 'x' . $matches[1];
+            }
+        }
+        return;
+    }
+
+    private function stringWidth(string $string): int
+    {
+        if (!function_exists('mb_strwidth')) {
+            return strlen($string);
+        }
+
+        if (false === $encoding = mb_detect_encoding($string)) {
+            return strlen($string);
+        }
+
+        return mb_strwidth($string, $encoding);
+    }
+
+    private function splitStringByWidth(string $string, int $width): array
+    {
+        if (!function_exists('mb_strwidth')) {
+            return str_split($string, $width);
+        }
+
+        if (false === $encoding = mb_detect_encoding($string)) {
+            return str_split($string, $width);
+        }
+
+        $utf8String = mb_convert_encoding($string, 'utf8', $encoding);
+        $lines      = [];
+        $line       = '';
+        foreach (preg_split('//u', $utf8String) as $char) {
+            if (mb_strwidth($line . $char, 'utf8') <= $width) {
+                $line .= $char;
+                continue;
+            }
+            $lines[] = str_pad($line, $width);
+            $line    = $char;
+        }
+        if (strlen($line)) {
+            $lines[] = count($lines) ? str_pad($line, $width) : $line;
+        }
+
+        mb_convert_variables($encoding, 'utf8', $lines);
+
+        return $lines;
+    }
+
+    private function isRunningOS400(): bool
+    {
+        $checks = [
+            function_exists('php_uname') ? php_uname('s') : '',
+            getenv('OSTYPE'),
+            PHP_OS,
+        ];
+        return false !== stripos(implode(';', $checks), 'OS400');
+    }
+
+    /**
+     * 当前环境是否支持写入控制台输出到stdout.
+     *
+     * @return bool
+     */
+    protected function hasStdoutSupport(): bool
+    {
+        return false === $this->isRunningOS400();
+    }
+
+    /**
+     * 当前环境是否支持写入控制台输出到stderr.
+     *
+     * @return bool
+     */
+    protected function hasStderrSupport(): bool
+    {
+        return false === $this->isRunningOS400();
+    }
+
+    /**
+     * @return resource
+     */
+    private function openOutputStream()
+    {
+        if (!$this->hasStdoutSupport()) {
+            return fopen('php://output', 'w');
+        }
+        return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w');
+    }
+
+    /**
+     * @return resource
+     */
+    private function openErrorStream()
+    {
+        return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w');
+    }
+
+    /**
+     * 将消息写入到输出。
+     * @param string $message 消息
+     * @param bool   $newline 是否另起一行
+     * @param null   $stream
+     */
+    protected function doWrite($message, $newline, $stream = null)
+    {
+        if (null === $stream) {
+            $stream = $this->stdout;
+        }
+        if (false === @fwrite($stream, $message . ($newline ? PHP_EOL : ''))) {
+            throw new \RuntimeException('Unable to write output.');
+        }
+
+        fflush($stream);
+    }
+
+    /**
+     * 是否支持着色
+     * @param $stream
+     * @return bool
+     */
+    protected function hasColorSupport($stream): bool
+    {
+        if (DIRECTORY_SEPARATOR === '\\') {
+            return
+            '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD
+            || false !== getenv('ANSICON')
+            || 'ON' === getenv('ConEmuANSI')
+            || 'xterm' === getenv('TERM');
+        }
+
+        return function_exists('posix_isatty') && @posix_isatty($stream);
+    }
+
+}

+ 33 - 0
vendor/topthink/framework/src/think/console/output/driver/Nothing.php

@@ -0,0 +1,33 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output\driver;
+
+use think\console\Output;
+
+class Nothing
+{
+
+    public function __construct(Output $output)
+    {
+        // do nothing
+    }
+
+    public function write($messages, bool $newline = false, int $options = 0)
+    {
+        // do nothing
+    }
+
+    public function renderException(\Throwable $e)
+    {
+        // do nothing
+    }
+}

+ 116 - 0
vendor/topthink/framework/src/think/console/output/formatter/Stack.php

@@ -0,0 +1,116 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output\formatter;
+
+class Stack
+{
+
+    /**
+     * @var Style[]
+     */
+    private $styles;
+
+    /**
+     * @var Style
+     */
+    private $emptyStyle;
+
+    /**
+     * 构造方法
+     * @param Style|null $emptyStyle
+     */
+    public function __construct(Style $emptyStyle = null)
+    {
+        $this->emptyStyle = $emptyStyle ?: new Style();
+        $this->reset();
+    }
+
+    /**
+     * 重置堆栈
+     */
+    public function reset(): void
+    {
+        $this->styles = [];
+    }
+
+    /**
+     * 推一个样式进入堆栈
+     * @param Style $style
+     */
+    public function push(Style $style): void
+    {
+        $this->styles[] = $style;
+    }
+
+    /**
+     * 从堆栈中弹出一个样式
+     * @param Style|null $style
+     * @return Style
+     * @throws \InvalidArgumentException
+     */
+    public function pop(Style $style = null): Style
+    {
+        if (empty($this->styles)) {
+            return $this->emptyStyle;
+        }
+
+        if (null === $style) {
+            return array_pop($this->styles);
+        }
+
+        /**
+         * @var int   $index
+         * @var Style $stackedStyle
+         */
+        foreach (array_reverse($this->styles, true) as $index => $stackedStyle) {
+            if ($style->apply('') === $stackedStyle->apply('')) {
+                $this->styles = array_slice($this->styles, 0, $index);
+
+                return $stackedStyle;
+            }
+        }
+
+        throw new \InvalidArgumentException('Incorrectly nested style tag found.');
+    }
+
+    /**
+     * 计算堆栈的当前样式。
+     * @return Style
+     */
+    public function getCurrent(): Style
+    {
+        if (empty($this->styles)) {
+            return $this->emptyStyle;
+        }
+
+        return $this->styles[count($this->styles) - 1];
+    }
+
+    /**
+     * @param Style $emptyStyle
+     * @return Stack
+     */
+    public function setEmptyStyle(Style $emptyStyle)
+    {
+        $this->emptyStyle = $emptyStyle;
+
+        return $this;
+    }
+
+    /**
+     * @return Style
+     */
+    public function getEmptyStyle(): Style
+    {
+        return $this->emptyStyle;
+    }
+}

+ 190 - 0
vendor/topthink/framework/src/think/console/output/formatter/Style.php

@@ -0,0 +1,190 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think\console\output\formatter;
+
+class Style
+{
+    protected static $availableForegroundColors = [
+        'black'   => ['set' => 30, 'unset' => 39],
+        'red'     => ['set' => 31, 'unset' => 39],
+        'green'   => ['set' => 32, 'unset' => 39],
+        'yellow'  => ['set' => 33, 'unset' => 39],
+        'blue'    => ['set' => 34, 'unset' => 39],
+        'magenta' => ['set' => 35, 'unset' => 39],
+        'cyan'    => ['set' => 36, 'unset' => 39],
+        'white'   => ['set' => 37, 'unset' => 39],
+    ];
+
+    protected static $availableBackgroundColors = [
+        'black'   => ['set' => 40, 'unset' => 49],
+        'red'     => ['set' => 41, 'unset' => 49],
+        'green'   => ['set' => 42, 'unset' => 49],
+        'yellow'  => ['set' => 43, 'unset' => 49],
+        'blue'    => ['set' => 44, 'unset' => 49],
+        'magenta' => ['set' => 45, 'unset' => 49],
+        'cyan'    => ['set' => 46, 'unset' => 49],
+        'white'   => ['set' => 47, 'unset' => 49],
+    ];
+
+    protected static $availableOptions = [
+        'bold'       => ['set' => 1, 'unset' => 22],
+        'underscore' => ['set' => 4, 'unset' => 24],
+        'blink'      => ['set' => 5, 'unset' => 25],
+        'reverse'    => ['set' => 7, 'unset' => 27],
+        'conceal'    => ['set' => 8, 'unset' => 28],
+    ];
+
+    private $foreground;
+    private $background;
+    private $options = [];
+
+    /**
+     * 初始化输出的样式
+     * @param string|null $foreground 字体颜色
+     * @param string|null $background 背景色
+     * @param array       $options    格式
+     * @api
+     */
+    public function __construct($foreground = null, $background = null, array $options = [])
+    {
+        if (null !== $foreground) {
+            $this->setForeground($foreground);
+        }
+        if (null !== $background) {
+            $this->setBackground($background);
+        }
+        if (count($options)) {
+            $this->setOptions($options);
+        }
+    }
+
+    /**
+     * 设置字体颜色
+     * @param string|null $color 颜色名
+     * @throws \InvalidArgumentException
+     * @api
+     */
+    public function setForeground($color = null)
+    {
+        if (null === $color) {
+            $this->foreground = null;
+
+            return;
+        }
+
+        if (!isset(static::$availableForegroundColors[$color])) {
+            throw new \InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableForegroundColors))));
+        }
+
+        $this->foreground = static::$availableForegroundColors[$color];
+    }
+
+    /**
+     * 设置背景色
+     * @param string|null $color 颜色名
+     * @throws \InvalidArgumentException
+     * @api
+     */
+    public function setBackground($color = null)
+    {
+        if (null === $color) {
+            $this->background = null;
+
+            return;
+        }
+
+        if (!isset(static::$availableBackgroundColors[$color])) {
+            throw new \InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableBackgroundColors))));
+        }
+
+        $this->background = static::$availableBackgroundColors[$color];
+    }
+
+    /**
+     * 设置字体格式
+     * @param string $option 格式名
+     * @throws \InvalidArgumentException When the option name isn't defined
+     * @api
+     */
+    public function setOption(string $option): void
+    {
+        if (!isset(static::$availableOptions[$option])) {
+            throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions))));
+        }
+
+        if (!in_array(static::$availableOptions[$option], $this->options)) {
+            $this->options[] = static::$availableOptions[$option];
+        }
+    }
+
+    /**
+     * 重置字体格式
+     * @param string $option 格式名
+     * @throws \InvalidArgumentException
+     */
+    public function unsetOption(string $option): void
+    {
+        if (!isset(static::$availableOptions[$option])) {
+            throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions))));
+        }
+
+        $pos = array_search(static::$availableOptions[$option], $this->options);
+        if (false !== $pos) {
+            unset($this->options[$pos]);
+        }
+    }
+
+    /**
+     * 批量设置字体格式
+     * @param array $options
+     */
+    public function setOptions(array $options)
+    {
+        $this->options = [];
+
+        foreach ($options as $option) {
+            $this->setOption($option);
+        }
+    }
+
+    /**
+     * 应用样式到文字
+     * @param string $text 文字
+     * @return string
+     */
+    public function apply(string $text): string
+    {
+        $setCodes   = [];
+        $unsetCodes = [];
+
+        if (null !== $this->foreground) {
+            $setCodes[]   = $this->foreground['set'];
+            $unsetCodes[] = $this->foreground['unset'];
+        }
+        if (null !== $this->background) {
+            $setCodes[]   = $this->background['set'];
+            $unsetCodes[] = $this->background['unset'];
+        }
+        if (count($this->options)) {
+            foreach ($this->options as $option) {
+                $setCodes[]   = $option['set'];
+                $unsetCodes[] = $option['unset'];
+            }
+        }
+
+        if (0 === count($setCodes)) {
+            return $text;
+        }
+
+        return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes));
+    }
+}

Some files were not shown because too many files changed in this diff