zhangxiaobin 1 year ago
parent
commit
424b643451
100 changed files with 29567 additions and 0 deletions
  1. BIN
      .DS_Store
  2. 10 0
      .bowerrc
  3. 11 0
      .env.sample
  4. 0 0
      .htaccess
  5. 7 0
      404.html
  6. 191 0
      LICENSE
  7. 1761 0
      Party.php
  8. 97 0
      README.md
  9. 34 0
      bower.json
  10. 25 0
      build.php
  11. 37 0
      composer.json
  12. 3034 0
      composer.lock
  13. 39 0
      index.html
  14. 0 0
      nginx.htaccess
  15. 5 0
      nohup.out
  16. 5 0
      server.php
  17. 37 0
      start.php
  18. 17 0
      think
  19. BIN
      thinkphp/.DS_Store
  20. 119 0
      thinkphp/CONTRIBUTING.md
  21. 32 0
      thinkphp/LICENSE.txt
  22. 114 0
      thinkphp/README.md
  23. 65 0
      thinkphp/base.php
  24. 12 0
      thinkphp/codecov.yml
  25. 35 0
      thinkphp/composer.json
  26. 20 0
      thinkphp/console.php
  27. 298 0
      thinkphp/convention.php
  28. 589 0
      thinkphp/helper.php
  29. 136 0
      thinkphp/lang/zh-cn.php
  30. BIN
      thinkphp/library/.DS_Store
  31. 677 0
      thinkphp/library/think/App.php
  32. 235 0
      thinkphp/library/think/Build.php
  33. 247 0
      thinkphp/library/think/Cache.php
  34. 467 0
      thinkphp/library/think/Collection.php
  35. 214 0
      thinkphp/library/think/Config.php
  36. 863 0
      thinkphp/library/think/Console.php
  37. 229 0
      thinkphp/library/think/Controller.php
  38. 268 0
      thinkphp/library/think/Cookie.php
  39. 180 0
      thinkphp/library/think/Db.php
  40. 252 0
      thinkphp/library/think/Debug.php
  41. 39 0
      thinkphp/library/think/Env.php
  42. 136 0
      thinkphp/library/think/Error.php
  43. 55 0
      thinkphp/library/think/Exception.php
  44. 478 0
      thinkphp/library/think/File.php
  45. 148 0
      thinkphp/library/think/Hook.php
  46. 265 0
      thinkphp/library/think/Lang.php
  47. 677 0
      thinkphp/library/think/Loader.php
  48. 237 0
      thinkphp/library/think/Log.php
  49. 2350 0
      thinkphp/library/think/Model.php
  50. 409 0
      thinkphp/library/think/Paginator.php
  51. 1205 0
      thinkphp/library/think/Process.php
  52. 1690 0
      thinkphp/library/think/Request.php
  53. 332 0
      thinkphp/library/think/Response.php
  54. 1645 0
      thinkphp/library/think/Route.php
  55. 366 0
      thinkphp/library/think/Session.php
  56. 1139 0
      thinkphp/library/think/Template.php
  57. 333 0
      thinkphp/library/think/Url.php
  58. 1371 0
      thinkphp/library/think/Validate.php
  59. 239 0
      thinkphp/library/think/View.php
  60. 231 0
      thinkphp/library/think/cache/Driver.php
  61. 268 0
      thinkphp/library/think/cache/driver/File.php
  62. 187 0
      thinkphp/library/think/cache/driver/Lite.php
  63. 177 0
      thinkphp/library/think/cache/driver/Memcache.php
  64. 187 0
      thinkphp/library/think/cache/driver/Memcached.php
  65. 188 0
      thinkphp/library/think/cache/driver/Redis.php
  66. 199 0
      thinkphp/library/think/cache/driver/Sqlite.php
  67. 152 0
      thinkphp/library/think/cache/driver/Wincache.php
  68. 155 0
      thinkphp/library/think/cache/driver/Xcache.php
  69. 24 0
      thinkphp/library/think/config/driver/Ini.php
  70. 24 0
      thinkphp/library/think/config/driver/Json.php
  71. 31 0
      thinkphp/library/think/config/driver/Xml.php
  72. 470 0
      thinkphp/library/think/console/Command.php
  73. 464 0
      thinkphp/library/think/console/Input.php
  74. 19 0
      thinkphp/library/think/console/LICENSE
  75. 222 0
      thinkphp/library/think/console/Output.php
  76. 1 0
      thinkphp/library/think/console/bin/README.md
  77. BIN
      thinkphp/library/think/console/bin/hiddeninput.exe
  78. 56 0
      thinkphp/library/think/console/command/Build.php
  79. 63 0
      thinkphp/library/think/console/command/Clear.php
  80. 69 0
      thinkphp/library/think/console/command/Help.php
  81. 74 0
      thinkphp/library/think/console/command/Lists.php
  82. 110 0
      thinkphp/library/think/console/command/Make.php
  83. 50 0
      thinkphp/library/think/console/command/make/Controller.php
  84. 36 0
      thinkphp/library/think/console/command/make/Model.php
  85. 10 0
      thinkphp/library/think/console/command/make/stubs/controller.plain.stub
  86. 85 0
      thinkphp/library/think/console/command/make/stubs/controller.stub
  87. 10 0
      thinkphp/library/think/console/command/make/stubs/model.stub
  88. 294 0
      thinkphp/library/think/console/command/optimize/Autoload.php
  89. 93 0
      thinkphp/library/think/console/command/optimize/Config.php
  90. 75 0
      thinkphp/library/think/console/command/optimize/Route.php
  91. 118 0
      thinkphp/library/think/console/command/optimize/Schema.php
  92. 115 0
      thinkphp/library/think/console/input/Argument.php
  93. 375 0
      thinkphp/library/think/console/input/Definition.php
  94. 190 0
      thinkphp/library/think/console/input/Option.php
  95. 340 0
      thinkphp/library/think/console/output/Ask.php
  96. 319 0
      thinkphp/library/think/console/output/Descriptor.php
  97. 198 0
      thinkphp/library/think/console/output/Formatter.php
  98. 211 0
      thinkphp/library/think/console/output/Question.php
  99. 149 0
      thinkphp/library/think/console/output/descriptor/Console.php
  100. 52 0
      thinkphp/library/think/console/output/driver/Buffer.php

BIN
.DS_Store


+ 10 - 0
.bowerrc

@@ -0,0 +1,10 @@
+{
+  "directory": "public/assets/libs",
+  "ignoredDependencies": [
+    "es6-promise",
+    "file-saver",
+    "html2canvas",
+    "jspdf",
+    "jspdf-autotable"
+  ]
+}

+ 11 - 0
.env.sample

@@ -0,0 +1,11 @@
+[app]
+debug = false
+trace = false
+
+[database]
+hostname = 127.0.0.1
+database = fastadmin
+username = root
+password = root
+hostport = 3306
+prefix = fa_

+ 0 - 0
.htaccess


+ 7 - 0
404.html

@@ -0,0 +1,7 @@
+<html>
+<head><title>404 Not Found</title></head>
+<body>
+<center><h1>404 Not Found</h1></center>
+<hr><center>nginx</center>
+</body>
+</html>

+ 191 - 0
LICENSE

@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "{}" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+   Copyright 2017 Karson
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 1761 - 0
Party.php

@@ -0,0 +1,1761 @@
+<?php
+
+namespace app\api\controller;
+
+use app\api\controller\Common;
+use app\common\model\PartyJoin;
+use fast\Random;
+use app\common\controller\RedisLeaderboard;
+use Redis;
+use think\Db;
+use think\Request;
+
+/**
+ * 派对信息接口
+ */
+class Party extends Common
+{
+    protected $noNeedLogin = ['updatePartyInfo', 'getPatyType', 'addUserPositionToParty', 'clearMoney', 'clearCharm', 'getPartyRankList', 'getDefaultBackground', 'getPartGifList', 'getPartHeadgifList', 'isNotalk', 'getMusicList', 'updateTops'];
+    protected $noNeedRight = ['*'];
+
+    public function __construct(Request $request = null)
+    {
+        $this->roomTypeArr = [1 => "party", 2 => "live"];
+
+        parent::__construct($request);
+    }
+
+
+    /**
+     * 创建/进入派对
+     */
+    public function createParty()
+    {
+        $room_type = $this->request->request('room_type', 1); // 房间类型:1=派对,2=直播
+        $partyModel = new \app\common\model\Party();
+
+        $user_id = $this->auth->id;
+        // 开直播时,判断当前用户是否为主播
+        if ($room_type == 2) {
+            $anchorInfo = \app\common\model\UserAnchor::where(["user_id" => $user_id])->find();
+
+            $anchorInfo || $this->error("您还未申请主播!");
+            $anchorInfo->status == 0 && $this->error("您的主播申请信息还未受理,请耐心等待!");
+            $anchorInfo->status == 2 && $this->error("您的主播申请未通过,请重新提交!");
+        }
+
+        // 1。实名认证
+        $userAuthInfo = \app\common\model\UserAuth::where(["user_id" => $user_id])->find();
+        if ($userAuthInfo) {
+            if ($userAuthInfo->status == 0) {
+                $this->error("您的实名认证还在这审核中...,请耐心等待!");
+            } elseif ($userAuthInfo->status == 2) {
+                $this->error("您的实名认证审核未通过,请重新审核!");
+            }
+        } else {
+            $this->error("请先申请实名认证!");
+        }
+
+        if ($room_type == 1) {
+            // 2。开厅申请
+            $authInfo = \app\common\model\GuildApply::where(["user_id" => $user_id])->find();
+            if ($authInfo) {
+                if ($authInfo->status == 0) {
+                    $this->error("您的开厅申请还在这审核中...,请耐心等待!");
+                } elseif ($authInfo->status == -1) {
+                    $this->error("您的开厅申请审核未通过,请重新审核!");
+                }
+            } else {
+                $this->error("请先申请开厅资质!");
+            }
+        }
+
+
+        // 判断派对是否存在
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+
+        $where = [];
+        $where["user_id"] = $user_id;
+        $where["room_type"] = $room_type;
+        $sqlPartyInfo = $partyModel->where($where)->find();
+
+        if (!$sqlPartyInfo) { // sql中不存在派对信息
+            $partylimit = config("site.roomLimit");
+            $partycount = $partyModel->count("id");
+            if ($partycount >= $partylimit && $partylimit != -1) {
+                $this->error("房间数量已达上线,请联系客服!");
+            }
+
+            $party_name = $this->auth->nickname . "的房间"; // 派对名称
+            $party_logo = "/assets/api/party_logo.png"; // 派对logo
+            $party_type = 0; // 派对类型(情感互动,心动点单 等)
+            $party_notice = "请编辑房间公告"; // 派对公告
+            $party_notice_detail = "请编辑房间公告内容!"; // 派对公告详情
+            if (!$party_name || !$party_logo || !$party_notice) {
+                $this->error(__('Invalid parameters'));
+            }
+            $party_ids = $partyModel->column("party_id");
+            // 创建派对ID (临时ID四位,派对数不超过8999)
+            $party_id = $this->auth->getUinqueId(4, $party_ids);
+            if ($party_id > 9999) {
+                $this->error("派对超限,请联系客服");
+            }
+            $data = [];
+            $data["user_id"] = $this->auth->id;
+            $data["room_type"] = $room_type;
+            $data["party_id"] = $party_id;
+            $data["party_hot"] = 0;
+            $data["party_name"] = $party_name;
+            $data["party_logo"] = $party_logo;
+            $data["party_type"] = $party_type;
+            $data["party_notice"] = $party_notice;
+            $data["party_notice_detail"] = $party_notice_detail;
+            $data["is_online"] = 1;
+            $data["status"] = 0;
+            $data["is_recommend"] = 0;
+            $data["createtime"] = time();
+            $id = $partyModel->insertGetId($data);
+            if (!$id) {
+                $this->error("派对创建失败,请稍后重试!");
+            }
+            $data["id"] = $id;
+
+            if ($room_type == 2) {
+                \app\common\model\User::update(["is_live" => 1], ["id" => $user_id]);
+            }
+
+            $partyInfo = $partyModel->where(["id" => $id])->find();
+            $partyInfo["is_new"] = 1;
+
+            // 冠名
+            $userInfo = [];
+            if ($partyInfo["naming"]) {
+                $userInfo = \app\common\model\User::field("id,nickname,avatar")->where(["id" => $partyInfo["naming"]])->find();
+            }
+            // 头像
+            if ($partyInfo["user_id"] > 0 && $room_type == 2) {
+                $partyInfo["avatar"] = \app\common\model\User::where(["id" => $partyInfo["user_id"]])->value("avatar");
+            }
+            // 派对类型
+            $partyTypeName = "普通房";
+            if ($partyInfo["party_type"]) {
+                $partyTypeName = \app\common\model\PartyType::where(["id" => $partyInfo["party_type"]])->value("name");
+            }
+            $partyInfo["naming"] = $userInfo;
+            $partyInfo["type_name"] = $partyTypeName;
+
+            // 加入缓存排序
+            $redis->zAdd($this->roomTypeArr[$room_type] . "Rank", $partyInfo['party_hot'], $partyInfo["id"]);
+            // 加入缓存
+            $redis->set($this->roomTypeArr[$room_type] . "_" . $partyInfo["id"], json_encode($partyInfo));
+
+            if ($room_type != 2) {
+                // 创建公会
+                \app\common\model\Guild::createGuild($partyInfo["id"], $partyInfo["party_name"], $user_id);
+                \app\common\model\User::update(["is_guild" => 3], ["id" => $user_id]);
+            }
+
+        } else {
+            if ($sqlPartyInfo["is_close"] == 1) $this->error("该房间已被关闭!");
+            if ($sqlPartyInfo["status"] != 1) $this->error("该房间为预创建房间,请联系管理员正式开通!");
+            $partyInfo = $sqlPartyInfo;
+            $partyInfo["is_new"] = 0;
+        }
+
+        $this->success("获取成功!", $partyInfo);
+    }
+
+    /**
+     * 更新房间状态为正常
+     */
+    public function savePartyStatus()
+    {
+        $party_id = $this->request->request('party_id'); // 派对ID
+        $room_type = $this->request->request('room_type', 1); // 房间类型:1=派对,2=直播
+        if (!$party_id || !in_array($room_type, [1, 2])) $this->error(__('Invalid parameters'));
+        $user_id = $this->auth->id;
+        $partyInfo = \app\common\model\Party::get($party_id);
+        if ($partyInfo->status < 0) $this->error("当前状态不支持变更!");
+        if ($partyInfo->user_id !== $user_id) $this->error("当前账号异常,非工会长禁止操作!");
+        $partyInfo->status = 1;
+        $res = $partyInfo->save();
+
+        if ($res) {
+            // 更新redis  加入缓存排序
+            $redis = new Redis();
+            $redisconfig = config("redis");
+            $redis->connect($redisconfig["host"], $redisconfig["port"]);
+
+            // 更新redis  加入缓存
+            $redPartyInfo = $redis->get($this->roomTypeArr[$room_type] . "_" . $partyInfo["id"]);
+            if ($redPartyInfo) {
+                $redPartyInfo = json_decode($redPartyInfo, true);
+                $redPartyInfo["status"] = 1;
+                $redis->set($this->roomTypeArr[$room_type] . "_" . $partyInfo["id"], json_encode($redPartyInfo));
+            }
+            $this->success("更新公会状态成功!");
+        } else {
+            $this->error("网络错误,请稍后重试!");
+        }
+
+
+    }
+
+    /**
+     * 删除派对(群解散)
+     */
+    public function closeParty()
+    {
+        $party_id = $this->request->request('party_id'); // 派对ID
+        $room_type = $this->request->request('room_type', 1); // 房间类型:1=派对,2=直播
+        if (!$party_id || !in_array($room_type, [1, 2])) $this->error(__('Invalid parameters'));
+        $partyInfo = \app\common\model\Party::where(["id" => $party_id])->find();
+        if (!$partyInfo) $this->error(__('派对不存在!'));
+        $res = $partyInfo->delete();
+        if ($res !== false) {
+            // redis 删除
+            $redis = new Redis();
+            $redisconfig = config("redis");
+            $redis->connect($redisconfig["host"], $redisconfig["port"]);
+            $redis->zRem($this->roomTypeArr[$room_type] . "Rank", $party_id);
+            $redis->del($this->roomTypeArr[$room_type] . "_" . $party_id);
+            $this->success("删除成功!");
+        } else {
+            $this->error("删除失败!");
+        }
+    }
+
+    /**
+     * 获取派对列表排序
+     */
+    public function getPartyRankList()
+    {
+        $thispage = $this->request->request('thispage', 1, "intval"); // 当前页数
+        $pagenum = $this->request->request('pagenum', 10, "intval"); // 每页显示条数0=不做分页
+        $type_id = $this->request->request('type_id'); // 派对类型
+        $room_type = $this->request->request('room_type', 1); // 房间类型:1=派对,2=直播
+        $is_recommend = $this->request->request('is_recommend'); // 推荐0=否1=是
+        $all = $this->request->request('all'); // 全部分类0=否1=是
+        $index = $this->request->request('index', 0); // 全部分类0=否1=是
+
+        $start = ($thispage - 1) * $pagenum;
+        $end = $start + ($pagenum - 1);
+        // 获取排序
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+        $redisPartyRankList = $redis->zRevRange($this->roomTypeArr[$room_type] . "Rank", 0, -1, true);
+        $partyModel = new \app\common\model\Party();
+        if (!$redisPartyRankList) {
+            $userModel = new \app\common\model\User();
+            // 直接从数据库获取所有数据
+            $where = [];
+            $where["a.status"] = 1;
+            $where["a.room_type"] = $room_type;
+            $sqlPartyList = $partyModel->alias("a")->field("a.*,b.name as type_name")
+                ->join("hx_party_type b", "a.party_type = b.id", "left")->where($where)->select();
+            $userList = $userModel->field("id,nickname,avatar")->where(["status" => "normal"])->select();
+            $userInfoArr = [];
+            if ($userList) foreach ($userList as $k => $v) $userInfoArr[$v["id"]] = $v;
+            if ($sqlPartyList) {
+                foreach ($sqlPartyList as $k => $v) {
+                    // 加入缓存排序
+                    $redis->zAdd($this->roomTypeArr[$room_type] . "Rank", $v['party_hot'], $v["id"]);
+                    // 设置冠名
+                    $sqlPartyList[$k]["naming"] = isset($userInfoArr[$v["naming"]]) ? $userInfoArr[$v["naming"]] : [];
+                    // 设置房主头像
+                    $sqlPartyList[$k]["avatar"] = isset($userInfoArr[$v["user_id"]]) ? $userInfoArr[$v["user_id"]]["avatar"] : [];
+                    // 加入缓存
+                    $redis->set($this->roomTypeArr[$room_type] . "_" . $v["id"], json_encode($v));
+                }
+                $redisPartyRankList = $redis->zRevRange($this->roomTypeArr[$room_type] . "Rank", 0, -1, true);
+            }
+        }
+        if ($redisPartyRankList) {
+            $resultInfo = $partyModel->getPatyInfoByPartyId($redisPartyRankList, $this->roomTypeArr[$room_type], $type_id, $is_recommend, $all, $start, $end, $index);
+            $this->success("获取成功!", $resultInfo);
+        } else {
+            $this->success("获取成功!", []);
+        }
+
+
+    }
+
+    /**
+     * 获取派对用户排序
+     */
+    public function getPartyUserRank()
+    {
+        $room_type = $this->request->request('room_type', 1); // 房间类型
+        $party_id = $this->request->request("party_id");// 派对ID
+        if (!$party_id) $this->error(__('Invalid parameters'));
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+
+        $res = [];
+        // 获取今天
+        $day = date("Ymd");
+        // 获取本周第一天
+        $weekday = $this->firstOfWeek(date("Y-m-d H:i:s"));
+        // 获取本月第一天
+        $monthday = date("Ym01");
+        $userModel = new \app\common\model\User();
+        // 获取条数
+        $num = 50;
+
+        // 获取50条财富排行日记录
+        $getday = $redis->zRevRange($this->roomTypeArr[$room_type] . "_jewel_get_" . $party_id . ":" . $day . "d", 0, $num - 1, true);
+        $res['getRankListDay'] = $userModel->rankList($getday);
+
+        // 获取50条财富排行日记录
+        $today = $redis->zRevRange($this->roomTypeArr[$room_type] . "_jewel_to_" . $party_id . ":" . $day . "d", 0, $num - 1, true);
+        $res['toRankListDay'] = $userModel->rankList($today);
+
+
+        // 获取50条财富排行周记录
+        $getweek = $redis->zRevRange($this->roomTypeArr[$room_type] . "_jewel_get_" . $party_id . ":" . $weekday . "w", 0, $num - 1, true);
+        $res['getRankListWeek'] = $userModel->rankList($getweek);
+
+        // 获取50条贡献排行周记录
+        $toweek = $redis->zRevRange($this->roomTypeArr[$room_type] . "_jewel_to_" . $party_id . ":" . $weekday . "w", 0, $num - 1, true);
+        $res['toRankListWeek'] = $userModel->rankList($toweek);
+
+
+        // 获取50条财富排行月记录
+        $toweek = $redis->zRevRange($this->roomTypeArr[$room_type] . "_jewel_get_" . $party_id . ":" . $monthday . "m", 0, $num - 1, true);
+        $res['getRankListMonth'] = $userModel->rankList($toweek);
+
+        // 获取50条贡献排行周记录
+        $toweek = $redis->zRevRange($this->roomTypeArr[$room_type] . "_jewel_to_" . $party_id . ":" . $monthday . "m", 0, $num - 1, true);
+        $res['toRankListMonth'] = $userModel->rankList($toweek);
+
+        return $this->success("获取成功!", $res);
+    }
+
+    /**
+     * 派对热度更新(已废弃)
+     */
+    public function changeUserPartyhot()
+    {
+        $party_id = $this->request->request('party_id', 0, "intval"); // 派对ID
+        $room_type = $this->request->request('room_type', 1); // 房间类型
+        $party_hot = $this->request->request('party_hot'); // 房间热度(正数表示提高的热度值,负数表示降低的热度值)
+        if (!$party_id || !$party_hot) {
+            $this->error(__('Invalid parameters'));
+        }
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+        // 更新热度
+        $redis->zIncrBy($this->roomTypeArr[$room_type] . "Rank", $party_hot, $party_id);
+        return $this->success("更新成功!");
+    }
+
+
+    /**
+     * 更新主持人和麦位前四位至首页房间排行
+     */
+    public function addUserPositionToParty()
+    {
+        $party_id = $this->request->request('party_id', 0, "intval"); // 直播间ID
+        $user_id = $this->request->request('user_id', 0, "intval"); // 用户ID
+        $upOrdown = $this->request->request('upordown'); // 上下麦:1=上麦-1=下麦
+        $room_type = $this->request->request('room_type', 1); // 房间类型
+        $avatar = $this->request->request('avatar'); // 用户头像 (upOrdown参数为-1时可以不传)
+        $position = $this->request->request('position'); // 麦位置:0=支持人,1=1号麦,2=2号麦,3=3号麦,4=4号麦
+        if (!$party_id || !$user_id || !in_array($position, [0, 1, 2, 3, 4, 5, 6, 7, 8]) || !in_array($upOrdown, [1, -1])) {
+            $this->error(__('Invalid parameters'));
+        }
+
+        if ($upOrdown == 1) {
+            // 先完成所有麦上记录
+            \app\common\model\UserOnsiteTime::update(["status" => 2], ["user_id" => $user_id]);
+
+            // 保存上麦记录
+            $data = [];
+            $data["user_id"] = $user_id;
+            $data["party_id"] = $party_id;
+            $data["onsite_time"] = time();
+            \app\common\model\UserOnsiteTime::insert($data);
+        }
+        if ($upOrdown == -1) {
+            // 更新下麦时间
+            $update = [];
+            $update["offsite_time"] = time();
+            $update["status"] = 2;
+            \app\common\model\UserOnsiteTime::update($update, ["user_id" => $user_id, "status" => 1]);
+        }
+
+
+        if (in_array($position, [5, 6, 7, 8])) return $this->success("设置成功!");
+
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+        $redisData = $redis->get($this->roomTypeArr[$room_type] . "_" . $party_id);
+        if ($redisData) {
+            $partyInfo = json_decode($redisData, true);
+            if ($upOrdown == 1) {
+                // 删掉已有头像,防止重复
+                $partyuser = isset($partyInfo["party_user"]) ? $partyInfo["party_user"] : "";
+                if (is_array($partyuser)) foreach ($partyuser as $k => $v) if ($v === $avatar) unset($partyInfo["party_user"][$k]);
+
+                $partyInfo["party_user"][$position] = $avatar;
+            } else {
+                if (isset($partyInfo["party_user"])) unset($partyInfo["party_user"][$position]);
+            }
+//        sort($partyInfo["party_user"]);
+            $redis->set($this->roomTypeArr[$room_type] . "_" . $party_id, json_encode($partyInfo));
+        }
+
+        return $this->success("设置成功!");
+    }
+
+    /**
+     * 获取派对在线人数列表
+     */
+    public function getOnlieList()
+    {
+        $party_id = $this->request->request('party_id', 0, "intval"); // 直播间ID 逻辑ID
+        $page = $this->request->request('page', 1); // 分页
+        $pageNum = $this->request->request('pageNum', 10); // 分页
+        // 分页搜索构建
+        $pageStart = ($page - 1) * $pageNum;
+        if (!$party_id) {
+            $this->error(__('Invalid parameters'));
+        }
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+
+        $res = $redis->hGetAll("online_" . $party_id);
+        $user_ids = [];
+        $userList = [];
+        if ($res) $user_ids = array_values($res);
+        // 获取用户列表信息
+        $user_ids && $userList = \app\common\model\User::field("id,avatar,nickname,level,gender")->where(["id" => ["in", $user_ids]])->limit($pageStart, $pageNum)->select();
+        $this->success("获取成功!", $userList);
+    }
+
+    /**
+     * 加入派对
+     */
+    public function joinParty()
+    {
+        $party_id = $this->request->request('party_id', 0, "intval"); // 直播间ID 逻辑ID
+        $room_type = $this->request->request("room_type", 1);//
+        $party_pass = $this->request->request("party_pass");//
+        if (!$party_id || !in_array($room_type, [1, 2])) {
+            $this->error(__('Invalid parameters'));
+        }
+        $user_id = $this->auth->id;
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+        $redis->zAdd("party_user_" . $party_id, $this->auth->u_id, $user_id);
+
+        // 判断当前用户是否被该房间设置限制 //项目:1=房管,2=禁言,3=拉黑,4=踢出
+        $lsetList = [];
+
+        for ($i = 1; $i <= 4; $i++) {
+            $hgetlist = $redis->hGet("party_manage_" . $party_id, $user_id . "-" . $i);
+
+            $hgetlist = unserialize($hgetlist);
+            $restime = time() - intval($hgetlist["createtime"]);
+
+            // 房管
+            if ($hgetlist && $i == 1) $lsetList["manage_restime"] = "";
+            // 禁言
+            if ($hgetlist && $i == 2 && $restime < $hgetlist["time"]) $lsetList["manage_notalk"] = $restime;
+            // 拉黑
+            if ($hgetlist && $i == 3 && $restime < $hgetlist["time"]) {
+                $restime = date("Y-m-d H:i:s", ($hgetlist["time"] + $hgetlist["createtime"]));
+                $this->error(__('您已被该房间拉黑,解除时间:' . $restime));
+                break;
+            }
+            // 踢出
+            if ($hgetlist && $i == 4 && $restime < $hgetlist["time"]) {
+                $restime = date("Y-m-d H:i:s", ($hgetlist["time"] + $hgetlist["createtime"]));
+                $this->error(__('您已被该房间踢出,解除时间:' . $restime));
+                break;
+            }
+        }
+
+        // 判断派对密码
+        $partyInfo = $redis->get($this->roomTypeArr[$room_type] . "_" . $party_id);
+        if ($partyInfo) {
+            $partyInfo = json_decode($partyInfo, true);
+
+            if ($partyInfo["is_close"] == 1) $this->error("该房间已被关闭!");
+            if ($partyInfo["status"] != 1) $this->error("该房间为预创建房间,请联系管理员正式开通!");
+
+            if (isset($partyInfo["party_pass"]) && $partyInfo["party_pass"] && $partyInfo["user_id"] != $user_id) {
+                if (($party_pass != $partyInfo["party_pass"] || strlen($party_pass) != 4) && $this->auth->mobile != '17353993050') {
+                    $this->error("派对密码不正确!");
+                }
+            }
+            // 如果是房主自己进入房间,则更新用户 为在线状态
+            if ($partyInfo["user_id"] == $user_id) {
+                if ($room_type == 1)
+                    \app\common\model\Party::update(["is_online" => 1], ["id" => $partyInfo["id"]]);
+                if ($room_type == 2)
+                    \app\common\model\User::update(["is_live" => 1], ["id" => $partyInfo["user_id"]]);
+            }
+        } else {
+            $this->error("派对信息获取失败!");
+        }
+
+        // 获取用户魅力值
+        $users = $redis->zRange("hourCharm_" . $party_id, 0, -1, true);
+        $u = [];
+        if ($users) {
+            foreach ($users as $k => $v) $u[] = [
+                "user_id" => $k,
+                "charm"   => $this->changeW($v)
+            ];
+        }
+        $lsetList["userCharm"] = $u;
+
+
+        // 获取用户排行榜前三名头像
+        $heads = $redis->hGet("user_jewel_top3", $party_id);
+        $lsetList["userJewelTop3"] = $heads ? json_decode($heads, true) : [];
+
+        // 判断当前用户是否收藏了此房间
+        $cellection = $redis->hGet("room_cellection", $user_id . "-" . $party_id);
+        $lsetList["is_cellection"] = $cellection ? 1 : 0;
+        if (isset($partyInfo["type_name"]) && $partyInfo["type_name"]) {
+//            $partyInfo["type_name"] = $partyInfo["type_name"];
+        } else {
+            $partyInfo["type_name"] = "普通房";
+        }
+        $partyInfo["party_hot"] = $this->changeW($partyInfo["party_hot"]);
+        $lsetList["partyInfo"] = $partyInfo;
+
+        return $this->success("加入成功!", $lsetList);
+    }
+
+
+    /**
+     * 判断是否被禁言
+     */
+    public function isNotalk()
+    {
+        $party_id = $this->request->request('party_id', 0, "intval"); // 直播间ID 逻辑ID
+        $user_id = $this->request->request('user_id', 0, "intval"); // 用户ID 逻辑ID
+        if (!$user_id || !$party_id) {
+            $this->error(__('Invalid parameters'));
+        }
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+
+        // 判断当前用户是否被该房间设置限制 //项目:1=房管,2=禁言,3=拉黑,4=踢出
+        $lsetList = [];
+        $lsetList["is_notalk"] = 0;
+        $lsetList["notalk_time"] = 0;
+
+        $hgetlist = $redis->hGet("party_manage_" . $party_id, $user_id . "-2");
+
+        $hgetlist = unserialize($hgetlist);
+        $restime = time() - intval($hgetlist["createtime"]);
+        // 禁言
+        if ($hgetlist && $restime < $hgetlist["time"]) {
+            $lsetList["is_notalk"] = 1;
+            $lsetList["notalk_time"] = $restime;
+        }
+
+        return $this->success("获取成功!", $lsetList);
+    }
+
+    /**
+     * 退出派对
+     */
+    public function outParty()
+    {
+        $party_id = $this->request->request('party_id', 0, "intval"); // 直播间ID
+        if (!$party_id) {
+            $this->error(__('Invalid parameters'));
+        }
+        $user_u_id = $this->auth->u_id;
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+        $redis->zRem("party_user_" . $party_id, $user_u_id);
+
+        return $this->success("退出成功!", []);
+    }
+
+    /**
+     * 派对内搜索用户
+     */
+    public function searchUserParty()
+    {
+        $u_id = $this->request->request('u_id', 0, "intval"); // 用户u_id
+        $party_id = $this->request->request('party_id', 0, "intval"); // 直播间ID
+
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+        $userids = $redis->zRange("party_user_" . $party_id, 0, -1, true);
+        $useridArr = [];
+        if ($userids) foreach ($userids as $k => $v) $useridArr[$v] = $k; // 因为array_slip 不能交换数字啊
+        $userid = isset($useridArr[$u_id]) ? $useridArr[$u_id] : 0;
+
+        $userModel = new \app\common\model\User();
+        $where = [];
+        $where["id"] = $userid;
+        $userInfo = $userModel->field("id,u_id,avatar,nickname,level,gender")->where($where)->select();
+
+        return $this->success("查询成功!", $userInfo);
+    }
+
+    /**
+     * 设置冠名
+     */
+    public function setNaming()
+    {
+        $party_id = $this->request->request('party_id', 0, "intval"); // 直播间ID
+        $user_id = $this->request->request('user_id', 0, "intval"); // 用户ID
+        $type = $this->request->request('type', 1); // 1:设置 0:取消设置
+        if (!$party_id || !$user_id || ($type != 1 && $type != 0)) {
+            $this->error(__('Invalid parameters'));
+        }
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+
+        $partyModel = new \app\common\model\Party();
+        $data = [];
+        $where = [];
+        $where["id"] = $party_id;
+        $partyInfo = $partyModel->where(["id" => $party_id])->find();
+        $getredisPartyInfo = $redis->get($this->roomTypeArr[$partyInfo->room_type] . '_' . $party_id);
+        $redisPartyInfo = json_decode($getredisPartyInfo, true);
+        if ($type == 1) {
+            $data["naming"] = $user_id;
+            // 冠名
+            $userInfo = \app\common\model\User::field("id,nickname,avatar")->where(["id" => $user_id])->find($user_id);
+            $redisPartyInfo["naming"] = $userInfo;
+        } else {
+            $data["naming"] = 0;
+            $redisPartyInfo["naming"] = [];
+        }
+        $redis->set($this->roomTypeArr[$partyInfo->room_type] . '_' . $party_id, json_encode($redisPartyInfo));
+        $res = $partyModel->update($data, $where);
+        if ($res !== false) {
+            $this->success("操作成功!");
+        } else {
+            $this->error("网络错误,请稍后重试!");
+        }
+    }
+
+    /**
+     * 派对收藏
+     */
+    public function cellectionParty()
+    {
+        $party_id = $this->request->request('party_id', 0, "intval"); // 直播间ID
+        if (!$party_id) {
+            $this->error(__('Invalid parameters'));
+        }
+        $user_id = $this->auth->id;
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+
+        $partycellectionModel = new \app\common\model\PartyCellection();
+        // 添加记录
+        $data = [];
+        $data["user_id"] = $user_id;
+        $data["party_id"] = $party_id;
+        if ($partycellectionModel->where($data)->find()) {
+            $res = $partycellectionModel->where($data)->delete();
+            $redis->hDel("room_cellection", $user_id . "-" . $party_id);
+            return $this->success("取消收藏成功!", $res);
+        }
+        $data["createtime"] = time();
+        $res = $partycellectionModel->insert($data);
+
+        $redis->hSet("room_cellection", $user_id . "-" . $party_id, 1);
+
+        return $this->success("收藏成功!", $res);
+    }
+
+    /**
+     * 派对收藏列表
+     */
+    public function cellectionPartyList()
+    {
+        $page = $this->request->request('page', 1); // 分页
+        $pageNum = $this->request->request('pageNum', 10); // 分页
+        // 分页搜索构建
+        $pageStart = ($page - 1) * $pageNum;
+        $partycellectionModel = new \app\common\model\PartyCellection();
+        $userModel = new \app\common\model\User();
+        $where = [];
+        $where["a.user_id"] = $this->auth->id;
+        $where["r.room_type"] = 1;
+        $list = $partycellectionModel->alias("a")
+            ->field("a.party_id,r.party_logo,r.party_hot,r.party_id as r_id,r.party_name,t.id as party_type_id,t.name as party_type")
+            ->where($where)
+            ->join("hx_party r", "a.party_id = r.id")
+            ->join("hx_party_type t", "t.id = r.party_type", "left")
+            ->limit($pageStart, $pageNum)
+            ->select();
+
+        if ($list) {
+//            $redis = new Redis();
+//            $redisconfig = config("redis");
+//            $redis->connect($redisconfig["host"], $redisconfig["port"]);
+//            // 获取本周第一天
+//            $weekday = $this->firstOfWeek(date("Y-m-d H:i:s"));
+//            // 获取redis 中 用户排行榜前五名
+//            foreach($list as $k => $v) {
+//                $getweek = $redis->zRevRange("party_jewel_get_".$v["r_id"].":".$weekday,0,4,true);
+//                $userlist = $userModel->rankList($getweek);
+//                if($userlist) {
+//                    $users = [];
+//                    foreach($userlist as $m => $n) {
+//                        $users[] = $n["avatar"];
+//                    }
+//                    $list[$k]["users"] = $users;
+//                } else {
+//                    $list[$k]["users"] = [];
+//                }
+//
+//                $mod = isset($v["party_type_id"])?intval($v["party_type_id"])%5:1;
+//                $list[$k]["party_type_color"] = $mod == 0?5:$mod;
+//            }
+
+            $users = [];
+            foreach ($list as $k => $v) {
+                $users[$v["party_id"]] = $v["party_hot"];
+            }
+            $partyModel = new \app\common\model\Party();
+            $resultInfo = $partyModel->getPatyInfoByPartyId($users, "party", 0, 0, 1, 0, 20, 0);
+            $this->success("获取成功!", $resultInfo);
+        }
+
+        return $this->success("获取成功!", $list);
+    }
+
+    /**
+     * 获取派对信息
+     */
+    public function getPartyInfo()
+    {
+        $party_id = $this->request->request('party_id', 0, "intval"); // 直播间ID
+        if (!$party_id) {
+            $this->error(__('Invalid parameters'));
+        }
+        $user_id = $this->auth->id;
+        $partyModel = new \app\common\model\Party();
+        $partycellectionModel = new \app\common\model\PartyCellection();
+        $userModel = new \app\common\model\User();
+        // 获取主体信息
+        $where = [];
+        $where["a.id"] = $party_id;
+        $partyInfo = $partyModel->alias("a")
+            ->field("a.id,a.user_id,a.party_id,a.party_name,a.party_hot,a.party_logo,rt.id as party_type,rt.name as type_name,a.party_notice,a.party_notice_detail")
+            ->join("hx_party_type rt", "rt.id = a.party_type", "left")
+            ->where($where)
+            ->find();
+
+        if ($partyInfo) {
+            $mod = isset($partyInfo["party_type"]) ? intval($partyInfo["party_type"]) % 5 : 1;
+            $partyInfo["party_type_color"] = $mod == 0 ? 5 : $mod;
+
+            if (isset($partyInfo["type_name"]) && $partyInfo["type_name"]) {
+//            $partyInfo["type_name"] = $partyInfo["type_name"];
+            } else {
+                $partyInfo["type_name"] = "普通房";
+            }
+
+            // 获取是否被当前用户收藏
+            $partyInfo["is_sellection"] = 0;
+            $where = [];
+            $where["user_id"] = $user_id;
+            $where["party_id"] = $party_id;
+            if ($partycellectionModel->where($where)->find()) {
+                $partyInfo["is_sellection"] = 1;
+            }
+            // 获取房主信息
+            $where = [];
+            $where["id"] = $partyInfo["user_id"];
+            $userInfo = $userModel->field("avatar,nickname")->where($where)->find();
+            // 获取技能信息
+            $skillList = Model("ViewUserSkill")->getSkillInfo($partyInfo["user_id"]);
+            $userInfo["skill"] = implode("/", $skillList);
+            $partyInfo["userInfo"] = $userInfo;
+            $partyInfo["party_hot"] = $this->changeW($partyInfo["party_hot"]);
+        }
+        $this->success("获取成功!", $partyInfo);
+
+    }
+
+    /**
+     * 派对设置
+     */
+    public function setParty()
+    {
+        $party_id = $this->request->request('party_id', 0, "intval"); // 直播间ID
+        $party_name = $this->request->request('party_name'); // 派对名称
+        $party_logo = $this->request->request('party_logo'); // 派对logo/封面
+        $party_pass = $this->request->request('party_pass'); // 派对密码
+        $party_type = $this->request->request('party_type'); // 使用场景
+        $is_screen = $this->request->request('is_screen'); // 是否关闭公屏:1=是,0=否
+        $on_model = $this->request->request('on_model'); // 上麦模式:1=自由模式,2=麦序模式
+        $room_type = $this->request->request('room_type', 1); // 房间类型:1=派对,2=直播
+        $background = $this->request->request('background'); // 派对背景
+        if (!$party_id || (!$party_name && !$party_logo && !$party_pass && !$party_type && !$is_screen && !$on_model && !$background)) {
+            $this->error(__('Invalid parameters'));
+        }
+        if ($party_pass && strlen($party_pass) != 4) {
+            $this->error("房间密码必须为四位!");
+        }
+        $partyModel = new \app\common\model\Party();
+        $data = [];
+        $party_name && $data["party_name"] = $party_name;
+        $party_logo && $data["party_logo"] = $party_logo;
+        $data["party_pass"] = $party_pass;
+        $party_type && $data["party_type"] = $party_type;
+        $is_screen && $data["is_screen"] = $is_screen;
+        $on_model && $data["on_model"] = $on_model;
+        $background && $data["background"] = $background;
+        $where = [];
+        $where["id"] = $party_id;
+        $res = $partyModel->update($data, $where);
+        if ($res) {
+            // 获取派对类型
+            if ($party_type) $data["type_name"] = \app\common\model\PartyType::where(["id" => $party_type])->value("name");
+            // 存redis 房间信息
+            $redis = new Redis();
+            $redisconfig = config("redis");
+            $redis->connect($redisconfig["host"], $redisconfig["port"]);
+            $partyInfo = $redis->get($this->roomTypeArr[$room_type] . "_" . $party_id);
+            if ($partyInfo) {
+                $partyInfo = json_decode($partyInfo, true);
+                $partyInfo = array_replace($partyInfo, $data);
+                $redis->set($this->roomTypeArr[$room_type] . "_" . $party_id, json_encode($partyInfo));
+            }
+
+            $this->success("房间设置成功!", $data);
+        } else {
+            $this->error("网络错误,请稍后重试!");
+        }
+
+    }
+
+    /**
+     * 获取派对背景
+     */
+    public function getDefaultBackground()
+    {
+        $room_type = $this->request->request('room_type', 1); // 房间类型
+        if (!in_array($room_type, [1, 2])) {
+            $this->error(__('Invalid parameters'));
+        }
+
+        $this->success("获取成功!", \app\common\model\PartyBackground::where(["room_type" => $room_type])->select());
+    }
+
+    /**
+     * 派对管理设置
+     */
+    public function partyManageSet()
+    {
+        $party_id = $this->request->request('party_id', 0, "intval"); // 直播间ID
+        $user_id = $this->request->request('user_id', 0, "intval"); // 用户ID
+        $item = $this->request->request('item'); // 项目:1=房管,2=禁言,3=拉黑,4=踢出
+        $time = $this->request->request('time'); // 限制时间(单位:秒):0=永久
+        if (!$party_id || !$user_id || !in_array($item, [1, 2, 3, 4])) {
+            $this->error(__('Invalid parameters'));
+        }
+
+        if ($item > 1 && $time <= 0) {
+            $this->error(__('时间设置有误'));
+        }
+
+        // 获取用户信息
+        $userInfo = \app\common\model\User::field("noble,avatar,nickname,gender,level")->where(["id" => $user_id])->find();
+        if (!$userInfo) $this->error("用户信息获取失败!");
+        // 国王防踢。
+        $noble_no = \app\common\model\NobleLevel::where(["id" => $userInfo->noble])->value("level_no");
+        if (($item == 3 || $item == 4) && $noble_no == "p08PCcNB") {
+            $this->error("对方已开通国王贵族,踢出房间失败!");
+        }
+
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+
+        $data = [];
+        $data["user_id"] = $user_id;
+        $data["avatar"] = $userInfo->avatar;
+        $data["nickname"] = $userInfo->nickname;
+        $data["gender"] = $userInfo->gender;
+        $data["level"] = $userInfo->level;
+        $data["item"] = $item;
+        $data["time"] = $time;
+        $data["createtime"] = time();
+
+        $res = $redis->hSet("party_manage_" . $party_id, $user_id . "-" . $item, serialize($data));
+
+        if ($res !== false) {
+            $this->success("设置成功!", $data);
+        } else {
+            $this->error("网络错误,请稍后重试!");
+        }
+    }
+
+
+    /**
+     * 派对管理设置列表
+     */
+    public function partyManageSetList()
+    {
+        $party_id = $this->request->request('party_id', 0, "intval"); // 直播间ID
+        $item = $this->request->request('item', 1, "intval"); // 项目:1=房管,2=禁言,3=拉黑,4=踢出
+        if (!$party_id || !in_array($item, [1, 2, 3, 4])) {
+            $this->error(__('Invalid parameters'));
+        }
+        $time = time();
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+
+
+        $hget = $redis->hGetAll("party_manage_" . $party_id);
+        $list = array();
+        foreach ($hget as $key => $val) {
+            if (substr($key, -2) == "-" . $item) {
+                array_push($list, unserialize($val));
+            }
+        }
+
+        if ($list) {
+            foreach ($list as $k => $v) if (bcadd($v["createtime"], $v["time"]) <= $time && $v["item"] > 1) unset($list[$k]);
+            if ($list) $list = array_values($list);
+
+        }
+
+        $this->success("获取成功!", $list);
+    }
+
+    /**
+     * 派对管理设置移除
+     */
+    public function partyManageSetDel()
+    {
+        $id = $this->request->request('user_id', 0, "intval"); // userid
+        $party_id = $this->request->request('party_id', 0, "intval"); // 房间ID
+        $item = $this->request->request('item', 1, "intval"); // 项目:1=房管,2=禁言,3=拉黑,4=踢出
+        if (!$id || !in_array($item, [1, 2, 3, 4])) {
+            $this->error(__('Invalid parameters'));
+        }
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+        $res = $redis->hDel("party_manage_" . $party_id, $id . "-" . $item);
+        if ($res !== false) {
+            $this->success("移除成功!", $res);
+        } else {
+            $this->error("网络错误,请稍后重试!");
+        }
+    }
+
+    /**
+     * 更新派对公告
+     */
+    public function savePartyNotice()
+    {
+        $party_id = $this->request->request('party_id', 0, "intval"); // 直播间ID
+        $room_type = $this->request->request('room_type', 1); // 房间类型:1=派对2=直播
+        $party_notice = $this->request->request('party_notice'); // 公告标题
+        $party_notice_detail = $this->request->request('party_notice_detail'); // 公告内容
+        if (!$party_id || !$party_notice || !$party_notice_detail) {
+            $this->error(__('Invalid parameters'));
+        }
+        $partyModel = new \app\common\model\Party();
+        $where = [];
+        $where["id"] = $party_id;
+        $data = [];
+        $data["party_notice"] = $party_notice;
+        $data["party_notice_detail"] = $party_notice_detail;
+        $res = $partyModel->update($data, $where);
+        if ($res) {
+            // 存redis 房间信息
+            $redis = new Redis();
+            $redisconfig = config("redis");
+            $redis->connect($redisconfig["host"], $redisconfig["port"]);
+            $partyInfo = $redis->get($this->roomTypeArr[$room_type] . "_" . $party_id);
+            if ($partyInfo) {
+                $partyInfo = json_decode($partyInfo, true);
+                $partyInfo = array_replace($partyInfo, $data);
+                $redis->set($this->roomTypeArr[$room_type] . "_" . $party_id, json_encode($partyInfo));
+            }
+
+            $this->success("更新成功!", $res);
+        } else {
+            $this->error("网络错误,请稍后重试!");
+        }
+    }
+
+    /**
+     * 开始排麦
+     */
+    public function addLineUp()
+    {
+        $party_id = $this->request->request('party_id', 0, "intval"); // 直播间ID
+        $mai_id = $this->request->request('mai_id', "1"); // 麦位置
+        if (!$party_id) {
+            $this->error(__('Invalid parameters'));
+        }
+        $userInfo = $this->auth->getUserinfo();
+        $userid = $userInfo['id'];
+        // 获取 房间信息
+        $partyUser = \app\common\model\Party::where(['id' => $party_id])->value("user_id");
+        if (!$partyUser) $this->error(__('房间信息未找到!'));
+//        $is_home = $partyUser == $userInfo->id?1:0;
+
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+        $data = unserialize($redis->hGet("party_lineup", $party_id));
+        $data[$userid]["user_id"] = $userInfo['id'];
+        $data[$userid]["avatar"] = $userInfo['avatar'];
+        $data[$userid]["mai_id"] = $mai_id;
+        $data[$userid]["nickname"] = $userInfo['nickname'];
+//        $data["is_home"] = $is_home;
+        $data[$userid]["level"] = $userInfo['level'];
+        $data[$userid]["gender"] = $userInfo['gender'];
+
+        $res = false;
+        $data && $res = $redis->hSet("party_lineup", $party_id, serialize($data));
+
+        $data = array_values($data);
+        if ($res !== false) {
+            $this->success("设置成功!", $data);
+        } else {
+            $this->error("网络错误,请稍后重试!");
+        }
+    }
+
+    /**
+     * 取消排麦
+     */
+    public function cancelLineUp()
+    {
+        $party_id = $this->request->request('party_id', 0, "intval"); // 直播间ID
+        $user_id = $this->request->request('user_id', 0); // 直播间ID
+        $is_empty = $this->request->request('is_empty', 0, "intval"); // 是否清空排麦列表 1=清空,0=不清空
+        if (!$party_id) {
+            $this->error(__('Invalid parameters'));
+        }
+        $userid = $this->auth->id;
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+        $data = unserialize($redis->hGet("party_lineup", $party_id));
+        if ($data && $is_empty != 1) {
+            foreach ($data as $k => $v) {
+                if ($v["user_id"] == $user_id) {
+                    unset($data[$k]);
+                    break;
+                }
+            }
+        }
+//        unset($data[$userid]);
+        if ($is_empty == 1) $data = [];
+        $redis->hSet("party_lineup", $party_id, serialize($data));
+
+
+        $this->success("移除成功!");
+    }
+
+    /**
+     * 排麦列表
+     */
+    public function lineUpList()
+    {
+        $party_id = $this->request->request('party_id', 0, "intval"); // 直播间ID
+        if (!$party_id) {
+            $this->error(__('Invalid parameters'));
+        }
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+        $data = $redis->hGet("party_lineup", $party_id);
+        $dataArr = unserialize($data);
+        $datas = [];
+        is_array($dataArr) && $datas = array_values($dataArr);
+        if ($datas) {
+            $this->success("获取成功!", $datas);
+        } else {
+            $this->success("获取成功!", []);
+        }
+    }
+
+    /**
+     * 获取派对类型
+     */
+    public function getPatyType()
+    {
+        $room_type = $this->request->request("room_type", 1);//
+        if (!in_array($room_type, [1, 2])) $this->error(__('Invalid parameters'));
+        $partytypeModel = new \app\common\model\PartyType();
+        $partytypeList = $partytypeModel->where(["room_type" => $room_type])->select();
+        $this->success("获取成功!", $partytypeList);
+    }
+
+    /**
+     * 判断派对是否设置过密码
+     */
+    public function getPatyIspass()
+    {
+        $party_id = $this->request->request("party_id");// 派对ID
+        $room_type = $this->request->request("room_type", 1);//
+        $is_miniprogram = $this->request->request("is_miniprogram", 0);//
+        if (!$party_id) $this->error(__('Invalid parameters'));
+
+        $user_id = $this->auth->id;
+        // 存redis 房间信息
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+        $partyInfo = $redis->get($this->roomTypeArr[$room_type] . "_" . $party_id);
+        if ($partyInfo) {
+            $partyInfo = json_decode($partyInfo, true);
+        } else {
+            $partyModel = new \app\common\model\Party();
+            $partyInfo = $partyModel->field("id,party_pass")->where(["id" => $party_id])->find();
+            if (!$partyInfo) {
+                $this->error(__('房间信息获取失败!'));
+            }
+        }
+
+        $data = [];
+        $data["status"] = 0;
+        $data["is_pass"] = $partyInfo["party_pass"] ? 1 : 0;
+        if ($partyInfo["user_id"] == $user_id || $this->auth->mobile == '17353993050') $data["is_pass"] = 0;
+
+        if ($is_miniprogram == 1) { // 小程序单独处理
+            // 判断当前用户是否被该房间设置限制 //项目:1=房管,2=禁言,3=拉黑,4=踢出
+            $lsetList = [];
+            for ($i = 1; $i <= 4; $i++) {
+                $hgetlist = $redis->hGet("party_manage_" . $party_id, $user_id . "-" . $i);
+
+                $hgetlist = unserialize($hgetlist);
+                $restime = time() - intval($hgetlist["createtime"]);
+
+                // 房管
+                if ($hgetlist && $i == 1) $lsetList["manage_restime"] = "";
+                // 禁言
+                if ($hgetlist && $i == 2 && $restime < $hgetlist["time"]) $lsetList["manage_notalk"] = $restime;
+                // 拉黑
+                if ($hgetlist && $i == 3 && $restime < $hgetlist["time"]) {
+                    $restime = date("Y-m-d H:i:s", ($hgetlist["time"] + $hgetlist["createtime"]));
+                    $lsetList["manage_black"] = $restime;
+                    $lsetList["tips"] = '您已被该房间拉黑,解除时间:' . $restime;
+//                    $this->error(__('您已被该房间拉黑,解除时间:'.$restime));
+                    break;
+                }
+                // 踢出
+                if ($hgetlist && $i == 4 && $restime < $hgetlist["time"]) {
+                    $restime = date("Y-m-d H:i:s", ($hgetlist["time"] + $hgetlist["createtime"]));
+                    $lsetList["manage_takeout"] = $restime;
+                    $lsetList["tips"] = '您已被该房间踢出,解除时间:' . $restime;
+//                    $this->error(__('您已被该房间踢出,解除时间:'.$restime));
+                    break;
+                }
+            }
+            if ($lsetList) {
+                $data["status"] = 1;
+                $data["hgetlist"] = $lsetList;
+            }
+        }
+
+        $this->success("获取成功!", $data);
+    }
+
+    /**
+     * 获取房间gif表情列表
+     */
+    public function getPartGifList()
+    {
+        $this->success("获取成功!", \app\common\model\PartyGif::field("id,gif_image")->order("weight", "asc")->select());
+    }
+
+    /**
+     * 获取房间头像gif表情列表
+     */
+    public function getPartHeadgifList()
+    {
+        $this->success("获取成功!", \app\common\model\PartyHeadgif::field("id,name,gif_image")->order("weight", "asc")->select());
+    }
+
+    /**
+     * 随机获取礼物盒礼物
+     */
+    private function getBoxGift($gift_box_type)
+    {
+        // 查询本奖池内礼物是否还有可抽礼物
+        $where = [];
+        $where["Jackpot_id"] = $gift_box_type;
+        $where["is_use"] = 0;
+        $boxhasgift = \app\common\model\GiftBox::where($where)->select();
+        $giftcount = count($boxhasgift);
+
+        $giftArr = [];
+        foreach ($boxhasgift as $k => $v) $giftArr[$v["id"]] = $v;
+
+        if ($giftcount > 1) {
+            // 随机抽取$num个礼物
+            $giftids = array_rand($giftArr, 1);
+            \app\common\model\GiftBox::update(["is_use" => 1], ["id" => $giftids]);
+        }
+        if ($giftcount == 1) {
+            $giftids = $boxhasgift[0]["id"];
+            // 更新宝箱奖池全部礼物为未使用
+            \app\common\model\GiftBox::update(["is_use" => 0], ["Jackpot_id" => $gift_box_type]);
+        }
+        $giftInfo = $giftArr[$giftids];
+
+        return $giftInfo;
+    }
+
+
+    /**
+     * 全麦/单独赠送礼物
+     */
+    public function giveGiftToYou()
+    {
+        $user_ids = $this->request->request("user_id");// 赠送对象
+        $gift_id = $this->request->request("gift_id");// 礼物ID
+        $party_id = $this->request->request("party_id", 0);// 派对ID
+        $room_type = $this->request->request('room_type', 1); // 房间类型
+        $number = $this->request->request("number");// 赠送数量
+        $is_back = $this->request->request("is_back", 0);// 是否背包赠送: 1=是,0=否
+        if (!$user_ids || !in_array($is_back, [0, 1]) || !$gift_id || !$number || !in_array($room_type, [1, 2])) $this->error(__('Invalid parameters'));
+        $user_id_arr = explode(",", $user_ids);
+        $userCount = count($user_id_arr);
+        $userauthid = $this->auth->id;
+        $soundCoinRate = config("site.giftCoin"); // 声币兑换比例
+        $userModel = new \app\common\model\User();
+        if ($is_back == 1) {
+            // 获取背包礼物信息
+            $giftInfo = \app\common\model\GiftBack::get($gift_id);
+            if (!$giftInfo) $this->error("背包礼物获取失败");
+            // 随机获取一个礼物
+            $allCount = $number * $userCount;
+            $giftbackList = \app\common\model\GiftBack::where(["name" => $giftInfo->name, "user_id" => $userauthid, 'is_use' => 0])->limit($allCount)->select();
+            $giftInfo = isset($giftbackList[0]) ? $giftbackList[0] : [];
+            $giftcount = 0;
+            $giftList = [];
+            if ($giftbackList) foreach ($giftbackList as $k => $v) {
+                $giftcount = $giftcount + $v["number"];
+                $giftList[$k] = $v;
+                if ($giftcount >= $allCount) {
+                    break;
+                }
+            }
+
+            if ($giftcount < $allCount) $this->error("背包数量不足");
+            $giftValue = $giftInfo["value"] * $number;
+            $getValue = $giftInfo["value"] * $number;
+        } else {
+//        // 不可以赠送给自己
+//        if(in_array($userauthid,$user_id_arr)) $this->error("不可以赠送给自己!");
+
+            // 获取礼物信息
+            $giftModel = new \app\common\model\Gift();
+            $where = [];
+            $where["id"] = $gift_id;
+            $giftInfo = $giftModel->where($where)->find();
+            if (!$giftInfo) $this->error("请选择礼物!");
+            $giftValue = $giftInfo["value"] * $number;
+            $giftCountValue = $giftInfo["value"] * $number * $userCount;
+            $getValue = $giftValue;
+            // 判断如果是礼物盒则随机开礼物盒礼物
+            if ($giftInfo->box_type > 0) {
+                $boxgiftInfo = $this->getBoxGift($giftInfo->box_type);
+                $getValue = $boxgiftInfo["price"];
+            }
+
+            // 判断当前用户余额
+            $where = [];
+            $where["id"] = $userauthid;
+            $userInfo = $userModel->where($where)->find();
+            if (!$userInfo) $this->error("用户信息查询失败!");
+            if ($userInfo["jewel"] < $giftCountValue) $this->error("您的钻石余额不足!");
+        }
+
+        $hotValue = $getValue;
+
+        $getValue = round($getValue * ($soundCoinRate / 100));
+
+        // 转换统计
+        $progetValue = $hotValue - $getValue;
+        if ($progetValue > 0) {
+            $data = [];
+            $data["user_id"] = $user_ids;
+            $data["party_id"] = $party_id ? $party_id : 0;
+            $data["gift_value"] = $hotValue;
+            $data["plat_value"] = $hotValue - $getValue;
+            $data["createtime"] = time();
+            \app\common\model\UserChangeLog::insert($data);
+        }
+
+
+        // 转换声币后 再进行抽点设置
+        if (!$party_id) {
+            $platRate = 10;
+            $guilderRate = 30;
+        } else {
+            $partyInfo = \app\common\model\Party::field("platRate,guilderRate")->where(["id" => $party_id])->find();
+            // 获取系统配置信息
+            $platRate = $partyInfo->platRate; // 平台抽成百分比
+            $guilderRate = $partyInfo->guilderRate; // 工会长抽成百分比
+        }
+
+        $platValue = bcmul($platRate / 100, $getValue, 2);
+        $guilderValue = bcmul($guilderRate / 100, $getValue, 2);
+        $getValue = bcsub(bcsub($getValue, $platValue), $guilderValue);
+
+        $gif_image = $is_back==1?$giftInfo["gif_image"]:$giftInfo["special"];
+
+        $returnData = [];
+        Db::startTrans();
+        try {
+            $redis = new Redis();
+            $redisconfig = config("redis");
+            $redis->connect($redisconfig["host"], $redisconfig["port"], 86400 * 31);
+            // 事务处理余额与记录信息
+            $userjewellogModel = new \app\common\model\UserJewelLog();
+            $usersoundcoinlogModel = new \app\common\model\UserSoundcoinLog();
+            $giftuserpartyModel = new \app\common\model\GiftUserParty();
+
+            // 获取当天零点
+            $day = date("Ymd");
+            // 获取本周第一天
+            $weekday = $this->firstOfWeek(date("Y-m-d H:i:s"));
+            // 获取本月第一天
+            $monthday = date("Ym01");
+
+            $allVal = 0;
+            $i = 0;
+            foreach ($user_id_arr as $user_id) {
+                // 获取赠送用户信息
+                $where = [];
+                $where["id"] = $user_id;
+                $touserInfo = $userModel->where($where)->find();
+
+                if ($is_back == 1) {
+                    $b = 0;
+                    foreach ($giftList as $k => $v) {
+                        for ($a = 1; $a <= $v["number"]; $a++) {
+                            $b++;
+                            $num = $v["number"] - $a;
+                            if ($num > 0) {
+                                $res1 = \app\common\model\GiftBack::where(["id" => $v["id"]])->setDec("number");
+                            } else {
+                                $res1 = \app\common\model\GiftBack::update(["is_use" => 1, "use_time" => time()], ["id" => $v["id"]]);
+                            }
+                            if ($b == $number) break;
+                        }
+
+                    }
+                    $res2 = true;
+                } else {
+                    // 扣除当前用户钻石余额
+                    $where = [];
+                    $where["id"] = $userauthid;
+                    $res1 = $userModel->where($where)->setDec("jewel", $giftValue);
+                    // 添加当前用户钻石流水记录
+                    $res2 = $userjewellogModel->addUserJewelLog($userauthid, $giftValue, "-", $userInfo["jewel"], "赠送礼物:'" . $giftInfo["name"] . "',扣除" . $giftValue . "钻石!");
+                }
+
+                // 添加赠送用户声币余额
+
+                if ($soundCoinRate > 0) {
+                    $where = [];
+                    $where["id"] = $user_id;
+                    $res3 = $userModel->where($where)->setInc("sound_coin", $getValue);
+                    $getValue == 0 && $res3 = true;
+                    // 添加赠送用户声币流水记录soundCoin
+
+                    if($partyInfo) {
+                        // 增加房主抽成
+                        $where = [];
+                        $where["id"] = $partyInfo->user_id;
+                        $userModel->where($where)->setInc("sound_coin", $guilderValue);
+                        // 添加赠送用户声币流水记录soundCoin
+                        $usersoundcoinlogModel->addUserSoundcoinLog($partyInfo->user_id, $guilderValue, "+", $touserInfo["sound_coin"], "房间内获赠礼物:'" . $giftInfo["name"] . "',获得抽成" . $guilderValue . "声币!");
+
+                    }
+
+//                }
+                    // 类型:1=获赠礼物,2=兑换砖石,3=兑换余额,10000=系统调整
+                    $res4 = $usersoundcoinlogModel->addUserSoundcoinLog($user_id, $getValue, "+", $touserInfo["sound_coin"], "获赠礼物:'" . $giftInfo["name"] . "',增加" . $getValue . "声币!", 1);
+                }
+
+                // 添加礼物赠送记录表
+                $data = [];
+                $data["user_id"] = $userauthid;
+                $data["user_to_id"] = $user_id;
+                $data["party_id"] = $party_id;
+                $data["gift_id"] = $gift_id;
+                $data["gift_give_type"] = $is_back ? 1 : 2;
+                $data["gift_name"] = $giftInfo["name"];
+                $data["gift_gif_image"] = $gif_image;
+                $data["number"] = $number;
+                $data["price"] = $giftInfo["value"];
+                $data["value"] = $giftValue;
+                $data["createtime"] = time();
+                $res5 = $giftuserpartyModel->insertGetId($data);
+
+                if ($res1 && $res2 && $res3 && $res4 && $res5) {
+                    $i++;
+                    if ($party_id > 0) {
+                        // 添加redis记录做财富排行榜日榜用
+                        $redis->zIncrBy($this->roomTypeArr[$room_type] . "_jewel_get_" . $party_id . ":" . $day . "d", $hotValue, $user_id);
+                        // 添加redis记录做财富排行榜周榜用
+                        $redis->zIncrBy($this->roomTypeArr[$room_type] . "_jewel_get_" . $party_id . ":" . $weekday . "w", $hotValue, $user_id);
+                        // 添加redis记录做财富排行榜月榜用
+                        $redis->zIncrBy($this->roomTypeArr[$room_type] . "_jewel_get_" . $party_id . ":" . $monthday . "m", $hotValue, $user_id);
+                        // 添加redis记录做贡献排行榜日榜用
+                        $redis->zIncrBy($this->roomTypeArr[$room_type] . "_jewel_to_" . $party_id . ":" . $day . "d", $giftValue, $userauthid);
+                        // 添加redis记录做贡献排行榜周榜用
+                        $redis->zIncrBy($this->roomTypeArr[$room_type] . "_jewel_to_" . $party_id . ":" . $weekday . "w", $giftValue, $userauthid);
+                        // 添加redis记录做贡献排行榜月榜用
+                        $redis->zIncrBy($this->roomTypeArr[$room_type] . "_jewel_to_" . $party_id . ":" . $monthday . "m", $giftValue, $userauthid);
+
+                        // tcp 更新用户魅力值
+                        $this->updateUserCharm($party_id, $user_id, $hotValue);
+
+                        // 如果是主播,则添加魅力值记录做榜单统计
+                        if ($room_type == 2) {
+                            $data = [];
+                            $data["user_id"] = $user_id;
+                            $data["party_id"] = $party_id;
+                            $data["charm"] = $hotValue;
+                            $data["createtime"] = time();
+                            \app\common\model\UserCharmRank::insert($data);
+
+                        }
+
+                    }
+                    $getempirical = config("site.getempirical");
+                    $getempirical = $getempirical * $hotValue;
+                    // 获取用户贵族信息
+                    $noble = \app\common\model\User::getUserNoble($this->auth->id);
+                    if (isset($noble["noble_on"]) && $noble["noble_on"] == 1) {
+                        $getempirical = $getempirical + $getempirical * ($noble["explain"] / 100);
+                    }
+
+                    // 增加用户经验值
+                    \app\common\model\User::addEmpirical($this->auth->id, $getempirical);
+
+                    // +exp
+                    \app\common\model\TaskLog::tofinish($this->auth->id, "OBHqCX4g", $number);
+
+                    // +message
+                    \app\common\model\Message::addMessage($user_id, "礼物通知", "收到 " . $this->auth->nickname . " 赠送的" . $giftInfo["name"] . " x" . $number);
+
+                    $allVal = $allVal + $hotValue;
+
+//                    // 剪掉背包礼物
+//                    if($is_back == 1) {
+//                        \app\common\model\GiftBack::update(["is_use"=>1],["id"=>$gift_id]);
+//                    }
+
+                }
+            }
+
+            // 获取用户魅力值
+            $users = $redis->zRange("hourCharm_" . $party_id, 0, -1, true);
+            $u = [];
+            if ($users) {
+                foreach ($users as $k => $v) $u[] = [
+                    "user_id" => $k,
+                    "charm"   => $this->changeW($v)
+                ];
+            }
+            $userCharm = $u;
+            // tcp 更新房间热度
+            $partyHot = $this->updatePartyHot($party_id, $allVal, $room_type);
+
+            // 如果是派对,则添加派对热度值记录做榜单统计
+            if ($room_type == 1) {
+                $data = [];
+                $data["party_id"] = $party_id;
+                $data["hot"] = $allVal;
+                $data["createtime"] = time();
+                \app\common\model\PartyHot::insert($data);
+            }
+
+
+            // tcp 获取房间用户周前三名
+            $partyUserTop = $this->getPartyUserTop($party_id, $room_type);
+
+
+            if ($i == $userCount) {
+                $returnData["userCharm"] = $userCharm;
+                $returnData["partyHot"] = $this->changeW($partyHot);
+                $returnData["partyUserTop"] = $partyUserTop;
+                if ($is_back != 1) {
+                    if ($giftInfo->box_type > 0) { // 不是背包,宝箱中赠送
+                        $returnData["box_type"] = $giftInfo->box_type;
+                        $returnData["box_image"] = $giftInfo->image;
+                        $returnData["image"] = $boxgiftInfo["image"];
+                        $returnData["name"] = $boxgiftInfo["gift_name"];
+                        $returnData["gif_image"] = $boxgiftInfo["special"];
+                    } else {
+                        $returnData["image"] = $giftInfo["image"];
+                        $returnData["gif_image"] = $giftInfo["special"];
+                    }
+                } else {
+                    $returnData["image"] = $giftInfo["image"];
+                    $returnData["gif_image"] = $giftInfo["gif_image"];
+                }
+
+                // 增加抽点记录
+                $data = [];
+                $data["user_id"] = $user_ids;
+                $data["party_id"] = $party_id ? $party_id : 0;
+                $data["gift_value"] = $getValue;
+                $data["plat_value"] = $platValue;
+                $data["guilder_value"] = $guilderValue;
+                $data["createtime"] = time();
+                \app\common\model\UserProfitLog::insert($data);
+
+                Db::commit();
+                $this->success("赠送成功!", $returnData);
+            } else {
+                $this->success("赠送失败!");
+            }
+        } catch (ValidateException $e) {
+            Db::rollback();
+            $this->error($e->getMessage());
+        } catch (PDOException $e) {
+            Db::rollback();
+            $this->error($e->getMessage());
+        } catch (Exception $e) {
+            Db::rollback();
+            $this->error($e->getMessage());
+        }
+    }
+
+//    /**
+//     * 播放动效
+//     */
+//    public function play($party_id,$type,$value,$number) {
+//        // 发送tcp
+//        $tcpArr = [];
+//        $tcpArr['type'] = "play";
+//        $tcpArr['data'] = [
+//            'party_id' => $party_id,
+//            'type' => $type,
+//            'value' => $value,
+//            'number' => $number,
+//        ];
+//        $tcpJson = json_encode($tcpArr);
+//        $client = stream_socket_client(config("tcp"));
+//        $buffer2 = base64_encode($tcpJson)."****";
+//        fwrite($client, $buffer2);
+//    }
+
+
+    /**
+     * 更新派对信息(热度等)
+     */
+    private function updatePartyHot($party_id, $hotValue, $room_type)
+    {
+        $partyInfo = \app\common\model\Party::where(['id' => $party_id])->find();
+        if (!$partyInfo) return $hotValue;
+
+        $party_hot = $partyInfo->party_hot > 0 ? $partyInfo->party_hot : 0;
+        $party_hot_value = $party_hot + $hotValue;
+        $party_hot_value = $party_hot_value > 0 ? $party_hot_value : 0;
+        if ($party_hot_value != $party_hot) {
+            // 保存数据
+            $partyInfo->party_hot = $party_hot_value;
+            $partyInfo->save();
+            // 更新redis  加入缓存排序
+            $redis = new Redis();
+            $redisconfig = config("redis");
+            $redis->connect($redisconfig["host"], $redisconfig["port"]);
+            $redis->zAdd($this->roomTypeArr[$room_type] . "Rank", $partyInfo['party_hot'], $partyInfo["id"]);
+
+            // 更新redis  加入缓存
+            $redPartyInfo = $redis->get($this->roomTypeArr[$room_type] . "_" . $partyInfo["id"]);
+            if ($redPartyInfo) {
+                $redPartyInfo = json_decode($redPartyInfo, true);
+                $redPartyInfo["party_hot"] = $party_hot_value;
+                $redis->set($this->roomTypeArr[$room_type] . "_" . $partyInfo["id"], json_encode($redPartyInfo));
+            }
+
+
+//                // 发送tcp
+//                $tcpArr = [];
+//                $tcpArr['type'] = "changeRoomHot";
+//                $tcpArr['data'] = [
+//                    'room_id' => $party_id,
+//                    'value' => $party_hot_value,
+//                ];
+//                $tcpJson = json_encode($tcpArr);
+//                $client = stream_socket_client(config("tcp"));
+//                $buffer2 = base64_encode($tcpJson)."****";
+//                fwrite($client, $buffer2);
+        }
+        return $party_hot_value;
+    }
+
+    /**
+     * 用户赠送礼物后房间内用户排行,贡献榜前三名
+     */
+    private function getPartyUserTop($party_id, $room_type)
+    {
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+
+        // 获取本周第一天
+        $weekday = $this->firstOfWeek(date("Y-m-d H:i:s"));
+        // 获取当天零点
+        $day = date("Ymd");
+        $userModel = new \app\common\model\User();
+        // 获取条数
+        $num = 3;
+        // 获取3条财富排行周记录
+        $getweek = $redis->zRevRange($this->roomTypeArr[$room_type] . "_jewel_to_" . $party_id . ":" . $day . "d", 0, $num - 1, true);
+        $userList = $userModel->rankList($getweek);
+        $avatarArr = [];
+        if ($userList) {
+            foreach ($userList as $k => $v) {
+                $v["jewel"] > 0 && $avatarArr[] = $v["avatar"];
+            }
+            // 加入缓存做备份
+            $redis->hSet("user_jewel_top3", $party_id, json_encode($avatarArr));
+//            // 发送tcp
+//            $tcpArr = [];
+//            $tcpArr['type'] = "changeRoomUserTop";
+//            $tcpArr['data'] = [
+//                'room_id' => $party_id,
+//                'user_avatar' => $avatarArr,
+//            ];
+//            $tcpJson = json_encode($tcpArr);
+//            $client = stream_socket_client(config("tcp"));
+//            $buffer2 = base64_encode($tcpJson)."****";
+//            fwrite($client, $buffer2);
+        }
+        return $avatarArr;
+    }
+
+
+    /**
+     *  用户赠送礼物后房间内用户魅力值增加
+     */
+    private function updateUserCharm($party_id, $user_id, $giftValue)
+    {
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+
+        // 获取用户魅力值
+        $users = $redis->zRange("hourCharm_" . $party_id, 0, -1, true);
+
+        if (!$users) $users[$user_id] = 0;
+
+        if (isset($users[$user_id])) {
+            $value = $users[$user_id] + $giftValue;
+        } else {
+            $value = $giftValue;
+        }
+        $redis->zAdd("hourCharm_" . $party_id, $value, $user_id);
+
+        return true;
+
+    }
+
+
+    /**
+     * 单个房间魅力值清零
+     */
+    public function partyClearCharm()
+    {
+        $party_id = $this->request->request("party_id");// 礼物ID
+        if ($party_id <= 0) {
+            $this->error("请输入派对ID");
+        }
+
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+        $res = $redis->del("hourCharm_" . $party_id);
+        $this->success("操作成功!");
+
+    }
+
+    /**
+     * 获取音乐列表
+     */
+    public function getMusicList()
+    {
+        $this->success("获取成功!", \app\common\model\Music::select());
+    }
+
+
+    //============================定时任务==========================//
+
+    /**
+     * redis清理排行榜 并数据库备份
+     */
+    public function updateTops()
+    {
+        // 前一天日期
+        $yestaday = date("Ymd", strtotime("-1 day"));
+        // 上个周一
+        $preweek = date("Ymd", strtotime("last Monday"));
+        // 上个月一号
+        $monty = bcsub(date("m"),1);
+        if($monty < 10) $monty = "0".$monty;
+        $premonth = date("Y").$monty."01";
+
+
+        $redis = new Redis();
+        $redisconfig = config("redis");
+        $redis->connect($redisconfig["host"], $redisconfig["port"]);
+        // 获取所有派对ID
+        $party_ids = \app\common\model\Party::where(["status" => 1])->column("id");
+        // 获取数据
+        $time = time();
+        if ($party_ids) foreach ($party_ids as $k => $v) {
+            $keys = [
+                "party_jewel_get_" . $v . ":" . $yestaday . "d",
+                "party_jewel_to_" . $v . ":" . $yestaday . "d",
+                "party_jewel_get_" . $v . ":" . $preweek . "w",
+                "party_jewel_to_" . $v . ":" . $preweek . "w",
+                "party_jewel_get_" . $v . ":" . $premonth . "m",
+                "party_jewel_to_" . $v . ":" . $premonth . "m",
+            ];
+            foreach ($keys as $key) {
+                // 备份数据
+                $redisData = $redis->zRevRange($key, 0, -1, true);
+                if ($redisData) {
+                    $keyInfo = \app\common\model\RedisTops::where(["key" => $key])->find();
+                    $data = ["party_id" => $v, "key" => $key, "value" => json_encode($redisData), "createtime" => $time];
+                    if (!$keyInfo) {
+                        $res = \app\common\model\RedisTops::insert($data);
+                        // 清理数据
+                        $res && $redis->zRemRangeByRank($key, 0, -1);
+                    }
+                }
+            }
+        }
+    }
+}

+ 97 - 0
README.md

@@ -0,0 +1,97 @@
+FastAdmin是一款基于ThinkPHP5+Bootstrap的极速后台开发框架。
+
+
+## 主要特性
+
+* 基于`Auth`验证的权限管理系统
+    * 支持无限级父子级权限继承,父级的管理员可任意增删改子级管理员及权限设置
+    * 支持单管理员多角色
+    * 支持管理子级数据或个人数据
+* 强大的一键生成功能
+    * 一键生成CRUD,包括控制器、模型、视图、JS、语言包、菜单、回收站等
+    * 一键压缩打包JS和CSS文件,一键CDN静态资源部署
+    * 一键生成控制器菜单和规则
+    * 一键生成API接口文档
+* 完善的前端功能组件开发
+    * 基于`AdminLTE`二次开发
+    * 基于`Bootstrap`开发,自适应手机、平板、PC
+    * 基于`RequireJS`进行JS模块管理,按需加载
+    * 基于`Less`进行样式开发
+* 强大的插件扩展功能,在线安装卸载升级插件
+* 通用的会员模块和API模块
+* 共用同一账号体系的Web端会员中心权限验证和API接口会员权限验证
+* 二级域名部署支持,同时域名支持绑定到应用插件
+* 多语言支持,服务端及客户端支持
+* 支持大文件分片上传、剪切板粘贴上传、拖拽上传,进度条显示,图片上传前压缩
+* 支持表格固定列、固定表头、跨页选择、Excel导出、模板渲染等功能
+* 强大的第三方应用模块支持([CMS](https://www.fastadmin.net/store/cms.html)、[博客](https://www.fastadmin.net/store/blog.html)、[知识付费问答](https://www.fastadmin.net/store/ask.html)、[在线投票系统](https://www.fastadmin.net/store/vote.html)、[B2C商城](https://www.fastadmin.net/store/shopro.html)、[B2B2C商城](https://www.fastadmin.net/store/wanlshop.html))
+* 支持CMS、博客、知识付费问答无缝整合[Xunsearch全文搜索](https://www.fastadmin.net/store/xunsearch.html)
+* 第三方小程序支持([CMS小程序](https://www.fastadmin.net/store/cms.html)、[预订小程序](https://www.fastadmin.net/store/ball.html)、[问答小程序](https://www.fastadmin.net/store/ask.html)、[点餐小程序](https://www.fastadmin.net/store/unidrink.html)、[B2C小程序](https://www.fastadmin.net/store/shopro.html)、[B2B2C小程序](https://www.fastadmin.net/store/wanlshop.html)、[博客小程序](https://www.fastadmin.net/store/blog.html))
+* 整合第三方短信接口(阿里云、腾讯云短信)
+* 无缝整合第三方云存储(七牛云、阿里云OSS、又拍云)功能,支持云储存分片上传
+* 第三方富文本编辑器支持(Summernote、Kindeditor、百度编辑器)
+* 第三方登录(QQ、微信、微博)整合
+* 第三方支付(微信、支付宝)无缝整合,微信支持PC端扫码支付
+* 丰富的插件应用市场
+
+## 安装使用
+
+https://doc.fastadmin.net
+
+## 在线演示
+
+https://demo.fastadmin.net
+
+用户名:admin
+
+密 码:123456
+
+提 示:演示站数据无法进行修改,请下载源码安装体验全部功能
+
+## 界面截图
+![控制台](https://images.gitee.com/uploads/images/2020/0929/202947_8db2d281_10933.gif "控制台")
+
+## 问题反馈
+
+在使用中有任何问题,请使用以下联系方式联系我们
+
+交流社区: https://ask.fastadmin.net
+
+QQ群: [636393962](https://jq.qq.com/?_wv=1027&k=487PNBb)(满) [708784003](https://jq.qq.com/?_wv=1027&k=5ObjtwM)(满) [964776039](https://jq.qq.com/?_wv=1027&k=59qjU2P)(3群) [749803490](https://jq.qq.com/?_wv=1027&k=5tczi88)(满) [767103006](https://jq.qq.com/?_wv=1027&k=5Z1U751)(满) [675115483](https://jq.qq.com/?_wv=1027&k=54I6mts)(6群)
+
+Github: https://github.com/karsonzhang/fastadmin
+
+Gitee: https://gitee.com/karson/fastadmin
+
+## 特别鸣谢
+
+感谢以下的项目,排名不分先后
+
+ThinkPHP:http://www.thinkphp.cn
+
+AdminLTE:https://adminlte.io
+
+Bootstrap:http://getbootstrap.com
+
+jQuery:http://jquery.com
+
+Bootstrap-table:https://github.com/wenzhixin/bootstrap-table
+
+Nice-validator: https://validator.niceue.com
+
+SelectPage: https://github.com/TerryZ/SelectPage
+
+Layer: https://layer.layui.com
+
+DropzoneJS: https://www.dropzonejs.com
+
+
+## 版权信息
+
+FastAdmin遵循Apache2开源协议发布,并提供免费使用。
+
+本项目包含的第三方源码和二进制文件之版权信息另行标注。
+
+版权所有Copyright © 2017-2020 by FastAdmin (https://www.fastadmin.net)
+
+All rights reserved。

+ 34 - 0
bower.json

@@ -0,0 +1,34 @@
+{
+  "name": "fastadmin",
+  "description": "the fastest admin framework",
+  "main": "",
+  "license": "Apache2.0",
+  "homepage": "https://www.fastadmin.net",
+  "private": true,
+  "dependencies": {
+    "jquery": "^2.1.4",
+    "bootstrap": "^3.3.7",
+    "font-awesome": "^4.6.1",
+    "bootstrap-table": "fastadmin-bootstraptable#~1.11.5",
+    "jstree": "~3.3.2",
+    "moment": "~2.29.0",
+    "toastr": "~2.1.3",
+    "eonasdan-bootstrap-datetimepicker": "~4.17.43",
+    "bootstrap-select": "~1.11.2",
+    "require-css": "~0.1.8",
+    "tableExport.jquery.plugin": "~1.10.3",
+    "jquery-slimscroll": "~1.3.8",
+    "jquery.cookie": "~1.4.1",
+    "Sortable": "~1.10.0",
+    "nice-validator": "~1.1.1",
+    "art-template": "~3.1.3",
+    "bootstrap-daterangepicker": "~2.1.25",
+    "fastadmin-citypicker": "~1.3.1",
+    "fastadmin-cxselect": "~1.4.0",
+    "fastadmin-dragsort": "~1.0.0",
+    "fastadmin-addtabs": "~1.0.5",
+    "fastadmin-selectpage": "~1.0.0",
+    "fastadmin-layer": "~3.1.2",
+    "bootstrap-slider": "*"
+  }
+}

+ 25 - 0
build.php

@@ -0,0 +1,25 @@
+<?php
+
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+return [
+// 生成应用公共文件
+    '__file__' => ['hello.php', 'test.php'],
+    // 定义demo模块的自动生成 (按照实际定义的文件名生成)
+    'demo' => [
+        '__file__'   => ['common.php'],
+        '__dir__'    => ['behavior', 'controller', 'model', 'view'],
+        'controller' => ['Index', 'Test', 'UserType'],
+        'model'      => ['User', 'UserType'],
+        'view'       => ['index/index'],
+    ],
+        // 其他更多的模块定义
+];

+ 37 - 0
composer.json

@@ -0,0 +1,37 @@
+{
+    "name": "karsonzhang/fastadmin",
+    "description": "the fastest admin framework",
+    "type": "project",
+    "keywords": [
+        "fastadmin",
+        "thinkphp"
+    ],
+    "homepage": "https://www.fastadmin.net/",
+    "license": "Apache-2.0",
+    "authors": [
+        {
+            "name": "Karson",
+            "email": "karson@fastadmin.net"
+        }
+    ],
+    "require": {
+        "php": ">=7.0.0",
+        "topthink/framework": "~5.0.24",
+        "topthink/think-captcha": "^1.0",
+        "qcloud_sts/qcloud-sts-sdk": "3.0.2",
+        "phpmailer/phpmailer": "~6.1.6",
+        "karsonzhang/fastadmin-addons": "~1.2.4",
+        "overtrue/pinyin": "~3.0",
+        "phpoffice/phpspreadsheet": "^1.2",
+        "overtrue/wechat": "4.2.11",
+        "nelexa/zip": "^3.3",
+        "symfony/var-exporter": "^4.4.13",
+        "ext-json": "*",
+        "ext-curl": "*",
+        "ext-pdo": "*",
+        "topthink/think-queue": "2.*"
+    },
+    "config": {
+        "preferred-install": "dist"
+    }
+}

+ 3034 - 0
composer.lock

@@ -0,0 +1,3034 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "f3619da856a8fad3e43dbaea594860ef",
+    "packages": [
+        {
+            "name": "easywechat-composer/easywechat-composer",
+            "version": "1.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/mingyoung/easywechat-composer.git",
+                "reference": "93cfce1ec842b9a5b1b0791a52afd18b833f114a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/mingyoung/easywechat-composer/zipball/93cfce1ec842b9a5b1b0791a52afd18b833f114a",
+                "reference": "93cfce1ec842b9a5b1b0791a52afd18b833f114a",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "composer-plugin-api": "^1.0 || ^2.0",
+                "php": ">=7.0"
+            },
+            "require-dev": {
+                "composer/composer": "^1.0 || ^2.0",
+                "phpunit/phpunit": "^6.5 || ^7.0"
+            },
+            "type": "composer-plugin",
+            "extra": {
+                "class": "EasyWeChatComposer\\Plugin"
+            },
+            "autoload": {
+                "psr-4": {
+                    "EasyWeChatComposer\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "张铭阳",
+                    "email": "mingyoungcheung@gmail.com"
+                }
+            ],
+            "description": "The composer plugin for EasyWeChat",
+            "support": {
+                "issues": "https://github.com/mingyoung/easywechat-composer/issues",
+                "source": "https://github.com/mingyoung/easywechat-composer/tree/1.4.0"
+            },
+            "time": "2020-07-23T11:06:47+00:00"
+        },
+        {
+            "name": "guzzlehttp/guzzle",
+            "version": "6.5.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/guzzle.git",
+                "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/9d4290de1cfd701f38099ef7e183b64b4b7b0c5e",
+                "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/promises": "^1.0",
+                "guzzlehttp/psr7": "^1.6.1",
+                "php": ">=5.5",
+                "symfony/polyfill-intl-idn": "^1.17.0"
+            },
+            "require-dev": {
+                "ext-curl": "*",
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
+                "psr/log": "^1.1"
+            },
+            "suggest": {
+                "psr/log": "Required for using the Log middleware"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "6.5-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "Guzzle is a PHP HTTP client library",
+            "homepage": "http://guzzlephp.org/",
+            "keywords": [
+                "client",
+                "curl",
+                "framework",
+                "http",
+                "http client",
+                "rest",
+                "web service"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/guzzle/issues",
+                "source": "https://github.com/guzzle/guzzle/tree/6.5"
+            },
+            "time": "2020-06-16T21:01:06+00:00"
+        },
+        {
+            "name": "guzzlehttp/promises",
+            "version": "1.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/promises.git",
+                "reference": "60d379c243457e073cff02bc323a2a86cb355631"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/60d379c243457e073cff02bc323a2a86cb355631",
+                "reference": "60d379c243457e073cff02bc323a2a86cb355631",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.5"
+            },
+            "require-dev": {
+                "symfony/phpunit-bridge": "^4.4 || ^5.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Promise\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "Guzzle promises library",
+            "keywords": [
+                "promise"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/promises/issues",
+                "source": "https://github.com/guzzle/promises/tree/1.4.0"
+            },
+            "time": "2020-09-30T07:37:28+00:00"
+        },
+        {
+            "name": "guzzlehttp/psr7",
+            "version": "1.7.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/psr7.git",
+                "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/53330f47520498c0ae1f61f7e2c90f55690c06a3",
+                "reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.4.0",
+                "psr/http-message": "~1.0",
+                "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
+            },
+            "provide": {
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "ext-zlib": "*",
+                "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10"
+            },
+            "suggest": {
+                "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.7-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Psr7\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "PSR-7 message implementation that also provides common utility methods",
+            "keywords": [
+                "http",
+                "message",
+                "psr-7",
+                "request",
+                "response",
+                "stream",
+                "uri",
+                "url"
+            ],
+            "support": {
+                "issues": "https://github.com/guzzle/psr7/issues",
+                "source": "https://github.com/guzzle/psr7/tree/1.7.0"
+            },
+            "time": "2020-09-30T07:37:11+00:00"
+        },
+        {
+            "name": "karsonzhang/fastadmin-addons",
+            "version": "1.2.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/karsonzhang/fastadmin-addons.git",
+                "reference": "44bab4cb1f328d5a1593628a6719eb8263b4c52f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/karsonzhang/fastadmin-addons/zipball/44bab4cb1f328d5a1593628a6719eb8263b4c52f",
+                "reference": "44bab4cb1f328d5a1593628a6719eb8263b4c52f",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "nelexa/zip": "^3.3",
+                "php": ">=7.0.0",
+                "symfony/var-exporter": "^4.4.13"
+            },
+            "type": "library",
+            "extra": {
+                "think-config": {
+                    "addons": "src/config.php"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "think\\": "src/"
+                },
+                "files": [
+                    "src/common.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Karson",
+                    "email": "karson@fastadmin.net"
+                },
+                {
+                    "name": "xiaobo.sun",
+                    "email": "xiaobo.sun@qq.com"
+                }
+            ],
+            "description": "addons package for fastadmin",
+            "homepage": "https://github.com/karsonzhang/fastadmin-addons",
+            "support": {
+                "issues": "https://github.com/karsonzhang/fastadmin-addons/issues",
+                "source": "https://github.com/karsonzhang/fastadmin-addons/tree/v1.2.8"
+            },
+            "time": "2020-10-15T15:21:32+00:00"
+        },
+        {
+            "name": "markbaker/complex",
+            "version": "1.5.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/MarkBaker/PHPComplex.git",
+                "reference": "c3131244e29c08d44fefb49e0dd35021e9e39dd2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/c3131244e29c08d44fefb49e0dd35021e9e39dd2",
+                "reference": "c3131244e29c08d44fefb49e0dd35021e9e39dd2",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^5.6.0|^7.0"
+            },
+            "require-dev": {
+                "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
+                "phpcompatibility/php-compatibility": "^9.0",
+                "phpdocumentor/phpdocumentor": "2.*",
+                "phploc/phploc": "^4.0|^5.0|^6.0|^7.0",
+                "phpmd/phpmd": "2.*",
+                "phpunit/phpunit": "^4.8.35|^5.0|^6.0|^7.0",
+                "sebastian/phpcpd": "2.*",
+                "squizlabs/php_codesniffer": "^3.4.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Complex\\": "classes/src/"
+                },
+                "files": [
+                    "classes/src/functions/abs.php",
+                    "classes/src/functions/acos.php",
+                    "classes/src/functions/acosh.php",
+                    "classes/src/functions/acot.php",
+                    "classes/src/functions/acoth.php",
+                    "classes/src/functions/acsc.php",
+                    "classes/src/functions/acsch.php",
+                    "classes/src/functions/argument.php",
+                    "classes/src/functions/asec.php",
+                    "classes/src/functions/asech.php",
+                    "classes/src/functions/asin.php",
+                    "classes/src/functions/asinh.php",
+                    "classes/src/functions/atan.php",
+                    "classes/src/functions/atanh.php",
+                    "classes/src/functions/conjugate.php",
+                    "classes/src/functions/cos.php",
+                    "classes/src/functions/cosh.php",
+                    "classes/src/functions/cot.php",
+                    "classes/src/functions/coth.php",
+                    "classes/src/functions/csc.php",
+                    "classes/src/functions/csch.php",
+                    "classes/src/functions/exp.php",
+                    "classes/src/functions/inverse.php",
+                    "classes/src/functions/ln.php",
+                    "classes/src/functions/log2.php",
+                    "classes/src/functions/log10.php",
+                    "classes/src/functions/negative.php",
+                    "classes/src/functions/pow.php",
+                    "classes/src/functions/rho.php",
+                    "classes/src/functions/sec.php",
+                    "classes/src/functions/sech.php",
+                    "classes/src/functions/sin.php",
+                    "classes/src/functions/sinh.php",
+                    "classes/src/functions/sqrt.php",
+                    "classes/src/functions/tan.php",
+                    "classes/src/functions/tanh.php",
+                    "classes/src/functions/theta.php",
+                    "classes/src/operations/add.php",
+                    "classes/src/operations/subtract.php",
+                    "classes/src/operations/multiply.php",
+                    "classes/src/operations/divideby.php",
+                    "classes/src/operations/divideinto.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mark Baker",
+                    "email": "mark@lange.demon.co.uk"
+                }
+            ],
+            "description": "PHP Class for working with complex numbers",
+            "homepage": "https://github.com/MarkBaker/PHPComplex",
+            "keywords": [
+                "complex",
+                "mathematics"
+            ],
+            "support": {
+                "issues": "https://github.com/MarkBaker/PHPComplex/issues",
+                "source": "https://github.com/MarkBaker/PHPComplex/tree/1.5.0"
+            },
+            "time": "2020-08-26T19:47:57+00:00"
+        },
+        {
+            "name": "markbaker/matrix",
+            "version": "1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/MarkBaker/PHPMatrix.git",
+                "reference": "182d44c3b2e3b063468f7481ae3ef71c69dc1409"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/182d44c3b2e3b063468f7481ae3ef71c69dc1409",
+                "reference": "182d44c3b2e3b063468f7481ae3ef71c69dc1409",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^5.6.0|^7.0.0"
+            },
+            "require-dev": {
+                "dealerdirect/phpcodesniffer-composer-installer": "dev-master",
+                "phpcompatibility/php-compatibility": "dev-master",
+                "phploc/phploc": "^4",
+                "phpmd/phpmd": "dev-master",
+                "phpunit/phpunit": "^5.7|^6.0|7.0",
+                "sebastian/phpcpd": "^3.0",
+                "squizlabs/php_codesniffer": "^3.0@dev"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Matrix\\": "classes/src/"
+                },
+                "files": [
+                    "classes/src/functions/adjoint.php",
+                    "classes/src/functions/antidiagonal.php",
+                    "classes/src/functions/cofactors.php",
+                    "classes/src/functions/determinant.php",
+                    "classes/src/functions/diagonal.php",
+                    "classes/src/functions/identity.php",
+                    "classes/src/functions/inverse.php",
+                    "classes/src/functions/minors.php",
+                    "classes/src/functions/trace.php",
+                    "classes/src/functions/transpose.php",
+                    "classes/src/operations/add.php",
+                    "classes/src/operations/directsum.php",
+                    "classes/src/operations/subtract.php",
+                    "classes/src/operations/multiply.php",
+                    "classes/src/operations/divideby.php",
+                    "classes/src/operations/divideinto.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mark Baker",
+                    "email": "mark@lange.demon.co.uk"
+                }
+            ],
+            "description": "PHP Class for working with matrices",
+            "homepage": "https://github.com/MarkBaker/PHPMatrix",
+            "keywords": [
+                "mathematics",
+                "matrix",
+                "vector"
+            ],
+            "support": {
+                "issues": "https://github.com/MarkBaker/PHPMatrix/issues",
+                "source": "https://github.com/MarkBaker/PHPMatrix/tree/1.2"
+            },
+            "time": "2020-08-28T19:41:55+00:00"
+        },
+        {
+            "name": "monolog/monolog",
+            "version": "1.25.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Seldaek/monolog.git",
+                "reference": "1817faadd1846cd08be9a49e905dc68823bc38c0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1817faadd1846cd08be9a49e905dc68823bc38c0",
+                "reference": "1817faadd1846cd08be9a49e905dc68823bc38c0",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0",
+                "psr/log": "~1.0"
+            },
+            "provide": {
+                "psr/log-implementation": "1.0.0"
+            },
+            "require-dev": {
+                "aws/aws-sdk-php": "^2.4.9 || ^3.0",
+                "doctrine/couchdb": "~1.0@dev",
+                "graylog2/gelf-php": "~1.0",
+                "php-amqplib/php-amqplib": "~2.4",
+                "php-console/php-console": "^3.1.3",
+                "php-parallel-lint/php-parallel-lint": "^1.0",
+                "phpunit/phpunit": "~4.5",
+                "ruflin/elastica": ">=0.90 <3.0",
+                "sentry/sentry": "^0.13",
+                "swiftmailer/swiftmailer": "^5.3|^6.0"
+            },
+            "suggest": {
+                "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+                "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+                "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+                "ext-mongo": "Allow sending log messages to a MongoDB server",
+                "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+                "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
+                "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+                "php-console/php-console": "Allow sending log messages to Google Chrome",
+                "rollbar/rollbar": "Allow sending log messages to Rollbar",
+                "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
+                "sentry/sentry": "Allow sending log messages to a Sentry server"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Monolog\\": "src/Monolog"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jordi Boggiano",
+                    "email": "j.boggiano@seld.be",
+                    "homepage": "http://seld.be"
+                }
+            ],
+            "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
+            "homepage": "http://github.com/Seldaek/monolog",
+            "keywords": [
+                "log",
+                "logging",
+                "psr-3"
+            ],
+            "support": {
+                "issues": "https://github.com/Seldaek/monolog/issues",
+                "source": "https://github.com/Seldaek/monolog/tree/1.25.5"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/Seldaek",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-07-23T08:35:51+00:00"
+        },
+        {
+            "name": "nelexa/zip",
+            "version": "3.3.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Ne-Lexa/php-zip.git",
+                "reference": "501b52f6fc393a599b44ff348a42740e1eaac7c6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Ne-Lexa/php-zip/zipball/501b52f6fc393a599b44ff348a42740e1eaac7c6",
+                "reference": "501b52f6fc393a599b44ff348a42740e1eaac7c6",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-zlib": "*",
+                "paragonie/random_compat": "*",
+                "php": "^5.5.9 || ^7.0",
+                "psr/http-message": "^1.0",
+                "symfony/finder": "^3.0|^4.0|^5.0"
+            },
+            "require-dev": {
+                "ext-bz2": "*",
+                "ext-fileinfo": "*",
+                "ext-openssl": "*",
+                "ext-xml": "*",
+                "guzzlehttp/psr7": "^1.6",
+                "phpunit/phpunit": "^4.8|^5.7",
+                "symfony/var-dumper": "^3.0|^4.0|^5.0"
+            },
+            "suggest": {
+                "ext-bz2": "Needed to support BZIP2 compression",
+                "ext-fileinfo": "Needed to get mime-type file",
+                "ext-mcrypt": "Needed to support encrypt zip entries or use ext-openssl",
+                "ext-openssl": "Needed to support encrypt zip entries or use ext-mcrypt"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PhpZip\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ne-Lexa",
+                    "email": "alexey@nelexa.ru",
+                    "role": "Developer"
+                }
+            ],
+            "description": "PhpZip is a php-library for extended work with ZIP-archives. Open, create, update, delete, extract and get info tool. Supports appending to existing ZIP files, WinZip AES encryption, Traditional PKWARE Encryption, ZipAlign tool, BZIP2 compression, external file attributes and ZIP64 extensions. Alternative ZipArchive. It does not require php-zip extension.",
+            "homepage": "https://github.com/Ne-Lexa/php-zip",
+            "keywords": [
+                "archive",
+                "extract",
+                "unzip",
+                "winzip",
+                "zip",
+                "zipalign",
+                "ziparchive"
+            ],
+            "support": {
+                "issues": "https://github.com/Ne-Lexa/php-zip/issues",
+                "source": "https://github.com/Ne-Lexa/php-zip/tree/3.3.3"
+            },
+            "time": "2020-07-11T21:01:42+00:00"
+        },
+        {
+            "name": "overtrue/pinyin",
+            "version": "3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/overtrue/pinyin.git",
+                "reference": "3b781d267197b74752daa32814d3a2cf5d140779"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/overtrue/pinyin/zipball/3b781d267197b74752daa32814d3a2cf5d140779",
+                "reference": "3b781d267197b74752daa32814d3a2cf5d140779",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.8"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Overtrue\\Pinyin\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Carlos",
+                    "homepage": "http://github.com/overtrue"
+                }
+            ],
+            "description": "Chinese to pinyin translator.",
+            "homepage": "https://github.com/overtrue/pinyin",
+            "keywords": [
+                "Chinese",
+                "Pinyin",
+                "cn2pinyin"
+            ],
+            "support": {
+                "issues": "https://github.com/overtrue/pinyin/issues",
+                "source": "https://github.com/overtrue/pinyin/tree/master"
+            },
+            "time": "2017-07-10T07:20:01+00:00"
+        },
+        {
+            "name": "overtrue/socialite",
+            "version": "2.0.21",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/overtrue/socialite.git",
+                "reference": "a4a91039601f846494a8a9b06201958896d129bf"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/overtrue/socialite/zipball/a4a91039601f846494a8a9b06201958896d129bf",
+                "reference": "a4a91039601f846494a8a9b06201958896d129bf",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/guzzle": "^5.0|^6.0|^7.0",
+                "php": ">=5.6",
+                "symfony/http-foundation": "^2.7|^3.0|^4.0|^5.0"
+            },
+            "conflict": {
+                "socialiteproviders/weixin": "*"
+            },
+            "require-dev": {
+                "mockery/mockery": "~1.2",
+                "phpunit/phpunit": "~6"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Overtrue\\Socialite\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "overtrue",
+                    "email": "anzhengchao@gmail.com"
+                }
+            ],
+            "description": "A collection of OAuth 2 packages that extracts from laravel/socialite.",
+            "keywords": [
+                "login",
+                "oauth",
+                "qq",
+                "social",
+                "wechat",
+                "weibo"
+            ],
+            "support": {
+                "issues": "https://github.com/overtrue/socialite/issues",
+                "source": "https://github.com/overtrue/socialite/tree/2.0.21"
+            },
+            "funding": [
+                {
+                    "url": "https://www.patreon.com/overtrue",
+                    "type": "patreon"
+                }
+            ],
+            "time": "2020-10-23T15:14:08+00:00"
+        },
+        {
+            "name": "overtrue/wechat",
+            "version": "4.2.11",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/overtrue/wechat.git",
+                "reference": "853e0772e6aa53a71edf1b5d251c7ff1e6b2a2bf"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/overtrue/wechat/zipball/853e0772e6aa53a71edf1b5d251c7ff1e6b2a2bf",
+                "reference": "853e0772e6aa53a71edf1b5d251c7ff1e6b2a2bf",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "easywechat-composer/easywechat-composer": "^1.1",
+                "ext-fileinfo": "*",
+                "ext-openssl": "*",
+                "ext-simplexml": "*",
+                "guzzlehttp/guzzle": "^6.2",
+                "monolog/monolog": "^1.22 || ^2.0",
+                "overtrue/socialite": "~2.0",
+                "php": ">=7.1",
+                "pimple/pimple": "^3.0",
+                "psr/simple-cache": "^1.0",
+                "symfony/cache": "^3.3 || ^4.3",
+                "symfony/event-dispatcher": "^4.3",
+                "symfony/http-foundation": "^2.7 || ^3.0 || ^4.0",
+                "symfony/psr-http-message-bridge": "^0.3 || ^1.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "^2.15",
+                "mikey179/vfsstream": "^1.6",
+                "mockery/mockery": "^1.2.3",
+                "phpstan/phpstan": "^0.11.12",
+                "phpunit/phpunit": "^7.5"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "EasyWeChat\\": "src/"
+                },
+                "files": [
+                    "src/Kernel/Support/Helpers.php",
+                    "src/Kernel/Helpers.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "overtrue",
+                    "email": "anzhengchao@gmail.com"
+                }
+            ],
+            "description": "微信SDK",
+            "keywords": [
+                "sdk",
+                "wechat",
+                "weixin",
+                "weixin-sdk"
+            ],
+            "support": {
+                "issues": "https://github.com/overtrue/wechat/issues",
+                "source": "https://github.com/overtrue/wechat/tree/4.2.11"
+            },
+            "time": "2019-11-27T16:38:00+00:00"
+        },
+        {
+            "name": "paragonie/random_compat",
+            "version": "v9.99.100",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/paragonie/random_compat.git",
+                "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
+                "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">= 7"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "4.*|5.*",
+                "vimeo/psalm": "^1"
+            },
+            "suggest": {
+                "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+            },
+            "type": "library",
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Paragon Initiative Enterprises",
+                    "email": "security@paragonie.com",
+                    "homepage": "https://paragonie.com"
+                }
+            ],
+            "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+            "keywords": [
+                "csprng",
+                "polyfill",
+                "pseudorandom",
+                "random"
+            ],
+            "support": {
+                "email": "info@paragonie.com",
+                "issues": "https://github.com/paragonie/random_compat/issues",
+                "source": "https://github.com/paragonie/random_compat"
+            },
+            "time": "2020-10-15T08:29:30+00:00"
+        },
+        {
+            "name": "phpmailer/phpmailer",
+            "version": "v6.1.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPMailer/PHPMailer.git",
+                "reference": "2c2370ba3df7034f9eb7b8f387c97b52b2ba5ad0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2c2370ba3df7034f9eb7b8f387c97b52b2ba5ad0",
+                "reference": "2c2370ba3df7034f9eb7b8f387c97b52b2ba5ad0",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-ctype": "*",
+                "ext-filter": "*",
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "doctrine/annotations": "^1.2",
+                "friendsofphp/php-cs-fixer": "^2.2",
+                "phpunit/phpunit": "^4.8 || ^5.7"
+            },
+            "suggest": {
+                "ext-mbstring": "Needed to send email in multibyte encoding charset",
+                "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
+                "league/oauth2-google": "Needed for Google XOAUTH2 authentication",
+                "psr/log": "For optional PSR-3 debug logging",
+                "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication",
+                "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PHPMailer\\PHPMailer\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-2.1-only"
+            ],
+            "authors": [
+                {
+                    "name": "Marcus Bointon",
+                    "email": "phpmailer@synchromedia.co.uk"
+                },
+                {
+                    "name": "Jim Jagielski",
+                    "email": "jimjag@gmail.com"
+                },
+                {
+                    "name": "Andy Prevost",
+                    "email": "codeworxtech@users.sourceforge.net"
+                },
+                {
+                    "name": "Brent R. Matzelle"
+                }
+            ],
+            "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
+            "support": {
+                "issues": "https://github.com/PHPMailer/PHPMailer/issues",
+                "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.1.7"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/synchro",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-07-14T18:50:27+00:00"
+        },
+        {
+            "name": "phpoffice/phpspreadsheet",
+            "version": "1.12.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
+                "reference": "f79611d6dc1f6b7e8e30b738fc371b392001dbfd"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/f79611d6dc1f6b7e8e30b738fc371b392001dbfd",
+                "reference": "f79611d6dc1f6b7e8e30b738fc371b392001dbfd",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-ctype": "*",
+                "ext-dom": "*",
+                "ext-fileinfo": "*",
+                "ext-gd": "*",
+                "ext-iconv": "*",
+                "ext-libxml": "*",
+                "ext-mbstring": "*",
+                "ext-simplexml": "*",
+                "ext-xml": "*",
+                "ext-xmlreader": "*",
+                "ext-xmlwriter": "*",
+                "ext-zip": "*",
+                "ext-zlib": "*",
+                "markbaker/complex": "^1.4",
+                "markbaker/matrix": "^1.2",
+                "php": "^7.1",
+                "psr/simple-cache": "^1.0"
+            },
+            "require-dev": {
+                "dompdf/dompdf": "^0.8.3",
+                "friendsofphp/php-cs-fixer": "^2.16",
+                "jpgraph/jpgraph": "^4.0",
+                "mpdf/mpdf": "^8.0",
+                "phpcompatibility/php-compatibility": "^9.3",
+                "phpunit/phpunit": "^7.5",
+                "squizlabs/php_codesniffer": "^3.5",
+                "tecnickcom/tcpdf": "^6.3"
+            },
+            "suggest": {
+                "dompdf/dompdf": "Option for rendering PDF with PDF Writer",
+                "jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
+                "mpdf/mpdf": "Option for rendering PDF with PDF Writer",
+                "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Maarten Balliauw",
+                    "homepage": "https://blog.maartenballiauw.be"
+                },
+                {
+                    "name": "Mark Baker",
+                    "homepage": "https://markbakeruk.net"
+                },
+                {
+                    "name": "Franck Lefevre",
+                    "homepage": "https://rootslabs.net"
+                },
+                {
+                    "name": "Erik Tilt"
+                },
+                {
+                    "name": "Adrien Crivelli"
+                }
+            ],
+            "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
+            "homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
+            "keywords": [
+                "OpenXML",
+                "excel",
+                "gnumeric",
+                "ods",
+                "php",
+                "spreadsheet",
+                "xls",
+                "xlsx"
+            ],
+            "support": {
+                "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
+                "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.12.0"
+            },
+            "time": "2020-04-27T08:12:48+00:00"
+        },
+        {
+            "name": "pimple/pimple",
+            "version": "v3.2.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/silexphp/Pimple.git",
+                "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32",
+                "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0",
+                "psr/container": "^1.0"
+            },
+            "require-dev": {
+                "symfony/phpunit-bridge": "^3.2"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.2.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Pimple": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                }
+            ],
+            "description": "Pimple, a simple Dependency Injection Container",
+            "homepage": "http://pimple.sensiolabs.org",
+            "keywords": [
+                "container",
+                "dependency injection"
+            ],
+            "support": {
+                "issues": "https://github.com/silexphp/Pimple/issues",
+                "source": "https://github.com/silexphp/Pimple/tree/master"
+            },
+            "time": "2018-01-21T07:42:36+00:00"
+        },
+        {
+            "name": "psr/cache",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/cache.git",
+                "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8",
+                "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Cache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for caching libraries",
+            "keywords": [
+                "cache",
+                "psr",
+                "psr-6"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/cache/tree/master"
+            },
+            "time": "2016-08-06T20:24:11+00:00"
+        },
+        {
+            "name": "psr/container",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/container.git",
+                "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+                "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Container\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common Container Interface (PHP FIG PSR-11)",
+            "homepage": "https://github.com/php-fig/container",
+            "keywords": [
+                "PSR-11",
+                "container",
+                "container-interface",
+                "container-interop",
+                "psr"
+            ],
+            "support": {
+                "issues": "https://github.com/php-fig/container/issues",
+                "source": "https://github.com/php-fig/container/tree/master"
+            },
+            "time": "2017-02-14T16:28:37+00:00"
+        },
+        {
+            "name": "psr/http-message",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-message.git",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP messages",
+            "homepage": "https://github.com/php-fig/http-message",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/http-message/tree/master"
+            },
+            "time": "2016-08-06T14:39:51+00:00"
+        },
+        {
+            "name": "psr/log",
+            "version": "1.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/log.git",
+                "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc",
+                "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Log\\": "Psr/Log/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for logging libraries",
+            "homepage": "https://github.com/php-fig/log",
+            "keywords": [
+                "log",
+                "psr",
+                "psr-3"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/log/tree/1.1.3"
+            },
+            "time": "2020-03-23T09:12:05+00:00"
+        },
+        {
+            "name": "psr/simple-cache",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/simple-cache.git",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\SimpleCache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interfaces for simple caching",
+            "keywords": [
+                "cache",
+                "caching",
+                "psr",
+                "psr-16",
+                "simple-cache"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/simple-cache/tree/master"
+            },
+            "time": "2017-10-23T01:57:42+00:00"
+        },
+        {
+            "name": "qcloud_sts/qcloud-sts-sdk",
+            "version": "3.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/tencentyun/qcloud-cos-sts-php-sdk.git",
+                "reference": "efd749256bdedb650ee12658fcb8d4e451a4fe72"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/tencentyun/qcloud-cos-sts-php-sdk/zipball/efd749256bdedb650ee12658fcb8d4e451a4fe72",
+                "reference": "efd749256bdedb650ee12658fcb8d4e451a4fe72",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "QCloud\\COSSTS\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "qcloudterminal",
+                    "email": "qcloudterminal@gmail.com"
+                }
+            ],
+            "description": "PHP SDK for QCloud STS",
+            "homepage": "https://github.com/tencentyun/qcloud-cos-sts-sdk",
+            "keywords": [
+                "cos",
+                "php",
+                "qcloud",
+                "sts"
+            ],
+            "support": {
+                "issues": "https://github.com/tencentyun/qcloud-cos-sts-php-sdk/issues",
+                "source": "https://github.com/tencentyun/qcloud-cos-sts-php-sdk/tree/master"
+            },
+            "time": "2020-05-12T07:19:15+00:00"
+        },
+        {
+            "name": "ralouphie/getallheaders",
+            "version": "3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ralouphie/getallheaders.git",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "php-coveralls/php-coveralls": "^2.1",
+                "phpunit/phpunit": "^5 || ^6.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/getallheaders.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ralph Khattar",
+                    "email": "ralph.khattar@gmail.com"
+                }
+            ],
+            "description": "A polyfill for getallheaders.",
+            "support": {
+                "issues": "https://github.com/ralouphie/getallheaders/issues",
+                "source": "https://github.com/ralouphie/getallheaders/tree/develop"
+            },
+            "time": "2019-03-08T08:55:37+00:00"
+        },
+        {
+            "name": "symfony/cache",
+            "version": "v4.4.16",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/cache.git",
+                "reference": "7ab1528cac0328566895ad303e2a5111aef2b440"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/cache/zipball/7ab1528cac0328566895ad303e2a5111aef2b440",
+                "reference": "7ab1528cac0328566895ad303e2a5111aef2b440",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.0.0",
+                "psr/cache": "~1.0",
+                "psr/log": "~1.0",
+                "symfony/cache-contracts": "^1.1.7|^2",
+                "symfony/service-contracts": "^1.1|^2",
+                "symfony/var-exporter": "^4.2|^5.0"
+            },
+            "conflict": {
+                "doctrine/dbal": "<2.5",
+                "symfony/dependency-injection": "<3.4",
+                "symfony/http-kernel": "<4.4",
+                "symfony/var-dumper": "<4.4"
+            },
+            "provide": {
+                "psr/cache-implementation": "1.0",
+                "psr/simple-cache-implementation": "1.0",
+                "symfony/cache-implementation": "1.0"
+            },
+            "require-dev": {
+                "cache/integration-tests": "dev-master",
+                "doctrine/cache": "^1.6",
+                "doctrine/dbal": "^2.5|^3.0",
+                "predis/predis": "^1.1",
+                "psr/simple-cache": "^1.0",
+                "symfony/config": "^4.2|^5.0",
+                "symfony/dependency-injection": "^3.4|^4.1|^5.0",
+                "symfony/filesystem": "^4.4|^5.0",
+                "symfony/var-dumper": "^4.4|^5.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Cache\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Cache component with PSR-6, PSR-16, and tags",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "caching",
+                "psr6"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/cache/tree/v4.4.16"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-10-25T19:32:35+00:00"
+        },
+        {
+            "name": "symfony/cache-contracts",
+            "version": "v1.1.10",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/cache-contracts.git",
+                "reference": "8d5489c10ef90aa7413e4921fc3c0520e24cbed7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/8d5489c10ef90aa7413e4921fc3c0520e24cbed7",
+                "reference": "8d5489c10ef90aa7413e4921fc3c0520e24cbed7",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.0.0",
+                "psr/cache": "^1.0"
+            },
+            "suggest": {
+                "symfony/cache-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                },
+                "thanks": {
+                    "name": "symfony/contracts",
+                    "url": "https://github.com/symfony/contracts"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Cache\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to caching",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/cache-contracts/tree/v1.1.10"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-09-02T16:08:58+00:00"
+        },
+        {
+            "name": "symfony/event-dispatcher",
+            "version": "v4.4.16",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/event-dispatcher.git",
+                "reference": "4204f13d2d0b7ad09454f221bb2195fccdf1fe98"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4204f13d2d0b7ad09454f221bb2195fccdf1fe98",
+                "reference": "4204f13d2d0b7ad09454f221bb2195fccdf1fe98",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.0.0",
+                "symfony/event-dispatcher-contracts": "^1.1"
+            },
+            "conflict": {
+                "symfony/dependency-injection": "<3.4"
+            },
+            "provide": {
+                "psr/event-dispatcher-implementation": "1.0",
+                "symfony/event-dispatcher-implementation": "1.1"
+            },
+            "require-dev": {
+                "psr/log": "~1.0",
+                "symfony/config": "^3.4|^4.0|^5.0",
+                "symfony/dependency-injection": "^3.4|^4.0|^5.0",
+                "symfony/error-handler": "~3.4|~4.4",
+                "symfony/expression-language": "^3.4|^4.0|^5.0",
+                "symfony/http-foundation": "^3.4|^4.0|^5.0",
+                "symfony/service-contracts": "^1.1|^2",
+                "symfony/stopwatch": "^3.4|^4.0|^5.0"
+            },
+            "suggest": {
+                "symfony/dependency-injection": "",
+                "symfony/http-kernel": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\EventDispatcher\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony EventDispatcher Component",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/event-dispatcher/tree/v4.4.16"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-10-24T11:50:19+00:00"
+        },
+        {
+            "name": "symfony/event-dispatcher-contracts",
+            "version": "v1.1.9",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/event-dispatcher-contracts.git",
+                "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/84e23fdcd2517bf37aecbd16967e83f0caee25a7",
+                "reference": "84e23fdcd2517bf37aecbd16967e83f0caee25a7",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.0.0"
+            },
+            "suggest": {
+                "psr/event-dispatcher": "",
+                "symfony/event-dispatcher-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                },
+                "thanks": {
+                    "name": "symfony/contracts",
+                    "url": "https://github.com/symfony/contracts"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\EventDispatcher\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to dispatching event",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v1.1.9"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-07-06T13:19:58+00:00"
+        },
+        {
+            "name": "symfony/finder",
+            "version": "v4.4.16",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/finder.git",
+                "reference": "26f63b8d4e92f2eecd90f6791a563ebb001abe31"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/26f63b8d4e92f2eecd90f6791a563ebb001abe31",
+                "reference": "26f63b8d4e92f2eecd90f6791a563ebb001abe31",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.0.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Finder\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Finder Component",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/finder/tree/v4.4.16"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-10-24T11:50:19+00:00"
+        },
+        {
+            "name": "symfony/http-foundation",
+            "version": "v4.4.16",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/http-foundation.git",
+                "reference": "827a00811ef699e809a201ceafac0b2b246bf38a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/827a00811ef699e809a201ceafac0b2b246bf38a",
+                "reference": "827a00811ef699e809a201ceafac0b2b246bf38a",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.0.0",
+                "symfony/mime": "^4.3|^5.0",
+                "symfony/polyfill-mbstring": "~1.1"
+            },
+            "require-dev": {
+                "predis/predis": "~1.0",
+                "symfony/expression-language": "^3.4|^4.0|^5.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\HttpFoundation\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony HttpFoundation Component",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/http-foundation/tree/v4.4.16"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-10-24T11:50:19+00:00"
+        },
+        {
+            "name": "symfony/mime",
+            "version": "v4.4.16",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/mime.git",
+                "reference": "360f9963b6d4db6c3454d58548fb2b085f97d3e2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/mime/zipball/360f9963b6d4db6c3454d58548fb2b085f97d3e2",
+                "reference": "360f9963b6d4db6c3454d58548fb2b085f97d3e2",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.0.0",
+                "symfony/polyfill-intl-idn": "^1.10",
+                "symfony/polyfill-mbstring": "^1.0"
+            },
+            "conflict": {
+                "symfony/mailer": "<4.4"
+            },
+            "require-dev": {
+                "egulias/email-validator": "^2.1.10",
+                "symfony/dependency-injection": "^3.4|^4.1|^5.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Mime\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "A library to manipulate MIME messages",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "mime",
+                "mime-type"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/mime/tree/v4.4.16"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-10-24T11:50:19+00:00"
+        },
+        {
+            "name": "symfony/polyfill-intl-idn",
+            "version": "v1.20.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-intl-idn.git",
+                "reference": "3b75acd829741c768bc8b1f84eb33265e7cc5117"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/3b75acd829741c768bc8b1f84eb33265e7cc5117",
+                "reference": "3b75acd829741c768bc8b1f84eb33265e7cc5117",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.1",
+                "symfony/polyfill-intl-normalizer": "^1.10",
+                "symfony/polyfill-php72": "^1.10"
+            },
+            "suggest": {
+                "ext-intl": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.20-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Intl\\Idn\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Laurent Bassin",
+                    "email": "laurent@bassin.info"
+                },
+                {
+                    "name": "Trevor Rowbotham",
+                    "email": "trevor.rowbotham@pm.me"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "idn",
+                "intl",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.20.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-10-23T14:02:19+00:00"
+        },
+        {
+            "name": "symfony/polyfill-intl-normalizer",
+            "version": "v1.20.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+                "reference": "727d1096295d807c309fb01a851577302394c897"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/727d1096295d807c309fb01a851577302394c897",
+                "reference": "727d1096295d807c309fb01a851577302394c897",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "suggest": {
+                "ext-intl": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.20-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ],
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for intl's Normalizer class and related functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "intl",
+                "normalizer",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.20.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-10-23T14:02:19+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.20.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "39d483bdf39be819deabf04ec872eb0b2410b531"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/39d483bdf39be819deabf04ec872eb0b2410b531",
+                "reference": "39d483bdf39be819deabf04ec872eb0b2410b531",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.20-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.20.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-10-23T14:02:19+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php72",
+            "version": "v1.20.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php72.git",
+                "reference": "cede45fcdfabdd6043b3592e83678e42ec69e930"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/cede45fcdfabdd6043b3592e83678e42ec69e930",
+                "reference": "cede45fcdfabdd6043b3592e83678e42ec69e930",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.20-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php72\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php72/tree/v1.20.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-10-23T14:02:19+00:00"
+        },
+        {
+            "name": "symfony/psr-http-message-bridge",
+            "version": "v1.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/psr-http-message-bridge.git",
+                "reference": "9d3e80d54d9ae747ad573cad796e8e247df7b796"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/9d3e80d54d9ae747ad573cad796e8e247df7b796",
+                "reference": "9d3e80d54d9ae747ad573cad796e8e247df7b796",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.1",
+                "psr/http-message": "^1.0",
+                "symfony/http-foundation": "^4.4 || ^5.0"
+            },
+            "require-dev": {
+                "nyholm/psr7": "^1.1",
+                "symfony/phpunit-bridge": "^4.4 || ^5.0",
+                "zendframework/zend-diactoros": "^1.4.1 || ^2.0"
+            },
+            "suggest": {
+                "nyholm/psr7": "For a super lightweight PSR-7/17 implementation"
+            },
+            "type": "symfony-bridge",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Bridge\\PsrHttpMessage\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "http://symfony.com/contributors"
+                }
+            ],
+            "description": "PSR HTTP message bridge",
+            "homepage": "http://symfony.com",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr-17",
+                "psr-7"
+            ],
+            "support": {
+                "issues": "https://github.com/symfony/psr-http-message-bridge/issues",
+                "source": "https://github.com/symfony/psr-http-message-bridge/tree/master"
+            },
+            "time": "2019-11-25T19:33:50+00:00"
+        },
+        {
+            "name": "symfony/service-contracts",
+            "version": "v1.1.9",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/service-contracts.git",
+                "reference": "b776d18b303a39f56c63747bcb977ad4b27aca26"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/b776d18b303a39f56c63747bcb977ad4b27aca26",
+                "reference": "b776d18b303a39f56c63747bcb977ad4b27aca26",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.0.0",
+                "psr/container": "^1.0"
+            },
+            "suggest": {
+                "symfony/service-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                },
+                "thanks": {
+                    "name": "symfony/contracts",
+                    "url": "https://github.com/symfony/contracts"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Service\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to writing services",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/service-contracts/tree/v1.1.9"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-07-06T13:19:58+00:00"
+        },
+        {
+            "name": "symfony/var-exporter",
+            "version": "v4.4.16",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/var-exporter.git",
+                "reference": "a07f9c350ebe30dadd30505d2b05d7c9bbcef2b1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/var-exporter/zipball/a07f9c350ebe30dadd30505d2b05d7c9bbcef2b1",
+                "reference": "a07f9c350ebe30dadd30505d2b05d7c9bbcef2b1",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.0.0"
+            },
+            "require-dev": {
+                "symfony/var-dumper": "^4.4.9|^5.0.9"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\VarExporter\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "A blend of var_export() + serialize() to turn any serializable data structure to plain PHP code",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "clone",
+                "construct",
+                "export",
+                "hydrate",
+                "instantiate",
+                "serialize"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/var-exporter/tree/v4.4.16"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-10-24T11:50:19+00:00"
+        },
+        {
+            "name": "topthink/framework",
+            "version": "v5.0.24",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/framework.git",
+                "reference": "c255c22b2f5fa30f320ecf6c1d29f7740eb3e8be"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/framework/zipball/c255c22b2f5fa30f320ecf6c1d29f7740eb3e8be",
+                "reference": "c255c22b2f5fa30f320ecf6c1d29f7740eb3e8be",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.4.0",
+                "topthink/think-installer": "~1.0"
+            },
+            "require-dev": {
+                "johnkary/phpunit-speedtrap": "^1.0",
+                "mikey179/vfsstream": "~1.6",
+                "phpdocumentor/reflection-docblock": "^2.0",
+                "phploc/phploc": "2.*",
+                "phpunit/phpunit": "4.8.*",
+                "sebastian/phpcpd": "2.*"
+            },
+            "type": "think-framework",
+            "autoload": {
+                "psr-4": {
+                    "think\\": "library/think"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                }
+            ],
+            "description": "the new 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/master"
+            },
+            "time": "2019-01-11T08:04:58+00:00"
+        },
+        {
+            "name": "topthink/think-captcha",
+            "version": "v1.0.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-captcha.git",
+                "reference": "1d64363c814c92f6086c4fa5e3223fe7e23db09d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-captcha/zipball/1d64363c814c92f6086c4fa5e3223fe7e23db09d",
+                "reference": "1d64363c814c92f6086c4fa5e3223fe7e23db09d",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "topthink/framework": "~5.0.0",
+                "topthink/think-installer": ">=1.0.10"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "think\\captcha\\": "src/"
+                },
+                "files": [
+                    "src/helper.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "captcha package for thinkphp5",
+            "support": {
+                "issues": "https://github.com/top-think/think-captcha/issues",
+                "source": "https://github.com/top-think/think-captcha/tree/master"
+            },
+            "time": "2019-01-28T04:48:36+00:00"
+        },
+        {
+            "name": "topthink/think-helper",
+            "version": "v3.1.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-helper.git",
+                "reference": "c28d37743bda4a0455286ca85b17b5791d626e10"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-helper/zipball/c28d37743bda4a0455286ca85b17b5791d626e10",
+                "reference": "c28d37743bda4a0455286ca85b17b5791d626e10",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.1.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "think\\": "src"
+                },
+                "files": [
+                    "src/helper.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "The ThinkPHP6 Helper Package",
+            "support": {
+                "issues": "https://github.com/top-think/think-helper/issues",
+                "source": "https://github.com/top-think/think-helper/tree/3.0"
+            },
+            "time": "2019-11-08T08:01:10+00:00"
+        },
+        {
+            "name": "topthink/think-installer",
+            "version": "v1.0.13",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-installer.git",
+                "reference": "532dc02efb3d8332b36fd8f63fc4f56aeb1987e7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-installer/zipball/532dc02efb3d8332b36fd8f63fc4f56aeb1987e7",
+                "reference": "532dc02efb3d8332b36fd8f63fc4f56aeb1987e7",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "composer-plugin-api": "^1.0||^2.0"
+            },
+            "require-dev": {
+                "composer/composer": "^1.0||^2.0"
+            },
+            "type": "composer-plugin",
+            "extra": {
+                "class": "think\\composer\\Plugin"
+            },
+            "autoload": {
+                "psr-4": {
+                    "think\\composer\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "support": {
+                "issues": "https://github.com/top-think/think-installer/issues",
+                "source": "https://github.com/top-think/think-installer/tree/v1.0.13"
+            },
+            "time": "2020-10-27T05:39:37+00:00"
+        },
+        {
+            "name": "topthink/think-queue",
+            "version": "v2.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-queue.git",
+                "reference": "465320c9cb7811df22d4ff8f29f58ead7d104348"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-queue/zipball/465320c9cb7811df22d4ff8f29f58ead7d104348",
+                "reference": "465320c9cb7811df22d4ff8f29f58ead7d104348",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "topthink/think-helper": ">=1.0.4",
+                "topthink/think-installer": ">=1.0.10"
+            },
+            "type": "think-extend",
+            "extra": {
+                "think-config": {
+                    "queue": "src/config.php"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "think\\": "src"
+                },
+                "files": [
+                    "src/common.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "yunwuxin",
+                    "email": "448901948@qq.com"
+                }
+            ],
+            "description": "The ThinkPHP5 Queue Package",
+            "support": {
+                "issues": "https://github.com/top-think/think-queue/issues",
+                "source": "https://github.com/top-think/think-queue/tree/master"
+            },
+            "time": "2018-05-04T05:29:53+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=7.0.0",
+        "ext-json": "*",
+        "ext-curl": "*",
+        "ext-pdo": "*"
+    },
+    "platform-dev": [],
+    "plugin-api-version": "2.1.0"
+}

+ 39 - 0
index.html

@@ -0,0 +1,39 @@
+<!doctype html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>恭喜,站点创建成功!</title>
+    <style>
+        .container {
+            width: 60%;
+            margin: 10% auto 0;
+            background-color: #f0f0f0;
+            padding: 2% 5%;
+            border-radius: 10px
+        }
+
+        ul {
+            padding-left: 20px;
+        }
+
+            ul li {
+                line-height: 2.3
+            }
+
+        a {
+            color: #20a53a
+        }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <h1>恭喜, 站点创建成功!</h1>
+        <h3>这是默认index.html,本页面由系统自动生成</h3>
+        <ul>
+            <li>本页面在FTP根目录下的index.html</li>
+            <li>您可以修改、删除或覆盖本页面</li>
+            <li>FTP相关信息,请到“面板系统后台 > FTP” 查看</li>
+        </ul>
+    </div>
+</body>
+</html>

+ 0 - 0
nginx.htaccess


+ 5 - 0
nohup.out

@@ -0,0 +1,5 @@
+[Wed Mar  3 17:29:22 2021] Failed to listen on 192.168.1.112:8888 (reason: Can't assign requested address)
+[Wed Mar  3 17:29:34 2021] Failed to listen on 192.168.1.112:8888 (reason: Can't assign requested address)
+[Wed Mar  3 17:31:02 2021] Failed to listen on 192.168.3.57:8888 (reason: Can't assign requested address)
+Invalid address: 192.168.3.57
+Invalid address: 192.168.3.57

+ 5 - 0
server.php

@@ -0,0 +1,5 @@
+<?php
+define('APP_PATH', __DIR__ . '/application/');
+define('BIND_MODULE','push/Worker');
+// 加载框架引导文件
+require __DIR__ . '/thinkphp/start.php';

+ 37 - 0
start.php

@@ -0,0 +1,37 @@
+<?php
+/**
+ * run with command
+ * php start.php start
+ */
+
+ini_set('display_errors', 'on');
+use Workerman\Worker;
+
+if(strpos(strtolower(PHP_OS), 'win') === 0)
+{
+    exit("start.php not support windows, please use start_for_win.bat\n");
+}
+
+// 检查扩展
+if(!extension_loaded('pcntl'))
+{
+    exit("Please install pcntl extension. See http://doc3.workerman.net/appendices/install-extension.html\n");
+}
+
+if(!extension_loaded('posix'))
+{
+    exit("Please install posix extension. See http://doc3.workerman.net/appendices/install-extension.html\n");
+}
+
+// 标记是全局启动
+define('GLOBAL_START', 1);
+
+require_once __DIR__ . '/vendor/autoload.php';
+
+// 加载所有Applications/*/start.php,以便启动所有服务
+foreach(glob(__DIR__.'/Applications/*/start*.php') as $start_file)
+{
+    require_once $start_file;
+}
+// 运行所有服务
+Worker::runAll();

+ 17 - 0
think

@@ -0,0 +1,17 @@
+#!/usr/bin/env php
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+// 定义项目路径
+define('APP_PATH', __DIR__ . '/application/');
+
+// 加载框架引导文件
+require './thinkphp/console.php';

BIN
thinkphp/.DS_Store


+ 119 - 0
thinkphp/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 5.4 5.5 5.6 7.0 和 HHVM 上测试修改,其中 HHVM 下的测试容许报错,请确保你的修改符合 PHP 5.4 ~ 5.6 和 PHP 7.0 的语法规范;
+* 管理员不会合并造成 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,不需要重新提交 `pull request`,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 5.4 ~ 5.6
+- 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
thinkphp/LICENSE.txt

@@ -0,0 +1,32 @@
+
+ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
+版权所有Copyright © 2006-2017 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.

+ 114 - 0
thinkphp/README.md

@@ -0,0 +1,114 @@
+ThinkPHP 5.0
+===============
+
+[![StyleCI](https://styleci.io/repos/48530411/shield?style=flat&branch=master)](https://styleci.io/repos/48530411)
+[![Build Status](https://travis-ci.org/top-think/framework.svg?branch=master)](https://travis-ci.org/top-think/framework)
+[![codecov.io](http://codecov.io/github/top-think/framework/coverage.svg?branch=master)](http://codecov.io/github/github/top-think/framework?branch=master)
+[![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)
+[![Latest Unstable Version](https://poser.pugx.org/topthink/framework/v/unstable)](https://packagist.org/packages/topthink/framework)
+[![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework)
+
+ThinkPHP5在保持快速开发和大道至简的核心理念不变的同时,PHP版本要求提升到5.4,优化核心,减少依赖,基于全新的架构思想和命名空间实现,是ThinkPHP突破原有框架思路的颠覆之作,其主要特性包括:
+
+ + 基于命名空间和众多PHP新特性
+ + 核心功能组件化
+ + 强化路由功能
+ + 更灵活的控制器
+ + 重构的模型和数据库类
+ + 配置文件可分离
+ + 重写的自动验证和完成
+ + 简化扩展机制
+ + API支持完善
+ + 改进的Log类
+ + 命令行访问支持
+ + REST支持
+ + 引导文件支持
+ + 方便的自动生成定义
+ + 真正惰性加载
+ + 分布式环境支持
+ + 支持Composer
+ + 支持MongoDb
+
+> ThinkPHP5的运行环境要求PHP5.4以上。
+
+详细开发文档参考 [ThinkPHP5完全开发手册](http://www.kancloud.cn/manual/thinkphp5) 以及[ThinkPHP5入门系列教程](http://www.kancloud.cn/special/thinkphp5_quickstart)
+
+## 目录结构
+
+初始的目录结构如下:
+
+~~~
+www  WEB部署目录(或者子目录)
+├─application           应用目录
+│  ├─common             公共模块目录(可以更改)
+│  ├─module_name        模块目录
+│  │  ├─config.php      模块配置文件
+│  │  ├─common.php      模块函数文件
+│  │  ├─controller      控制器目录
+│  │  ├─model           模型目录
+│  │  ├─view            视图目录
+│  │  └─ ...            更多类库目录
+│  │
+│  ├─command.php        命令行工具配置文件
+│  ├─common.php         公共函数文件
+│  ├─config.php         公共配置文件
+│  ├─route.php          路由配置文件
+│  ├─tags.php           应用行为扩展定义文件
+│  └─database.php       数据库配置文件
+│
+├─public                WEB目录(对外访问目录)
+│  ├─index.php          入口文件
+│  ├─router.php         快速测试文件
+│  └─.htaccess          用于apache的重写
+│
+├─thinkphp              框架系统目录
+│  ├─lang               语言文件目录
+│  ├─library            框架类库目录
+│  │  ├─think           Think类库包目录
+│  │  └─traits          系统Trait目录
+│  │
+│  ├─tpl                系统模板目录
+│  ├─base.php           基础定义文件
+│  ├─console.php        控制台入口文件
+│  ├─convention.php     框架惯例配置文件
+│  ├─helper.php         助手函数文件
+│  ├─phpunit.xml        phpunit配置文件
+│  └─start.php          框架入口文件
+│
+├─extend                扩展类库目录
+├─runtime               应用的运行时目录(可写,可定制)
+├─vendor                第三方类库目录(Composer依赖库)
+├─build.php             自动生成定义文件(参考)
+├─composer.json         composer 定义文件
+├─LICENSE.txt           授权说明文件
+├─README.md             README 文件
+├─think                 命令行入口文件
+~~~
+
+> router.php用于php自带webserver支持,可用于快速测试
+> 切换到public目录后,启动命令:php -S localhost:8888  router.php
+> 上面的目录结构和名称是可以改变的,这取决于你的入口文件和配置参数。
+
+## 命名规范
+
+ThinkPHP5的命名规范遵循`PSR-2`规范以及`PSR-4`自动加载规范。
+
+## 参与开发
+注册并登录 Github 帐号, fork 本项目并进行改动。
+
+更多细节参阅 [CONTRIBUTING.md](CONTRIBUTING.md)
+
+## 版权信息
+
+ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
+
+本项目包含的第三方源码和二进制文件之版权信息另行标注。
+
+版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn)
+
+All rights reserved。
+
+ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
+
+更多细节参阅 [LICENSE.txt](LICENSE.txt)

+ 65 - 0
thinkphp/base.php

@@ -0,0 +1,65 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+define('THINK_VERSION', '5.0.24');
+define('THINK_START_TIME', microtime(true));
+define('THINK_START_MEM', memory_get_usage());
+define('EXT', '.php');
+define('DS', DIRECTORY_SEPARATOR);
+defined('THINK_PATH') or define('THINK_PATH', __DIR__ . DS);
+define('LIB_PATH', THINK_PATH . 'library' . DS);
+define('CORE_PATH', LIB_PATH . 'think' . DS);
+define('TRAIT_PATH', LIB_PATH . 'traits' . DS);
+defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']) . DS);
+defined('ROOT_PATH') or define('ROOT_PATH', dirname(realpath(APP_PATH)) . DS);
+defined('EXTEND_PATH') or define('EXTEND_PATH', ROOT_PATH . 'extend' . DS);
+defined('VENDOR_PATH') or define('VENDOR_PATH', ROOT_PATH . 'vendor' . DS);
+defined('RUNTIME_PATH') or define('RUNTIME_PATH', ROOT_PATH . 'runtime' . DS);
+defined('LOG_PATH') or define('LOG_PATH', RUNTIME_PATH . 'log' . DS);
+defined('CACHE_PATH') or define('CACHE_PATH', RUNTIME_PATH . 'cache' . DS);
+defined('TEMP_PATH') or define('TEMP_PATH', RUNTIME_PATH . 'temp' . DS);
+defined('CONF_PATH') or define('CONF_PATH', APP_PATH); // 配置文件目录
+defined('CONF_EXT') or define('CONF_EXT', EXT); // 配置文件后缀
+defined('ENV_PREFIX') or define('ENV_PREFIX', 'PHP_'); // 环境变量的配置前缀
+
+// 环境常量
+define('IS_CLI', PHP_SAPI == 'cli' ? true : false);
+define('IS_WIN', strpos(PHP_OS, 'WIN') !== false);
+
+// 载入Loader类
+require CORE_PATH . 'Loader.php';
+
+// 加载环境变量配置文件
+if (is_file(ROOT_PATH . '.env')) {
+    $env = parse_ini_file(ROOT_PATH . '.env', true);
+
+    foreach ($env as $key => $val) {
+        $name = ENV_PREFIX . strtoupper($key);
+
+        if (is_array($val)) {
+            foreach ($val as $k => $v) {
+                $item = $name . '_' . strtoupper($k);
+                putenv("$item=$v");
+            }
+        } else {
+            putenv("$name=$val");
+        }
+    }
+}
+
+// 注册自动加载
+\think\Loader::register();
+
+// 注册错误和异常处理机制
+\think\Error::register();
+
+// 加载惯例配置文件
+\think\Config::set(include THINK_PATH . 'convention' . EXT);

+ 12 - 0
thinkphp/codecov.yml

@@ -0,0 +1,12 @@
+comment:
+  layout: header, changes, diff
+coverage:
+  ignore:
+  - base.php
+  - helper.php
+  - convention.php
+  - lang/zh-cn.php
+  - start.php
+  - console.php
+  status:
+    patch: false

+ 35 - 0
thinkphp/composer.json

@@ -0,0 +1,35 @@
+{
+    "name": "topthink/framework",
+    "description": "the new thinkphp framework",
+    "type": "think-framework",
+    "keywords": [
+        "framework",
+        "thinkphp",
+        "ORM"
+    ],
+    "homepage": "http://thinkphp.cn/",
+    "license": "Apache-2.0",
+    "authors": [
+        {
+            "name": "liu21st",
+            "email": "liu21st@gmail.com"
+        }
+    ],
+    "require": {
+        "php": ">=5.4.0",
+        "topthink/think-installer": "~1.0"
+    },
+    "require-dev": {
+        "phpunit/phpunit": "4.8.*",
+        "johnkary/phpunit-speedtrap": "^1.0",
+        "mikey179/vfsStream": "~1.6",
+        "phploc/phploc": "2.*",
+        "sebastian/phpcpd": "2.*",
+        "phpdocumentor/reflection-docblock": "^2.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "think\\": "library/think"
+        }
+    }
+}

+ 20 - 0
thinkphp/console.php

@@ -0,0 +1,20 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006-2017 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: yunwuxin <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+// ThinkPHP 引导文件
+// 加载基础文件
+require __DIR__ . '/base.php';
+
+// 执行应用
+App::initCommon();
+Console::init();

+ 298 - 0
thinkphp/convention.php

@@ -0,0 +1,298 @@
+<?php
+
+return [
+    // +----------------------------------------------------------------------
+    // | 应用设置
+    // +----------------------------------------------------------------------
+    // 默认Host地址
+    'app_host'               => '',
+    // 应用调试模式
+    'app_debug'              => false,
+    // 应用Trace
+    'app_trace'              => false,
+    // 应用模式状态
+    'app_status'             => '',
+    // 是否支持多模块
+    'app_multi_module'       => true,
+    // 入口自动绑定模块
+    'auto_bind_module'       => false,
+    // 注册的根命名空间
+    'root_namespace'         => [],
+    // 扩展函数文件
+    'extra_file_list'        => [THINK_PATH . 'helper' . EXT],
+    // 默认输出类型
+    'default_return_type'    => 'html',
+    // 默认AJAX 数据返回格式,可选json xml ...
+    'default_ajax_return'    => 'json',
+    // 默认JSONP格式返回的处理方法
+    'default_jsonp_handler'  => 'jsonpReturn',
+    // 默认JSONP处理方法
+    'var_jsonp_handler'      => 'callback',
+    // 默认时区
+    'default_timezone'       => 'PRC',
+    // 是否开启多语言
+    'lang_switch_on'         => false,
+    // 默认全局过滤方法 用逗号分隔多个
+    'default_filter'         => '',
+    // 默认语言
+    'default_lang'           => 'zh-cn',
+    // 应用类库后缀
+    'class_suffix'           => false,
+    // 控制器类后缀
+    'controller_suffix'      => false,
+
+    // +----------------------------------------------------------------------
+    // | 模块设置
+    // +----------------------------------------------------------------------
+
+    // 默认模块名
+    'default_module'         => 'index',
+    // 禁止访问模块
+    'deny_module_list'       => ['common'],
+    // 默认控制器名
+    'default_controller'     => 'Index',
+    // 默认操作名
+    'default_action'         => 'index',
+    // 默认验证器
+    'default_validate'       => '',
+    // 默认的空控制器名
+    'empty_controller'       => 'Error',
+    // 操作方法前缀
+    'use_action_prefix'      => false,
+    // 操作方法后缀
+    'action_suffix'          => '',
+    // 自动搜索控制器
+    'controller_auto_search' => false,
+
+    // +----------------------------------------------------------------------
+    // | URL设置
+    // +----------------------------------------------------------------------
+
+    // PATHINFO变量名 用于兼容模式
+    'var_pathinfo'           => 's',
+    // 兼容PATH_INFO获取
+    'pathinfo_fetch'         => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
+    // pathinfo分隔符
+    'pathinfo_depr'          => '/',
+    // HTTPS代理标识
+    'https_agent_name'       => '',
+    // URL伪静态后缀
+    'url_html_suffix'        => 'html',
+    // URL普通方式参数 用于自动生成
+    'url_common_param'       => false,
+    // URL参数方式 0 按名称成对解析 1 按顺序解析
+    'url_param_type'         => 0,
+    // 是否开启路由
+    'url_route_on'           => true,
+    // 路由配置文件(支持配置多个)
+    'route_config_file'      => ['route'],
+    // 路由使用完整匹配
+    'route_complete_match'   => false,
+    // 是否强制使用路由
+    'url_route_must'         => false,
+    // 域名部署
+    'url_domain_deploy'      => false,
+    // 域名根,如thinkphp.cn
+    'url_domain_root'        => '',
+    // 是否自动转换URL中的控制器和操作名
+    'url_convert'            => true,
+    // 默认的访问控制器层
+    'url_controller_layer'   => 'controller',
+    // 表单请求类型伪装变量
+    'var_method'             => '_method',
+    // 表单ajax伪装变量
+    'var_ajax'               => '_ajax',
+    // 表单pjax伪装变量
+    'var_pjax'               => '_pjax',
+    // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则
+    'request_cache'          => false,
+    // 请求缓存有效期
+    'request_cache_expire'   => null,
+    // 全局请求缓存排除规则
+    'request_cache_except'   => [],
+
+    // +----------------------------------------------------------------------
+    // | 模板设置
+    // +----------------------------------------------------------------------
+
+    'template'               => [
+        // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写
+        'auto_rule'    => 1,
+        // 模板引擎类型 支持 php think 支持扩展
+        'type'         => 'Think',
+        // 视图基础目录,配置目录为所有模块的视图起始目录
+        'view_base'    => '',
+        // 当前模板的视图目录 留空为自动获取
+        'view_path'    => '',
+        // 模板后缀
+        'view_suffix'  => 'html',
+        // 模板文件名分隔符
+        'view_depr'    => DS,
+        // 模板引擎普通标签开始标记
+        'tpl_begin'    => '{',
+        // 模板引擎普通标签结束标记
+        'tpl_end'      => '}',
+        // 标签库标签开始标记
+        'taglib_begin' => '{',
+        // 标签库标签结束标记
+        'taglib_end'   => '}',
+    ],
+
+    // 视图输出字符串内容替换
+    'view_replace_str'       => [],
+    // 默认跳转页面对应的模板文件
+    'dispatch_success_tmpl'  => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl',
+    'dispatch_error_tmpl'    => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl',
+
+    // +----------------------------------------------------------------------
+    // | 异常及错误设置
+    // +----------------------------------------------------------------------
+
+    // 异常页面的模板文件
+    'exception_tmpl'         => THINK_PATH . 'tpl' . DS . 'think_exception.tpl',
+
+    // 错误显示信息,非调试模式有效
+    'error_message'          => '页面错误!请稍后再试~',
+    // 显示错误信息
+    'show_error_msg'         => false,
+    // 异常处理handle类 留空使用 \think\exception\Handle
+    'exception_handle'       => '',
+    // 是否记录trace信息到日志
+    'record_trace'           => false,
+
+    // +----------------------------------------------------------------------
+    // | 日志设置
+    // +----------------------------------------------------------------------
+
+    'log'                    => [
+        // 日志记录方式,内置 file socket 支持扩展
+        'type'  => 'File',
+        // 日志保存目录
+        'path'  => LOG_PATH,
+        // 日志记录级别
+        'level' => [],
+    ],
+
+    // +----------------------------------------------------------------------
+    // | Trace设置 开启 app_trace 后 有效
+    // +----------------------------------------------------------------------
+    'trace'                  => [
+        // 内置Html Console 支持扩展
+        'type' => 'Html',
+    ],
+
+    // +----------------------------------------------------------------------
+    // | 缓存设置
+    // +----------------------------------------------------------------------
+
+    'cache'                  => [
+        // 驱动方式
+        'type'   => 'File',
+        // 缓存保存目录
+        'path'   => CACHE_PATH,
+        // 缓存前缀
+        'prefix' => '',
+        // 缓存有效期 0表示永久缓存
+        'expire' => 0,
+    ],
+
+    // +----------------------------------------------------------------------
+    // | 会话设置
+    // +----------------------------------------------------------------------
+
+    'session'                => [
+        'id'             => '',
+        // SESSION_ID的提交变量,解决flash上传跨域
+        'var_session_id' => '',
+        // SESSION 前缀
+        'prefix'         => 'think',
+        // 驱动方式 支持redis memcache memcached
+        'type'           => '',
+        // 是否自动开启 SESSION
+        'auto_start'     => true,
+        'httponly'       => true,
+        'secure'         => false,
+    ],
+
+    // +----------------------------------------------------------------------
+    // | Cookie设置
+    // +----------------------------------------------------------------------
+    'cookie'                 => [
+        // cookie 名称前缀
+        'prefix'    => '',
+        // cookie 保存时间
+        'expire'    => 0,
+        // cookie 保存路径
+        'path'      => '/',
+        // cookie 有效域名
+        'domain'    => '',
+        //  cookie 启用安全传输
+        'secure'    => false,
+        // httponly设置
+        'httponly'  => '',
+        // 是否使用 setcookie
+        'setcookie' => true,
+    ],
+
+    // +----------------------------------------------------------------------
+    // | 数据库设置
+    // +----------------------------------------------------------------------
+
+    'database'               => [
+        // 数据库类型
+        'type'            => 'mysql',
+        // 数据库连接DSN配置
+        'dsn'             => '',
+        // 服务器地址
+        'hostname'        => '127.0.0.1',
+        // 数据库名
+        'database'        => '',
+        // 数据库用户名
+        'username'        => 'root',
+        // 数据库密码
+        'password'        => '',
+        // 数据库连接端口
+        'hostport'        => '',
+        // 数据库连接参数
+        'params'          => [],
+        // 数据库编码默认采用utf8
+        'charset'         => 'utf8',
+        // 数据库表前缀
+        'prefix'          => '',
+        // 数据库调试模式
+        'debug'           => false,
+        // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
+        'deploy'          => 0,
+        // 数据库读写是否分离 主从式有效
+        'rw_separate'     => false,
+        // 读写分离后 主服务器数量
+        'master_num'      => 1,
+        // 指定从服务器序号
+        'slave_no'        => '',
+        // 是否严格检查字段是否存在
+        'fields_strict'   => true,
+        // 数据集返回类型
+        'resultset_type'  => 'array',
+        // 自动写入时间戳字段
+        'auto_timestamp'  => false,
+        // 时间字段取出后的默认时间格式
+        'datetime_format' => 'Y-m-d H:i:s',
+        // 是否需要进行SQL性能分析
+        'sql_explain'     => false,
+    ],
+
+    //分页配置
+    'paginate'               => [
+        'type'      => 'bootstrap',
+        'var_page'  => 'page',
+        'list_rows' => 15,
+    ],
+
+    //控制台配置
+    'console'                => [
+        'name'    => 'Think Console',
+        'version' => '0.1',
+        'user'    => null,
+    ],
+
+];

+ 589 - 0
thinkphp/helper.php

@@ -0,0 +1,589 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+//------------------------
+// ThinkPHP 助手函数
+//-------------------------
+
+use think\Cache;
+use think\Config;
+use think\Cookie;
+use think\Db;
+use think\Debug;
+use think\exception\HttpException;
+use think\exception\HttpResponseException;
+use think\Lang;
+use think\Loader;
+use think\Log;
+use think\Model;
+use think\Request;
+use think\Response;
+use think\Session;
+use think\Url;
+use think\View;
+
+if (!function_exists('load_trait')) {
+    /**
+     * 快速导入Traits PHP5.5以上无需调用
+     * @param string    $class trait库
+     * @param string    $ext 类库后缀
+     * @return boolean
+     */
+    function load_trait($class, $ext = EXT)
+    {
+        return Loader::import($class, TRAIT_PATH, $ext);
+    }
+}
+
+if (!function_exists('exception')) {
+    /**
+     * 抛出异常处理
+     *
+     * @param string    $msg  异常消息
+     * @param integer   $code 异常代码 默认为0
+     * @param string    $exception 异常类
+     *
+     * @throws Exception
+     */
+    function exception($msg, $code = 0, $exception = '')
+    {
+        $e = $exception ?: '\think\Exception';
+        throw new $e($msg, $code);
+    }
+}
+
+if (!function_exists('debug')) {
+    /**
+     * 记录时间(微秒)和内存使用情况
+     * @param string            $start 开始标签
+     * @param string            $end 结束标签
+     * @param integer|string    $dec 小数位 如果是m 表示统计内存占用
+     * @return mixed
+     */
+    function debug($start, $end = '', $dec = 6)
+    {
+        if ('' == $end) {
+            Debug::remark($start);
+        } else {
+            return 'm' == $dec ? Debug::getRangeMem($start, $end) : Debug::getRangeTime($start, $end, $dec);
+        }
+    }
+}
+
+if (!function_exists('lang')) {
+    /**
+     * 获取语言变量值
+     * @param string    $name 语言变量名
+     * @param array     $vars 动态变量值
+     * @param string    $lang 语言
+     * @return mixed
+     */
+    function lang($name, $vars = [], $lang = '')
+    {
+        return Lang::get($name, $vars, $lang);
+    }
+}
+
+if (!function_exists('config')) {
+    /**
+     * 获取和设置配置参数
+     * @param string|array  $name 参数名
+     * @param mixed         $value 参数值
+     * @param string        $range 作用域
+     * @return mixed
+     */
+    function config($name = '', $value = null, $range = '')
+    {
+        if (is_null($value) && is_string($name)) {
+            return 0 === strpos($name, '?') ? Config::has(substr($name, 1), $range) : Config::get($name, $range);
+        } else {
+            return Config::set($name, $value, $range);
+        }
+    }
+}
+
+if (!function_exists('input')) {
+    /**
+     * 获取输入数据 支持默认值和过滤
+     * @param string    $key 获取的变量名
+     * @param mixed     $default 默认值
+     * @param string    $filter 过滤方法
+     * @return mixed
+     */
+    function input($key = '', $default = null, $filter = '')
+    {
+        if (0 === strpos($key, '?')) {
+            $key = substr($key, 1);
+            $has = true;
+        }
+        if ($pos = strpos($key, '.')) {
+            // 指定参数来源
+            list($method, $key) = explode('.', $key, 2);
+            if (!in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) {
+                $key    = $method . '.' . $key;
+                $method = 'param';
+            }
+        } else {
+            // 默认为自动判断
+            $method = 'param';
+        }
+        if (isset($has)) {
+            return request()->has($key, $method, $default);
+        } else {
+            return request()->$method($key, $default, $filter);
+        }
+    }
+}
+
+if (!function_exists('widget')) {
+    /**
+     * 渲染输出Widget
+     * @param string    $name Widget名称
+     * @param array     $data 传入的参数
+     * @return mixed
+     */
+    function widget($name, $data = [])
+    {
+        return Loader::action($name, $data, 'widget');
+    }
+}
+
+if (!function_exists('model')) {
+    /**
+     * 实例化Model
+     * @param string    $name Model名称
+     * @param string    $layer 业务层名称
+     * @param bool      $appendSuffix 是否添加类名后缀
+     * @return \think\Model
+     */
+    function model($name = '', $layer = 'model', $appendSuffix = false)
+    {
+        return Loader::model($name, $layer, $appendSuffix);
+    }
+}
+
+if (!function_exists('validate')) {
+    /**
+     * 实例化验证器
+     * @param string    $name 验证器名称
+     * @param string    $layer 业务层名称
+     * @param bool      $appendSuffix 是否添加类名后缀
+     * @return \think\Validate
+     */
+    function validate($name = '', $layer = 'validate', $appendSuffix = false)
+    {
+        return Loader::validate($name, $layer, $appendSuffix);
+    }
+}
+
+if (!function_exists('db')) {
+    /**
+     * 实例化数据库类
+     * @param string        $name 操作的数据表名称(不含前缀)
+     * @param array|string  $config 数据库配置参数
+     * @param bool          $force 是否强制重新连接
+     * @return \think\db\Query
+     */
+    function db($name = '', $config = [], $force = false)
+    {
+        return Db::connect($config, $force)->name($name);
+    }
+}
+
+if (!function_exists('controller')) {
+    /**
+     * 实例化控制器 格式:[模块/]控制器
+     * @param string    $name 资源地址
+     * @param string    $layer 控制层名称
+     * @param bool      $appendSuffix 是否添加类名后缀
+     * @return \think\Controller
+     */
+    function controller($name, $layer = 'controller', $appendSuffix = false)
+    {
+        return Loader::controller($name, $layer, $appendSuffix);
+    }
+}
+
+if (!function_exists('action')) {
+    /**
+     * 调用模块的操作方法 参数格式 [模块/控制器/]操作
+     * @param string        $url 调用地址
+     * @param string|array  $vars 调用参数 支持字符串和数组
+     * @param string        $layer 要调用的控制层名称
+     * @param bool          $appendSuffix 是否添加类名后缀
+     * @return mixed
+     */
+    function action($url, $vars = [], $layer = 'controller', $appendSuffix = false)
+    {
+        return Loader::action($url, $vars, $layer, $appendSuffix);
+    }
+}
+
+if (!function_exists('import')) {
+    /**
+     * 导入所需的类库 同java的Import 本函数有缓存功能
+     * @param string    $class 类库命名空间字符串
+     * @param string    $baseUrl 起始路径
+     * @param string    $ext 导入的文件扩展名
+     * @return boolean
+     */
+    function import($class, $baseUrl = '', $ext = EXT)
+    {
+        return Loader::import($class, $baseUrl, $ext);
+    }
+}
+
+if (!function_exists('vendor')) {
+    /**
+     * 快速导入第三方框架类库 所有第三方框架的类库文件统一放到 系统的Vendor目录下面
+     * @param string    $class 类库
+     * @param string    $ext 类库后缀
+     * @return boolean
+     */
+    function vendor($class, $ext = EXT)
+    {
+        return Loader::import($class, VENDOR_PATH, $ext);
+    }
+}
+
+if (!function_exists('dump')) {
+    /**
+     * 浏览器友好的变量输出
+     * @param mixed     $var 变量
+     * @param boolean   $echo 是否输出 默认为true 如果为false 则返回输出字符串
+     * @param string    $label 标签 默认为空
+     * @return void|string
+     */
+    function dump($var, $echo = true, $label = null)
+    {
+        return Debug::dump($var, $echo, $label);
+    }
+}
+
+if (!function_exists('url')) {
+    /**
+     * Url生成
+     * @param string        $url 路由地址
+     * @param string|array  $vars 变量
+     * @param bool|string   $suffix 生成的URL后缀
+     * @param bool|string   $domain 域名
+     * @return string
+     */
+    function url($url = '', $vars = '', $suffix = true, $domain = false)
+    {
+        return Url::build($url, $vars, $suffix, $domain);
+    }
+}
+
+if (!function_exists('session')) {
+    /**
+     * Session管理
+     * @param string|array  $name session名称,如果为数组表示进行session设置
+     * @param mixed         $value session值
+     * @param string        $prefix 前缀
+     * @return mixed
+     */
+    function session($name, $value = '', $prefix = null)
+    {
+        if (is_array($name)) {
+            // 初始化
+            Session::init($name);
+        } elseif (is_null($name)) {
+            // 清除
+            Session::clear('' === $value ? null : $value);
+        } elseif ('' === $value) {
+            // 判断或获取
+            return 0 === strpos($name, '?') ? Session::has(substr($name, 1), $prefix) : Session::get($name, $prefix);
+        } elseif (is_null($value)) {
+            // 删除
+            return Session::delete($name, $prefix);
+        } else {
+            // 设置
+            return Session::set($name, $value, $prefix);
+        }
+    }
+}
+
+if (!function_exists('cookie')) {
+    /**
+     * Cookie管理
+     * @param string|array  $name cookie名称,如果为数组表示进行cookie设置
+     * @param mixed         $value cookie值
+     * @param mixed         $option 参数
+     * @return mixed
+     */
+    function cookie($name, $value = '', $option = null)
+    {
+        if (is_array($name)) {
+            // 初始化
+            Cookie::init($name);
+        } elseif (is_null($name)) {
+            // 清除
+            Cookie::clear($value);
+        } elseif ('' === $value) {
+            // 获取
+            return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name, $option);
+        } elseif (is_null($value)) {
+            // 删除
+            return Cookie::delete($name);
+        } else {
+            // 设置
+            return Cookie::set($name, $value, $option);
+        }
+    }
+}
+
+if (!function_exists('cache')) {
+    /**
+     * 缓存管理
+     * @param mixed     $name 缓存名称,如果为数组表示进行缓存设置
+     * @param mixed     $value 缓存值
+     * @param mixed     $options 缓存参数
+     * @param string    $tag 缓存标签
+     * @return mixed
+     */
+    function cache($name, $value = '', $options = null, $tag = null)
+    {
+        if (is_array($options)) {
+            // 缓存操作的同时初始化
+            $cache = Cache::connect($options);
+        } elseif (is_array($name)) {
+            // 缓存初始化
+            return Cache::connect($name);
+        } else {
+            $cache = Cache::init();
+        }
+
+        if (is_null($name)) {
+            return $cache->clear($value);
+        } elseif ('' === $value) {
+            // 获取缓存
+            return 0 === strpos($name, '?') ? $cache->has(substr($name, 1)) : $cache->get($name);
+        } elseif (is_null($value)) {
+            // 删除缓存
+            return $cache->rm($name);
+        } elseif (0 === strpos($name, '?') && '' !== $value) {
+            $expire = is_numeric($options) ? $options : null;
+            return $cache->remember(substr($name, 1), $value, $expire);
+        } else {
+            // 缓存数据
+            if (is_array($options)) {
+                $expire = isset($options['expire']) ? $options['expire'] : null; //修复查询缓存无法设置过期时间
+            } else {
+                $expire = is_numeric($options) ? $options : null; //默认快捷缓存设置过期时间
+            }
+            if (is_null($tag)) {
+                return $cache->set($name, $value, $expire);
+            } else {
+                return $cache->tag($tag)->set($name, $value, $expire);
+            }
+        }
+    }
+}
+
+if (!function_exists('trace')) {
+    /**
+     * 记录日志信息
+     * @param mixed     $log log信息 支持字符串和数组
+     * @param string    $level 日志级别
+     * @return void|array
+     */
+    function trace($log = '[think]', $level = 'log')
+    {
+        if ('[think]' === $log) {
+            return Log::getLog();
+        } else {
+            Log::record($log, $level);
+        }
+    }
+}
+
+if (!function_exists('request')) {
+    /**
+     * 获取当前Request对象实例
+     * @return Request
+     */
+    function request()
+    {
+        return Request::instance();
+    }
+}
+
+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')
+    {
+        return Response::create($data, $type, $code, $header);
+    }
+}
+
+if (!function_exists('view')) {
+    /**
+     * 渲染模板输出
+     * @param string    $template 模板文件
+     * @param array     $vars 模板变量
+     * @param array     $replace 模板替换
+     * @param integer   $code 状态码
+     * @return \think\response\View
+     */
+    function view($template = '', $vars = [], $replace = [], $code = 200)
+    {
+        return Response::create($template, 'view', $code)->replace($replace)->assign($vars);
+    }
+}
+
+if (!function_exists('json')) {
+    /**
+     * 获取\think\response\Json对象实例
+     * @param mixed   $data 返回的数据
+     * @param integer $code 状态码
+     * @param array   $header 头部
+     * @param array   $options 参数
+     * @return \think\response\Json
+     */
+    function json($data = [], $code = 200, $header = [], $options = [])
+    {
+        return Response::create($data, 'json', $code, $header, $options);
+    }
+}
+
+if (!function_exists('jsonp')) {
+    /**
+     * 获取\think\response\Jsonp对象实例
+     * @param mixed   $data    返回的数据
+     * @param integer $code    状态码
+     * @param array   $header 头部
+     * @param array   $options 参数
+     * @return \think\response\Jsonp
+     */
+    function jsonp($data = [], $code = 200, $header = [], $options = [])
+    {
+        return Response::create($data, 'jsonp', $code, $header, $options);
+    }
+}
+
+if (!function_exists('xml')) {
+    /**
+     * 获取\think\response\Xml对象实例
+     * @param mixed   $data    返回的数据
+     * @param integer $code    状态码
+     * @param array   $header  头部
+     * @param array   $options 参数
+     * @return \think\response\Xml
+     */
+    function xml($data = [], $code = 200, $header = [], $options = [])
+    {
+        return Response::create($data, 'xml', $code, $header, $options);
+    }
+}
+
+if (!function_exists('redirect')) {
+    /**
+     * 获取\think\response\Redirect对象实例
+     * @param mixed         $url 重定向地址 支持Url::build方法的地址
+     * @param array|integer $params 额外参数
+     * @param integer       $code 状态码
+     * @param array         $with 隐式传参
+     * @return \think\response\Redirect
+     */
+    function redirect($url = [], $params = [], $code = 302, $with = [])
+    {
+        if (is_integer($params)) {
+            $code   = $params;
+            $params = [];
+        }
+        return Response::create($url, 'redirect', $code)->params($params)->with($with);
+    }
+}
+
+if (!function_exists('abort')) {
+    /**
+     * 抛出HTTP异常
+     * @param integer|Response      $code 状态码 或者 Response对象实例
+     * @param string                $message 错误信息
+     * @param array                 $header 参数
+     */
+    function abort($code, $message = null, $header = [])
+    {
+        if ($code instanceof Response) {
+            throw new HttpResponseException($code);
+        } else {
+            throw new HttpException($code, $message, null, $header);
+        }
+    }
+}
+
+if (!function_exists('halt')) {
+    /**
+     * 调试变量并且中断输出
+     * @param mixed      $var 调试变量或者信息
+     */
+    function halt($var)
+    {
+        dump($var);
+        throw new HttpResponseException(new Response);
+    }
+}
+
+if (!function_exists('token')) {
+    /**
+     * 生成表单令牌
+     * @param string $name 令牌名称
+     * @param mixed  $type 令牌生成方法
+     * @return string
+     */
+    function token($name = '__token__', $type = 'md5')
+    {
+        $token = Request::instance()->token($name, $type);
+        return '<input type="hidden" name="' . $name . '" value="' . $token . '" />';
+    }
+}
+
+if (!function_exists('load_relation')) {
+    /**
+     * 延迟预载入关联查询
+     * @param mixed $resultSet 数据集
+     * @param mixed $relation 关联
+     * @return array
+     */
+    function load_relation($resultSet, $relation)
+    {
+        $item = current($resultSet);
+        if ($item instanceof Model) {
+            $item->eagerlyResultSet($resultSet, $relation);
+        }
+        return $resultSet;
+    }
+}
+
+if (!function_exists('collection')) {
+    /**
+     * 数组转换为数据集对象
+     * @param array $resultSet 数据集数组
+     * @return \think\model\Collection|\think\Collection
+     */
+    function collection($resultSet)
+    {
+        $item = current($resultSet);
+        if ($item instanceof Model) {
+            return \think\model\Collection::make($resultSet);
+        } else {
+            return \think\Collection::make($resultSet);
+        }
+    }
+}

+ 136 - 0
thinkphp/lang/zh-cn.php

@@ -0,0 +1,136 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 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'                                         => '方法不存在',
+    'module not exists'                                         => '模块不存在',
+    'controller not exists'                                     => '控制器不存在',
+    'class not exists'                                          => '类不存在',
+    'property not exists'                                       => '类的属性不存在',
+    'template not exists'                                       => '模板文件不存在',
+    'illegal controller name'                                   => '非法的控制器名称',
+    'illegal action name'                                       => '非法的操作名称',
+    'url suffix deny'                                           => '禁止的URL后缀访问',
+    'Route Not Found'                                           => '当前访问路由未定义',
+    'Undefined db type'                                         => '未定义数据库类型',
+    'variable type error'                                       => '变量类型错误',
+    'PSR-4 error'                                               => 'PSR-4 规范错误',
+    'not support total'                                         => '简洁模式下不能获取数据总数',
+    'not support last'                                          => '简洁模式下不能获取最后一页',
+    'error session handler'                                     => '错误的SESSION处理器类',
+    'not allow php tag'                                         => '模板不允许使用PHP语法',
+    'not support'                                               => '不支持',
+    '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'                                       => '查询表达式错误',
+    'not support data'                                          => '不支持的数据表达式',
+    '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方法',
+    '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} 创建失败!',
+
+    // Validate Error Message
+    ':attribute require'                                        => ':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'                                  => '规则错误',
+];

BIN
thinkphp/library/.DS_Store


+ 677 - 0
thinkphp/library/think/App.php

@@ -0,0 +1,677 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\exception\ClassNotFoundException;
+use think\exception\HttpException;
+use think\exception\HttpResponseException;
+use think\exception\RouteNotFoundException;
+
+/**
+ * App 应用管理
+ * @author liu21st <liu21st@gmail.com>
+ */
+class App
+{
+    /**
+     * @var bool 是否初始化过
+     */
+    protected static $init = false;
+
+    /**
+     * @var string 当前模块路径
+     */
+    public static $modulePath;
+
+    /**
+     * @var bool 应用调试模式
+     */
+    public static $debug = true;
+
+    /**
+     * @var string 应用类库命名空间
+     */
+    public static $namespace = 'app';
+
+    /**
+     * @var bool 应用类库后缀
+     */
+    public static $suffix = false;
+
+    /**
+     * @var bool 应用路由检测
+     */
+    protected static $routeCheck;
+
+    /**
+     * @var bool 严格路由检测
+     */
+    protected static $routeMust;
+
+    /**
+     * @var array 请求调度分发
+     */
+    protected static $dispatch;
+
+    /**
+     * @var array 额外加载文件
+     */
+    protected static $file = [];
+
+    /**
+     * 执行应用程序
+     * @access public
+     * @param  Request $request 请求对象
+     * @return Response
+     * @throws Exception
+     */
+    public static function run(Request $request = null)
+    {
+        $request = is_null($request) ? Request::instance() : $request;
+
+        try {
+            $config = self::initCommon();
+
+            // 模块/控制器绑定
+            if (defined('BIND_MODULE')) {
+                BIND_MODULE && Route::bind(BIND_MODULE);
+            } elseif ($config['auto_bind_module']) {
+                // 入口自动绑定
+                $name = pathinfo($request->baseFile(), PATHINFO_FILENAME);
+                if ($name && 'index' != $name && is_dir(APP_PATH . $name)) {
+                    Route::bind($name);
+                }
+            }
+
+            $request->filter($config['default_filter']);
+
+            // 默认语言
+            Lang::range($config['default_lang']);
+            // 开启多语言机制 检测当前语言
+            $config['lang_switch_on'] && Lang::detect();
+            $request->langset(Lang::range());
+
+            // 加载系统语言包
+            Lang::load([
+                THINK_PATH . 'lang' . DS . $request->langset() . EXT,
+                APP_PATH . 'lang' . DS . $request->langset() . EXT,
+            ]);
+
+            // 监听 app_dispatch
+            Hook::listen('app_dispatch', self::$dispatch);
+            // 获取应用调度信息
+            $dispatch = self::$dispatch;
+
+            // 未设置调度信息则进行 URL 路由检测
+            if (empty($dispatch)) {
+                $dispatch = self::routeCheck($request, $config);
+            }
+
+            // 记录当前调度信息
+            $request->dispatch($dispatch);
+
+            // 记录路由和请求信息
+            if (self::$debug) {
+                Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info');
+                Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info');
+                Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info');
+            }
+
+            // 监听 app_begin
+            Hook::listen('app_begin', $dispatch);
+
+            // 请求缓存检查
+            $request->cache(
+                $config['request_cache'],
+                $config['request_cache_expire'],
+                $config['request_cache_except']
+            );
+
+            $data = self::exec($dispatch, $config);
+        } catch (HttpResponseException $exception) {
+            $data = $exception->getResponse();
+        }
+
+        // 清空类的实例化
+        Loader::clearInstance();
+
+        // 输出数据到客户端
+        if ($data instanceof Response) {
+            $response = $data;
+        } elseif (!is_null($data)) {
+            // 默认自动识别响应输出类型
+            $type = $request->isAjax() ?
+            Config::get('default_ajax_return') :
+            Config::get('default_return_type');
+
+            $response = Response::create($data, $type);
+        } else {
+            $response = Response::create();
+        }
+
+        // 监听 app_end
+        Hook::listen('app_end', $response);
+
+        return $response;
+    }
+
+    /**
+     * 初始化应用,并返回配置信息
+     * @access public
+     * @return array
+     */
+    public static function initCommon()
+    {
+        if (empty(self::$init)) {
+            if (defined('APP_NAMESPACE')) {
+                self::$namespace = APP_NAMESPACE;
+            }
+
+            Loader::addNamespace(self::$namespace, APP_PATH);
+
+            // 初始化应用
+            $config       = self::init();
+            self::$suffix = $config['class_suffix'];
+
+            // 应用调试模式
+            self::$debug = Env::get('app_debug', Config::get('app_debug'));
+
+            if (!self::$debug) {
+                ini_set('display_errors', 'Off');
+            } elseif (!IS_CLI) {
+                // 重新申请一块比较大的 buffer
+                if (ob_get_level() > 0) {
+                    $output = ob_get_clean();
+                }
+
+                ob_start();
+
+                if (!empty($output)) {
+                    echo $output;
+                }
+
+            }
+
+            if (!empty($config['root_namespace'])) {
+                Loader::addNamespace($config['root_namespace']);
+            }
+
+            // 加载额外文件
+            if (!empty($config['extra_file_list'])) {
+                foreach ($config['extra_file_list'] as $file) {
+                    $file = strpos($file, '.') ? $file : APP_PATH . $file . EXT;
+                    if (is_file($file) && !isset(self::$file[$file])) {
+                        include $file;
+                        self::$file[$file] = true;
+                    }
+                }
+            }
+
+            // 设置系统时区
+            date_default_timezone_set($config['default_timezone']);
+
+            // 监听 app_init
+            Hook::listen('app_init');
+
+            self::$init = true;
+        }
+
+        return Config::get();
+    }
+
+    /**
+     * 初始化应用或模块
+     * @access public
+     * @param string $module 模块名
+     * @return array
+     */
+    private static function init($module = '')
+    {
+        // 定位模块目录
+        $module = $module ? $module . DS : '';
+
+        // 加载初始化文件
+        if (is_file(APP_PATH . $module . 'init' . EXT)) {
+            include APP_PATH . $module . 'init' . EXT;
+        } elseif (is_file(RUNTIME_PATH . $module . 'init' . EXT)) {
+            include RUNTIME_PATH . $module . 'init' . EXT;
+        } else {
+            // 加载模块配置
+            $config = Config::load(CONF_PATH . $module . 'config' . CONF_EXT);
+
+            // 读取数据库配置文件
+            $filename = CONF_PATH . $module . 'database' . CONF_EXT;
+            Config::load($filename, 'database');
+
+            // 读取扩展配置文件
+            if (is_dir(CONF_PATH . $module . 'extra')) {
+                $dir   = CONF_PATH . $module . 'extra';
+                $files = scandir($dir);
+                foreach ($files as $file) {
+                    if ('.' . pathinfo($file, PATHINFO_EXTENSION) === CONF_EXT) {
+                        $filename = $dir . DS . $file;
+                        Config::load($filename, pathinfo($file, PATHINFO_FILENAME));
+                    }
+                }
+            }
+
+            // 加载应用状态配置
+            if ($config['app_status']) {
+                Config::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT);
+            }
+
+            // 加载行为扩展文件
+            if (is_file(CONF_PATH . $module . 'tags' . EXT)) {
+                Hook::import(include CONF_PATH . $module . 'tags' . EXT);
+            }
+
+            // 加载公共文件
+            $path = APP_PATH . $module;
+            if (is_file($path . 'common' . EXT)) {
+                include $path . 'common' . EXT;
+            }
+
+            // 加载当前模块语言包
+            if ($module) {
+                Lang::load($path . 'lang' . DS . Request::instance()->langset() . EXT);
+            }
+        }
+
+        return Config::get();
+    }
+
+    /**
+     * 设置当前请求的调度信息
+     * @access public
+     * @param array|string  $dispatch 调度信息
+     * @param string        $type     调度类型
+     * @return void
+     */
+    public static function dispatch($dispatch, $type = 'module')
+    {
+        self::$dispatch = ['type' => $type, $type => $dispatch];
+    }
+
+    /**
+     * 执行函数或者闭包方法 支持参数调用
+     * @access public
+     * @param string|array|\Closure $function 函数或者闭包
+     * @param array                 $vars     变量
+     * @return mixed
+     */
+    public static function invokeFunction($function, $vars = [])
+    {
+        $reflect = new \ReflectionFunction($function);
+        $args    = self::bindParams($reflect, $vars);
+
+        // 记录执行信息
+        self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info');
+
+        return $reflect->invokeArgs($args);
+    }
+
+    /**
+     * 调用反射执行类的方法 支持参数绑定
+     * @access public
+     * @param string|array $method 方法
+     * @param array        $vars   变量
+     * @return mixed
+     */
+    public static function invokeMethod($method, $vars = [])
+    {
+        if (is_array($method)) {
+            $class   = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]);
+            $reflect = new \ReflectionMethod($class, $method[1]);
+        } else {
+            // 静态方法
+            $reflect = new \ReflectionMethod($method);
+        }
+
+        $args = self::bindParams($reflect, $vars);
+
+        self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info');
+
+        return $reflect->invokeArgs(isset($class) ? $class : null, $args);
+    }
+
+    /**
+     * 调用反射执行类的实例化 支持依赖注入
+     * @access public
+     * @param string $class 类名
+     * @param array  $vars  变量
+     * @return mixed
+     */
+    public static function invokeClass($class, $vars = [])
+    {
+        $reflect     = new \ReflectionClass($class);
+        $constructor = $reflect->getConstructor();
+        $args        = $constructor ? self::bindParams($constructor, $vars) : [];
+
+        return $reflect->newInstanceArgs($args);
+    }
+
+    /**
+     * 绑定参数
+     * @access private
+     * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类
+     * @param array                                 $vars    变量
+     * @return array
+     */
+    private static function bindParams($reflect, $vars = [])
+    {
+        // 自动获取请求变量
+        if (empty($vars)) {
+            $vars = Config::get('url_param_type') ?
+            Request::instance()->route() :
+            Request::instance()->param();
+        }
+
+        $args = [];
+        if ($reflect->getNumberOfParameters() > 0) {
+            // 判断数组类型 数字数组时按顺序绑定参数
+            reset($vars);
+            $type = key($vars) === 0 ? 1 : 0;
+
+            foreach ($reflect->getParameters() as $param) {
+                $args[] = self::getParamValue($param, $vars, $type);
+            }
+        }
+
+        return $args;
+    }
+
+    /**
+     * 获取参数值
+     * @access private
+     * @param \ReflectionParameter  $param 参数
+     * @param array                 $vars  变量
+     * @param string                $type  类别
+     * @return array
+     */
+    private static function getParamValue($param, &$vars, $type)
+    {
+        $name  = $param->getName();
+        $class = $param->getClass();
+
+        if ($class) {
+            $className = $class->getName();
+            $bind      = Request::instance()->$name;
+
+            if ($bind instanceof $className) {
+                $result = $bind;
+            } else {
+                if (method_exists($className, 'invoke')) {
+                    $method = new \ReflectionMethod($className, 'invoke');
+
+                    if ($method->isPublic() && $method->isStatic()) {
+                        return $className::invoke(Request::instance());
+                    }
+                }
+
+                $result = method_exists($className, 'instance') ?
+                $className::instance() :
+                new $className;
+            }
+        } elseif (1 == $type && !empty($vars)) {
+            $result = array_shift($vars);
+        } elseif (0 == $type && isset($vars[$name])) {
+            $result = $vars[$name];
+        } elseif ($param->isDefaultValueAvailable()) {
+            $result = $param->getDefaultValue();
+        } else {
+            throw new \InvalidArgumentException('method param miss:' . $name);
+        }
+
+        return $result;
+    }
+
+    /**
+     * 执行调用分发
+     * @access protected
+     * @param array $dispatch 调用信息
+     * @param array $config   配置信息
+     * @return Response|mixed
+     * @throws \InvalidArgumentException
+     */
+    protected static function exec($dispatch, $config)
+    {
+        switch ($dispatch['type']) {
+            case 'redirect': // 重定向跳转
+                $data = Response::create($dispatch['url'], 'redirect')
+                    ->code($dispatch['status']);
+                break;
+            case 'module': // 模块/控制器/操作
+                $data = self::module(
+                    $dispatch['module'],
+                    $config,
+                    isset($dispatch['convert']) ? $dispatch['convert'] : null
+                );
+                break;
+            case 'controller': // 执行控制器操作
+                $vars = array_merge(Request::instance()->param(), $dispatch['var']);
+                $data = Loader::action(
+                    $dispatch['controller'],
+                    $vars,
+                    $config['url_controller_layer'],
+                    $config['controller_suffix']
+                );
+                break;
+            case 'method': // 回调方法
+                $vars = array_merge(Request::instance()->param(), $dispatch['var']);
+                $data = self::invokeMethod($dispatch['method'], $vars);
+                break;
+            case 'function': // 闭包
+                $data = self::invokeFunction($dispatch['function']);
+                break;
+            case 'response': // Response 实例
+                $data = $dispatch['response'];
+                break;
+            default:
+                throw new \InvalidArgumentException('dispatch type not support');
+        }
+
+        return $data;
+    }
+
+    /**
+     * 执行模块
+     * @access public
+     * @param array $result  模块/控制器/操作
+     * @param array $config  配置参数
+     * @param bool  $convert 是否自动转换控制器和操作名
+     * @return mixed
+     * @throws HttpException
+     */
+    public static function module($result, $config, $convert = null)
+    {
+        if (is_string($result)) {
+            $result = explode('/', $result);
+        }
+
+        $request = Request::instance();
+
+        if ($config['app_multi_module']) {
+            // 多模块部署
+            $module    = strip_tags(strtolower($result[0] ?: $config['default_module']));
+            $bind      = Route::getBind('module');
+            $available = false;
+
+            if ($bind) {
+                // 绑定模块
+                list($bindModule) = explode('/', $bind);
+
+                if (empty($result[0])) {
+                    $module    = $bindModule;
+                    $available = true;
+                } elseif ($module == $bindModule) {
+                    $available = true;
+                }
+            } elseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module)) {
+                $available = true;
+            }
+
+            // 模块初始化
+            if ($module && $available) {
+                // 初始化模块
+                $request->module($module);
+                $config = self::init($module);
+
+                // 模块请求缓存检查
+                $request->cache(
+                    $config['request_cache'],
+                    $config['request_cache_expire'],
+                    $config['request_cache_except']
+                );
+            } else {
+                throw new HttpException(404, 'module not exists:' . $module);
+            }
+        } else {
+            // 单一模块部署
+            $module = '';
+            $request->module($module);
+        }
+
+        // 设置默认过滤机制
+        $request->filter($config['default_filter']);
+
+        // 当前模块路径
+        App::$modulePath = APP_PATH . ($module ? $module . DS : '');
+
+        // 是否自动转换控制器和操作名
+        $convert = is_bool($convert) ? $convert : $config['url_convert'];
+
+        // 获取控制器名
+        $controller = strip_tags($result[1] ?: $config['default_controller']);
+
+        if (!preg_match('/^[A-Za-z](\w|\.)*$/', $controller)) {
+            throw new HttpException(404, 'controller not exists:' . $controller);
+        }
+
+        $controller = $convert ? strtolower($controller) : $controller;
+
+        // 获取操作名
+        $actionName = strip_tags($result[2] ?: $config['default_action']);
+        if (!empty($config['action_convert'])) {
+            $actionName = Loader::parseName($actionName, 1);
+        } else {
+            $actionName = $convert ? strtolower($actionName) : $actionName;
+        }
+
+        // 设置当前请求的控制器、操作
+        $request->controller(Loader::parseName($controller, 1))->action($actionName);
+
+        // 监听module_init
+        Hook::listen('module_init', $request);
+
+        try {
+            $instance = Loader::controller(
+                $controller,
+                $config['url_controller_layer'],
+                $config['controller_suffix'],
+                $config['empty_controller']
+            );
+        } catch (ClassNotFoundException $e) {
+            throw new HttpException(404, 'controller not exists:' . $e->getClass());
+        }
+
+        // 获取当前操作名
+        $action = $actionName . $config['action_suffix'];
+
+        $vars = [];
+        if (is_callable([$instance, $action])) {
+            // 执行操作方法
+            $call = [$instance, $action];
+            // 严格获取当前操作方法名
+            $reflect    = new \ReflectionMethod($instance, $action);
+            $methodName = $reflect->getName();
+            $suffix     = $config['action_suffix'];
+            $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName;
+            $request->action($actionName);
+
+        } elseif (is_callable([$instance, '_empty'])) {
+            // 空操作
+            $call = [$instance, '_empty'];
+            $vars = [$actionName];
+        } else {
+            // 操作不存在
+            throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');
+        }
+
+        Hook::listen('action_begin', $call);
+
+        return self::invokeMethod($call, $vars);
+    }
+
+    /**
+     * URL路由检测(根据PATH_INFO)
+     * @access public
+     * @param  \think\Request $request 请求实例
+     * @param  array          $config  配置信息
+     * @return array
+     * @throws \think\Exception
+     */
+    public static function routeCheck($request, array $config)
+    {
+        $path   = $request->path();
+        $depr   = $config['pathinfo_depr'];
+        $result = false;
+
+        // 路由检测
+        $check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on'];
+        if ($check) {
+            // 开启路由
+            if (is_file(RUNTIME_PATH . 'route.php')) {
+                // 读取路由缓存
+                $rules = include RUNTIME_PATH . 'route.php';
+                is_array($rules) && Route::rules($rules);
+            } else {
+                $files = $config['route_config_file'];
+                foreach ($files as $file) {
+                    if (is_file(CONF_PATH . $file . CONF_EXT)) {
+                        // 导入路由配置
+                        $rules = include CONF_PATH . $file . CONF_EXT;
+                        is_array($rules) && Route::import($rules);
+                    }
+                }
+            }
+
+            // 路由检测(根据路由定义返回不同的URL调度)
+            $result = Route::check($request, $path, $depr, $config['url_domain_deploy']);
+            $must   = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must'];
+
+            if ($must && false === $result) {
+                // 路由无效
+                throw new RouteNotFoundException();
+            }
+        }
+
+        // 路由无效 解析模块/控制器/操作/参数... 支持控制器自动搜索
+        if (false === $result) {
+            $result = Route::parseUrl($path, $depr, $config['controller_auto_search']);
+        }
+
+        return $result;
+    }
+
+    /**
+     * 设置应用的路由检测机制
+     * @access public
+     * @param  bool $route 是否需要检测路由
+     * @param  bool $must  是否强制检测路由
+     * @return void
+     */
+    public static function route($route, $must = false)
+    {
+        self::$routeCheck = $route;
+        self::$routeMust  = $must;
+    }
+}

+ 235 - 0
thinkphp/library/think/Build.php

@@ -0,0 +1,235 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+class Build
+{
+    /**
+     * 根据传入的 build 资料创建目录和文件
+     * @access public
+     * @param  array  $build     build 列表
+     * @param  string $namespace 应用类库命名空间
+     * @param  bool   $suffix    类库后缀
+     * @return void
+     * @throws Exception
+     */
+    public static function run(array $build = [], $namespace = 'app', $suffix = false)
+    {
+        // 锁定
+        $lock = APP_PATH . 'build.lock';
+
+        // 如果锁定文件不可写(不存在)则进行处理,否则表示已经有程序在处理了
+        if (!is_writable($lock)) {
+            if (!touch($lock)) {
+                throw new Exception(
+                    '应用目录[' . APP_PATH . ']不可写,目录无法自动生成!<BR>请手动生成项目目录~',
+                    10006
+                );
+            }
+
+            foreach ($build as $module => $list) {
+                if ('__dir__' == $module) {
+                    // 创建目录列表
+                    self::buildDir($list);
+                } elseif ('__file__' == $module) {
+                    // 创建文件列表
+                    self::buildFile($list);
+                } else {
+                    // 创建模块
+                    self::module($module, $list, $namespace, $suffix);
+                }
+            }
+
+            // 解除锁定
+            unlink($lock);
+        }
+    }
+
+    /**
+     * 创建目录
+     * @access protected
+     * @param  array $list 目录列表
+     * @return void
+     */
+    protected static function buildDir($list)
+    {
+        foreach ($list as $dir) {
+            // 目录不存在则创建目录
+            !is_dir(APP_PATH . $dir) && mkdir(APP_PATH . $dir, 0755, true);
+        }
+    }
+
+    /**
+     * 创建文件
+     * @access protected
+     * @param  array $list 文件列表
+     * @return void
+     */
+    protected static function buildFile($list)
+    {
+        foreach ($list as $file) {
+            // 先创建目录
+            if (!is_dir(APP_PATH . dirname($file))) {
+                mkdir(APP_PATH . dirname($file), 0755, true);
+            }
+
+            // 再创建文件
+            if (!is_file(APP_PATH . $file)) {
+                file_put_contents(
+                    APP_PATH . $file,
+                    'php' == pathinfo($file, PATHINFO_EXTENSION) ? "<?php\n" : ''
+                );
+            }
+        }
+    }
+
+    /**
+     * 创建模块
+     * @access public
+     * @param  string $module    模块名
+     * @param  array  $list      build 列表
+     * @param  string $namespace 应用类库命名空间
+     * @param  bool   $suffix    类库后缀
+     * @return void
+     */
+    public static function module($module = '', $list = [], $namespace = 'app', $suffix = false)
+    {
+        $module = $module ?: '';
+
+        // 创建模块目录
+        !is_dir(APP_PATH . $module) && mkdir(APP_PATH . $module);
+
+        // 如果不是 runtime 目录则需要创建配置文件和公共文件、创建模块的默认页面
+        if (basename(RUNTIME_PATH) != $module) {
+            self::buildCommon($module);
+            self::buildHello($module, $namespace, $suffix);
+        }
+
+        // 未指定文件和目录,则创建默认的模块目录和文件
+        if (empty($list)) {
+            $list = [
+                '__file__' => ['config.php', 'common.php'],
+                '__dir__'  => ['controller', 'model', 'view'],
+            ];
+        }
+
+        // 创建子目录和文件
+        foreach ($list as $path => $file) {
+            $modulePath = APP_PATH . $module . DS;
+
+            if ('__dir__' == $path) {
+                // 生成子目录
+                foreach ($file as $dir) {
+                    self::checkDirBuild($modulePath . $dir);
+                }
+            } elseif ('__file__' == $path) {
+                // 生成(空白)文件
+                foreach ($file as $name) {
+                    if (!is_file($modulePath . $name)) {
+                        file_put_contents(
+                            $modulePath . $name,
+                            'php' == pathinfo($name, PATHINFO_EXTENSION) ? "<?php\n" : ''
+                        );
+                    }
+                }
+            } else {
+                // 生成相关 MVC 文件
+                foreach ($file as $val) {
+                    $val      = trim($val);
+                    $filename = $modulePath . $path . DS . $val . ($suffix ? ucfirst($path) : '') . EXT;
+                    $space    = $namespace . '\\' . ($module ? $module . '\\' : '') . $path;
+                    $class    = $val . ($suffix ? ucfirst($path) : '');
+
+                    switch ($path) {
+                        case 'controller': // 控制器
+                            $content = "<?php\nnamespace {$space};\n\nclass {$class}\n{\n\n}";
+                            break;
+                        case 'model': // 模型
+                            $content = "<?php\nnamespace {$space};\n\nuse think\Model;\n\nclass {$class} extends Model\n{\n\n}";
+                            break;
+                        case 'view': // 视图
+                            $filename = $modulePath . $path . DS . $val . '.html';
+                            self::checkDirBuild(dirname($filename));
+                            $content = '';
+                            break;
+                        default:
+                            // 其他文件
+                            $content = "<?php\nnamespace {$space};\n\nclass {$class}\n{\n\n}";
+                    }
+
+                    if (!is_file($filename)) {
+                        file_put_contents($filename, $content);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 创建模块的欢迎页面
+     * @access protected
+     * @param  string $module    模块名
+     * @param  string $namespace 应用类库命名空间
+     * @param  bool   $suffix    类库后缀
+     * @return void
+     */
+    protected static function buildHello($module, $namespace, $suffix = false)
+    {
+        $filename = APP_PATH . ($module ? $module . DS : '') .
+            'controller' . DS . 'Index' .
+            ($suffix ? 'Controller' : '') . EXT;
+
+        if (!is_file($filename)) {
+            $module = $module ? $module . '\\' : '';
+            $suffix = $suffix ? 'Controller' : '';
+            $content = str_replace(
+                ['{$app}', '{$module}', '{layer}', '{$suffix}'],
+                [$namespace, $module, 'controller', $suffix],
+                file_get_contents(THINK_PATH . 'tpl' . DS . 'default_index.tpl')
+            );
+
+            self::checkDirBuild(dirname($filename));
+            file_put_contents($filename, $content);
+        }
+    }
+
+    /**
+     * 创建模块的公共文件
+     * @access protected
+     * @param  string $module 模块名
+     * @return void
+     */
+    protected static function buildCommon($module)
+    {
+        $config = CONF_PATH . ($module ? $module . DS : '') . 'config.php';
+
+        self::checkDirBuild(dirname($config));
+
+        if (!is_file($config)) {
+            file_put_contents($config, "<?php\n//配置文件\nreturn [\n\n];");
+        }
+
+        $common = APP_PATH . ($module ? $module . DS : '') . 'common.php';
+        if (!is_file($common)) file_put_contents($common, "<?php\n");
+    }
+
+    /**
+     * 创建目录
+     * @access protected
+     * @param  string $dirname 目录名称
+     * @return void
+     */
+    protected static function checkDirBuild($dirname)
+    {
+        !is_dir($dirname) && mkdir($dirname, 0755, true);
+    }
+}

+ 247 - 0
thinkphp/library/think/Cache.php

@@ -0,0 +1,247 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\cache\Driver;
+
+class Cache
+{
+    /**
+     * @var array 缓存的实例
+     */
+    public static $instance = [];
+
+    /**
+     * @var int 缓存读取次数
+     */
+    public static $readTimes = 0;
+
+    /**
+     * @var int 缓存写入次数
+     */
+    public static $writeTimes = 0;
+
+    /**
+     * @var object 操作句柄
+     */
+    public static $handler;
+
+    /**
+     * 连接缓存驱动
+     * @access public
+     * @param  array       $options 配置数组
+     * @param  bool|string $name    缓存连接标识 true 强制重新连接
+     * @return Driver
+     */
+    public static function connect(array $options = [], $name = false)
+    {
+        $type = !empty($options['type']) ? $options['type'] : 'File';
+
+        if (false === $name) {
+            $name = md5(serialize($options));
+        }
+
+        if (true === $name || !isset(self::$instance[$name])) {
+            $class = false === strpos($type, '\\') ?
+            '\\think\\cache\\driver\\' . ucwords($type) :
+            $type;
+
+            // 记录初始化信息
+            App::$debug && Log::record('[ CACHE ] INIT ' . $type, 'info');
+
+            if (true === $name) {
+                return new $class($options);
+            }
+
+            self::$instance[$name] = new $class($options);
+        }
+
+        return self::$instance[$name];
+    }
+
+    /**
+     * 自动初始化缓存
+     * @access public
+     * @param  array $options 配置数组
+     * @return Driver
+     */
+    public static function init(array $options = [])
+    {
+        if (is_null(self::$handler)) {
+            if (empty($options) && 'complex' == Config::get('cache.type')) {
+                $default = Config::get('cache.default');
+                // 获取默认缓存配置,并连接
+                $options = Config::get('cache.' . $default['type']) ?: $default;
+            } elseif (empty($options)) {
+                $options = Config::get('cache');
+            }
+
+            self::$handler = self::connect($options);
+        }
+
+        return self::$handler;
+    }
+
+    /**
+     * 切换缓存类型 需要配置 cache.type 为 complex
+     * @access public
+     * @param  string $name 缓存标识
+     * @return Driver
+     */
+    public static function store($name = '')
+    {
+        if ('' !== $name && 'complex' == Config::get('cache.type')) {
+            return self::connect(Config::get('cache.' . $name), strtolower($name));
+        }
+
+        return self::init();
+    }
+
+    /**
+     * 判断缓存是否存在
+     * @access public
+     * @param  string $name 缓存变量名
+     * @return bool
+     */
+    public static function has($name)
+    {
+        self::$readTimes++;
+
+        return self::init()->has($name);
+    }
+
+    /**
+     * 读取缓存
+     * @access public
+     * @param  string $name    缓存标识
+     * @param  mixed  $default 默认值
+     * @return mixed
+     */
+    public static function get($name, $default = false)
+    {
+        self::$readTimes++;
+
+        return self::init()->get($name, $default);
+    }
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param  string   $name   缓存标识
+     * @param  mixed    $value  存储数据
+     * @param  int|null $expire 有效时间 0为永久
+     * @return boolean
+     */
+    public static function set($name, $value, $expire = null)
+    {
+        self::$writeTimes++;
+
+        return self::init()->set($name, $value, $expire);
+    }
+
+    /**
+     * 自增缓存(针对数值缓存)
+     * @access public
+     * @param  string $name 缓存变量名
+     * @param  int    $step 步长
+     * @return false|int
+     */
+    public static function inc($name, $step = 1)
+    {
+        self::$writeTimes++;
+
+        return self::init()->inc($name, $step);
+    }
+
+    /**
+     * 自减缓存(针对数值缓存)
+     * @access public
+     * @param  string $name 缓存变量名
+     * @param  int    $step 步长
+     * @return false|int
+     */
+    public static function dec($name, $step = 1)
+    {
+        self::$writeTimes++;
+
+        return self::init()->dec($name, $step);
+    }
+
+    /**
+     * 删除缓存
+     * @access public
+     * @param  string $name 缓存标识
+     * @return boolean
+     */
+    public static function  rm($name)
+    {
+        self::$writeTimes++;
+
+        return self::init()->rm($name);
+    }
+
+    /**
+     * 清除缓存
+     * @access public
+     * @param  string $tag 标签名
+     * @return boolean
+     */
+    public static function clear($tag = null)
+    {
+        self::$writeTimes++;
+
+        return self::init()->clear($tag);
+    }
+
+    /**
+     * 读取缓存并删除
+     * @access public
+     * @param  string $name 缓存变量名
+     * @return mixed
+     */
+    public static function pull($name)
+    {
+        self::$readTimes++;
+        self::$writeTimes++;
+
+        return self::init()->pull($name);
+    }
+
+    /**
+     * 如果不存在则写入缓存
+     * @access public
+     * @param  string $name   缓存变量名
+     * @param  mixed  $value  存储数据
+     * @param  int    $expire 有效时间 0为永久
+     * @return mixed
+     */
+    public static function remember($name, $value, $expire = null)
+    {
+        self::$readTimes++;
+
+        return self::init()->remember($name, $value, $expire);
+    }
+
+    /**
+     * 缓存标签
+     * @access public
+     * @param  string       $name    标签名
+     * @param  string|array $keys    缓存标识
+     * @param  bool         $overlay 是否覆盖
+     * @return Driver
+     */
+    public static function tag($name, $keys = null, $overlay = false)
+    {
+        return self::init()->tag($name, $keys, $overlay);
+    }
+
+}

+ 467 - 0
thinkphp/library/think/Collection.php

@@ -0,0 +1,467 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: zhangyajun <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use ArrayAccess;
+use ArrayIterator;
+use Countable;
+use IteratorAggregate;
+use JsonSerializable;
+
+class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable
+{
+    /**
+     * @var array 数据
+     */
+    protected $items = [];
+
+    /**
+     * Collection constructor.
+     * @access public
+     * @param  array $items 数据
+     */
+    public function __construct($items = [])
+    {
+        $this->items = $this->convertToArray($items);
+    }
+
+    /**
+     * 创建 Collection 实例
+     * @access public
+     * @param  array $items 数据
+     * @return static
+     */
+    public static function make($items = [])
+    {
+        return new static($items);
+    }
+
+    /**
+     * 判断数据是否为空
+     * @access public
+     * @return bool
+     */
+    public function isEmpty()
+    {
+        return empty($this->items);
+    }
+
+    /**
+     * 将数据转成数组
+     * @access public
+     * @return array
+     */
+    public function toArray()
+    {
+        return array_map(function ($value) {
+            return ($value instanceof Model || $value instanceof self) ?
+                $value->toArray() :
+                $value;
+        }, $this->items);
+    }
+
+    /**
+     * 获取全部的数据
+     * @access public
+     * @return array
+     */
+    public function all()
+    {
+        return $this->items;
+    }
+
+    /**
+     * 交换数组中的键和值
+     * @access public
+     * @return static
+     */
+    public function flip()
+    {
+        return new static(array_flip($this->items));
+    }
+
+    /**
+     * 返回数组中所有的键名组成的新 Collection 实例
+     * @access public
+     * @return static
+     */
+    public function keys()
+    {
+        return new static(array_keys($this->items));
+    }
+
+    /**
+     * 返回数组中所有的值组成的新 Collection 实例
+     * @access public
+     * @return static
+     */
+    public function values()
+    {
+        return new static(array_values($this->items));
+    }
+
+    /**
+     * 合并数组并返回一个新的 Collection 实例
+     * @access public
+     * @param  mixed $items 新的数据
+     * @return static
+     */
+    public function merge($items)
+    {
+        return new static(array_merge($this->items, $this->convertToArray($items)));
+    }
+
+    /**
+     * 比较数组,返回差集生成的新 Collection 实例
+     * @access public
+     * @param  mixed $items 做比较的数据
+     * @return static
+     */
+    public function diff($items)
+    {
+        return new static(array_diff($this->items, $this->convertToArray($items)));
+    }
+
+    /**
+     * 比较数组,返回交集组成的 Collection 新实例
+     * @access public
+     * @param  mixed $items 比较数据
+     * @return static
+     */
+    public function intersect($items)
+    {
+        return new static(array_intersect($this->items, $this->convertToArray($items)));
+    }
+
+    /**
+     * 返回并删除数据中的的最后一个元素(出栈)
+     * @access public
+     * @return mixed
+     */
+    public function pop()
+    {
+        return array_pop($this->items);
+    }
+
+    /**
+     * 返回并删除数据中首个元素
+     * @access public
+     * @return mixed
+     */
+    public function shift()
+    {
+        return array_shift($this->items);
+    }
+
+    /**
+     * 在数组开头插入一个元素
+     * @access public
+     * @param mixed $value 值
+     * @param mixed $key   键名
+     * @return void
+     */
+    public function unshift($value, $key = null)
+    {
+        if (is_null($key)) {
+            array_unshift($this->items, $value);
+        } else {
+            $this->items = [$key => $value] + $this->items;
+        }
+    }
+
+    /**
+     * 在数组结尾插入一个元素
+     * @access public
+     * @param  mixed $value 值
+     * @param  mixed $key   键名
+     * @return void
+     */
+    public function push($value, $key = null)
+    {
+        if (is_null($key)) {
+            $this->items[] = $value;
+        } else {
+            $this->items[$key] = $value;
+        }
+    }
+
+    /**
+     * 通过使用用户自定义函数,以字符串返回数组
+     * @access public
+     * @param  callable $callback 回调函数
+     * @param  mixed    $initial  初始值
+     * @return mixed
+     */
+    public function reduce(callable $callback, $initial = null)
+    {
+        return array_reduce($this->items, $callback, $initial);
+    }
+
+    /**
+     * 以相反的顺序创建一个新的 Collection 实例
+     * @access public
+     * @return static
+     */
+    public function reverse()
+    {
+        return new static(array_reverse($this->items));
+    }
+
+    /**
+     * 把数据分割为新的数组块
+     * @access public
+     * @param  int  $size         分隔长度
+     * @param  bool $preserveKeys 是否保持原数据索引
+     * @return static
+     */
+    public function chunk($size, $preserveKeys = false)
+    {
+        $chunks = [];
+
+        foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) {
+            $chunks[] = new static($chunk);
+        }
+
+        return new static($chunks);
+    }
+
+    /**
+     * 给数据中的每个元素执行回调
+     * @access public
+     * @param  callable $callback 回调函数
+     * @return $this
+     */
+    public function each(callable $callback)
+    {
+        foreach ($this->items as $key => $item) {
+            $result = $callback($item, $key);
+
+            if (false === $result) {
+                break;
+            }
+
+            if (!is_object($item)) {
+                $this->items[$key] = $result;
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * 用回调函数过滤数据中的元素
+     * @access public
+     * @param callable|null $callback 回调函数
+     * @return static
+     */
+    public function filter(callable $callback = null)
+    {
+        return new static(array_filter($this->items, $callback ?: null));
+    }
+
+    /**
+     * 返回数据中指定的一列
+     * @access public
+     * @param mixed $columnKey 键名
+     * @param null  $indexKey  作为索引值的列
+     * @return array
+     */
+    public function column($columnKey, $indexKey = null)
+    {
+        if (function_exists('array_column')) {
+            return array_column($this->items, $columnKey, $indexKey);
+        }
+
+        $result = [];
+        foreach ($this->items as $row) {
+            $key    = $value = null;
+            $keySet = $valueSet = false;
+
+            if (null !== $indexKey && array_key_exists($indexKey, $row)) {
+                $key    = (string) $row[$indexKey];
+                $keySet = true;
+            }
+
+            if (null === $columnKey) {
+                $valueSet = true;
+                $value    = $row;
+            } elseif (is_array($row) && array_key_exists($columnKey, $row)) {
+                $valueSet = true;
+                $value    = $row[$columnKey];
+            }
+
+            if ($valueSet) {
+                if ($keySet) {
+                    $result[$key] = $value;
+                } else {
+                    $result[] = $value;
+                }
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * 对数据排序,并返回排序后的数据组成的新 Collection 实例
+     * @access public
+     * @param  callable|null $callback 回调函数
+     * @return static
+     */
+    public function sort(callable $callback = null)
+    {
+        $items    = $this->items;
+        $callback = $callback ?: function ($a, $b) {
+            return $a == $b ? 0 : (($a < $b) ? -1 : 1);
+        };
+
+        uasort($items, $callback);
+        return new static($items);
+    }
+
+    /**
+     * 将数据打乱后组成新的 Collection 实例
+     * @access public
+     * @return static
+     */
+    public function shuffle()
+    {
+        $items = $this->items;
+
+        shuffle($items);
+        return new static($items);
+    }
+
+    /**
+     * 截取数据并返回新的 Collection 实例
+     * @access public
+     * @param  int  $offset       起始位置
+     * @param  int  $length       截取长度
+     * @param  bool $preserveKeys 是否保持原先的键名
+     * @return static
+     */
+    public function slice($offset, $length = null, $preserveKeys = false)
+    {
+        return new static(array_slice($this->items, $offset, $length, $preserveKeys));
+    }
+
+    /**
+     * 指定的键是否存在
+     * @access public
+     * @param  mixed $offset 键名
+     * @return bool
+     */
+    public function offsetExists($offset)
+    {
+        return array_key_exists($offset, $this->items);
+    }
+
+    /**
+     * 获取指定键对应的值
+     * @access public
+     * @param  mixed $offset 键名
+     * @return mixed
+     */
+    public function offsetGet($offset)
+    {
+        return $this->items[$offset];
+    }
+
+    /**
+     * 设置键值
+     * @access public
+     * @param  mixed $offset 键名
+     * @param  mixed $value  值
+     * @return void
+     */
+    public function offsetSet($offset, $value)
+    {
+        if (is_null($offset)) {
+            $this->items[] = $value;
+        } else {
+            $this->items[$offset] = $value;
+        }
+    }
+
+    /**
+     * 删除指定键值
+     * @access public
+     * @param  mixed $offset 键名
+     * @return void
+     */
+    public function offsetUnset($offset)
+    {
+        unset($this->items[$offset]);
+    }
+
+    /**
+     * 统计数据的个数
+     * @access public
+     * @return int
+     */
+    public function count()
+    {
+        return count($this->items);
+    }
+
+    /**
+     * 获取数据的迭代器
+     * @access public
+     * @return ArrayIterator
+     */
+    public function getIterator()
+    {
+        return new ArrayIterator($this->items);
+    }
+
+    /**
+     * 将数据反序列化成数组
+     * @access public
+     * @return array
+     */
+    public function jsonSerialize()
+    {
+        return $this->toArray();
+    }
+
+    /**
+     * 转换当前数据集为 JSON 字符串
+     * @access public
+     * @param  integer $options json 参数
+     * @return string
+     */
+    public function toJson($options = JSON_UNESCAPED_UNICODE)
+    {
+        return json_encode($this->toArray(), $options);
+    }
+
+    /**
+     * 将数据转换成字符串
+     * @access public
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->toJson();
+    }
+
+    /**
+     * 将数据转换成数组
+     * @access protected
+     * @param  mixed $items 数据
+     * @return array
+     */
+    protected function convertToArray($items)
+    {
+        return $items instanceof self ? $items->all() : (array) $items;
+    }
+}

+ 214 - 0
thinkphp/library/think/Config.php

@@ -0,0 +1,214 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+class Config
+{
+    /**
+     * @var array 配置参数
+     */
+    private static $config = [];
+
+    /**
+     * @var string 参数作用域
+     */
+    private static $range = '_sys_';
+
+    /**
+     * 设定配置参数的作用域
+     * @access public
+     * @param  string $range 作用域
+     * @return void
+     */
+    public static function range($range)
+    {
+        self::$range = $range;
+
+        if (!isset(self::$config[$range])) self::$config[$range] = [];
+    }
+
+    /**
+     * 解析配置文件或内容
+     * @access public
+     * @param  string $config 配置文件路径或内容
+     * @param  string $type   配置解析类型
+     * @param  string $name   配置名(如设置即表示二级配置)
+     * @param  string $range  作用域
+     * @return mixed
+     */
+    public static function parse($config, $type = '', $name = '', $range = '')
+    {
+        $range = $range ?: self::$range;
+
+        if (empty($type)) $type = pathinfo($config, PATHINFO_EXTENSION);
+
+        $class = false !== strpos($type, '\\') ?
+            $type :
+            '\\think\\config\\driver\\' . ucwords($type);
+
+        return self::set((new $class())->parse($config), $name, $range);
+    }
+
+    /**
+     * 加载配置文件(PHP格式)
+     * @access public
+     * @param  string $file  配置文件名
+     * @param  string $name  配置名(如设置即表示二级配置)
+     * @param  string $range 作用域
+     * @return mixed
+     */
+    public static function load($file, $name = '', $range = '')
+    {
+        $range = $range ?: self::$range;
+
+        if (!isset(self::$config[$range])) self::$config[$range] = [];
+
+        if (is_file($file)) {
+            $name = strtolower($name);
+            $type = pathinfo($file, PATHINFO_EXTENSION);
+
+            if ('php' == $type) {
+                return self::set(include $file, $name, $range);
+            }
+
+            if ('yaml' == $type && function_exists('yaml_parse_file')) {
+                return self::set(yaml_parse_file($file), $name, $range);
+            }
+
+            return self::parse($file, $type, $name, $range);
+        }
+
+        return self::$config[$range];
+    }
+
+    /**
+     * 检测配置是否存在
+     * @access public
+     * @param  string $name 配置参数名(支持二级配置 . 号分割)
+     * @param  string $range  作用域
+     * @return bool
+     */
+    public static function has($name, $range = '')
+    {
+        $range = $range ?: self::$range;
+
+        if (!strpos($name, '.')) {
+            return isset(self::$config[$range][strtolower($name)]);
+        }
+
+        // 二维数组设置和获取支持
+        $name = explode('.', $name, 2);
+        return isset(self::$config[$range][strtolower($name[0])][$name[1]]);
+    }
+
+    /**
+     * 获取配置参数 为空则获取所有配置
+     * @access public
+     * @param  string $name 配置参数名(支持二级配置 . 号分割)
+     * @param  string $range  作用域
+     * @return mixed
+     */
+    public static function get($name = null, $range = '')
+    {
+        $range = $range ?: self::$range;
+
+        // 无参数时获取所有
+        if (empty($name) && isset(self::$config[$range])) {
+            return self::$config[$range];
+        }
+
+        // 非二级配置时直接返回
+        if (!strpos($name, '.')) {
+            $name = strtolower($name);
+            return isset(self::$config[$range][$name]) ? self::$config[$range][$name] : null;
+        }
+
+        // 二维数组设置和获取支持
+        $name    = explode('.', $name, 2);
+        $name[0] = strtolower($name[0]);
+
+        if (!isset(self::$config[$range][$name[0]])) {
+            // 动态载入额外配置
+            $module = Request::instance()->module();
+            $file   = CONF_PATH . ($module ? $module . DS : '') . 'extra' . DS . $name[0] . CONF_EXT;
+
+            is_file($file) && self::load($file, $name[0]);
+        }
+
+        return isset(self::$config[$range][$name[0]][$name[1]]) ?
+            self::$config[$range][$name[0]][$name[1]] :
+            null;
+    }
+
+    /**
+     * 设置配置参数 name 为数组则为批量设置
+     * @access public
+     * @param  string|array $name  配置参数名(支持二级配置 . 号分割)
+     * @param  mixed        $value 配置值
+     * @param  string       $range 作用域
+     * @return mixed
+     */
+    public static function set($name, $value = null, $range = '')
+    {
+        $range = $range ?: self::$range;
+
+        if (!isset(self::$config[$range])) self::$config[$range] = [];
+
+        // 字符串则表示单个配置设置
+        if (is_string($name)) {
+            if (!strpos($name, '.')) {
+                self::$config[$range][strtolower($name)] = $value;
+            } else {
+                // 二维数组
+                $name = explode('.', $name, 2);
+                self::$config[$range][strtolower($name[0])][$name[1]] = $value;
+            }
+
+            return $value;
+        }
+
+        // 数组则表示批量设置
+        if (is_array($name)) {
+            if (!empty($value)) {
+                self::$config[$range][$value] = isset(self::$config[$range][$value]) ?
+                    array_merge(self::$config[$range][$value], $name) :
+                    $name;
+
+                return self::$config[$range][$value];
+            }
+
+            return self::$config[$range] = array_merge(
+                self::$config[$range], array_change_key_case($name)
+            );
+        }
+
+        // 为空直接返回已有配置
+        return self::$config[$range];
+    }
+
+    /**
+     * 重置配置参数
+     * @access public
+     * @param  string $range 作用域
+     * @return void
+     */
+    public static function reset($range = '')
+    {
+        $range = $range ?: self::$range;
+
+        if (true === $range) {
+            self::$config = [];
+        } else {
+            self::$config[$range] = [];
+        }
+    }
+}

+ 863 - 0
thinkphp/library/think/Console.php

@@ -0,0 +1,863 @@
+<?php
+// +----------------------------------------------------------------------
+// | TopThink [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2015 http://www.topthink.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: zhangyajun <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\console\Command;
+use think\console\command\Help as HelpCommand;
+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
+{
+    /**
+     * @var string 命令名称
+     */
+    private $name;
+
+    /**
+     * @var string 命令版本
+     */
+    private $version;
+
+    /**
+     * @var Command[] 命令
+     */
+    private $commands = [];
+
+    /**
+     * @var bool 是否需要帮助信息
+     */
+    private $wantHelps = false;
+
+    /**
+     * @var bool 是否捕获异常
+     */
+    private $catchExceptions = true;
+
+    /**
+     * @var bool 是否自动退出执行
+     */
+    private $autoExit = true;
+
+    /**
+     * @var InputDefinition 输入定义
+     */
+    private $definition;
+
+    /**
+     * @var string 默认执行的命令
+     */
+    private $defaultCommand;
+
+    /**
+     * @var array 默认提供的命令
+     */
+    private static $defaultCommands = [
+        "think\\console\\command\\Help",
+        "think\\console\\command\\Lists",
+        "think\\console\\command\\Build",
+        "think\\console\\command\\Clear",
+        "think\\console\\command\\make\\Controller",
+        "think\\console\\command\\make\\Model",
+        "think\\console\\command\\optimize\\Autoload",
+        "think\\console\\command\\optimize\\Config",
+        "think\\console\\command\\optimize\\Route",
+        "think\\console\\command\\optimize\\Schema",
+    ];
+
+    /**
+     * Console constructor.
+     * @access public
+     * @param  string     $name    名称
+     * @param  string     $version 版本
+     * @param null|string $user    执行用户
+     */
+    public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN', $user = null)
+    {
+        $this->name    = $name;
+        $this->version = $version;
+
+        if ($user) {
+            $this->setUser($user);
+        }
+
+        $this->defaultCommand = 'list';
+        $this->definition     = $this->getDefaultInputDefinition();
+
+        foreach ($this->getDefaultCommands() as $command) {
+            $this->add($command);
+        }
+    }
+
+    /**
+     * 设置执行用户
+     * @param $user
+     */
+    public function setUser($user)
+    {
+        $user = posix_getpwnam($user);
+        if ($user) {
+            posix_setuid($user['uid']);
+            posix_setgid($user['gid']);
+        }
+    }
+
+    /**
+     * 初始化 Console
+     * @access public
+     * @param  bool $run 是否运行 Console
+     * @return int|Console
+     */
+    public static function init($run = true)
+    {
+        static $console;
+
+        if (!$console) {
+            $config = Config::get('console');
+            // 实例化 console
+            $console = new self($config['name'], $config['version'], $config['user']);
+
+            // 读取指令集
+            if (is_file(CONF_PATH . 'command' . EXT)) {
+                $commands = include CONF_PATH . 'command' . EXT;
+
+                if (is_array($commands)) {
+                    foreach ($commands as $command) {
+                        class_exists($command) &&
+                        is_subclass_of($command, "\\think\\console\\Command") &&
+                        $console->add(new $command());  // 注册指令
+                    }
+                }
+            }
+        }
+
+        return $run ? $console->run() : $console;
+    }
+
+    /**
+     * 调用命令
+     * @access public
+     * @param  string $command
+     * @param  array  $parameters
+     * @param  string $driver
+     * @return Output
+     */
+    public static function call($command, array $parameters = [], $driver = 'buffer')
+    {
+        $console = self::init(false);
+
+        array_unshift($parameters, $command);
+
+        $input  = new Input($parameters);
+        $output = new Output($driver);
+
+        $console->setCatchExceptions(false);
+        $console->find($command)->run($input, $output);
+
+        return $output;
+    }
+
+    /**
+     * 执行当前的指令
+     * @access public
+     * @return int
+     * @throws \Exception
+     */
+    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) ?: 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]);
+        }
+
+        return $this->doRunCommand($this->find($name), $input, $output);
+    }
+
+    /**
+     * 设置输入参数定义
+     * @access public
+     * @param  InputDefinition $definition 输入定义
+     * @return $this;
+     */
+    public function setDefinition(InputDefinition $definition)
+    {
+        $this->definition = $definition;
+
+        return $this;
+    }
+
+    /**
+     * 获取输入参数定义
+     * @access public
+     * @return InputDefinition
+     */
+    public function getDefinition()
+    {
+        return $this->definition;
+    }
+
+    /**
+     * 获取帮助信息
+     * @access public
+     * @return string
+     */
+    public function getHelp()
+    {
+        return $this->getLongVersion();
+    }
+
+    /**
+     * 设置是否捕获异常
+     * @access public
+     * @param bool $boolean 是否捕获
+     * @return $this
+     */
+    public function setCatchExceptions($boolean)
+    {
+        $this->catchExceptions = (bool) $boolean;
+
+        return $this;
+    }
+
+    /**
+     * 设置是否自动退出
+     * @access public
+     * @param bool $boolean 是否自动退出
+     * @return $this
+     */
+    public function setAutoExit($boolean)
+    {
+        $this->autoExit = (bool) $boolean;
+
+        return $this;
+    }
+
+    /**
+     * 获取名称
+     * @access public
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * 设置名称
+     * @access public
+     * @param  string $name 名称
+     * @return $this
+     */
+    public function setName($name)
+    {
+        $this->name = $name;
+
+        return $this;
+    }
+
+    /**
+     * 获取版本
+     * @access public
+     * @return string
+     */
+    public function getVersion()
+    {
+        return $this->version;
+    }
+
+    /**
+     * 设置版本
+     * @access public
+     * @param  string $version 版本信息
+     * @return $this
+     */
+    public function setVersion($version)
+    {
+        $this->version = $version;
+
+        return $this;
+    }
+
+    /**
+     * 获取完整的版本号
+     * @access public
+     * @return string
+     */
+    public function getLongVersion()
+    {
+        if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) {
+            return sprintf(
+                '<info>%s</info> version <comment>%s</comment>',
+                $this->getName(),
+                $this->getVersion()
+            );
+        }
+
+        return '<info>Console Tool</info>';
+    }
+
+    /**
+     * 注册一个指令
+     * @access public
+     * @param string $name 指令名称
+     * @return Command
+     */
+    public function register($name)
+    {
+        return $this->add(new Command($name));
+    }
+
+    /**
+     * 批量添加指令
+     * @access public
+     * @param  Command[] $commands 指令实例
+     * @return $this
+     */
+    public function addCommands(array $commands)
+    {
+        foreach ($commands as $command) $this->add($command);
+
+        return $this;
+    }
+
+    /**
+     * 添加一个指令
+     * @access public
+     * @param  Command $command 命令实例
+     * @return Command|bool
+     */
+    public function add(Command $command)
+    {
+        if (!$command->isEnabled()) {
+            $command->setConsole(null);
+            return false;
+        }
+
+        $command->setConsole($this);
+
+        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 get($name)
+    {
+        if (!isset($this->commands[$name])) {
+            throw new \InvalidArgumentException(
+                sprintf('The command "%s" does not exist.', $name)
+            );
+        }
+
+        $command = $this->commands[$name];
+
+        if ($this->wantHelps) {
+            $this->wantHelps = false;
+
+            /** @var HelpCommand $helpCommand */
+            $helpCommand = $this->get('help');
+            $helpCommand->setCommand($command);
+
+            return $helpCommand;
+        }
+
+        return $command;
+    }
+
+    /**
+     * 某个指令是否存在
+     * @access public
+     * @param  string $name 指令名称
+     * @return bool
+     */
+    public function has($name)
+    {
+        return isset($this->commands[$name]);
+    }
+
+    /**
+     * 获取所有的命名空间
+     * @access public
+     * @return array
+     */
+    public function getNamespaces()
+    {
+        $namespaces = [];
+
+        foreach ($this->commands as $command) {
+            $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($namespace)
+    {
+        $expr = preg_replace_callback('{([^:]+|)}', function ($matches) {
+            return preg_quote($matches[1]) . '[^:]*';
+        }, $namespace);
+
+        $allNamespaces = $this->getNamespaces();
+        $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($name)
+    {
+        $expr = preg_replace_callback('{([^:]+|)}', function ($matches) {
+            return preg_quote($matches[1]) . '[^:]*';
+        }, $name);
+
+        $allCommands = array_keys($this->commands);
+        $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);
+        }
+
+        if (count($commands) > 1) {
+            $commandList = $this->commands;
+            $commands    = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) {
+                $commandName = $commandList[$nameOrAlias]->getName();
+
+                return $commandName === $nameOrAlias || !in_array($commandName, $commands);
+            });
+        }
+
+        $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->get($exact ? $name : reset($commands));
+    }
+
+    /**
+     * 获取所有的指令
+     * @access public
+     * @param  string $namespace 命名空间
+     * @return Command[]
+     */
+    public function all($namespace = null)
+    {
+        if (null === $namespace) return $this->commands;
+
+        $commands = [];
+
+        foreach ($this->commands as $name => $command) {
+            $ext = $this->extractNamespace($name, substr_count($namespace, ':') + 1);
+
+            if ($ext === $namespace) $commands[$name] = $command;
+        }
+
+        return $commands;
+    }
+
+    /**
+     * 获取可能的指令名
+     * @access public
+     * @param  array $names 指令名
+     * @return array
+     */
+    public static function getAbbreviations($names)
+    {
+        $abbrevs = [];
+        foreach ($names as $name) {
+            for ($len = strlen($name); $len > 0; --$len) {
+                $abbrev             = substr($name, 0, $len);
+                $abbrevs[$abbrev][] = $name;
+            }
+        }
+
+        return $abbrevs;
+    }
+
+    /**
+     * 配置基于用户的参数和选项的输入和输出实例
+     * @access protected
+     * @param  Input  $input  输入实例
+     * @param  Output $output 输出实例
+     * @return void
+     */
+    protected function configureIO(Input $input, Output $output)
+    {
+        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);
+        } else {
+            if ($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)
+    {
+        return $input->getFirstArgument();
+    }
+
+    /**
+     * 获取默认输入定义
+     * @access protected
+     * @return InputDefinition
+     */
+    protected function getDefaultInputDefinition()
+    {
+        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 protected
+     * @return Command[]
+     */
+    protected function getDefaultCommands()
+    {
+        $defaultCommands = [];
+
+        foreach (self::$defaultCommands as $class) {
+            if (class_exists($class) && is_subclass_of($class, "think\\console\\Command")) {
+                $defaultCommands[] = new $class();
+            }
+        }
+
+        return $defaultCommands;
+    }
+
+    /**
+     * 添加默认指令
+     * @access public
+     * @param  array $classes 指令
+     * @return void
+     */
+    public static function addDefaultCommands(array $classes)
+    {
+        self::$defaultCommands = array_merge(self::$defaultCommands, $classes);
+    }
+
+    /**
+     * 获取可能的建议
+     * @access private
+     * @param  array $abbrevs
+     * @return string
+     */
+    private function getAbbreviationSuggestions($abbrevs)
+    {
+        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  string $limit 部分的命名空间的最大数量
+     * @return string
+     */
+    public function extractNamespace($name, $limit = null)
+    {
+        $parts = explode(':', $name);
+        array_pop($parts);
+
+        return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit));
+    }
+
+    /**
+     * 查找可替代的建议
+     * @access private
+     * @param string             $name       指令名称
+     * @param array|\Traversable $collection 建议集合
+     * @return array
+     */
+    private function findAlternatives($name, $collection)
+    {
+        $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 public
+     * @param string $commandName 指令名称
+     * @return $this
+     */
+    public function setDefaultCommand($commandName)
+    {
+        $this->defaultCommand = $commandName;
+
+        return $this;
+    }
+
+    /**
+     * 返回所有的命名空间
+     * @access private
+     * @param  string $name 指令名称
+     * @return array
+     */
+    private function extractAllNamespaces($name)
+    {
+        $namespaces = [];
+
+        foreach (explode(':', $name, -1) as $part) {
+            if (count($namespaces)) {
+                $namespaces[] = end($namespaces) . ':' . $part;
+            } else {
+                $namespaces[] = $part;
+            }
+        }
+
+        return $namespaces;
+    }
+
+}

+ 229 - 0
thinkphp/library/think/Controller.php

@@ -0,0 +1,229 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\exception\ValidateException;
+use traits\controller\Jump;
+
+Loader::import('controller/Jump', TRAIT_PATH, EXT);
+
+class Controller
+{
+    use Jump;
+
+    /**
+     * @var \think\View 视图类实例
+     */
+    protected $view;
+
+    /**
+     * @var \think\Request Request 实例
+     */
+    protected $request;
+
+    /**
+     * @var bool 验证失败是否抛出异常
+     */
+    protected $failException = false;
+
+    /**
+     * @var bool 是否批量验证
+     */
+    protected $batchValidate = false;
+
+    /**
+     * @var array 前置操作方法列表
+     */
+    protected $beforeActionList = [];
+
+    /**
+     * 构造方法
+     * @access public
+     * @param Request $request Request 对象
+     */
+    public function __construct(Request $request = null)
+    {
+        $this->view    = View::instance(Config::get('template'), Config::get('view_replace_str'));
+        $this->request = is_null($request) ? Request::instance() : $request;
+
+        // 控制器初始化
+        $this->_initialize();
+
+        // 前置操作方法
+        if ($this->beforeActionList) {
+            foreach ($this->beforeActionList as $method => $options) {
+                is_numeric($method) ?
+                $this->beforeAction($options) :
+                $this->beforeAction($method, $options);
+            }
+        }
+    }
+
+    /**
+     * 初始化操作
+     * @access protected
+     */
+    protected function _initialize()
+    {
+    }
+
+    /**
+     * 前置操作
+     * @access protected
+     * @param  string $method  前置操作方法名
+     * @param  array  $options 调用参数 ['only'=>[...]] 或者 ['except'=>[...]]
+     * @return void
+     */
+    protected function beforeAction($method, $options = [])
+    {
+        if (isset($options['only'])) {
+            if (is_string($options['only'])) {
+                $options['only'] = explode(',', $options['only']);
+            }
+
+            if (!in_array($this->request->action(), $options['only'])) {
+                return;
+            }
+        } elseif (isset($options['except'])) {
+            if (is_string($options['except'])) {
+                $options['except'] = explode(',', $options['except']);
+            }
+
+            if (in_array($this->request->action(), $options['except'])) {
+                return;
+            }
+        }
+
+        call_user_func([$this, $method]);
+    }
+
+    /**
+     * 加载模板输出
+     * @access protected
+     * @param  string $template 模板文件名
+     * @param  array  $vars     模板输出变量
+     * @param  array  $replace  模板替换
+     * @param  array  $config   模板参数
+     * @return mixed
+     */
+    protected function fetch($template = '', $vars = [], $replace = [], $config = [])
+    {
+        return $this->view->fetch($template, $vars, $replace, $config);
+    }
+
+    /**
+     * 渲染内容输出
+     * @access protected
+     * @param  string $content 模板内容
+     * @param  array  $vars    模板输出变量
+     * @param  array  $replace 替换内容
+     * @param  array  $config  模板参数
+     * @return mixed
+     */
+    protected function display($content = '', $vars = [], $replace = [], $config = [])
+    {
+        return $this->view->display($content, $vars, $replace, $config);
+    }
+
+    /**
+     * 模板变量赋值
+     * @access protected
+     * @param  mixed $name  要显示的模板变量
+     * @param  mixed $value 变量的值
+     * @return $this
+     */
+    protected function assign($name, $value = '')
+    {
+        $this->view->assign($name, $value);
+
+        return $this;
+    }
+
+    /**
+     * 初始化模板引擎
+     * @access protected
+     * @param array|string $engine 引擎参数
+     * @return $this
+     */
+    protected function engine($engine)
+    {
+        $this->view->engine($engine);
+
+        return $this;
+    }
+
+    /**
+     * 设置验证失败后是否抛出异常
+     * @access protected
+     * @param bool $fail 是否抛出异常
+     * @return $this
+     */
+    protected function validateFailException($fail = true)
+    {
+        $this->failException = $fail;
+
+        return $this;
+    }
+
+    /**
+     * 验证数据
+     * @access protected
+     * @param  array        $data     数据
+     * @param  string|array $validate 验证器名或者验证规则数组
+     * @param  array        $message  提示信息
+     * @param  bool         $batch    是否批量验证
+     * @param  mixed        $callback 回调方法(闭包)
+     * @return array|string|true
+     * @throws ValidateException
+     */
+    protected function validate($data, $validate, $message = [], $batch = false, $callback = null)
+    {
+        if (is_array($validate)) {
+            $v = Loader::validate();
+            $v->rule($validate);
+        } else {
+            // 支持场景
+            if (strpos($validate, '.')) {
+                list($validate, $scene) = explode('.', $validate);
+            }
+
+            $v = Loader::validate($validate);
+
+            !empty($scene) && $v->scene($scene);
+        }
+
+        // 批量验证
+        if ($batch || $this->batchValidate) {
+            $v->batch(true);
+        }
+
+        // 设置错误信息
+        if (is_array($message)) {
+            $v->message($message);
+        }
+
+        // 使用回调验证
+        if ($callback && is_callable($callback)) {
+            call_user_func_array($callback, [$v, &$data]);
+        }
+
+        if (!$v->check($data)) {
+            if ($this->failException) {
+                throw new ValidateException($v->getError());
+            }
+
+            return $v->getError();
+        }
+
+        return true;
+    }
+}

+ 268 - 0
thinkphp/library/think/Cookie.php

@@ -0,0 +1,268 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+class Cookie
+{
+    /**
+     * @var array cookie 设置参数
+     */
+    protected static $config = [
+        'prefix'    => '', // cookie 名称前缀
+        'expire'    => 0, // cookie 保存时间
+        'path'      => '/', // cookie 保存路径
+        'domain'    => '', // cookie 有效域名
+        'secure'    => false, //  cookie 启用安全传输
+        'httponly'  => false, // httponly 设置
+        'setcookie' => true, // 是否使用 setcookie
+    ];
+
+    /**
+     * @var bool 是否完成初始化了
+     */
+    protected static $init;
+
+    /**
+     * Cookie初始化
+     * @access public
+     * @param  array $config 配置参数
+     * @return void
+     */
+    public static function init(array $config = [])
+    {
+        if (empty($config)) {
+            $config = Config::get('cookie');
+        }
+
+        self::$config = array_merge(self::$config, array_change_key_case($config));
+
+        if (!empty(self::$config['httponly'])) {
+            ini_set('session.cookie_httponly', 1);
+        }
+
+        self::$init = true;
+    }
+
+    /**
+     * 设置或者获取 cookie 作用域(前缀)
+     * @access public
+     * @param  string $prefix 前缀
+     * @return string|
+     */
+    public static function prefix($prefix = '')
+    {
+        if (empty($prefix)) {
+            return self::$config['prefix'];
+        }
+
+        return self::$config['prefix'] = $prefix;
+    }
+
+    /**
+     * Cookie 设置、获取、删除
+     * @access public
+     * @param  string $name   cookie 名称
+     * @param  mixed  $value  cookie 值
+     * @param  mixed  $option 可选参数 可能会是 null|integer|string
+     * @return void
+     */
+    public static function set($name, $value = '', $option = null)
+    {
+        !isset(self::$init) && self::init();
+
+        // 参数设置(会覆盖黙认设置)
+        if (!is_null($option)) {
+            if (is_numeric($option)) {
+                $option = ['expire' => $option];
+            } elseif (is_string($option)) {
+                parse_str($option, $option);
+            }
+
+            $config = array_merge(self::$config, array_change_key_case($option));
+        } else {
+            $config = self::$config;
+        }
+
+        $name = $config['prefix'] . $name;
+
+        // 设置 cookie
+        if (is_array($value)) {
+            array_walk_recursive($value, 'self::jsonFormatProtect', 'encode');
+            $value = 'think:' . json_encode($value);
+        }
+
+        $expire = !empty($config['expire']) ?
+        $_SERVER['REQUEST_TIME'] + intval($config['expire']) :
+        0;
+
+        if ($config['setcookie']) {
+            setcookie(
+                $name, $value, $expire, $config['path'], $config['domain'],
+                $config['secure'], $config['httponly']
+            );
+        }
+
+        $_COOKIE[$name] = $value;
+    }
+
+    /**
+     * 永久保存 Cookie 数据
+     * @access public
+     * @param  string $name   cookie 名称
+     * @param  mixed  $value  cookie 值
+     * @param  mixed  $option 可选参数 可能会是 null|integer|string
+     * @return void
+     */
+    public static function forever($name, $value = '', $option = null)
+    {
+        if (is_null($option) || is_numeric($option)) {
+            $option = [];
+        }
+
+        $option['expire'] = 315360000;
+
+        self::set($name, $value, $option);
+    }
+
+    /**
+     * 判断是否有 Cookie 数据
+     * @access public
+     * @param  string      $name   cookie 名称
+     * @param  string|null $prefix cookie 前缀
+     * @return bool
+     */
+    public static function has($name, $prefix = null)
+    {
+        !isset(self::$init) && self::init();
+
+        $prefix = !is_null($prefix) ? $prefix : self::$config['prefix'];
+
+        return isset($_COOKIE[$prefix . $name]);
+    }
+
+    /**
+     * 获取 Cookie 的值
+     * @access public
+     * @param string      $name   cookie 名称
+     * @param string|null $prefix cookie 前缀
+     * @return mixed
+     */
+    public static function get($name = '', $prefix = null)
+    {
+        !isset(self::$init) && self::init();
+
+        $prefix = !is_null($prefix) ? $prefix : self::$config['prefix'];
+        $key    = $prefix . $name;
+
+        if ('' == $name) {
+            // 获取全部
+            if ($prefix) {
+                $value = [];
+
+                foreach ($_COOKIE as $k => $val) {
+                    if (0 === strpos($k, $prefix)) {
+                        $value[$k] = $val;
+                    }
+
+                }
+            } else {
+                $value = $_COOKIE;
+            }
+        } elseif (isset($_COOKIE[$key])) {
+            $value = $_COOKIE[$key];
+
+            if (0 === strpos($value, 'think:')) {
+                $value = json_decode(substr($value, 6), true);
+                array_walk_recursive($value, 'self::jsonFormatProtect', 'decode');
+            }
+        } else {
+            $value = null;
+        }
+
+        return $value;
+    }
+
+    /**
+     * 删除 Cookie
+     * @access public
+     * @param  string      $name   cookie 名称
+     * @param  string|null $prefix cookie 前缀
+     * @return void
+     */
+    public static function delete($name, $prefix = null)
+    {
+        !isset(self::$init) && self::init();
+
+        $config = self::$config;
+        $prefix = !is_null($prefix) ? $prefix : $config['prefix'];
+        $name   = $prefix . $name;
+
+        if ($config['setcookie']) {
+            setcookie(
+                $name, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'],
+                $config['domain'], $config['secure'], $config['httponly']
+            );
+        }
+
+        // 删除指定 cookie
+        unset($_COOKIE[$name]);
+    }
+
+    /**
+     * 清除指定前缀的所有 cookie
+     * @access public
+     * @param  string|null $prefix cookie 前缀
+     * @return void
+     */
+    public static function clear($prefix = null)
+    {
+        if (empty($_COOKIE)) {
+            return;
+        }
+
+        !isset(self::$init) && self::init();
+
+        // 要删除的 cookie 前缀,不指定则删除 config 设置的指定前缀
+        $config = self::$config;
+        $prefix = !is_null($prefix) ? $prefix : $config['prefix'];
+
+        if ($prefix) {
+            foreach ($_COOKIE as $key => $val) {
+                if (0 === strpos($key, $prefix)) {
+                    if ($config['setcookie']) {
+                        setcookie(
+                            $key, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'],
+                            $config['domain'], $config['secure'], $config['httponly']
+                        );
+                    }
+
+                    unset($_COOKIE[$key]);
+                }
+            }
+        }
+    }
+
+    /**
+     * json 转换时的格式保护
+     * @access protected
+     * @param  mixed  $val  要转换的值
+     * @param  string $key  键名
+     * @param  string $type 转换类别
+     * @return void
+     */
+    protected static function jsonFormatProtect(&$val, $key, $type = 'encode')
+    {
+        if (!empty($val) && true !== $val) {
+            $val = 'decode' == $type ? urldecode($val) : urlencode($val);
+        }
+    }
+}

+ 180 - 0
thinkphp/library/think/Db.php

@@ -0,0 +1,180 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\db\Connection;
+use think\db\Query;
+
+/**
+ * Class Db
+ * @package think
+ * @method Query table(string $table) static 指定数据表(含前缀)
+ * @method Query name(string $name) static 指定数据表(不含前缀)
+ * @method Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件
+ * @method Query join(mixed $join, mixed $condition = null, string $type = 'INNER') static JOIN查询
+ * @method Query union(mixed $union, boolean $all = false) static UNION查询
+ * @method Query limit(mixed $offset, integer $length = null) static 查询LIMIT
+ * @method Query order(mixed $field, string $order = null) static 查询ORDER
+ * @method Query cache(mixed $key = null , integer $expire = null) static 设置查询缓存
+ * @method mixed value(string $field) static 获取某个字段的值
+ * @method array column(string $field, string $key = '') static 获取某个列的值
+ * @method Query view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询
+ * @method mixed find(mixed $data = null) static 查询单个记录
+ * @method mixed select(mixed $data = null) static 查询多个记录
+ * @method integer insert(array $data, boolean $replace = false, boolean $getLastInsID = false, string $sequence = null) static 插入一条记录
+ * @method integer insertGetId(array $data, boolean $replace = false, string $sequence = null) static 插入一条记录并返回自增ID
+ * @method integer insertAll(array $dataSet) static 插入多条记录
+ * @method integer update(array $data) static 更新记录
+ * @method integer delete(mixed $data = null) static 删除记录
+ * @method boolean chunk(integer $count, callable $callback, string $column = null) static 分块获取数据
+ * @method mixed query(string $sql, array $bind = [], boolean $master = false, bool $pdo = false) static SQL查询
+ * @method integer execute(string $sql, array $bind = [], boolean $fetch = false, boolean $getLastInsID = false, string $sequence = null) static SQL执行
+ * @method Paginator paginate(integer $listRows = 15, mixed $simple = null, array $config = []) static 分页查询
+ * @method mixed transaction(callable $callback) static 执行数据库事务
+ * @method void startTrans() static 启动事务
+ * @method void commit() static 用于非自动提交状态下面的查询提交
+ * @method void rollback() static 事务回滚
+ * @method boolean batchQuery(array $sqlArray) static 批处理执行SQL语句
+ * @method string quote(string $str) static SQL指令安全过滤
+ * @method string getLastInsID($sequence = null) static 获取最近插入的ID
+ */
+class Db
+{
+    /**
+     * @var Connection[] 数据库连接实例
+     */
+    private static $instance = [];
+
+    /**
+     * @var int 查询次数
+     */
+    public static $queryTimes = 0;
+
+    /**
+     * @var int 执行次数
+     */
+    public static $executeTimes = 0;
+
+    /**
+     * 数据库初始化,并取得数据库类实例
+     * @access public
+     * @param  mixed       $config 连接配置
+     * @param  bool|string $name   连接标识 true 强制重新连接
+     * @return Connection
+     * @throws Exception
+     */
+    public static function connect($config = [], $name = false)
+    {
+        if (false === $name) {
+            $name = md5(serialize($config));
+        }
+
+        if (true === $name || !isset(self::$instance[$name])) {
+            // 解析连接参数 支持数组和字符串
+            $options = self::parseConfig($config);
+
+            if (empty($options['type'])) {
+                throw new \InvalidArgumentException('Undefined db type');
+            }
+
+            $class = false !== strpos($options['type'], '\\') ?
+            $options['type'] :
+            '\\think\\db\\connector\\' . ucwords($options['type']);
+
+            // 记录初始化信息
+            if (App::$debug) {
+                Log::record('[ DB ] INIT ' . $options['type'], 'info');
+            }
+
+            if (true === $name) {
+                $name = md5(serialize($config));
+            }
+
+            self::$instance[$name] = new $class($options);
+        }
+
+        return self::$instance[$name];
+    }
+
+    /**
+     * 清除连接实例
+     * @access public
+     * @return void
+     */
+    public static function clear()
+    {
+        self::$instance = [];
+    }
+
+    /**
+     * 数据库连接参数解析
+     * @access private
+     * @param  mixed $config 连接参数
+     * @return array
+     */
+    private static function parseConfig($config)
+    {
+        if (empty($config)) {
+            $config = Config::get('database');
+        } elseif (is_string($config) && false === strpos($config, '/')) {
+            $config = Config::get($config); // 支持读取配置参数
+        }
+
+        return is_string($config) ? self::parseDsn($config) : $config;
+    }
+
+    /**
+     * DSN 解析
+     * 格式: mysql://username:passwd@localhost:3306/DbName?param1=val1&param2=val2#utf8
+     * @access private
+     * @param  string $dsnStr 数据库 DSN 字符串解析
+     * @return array
+     */
+    private static function parseDsn($dsnStr)
+    {
+        $info = parse_url($dsnStr);
+
+        if (!$info) {
+            return [];
+        }
+
+        $dsn = [
+            'type'     => $info['scheme'],
+            'username' => isset($info['user']) ? $info['user'] : '',
+            'password' => isset($info['pass']) ? $info['pass'] : '',
+            'hostname' => isset($info['host']) ? $info['host'] : '',
+            'hostport' => isset($info['port']) ? $info['port'] : '',
+            'database' => !empty($info['path']) ? ltrim($info['path'], '/') : '',
+            'charset'  => isset($info['fragment']) ? $info['fragment'] : 'utf8',
+        ];
+
+        if (isset($info['query'])) {
+            parse_str($info['query'], $dsn['params']);
+        } else {
+            $dsn['params'] = [];
+        }
+
+        return $dsn;
+    }
+
+    /**
+     * 调用驱动类的方法
+     * @access public
+     * @param  string $method 方法名
+     * @param  array  $params 参数
+     * @return mixed
+     */
+    public static function __callStatic($method, $params)
+    {
+        return call_user_func_array([self::connect(), $method], $params);
+    }
+}

+ 252 - 0
thinkphp/library/think/Debug.php

@@ -0,0 +1,252 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\exception\ClassNotFoundException;
+use think\response\Redirect;
+
+class Debug
+{
+    /**
+     * @var array 区间时间信息
+     */
+    protected static $info = [];
+
+    /**
+     * @var array 区间内存信息
+     */
+    protected static $mem = [];
+
+    /**
+     * 记录时间(微秒)和内存使用情况
+     * @access public
+     * @param  string $name  标记位置
+     * @param  mixed  $value 标记值(留空则取当前 time 表示仅记录时间 否则同时记录时间和内存)
+     * @return void
+     */
+    public static function remark($name, $value = '')
+    {
+        self::$info[$name] = is_float($value) ? $value : microtime(true);
+
+        if ('time' != $value) {
+            self::$mem['mem'][$name]  = is_float($value) ? $value : memory_get_usage();
+            self::$mem['peak'][$name] = memory_get_peak_usage();
+        }
+    }
+
+    /**
+     * 统计某个区间的时间(微秒)使用情况 返回值以秒为单位
+     * @access public
+     * @param  string  $start 开始标签
+     * @param  string  $end   结束标签
+     * @param  integer $dec   小数位
+     * @return string
+     */
+    public static function getRangeTime($start, $end, $dec = 6)
+    {
+        if (!isset(self::$info[$end])) {
+            self::$info[$end] = microtime(true);
+        }
+
+        return number_format((self::$info[$end] - self::$info[$start]), $dec);
+    }
+
+    /**
+     * 统计从开始到统计时的时间(微秒)使用情况 返回值以秒为单位
+     * @access public
+     * @param  integer $dec 小数位
+     * @return string
+     */
+    public static function getUseTime($dec = 6)
+    {
+        return number_format((microtime(true) - THINK_START_TIME), $dec);
+    }
+
+    /**
+     * 获取当前访问的吞吐率情况
+     * @access public
+     * @return string
+     */
+    public static function getThroughputRate()
+    {
+        return number_format(1 / self::getUseTime(), 2) . 'req/s';
+    }
+
+    /**
+     * 记录区间的内存使用情况
+     * @access public
+     * @param  string  $start 开始标签
+     * @param  string  $end   结束标签
+     * @param  integer $dec   小数位
+     * @return string
+     */
+    public static function getRangeMem($start, $end, $dec = 2)
+    {
+        if (!isset(self::$mem['mem'][$end])) {
+            self::$mem['mem'][$end] = memory_get_usage();
+        }
+
+        $size = self::$mem['mem'][$end] - self::$mem['mem'][$start];
+        $a    = ['B', 'KB', 'MB', 'GB', 'TB'];
+        $pos  = 0;
+
+        while ($size >= 1024) {
+            $size /= 1024;
+            $pos++;
+        }
+
+        return round($size, $dec) . " " . $a[$pos];
+    }
+
+    /**
+     * 统计从开始到统计时的内存使用情况
+     * @access public
+     * @param  integer $dec 小数位
+     * @return string
+     */
+    public static function getUseMem($dec = 2)
+    {
+        $size = memory_get_usage() - THINK_START_MEM;
+        $a    = ['B', 'KB', 'MB', 'GB', 'TB'];
+        $pos  = 0;
+
+        while ($size >= 1024) {
+            $size /= 1024;
+            $pos++;
+        }
+
+        return round($size, $dec) . " " . $a[$pos];
+    }
+
+    /**
+     * 统计区间的内存峰值情况
+     * @access public
+     * @param  string  $start 开始标签
+     * @param  string  $end   结束标签
+     * @param  integer $dec   小数位
+     * @return string
+     */
+    public static function getMemPeak($start, $end, $dec = 2)
+    {
+        if (!isset(self::$mem['peak'][$end])) {
+            self::$mem['peak'][$end] = memory_get_peak_usage();
+        }
+
+        $size = self::$mem['peak'][$end] - self::$mem['peak'][$start];
+        $a    = ['B', 'KB', 'MB', 'GB', 'TB'];
+        $pos  = 0;
+
+        while ($size >= 1024) {
+            $size /= 1024;
+            $pos++;
+        }
+
+        return round($size, $dec) . " " . $a[$pos];
+    }
+
+    /**
+     * 获取文件加载信息
+     * @access public
+     * @param  bool $detail 是否显示详细
+     * @return integer|array
+     */
+    public static function getFile($detail = false)
+    {
+        $files = get_included_files();
+
+        if ($detail) {
+            $info = [];
+
+            foreach ($files as $file) {
+                $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )';
+            }
+
+            return $info;
+        }
+
+        return count($files);
+    }
+
+    /**
+     * 浏览器友好的变量输出
+     * @access public
+     * @param  mixed       $var   变量
+     * @param  boolean     $echo  是否输出(默认为 true,为 false 则返回输出字符串)
+     * @param  string|null $label 标签(默认为空)
+     * @param  integer     $flags htmlspecialchars 的标志
+     * @return null|string
+     */
+    public static function dump($var, $echo = true, $label = null, $flags = ENT_SUBSTITUTE)
+    {
+        $label = (null === $label) ? '' : rtrim($label) . ':';
+
+        ob_start();
+        var_dump($var);
+        $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', ob_get_clean());
+
+        if (IS_CLI) {
+            $output = PHP_EOL . $label . $output . PHP_EOL;
+        } else {
+            if (!extension_loaded('xdebug')) {
+                $output = htmlspecialchars($output, $flags);
+            }
+
+            $output = '<pre>' . $label . $output . '</pre>';
+        }
+
+        if ($echo) {
+            echo($output);
+            return;
+        }
+
+        return $output;
+    }
+
+    /**
+     * 调试信息注入到响应中
+     * @access public
+     * @param  Response $response 响应实例
+     * @param  string   $content  返回的字符串
+     * @return void
+     */
+    public static function inject(Response $response, &$content)
+    {
+        $config = Config::get('trace');
+        $type   = isset($config['type']) ? $config['type'] : 'Html';
+        $class  = false !== strpos($type, '\\') ? $type : '\\think\\debug\\' . ucwords($type);
+
+        unset($config['type']);
+
+        if (!class_exists($class)) {
+            throw new ClassNotFoundException('class not exists:' . $class, $class);
+        }
+
+        /** @var \think\debug\Console|\think\debug\Html $trace */
+        $trace = new $class($config);
+
+        if ($response instanceof Redirect) {
+            // TODO 记录
+        } else {
+            $output = $trace->output($response, Log::getLog());
+
+            if (is_string($output)) {
+                // trace 调试信息注入
+                $pos = strripos($content, '</body>');
+                if (false !== $pos) {
+                    $content = substr($content, 0, $pos) . $output . substr($content, $pos);
+                } else {
+                    $content = $content . $output;
+                }
+            }
+        }
+    }
+}

+ 39 - 0
thinkphp/library/think/Env.php

@@ -0,0 +1,39 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+class Env
+{
+    /**
+     * 获取环境变量值
+     * @access public
+     * @param  string $name    环境变量名(支持二级 . 号分割)
+     * @param  string $default 默认值
+     * @return mixed
+     */
+    public static function get($name, $default = null)
+    {
+        $result = getenv(ENV_PREFIX . strtoupper(str_replace('.', '_', $name)));
+
+        if (false !== $result) {
+            if ('false' === $result) {
+                $result = false;
+            } elseif ('true' === $result) {
+                $result = true;
+            }
+
+            return $result;
+        }
+
+        return $default;
+    }
+}

+ 136 - 0
thinkphp/library/think/Error.php

@@ -0,0 +1,136 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\console\Output as ConsoleOutput;
+use think\exception\ErrorException;
+use think\exception\Handle;
+use think\exception\ThrowableError;
+
+class Error
+{
+    /**
+     * 注册异常处理
+     * @access public
+     * @return void
+     */
+    public static function register()
+    {
+        error_reporting(E_ALL);
+        set_error_handler([__CLASS__, 'appError']);
+        set_exception_handler([__CLASS__, 'appException']);
+        register_shutdown_function([__CLASS__, 'appShutdown']);
+    }
+
+    /**
+     * 异常处理
+     * @access public
+     * @param  \Exception|\Throwable $e 异常
+     * @return void
+     */
+    public static function appException($e)
+    {
+        if (!$e instanceof \Exception) {
+            $e = new ThrowableError($e);
+        }
+
+        $handler = self::getExceptionHandler();
+        $handler->report($e);
+
+        if (IS_CLI) {
+            $handler->renderForConsole(new ConsoleOutput, $e);
+        } else {
+            $handler->render($e)->send();
+        }
+    }
+
+    /**
+     * 错误处理
+     * @access public
+     * @param  integer $errno      错误编号
+     * @param  integer $errstr     详细错误信息
+     * @param  string  $errfile    出错的文件
+     * @param  integer $errline    出错行号
+     * @return void
+     * @throws ErrorException
+     */
+    public static function appError($errno, $errstr, $errfile = '', $errline = 0)
+    {
+        $exception = new ErrorException($errno, $errstr, $errfile, $errline);
+
+        // 符合异常处理的则将错误信息托管至 think\exception\ErrorException
+        if (error_reporting() & $errno) {
+            throw $exception;
+        }
+
+        self::getExceptionHandler()->report($exception);
+    }
+
+    /**
+     * 异常中止处理
+     * @access public
+     * @return void
+     */
+    public static function appShutdown()
+    {
+        // 将错误信息托管至 think\ErrorException
+        if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) {
+            self::appException(new ErrorException(
+                $error['type'], $error['message'], $error['file'], $error['line']
+            ));
+        }
+
+        // 写入日志
+        Log::save();
+    }
+
+    /**
+     * 确定错误类型是否致命
+     * @access protected
+     * @param  int $type 错误类型
+     * @return bool
+     */
+    protected static function isFatal($type)
+    {
+        return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]);
+    }
+
+    /**
+     * 获取异常处理的实例
+     * @access public
+     * @return Handle
+     */
+    public static function getExceptionHandler()
+    {
+        static $handle;
+
+        if (!$handle) {
+            // 异常处理 handle
+            $class = Config::get('exception_handle');
+
+            if ($class && is_string($class) && class_exists($class) &&
+                is_subclass_of($class, "\\think\\exception\\Handle")
+            ) {
+                $handle = new $class;
+            } else {
+                $handle = new Handle;
+
+                if ($class instanceof \Closure) {
+                    $handle->setRender($class);
+                }
+
+            }
+        }
+
+        return $handle;
+    }
+}

+ 55 - 0
thinkphp/library/think/Exception.php

@@ -0,0 +1,55 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://zjzit.cn>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+class Exception extends \Exception
+{
+    /**
+     * @var array 保存异常页面显示的额外 Debug 数据
+     */
+    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  需要显示的数据,必须为关联数组
+     * @return void
+     */
+    final protected function setData($label, array $data)
+    {
+        $this->data[$label] = $data;
+    }
+
+    /**
+     * 获取异常额外 Debug 数据
+     * 主要用于输出到异常页面便于调试
+     * @access public
+     * @return array
+     */
+    final public function getData()
+    {
+        return $this->data;
+    }
+
+}

+ 478 - 0
thinkphp/library/think/File.php

@@ -0,0 +1,478 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use SplFileObject;
+
+class File extends SplFileObject
+{
+    /**
+     * @var string 错误信息
+     */
+    private $error = '';
+
+    /**
+     * @var string 当前完整文件名
+     */
+    protected $filename;
+
+    /**
+     * @var string 上传文件名
+     */
+    protected $saveName;
+
+    /**
+     * @var string 文件上传命名规则
+     */
+    protected $rule = 'date';
+
+    /**
+     * @var array 文件上传验证规则
+     */
+    protected $validate = [];
+
+    /**
+     * @var bool 单元测试
+     */
+    protected $isTest;
+
+    /**
+     * @var array 上传文件信息
+     */
+    protected $info;
+
+    /**
+     * @var array 文件 hash 信息
+     */
+    protected $hash = [];
+
+    /**
+     * File constructor.
+     * @access public
+     * @param  string $filename 文件名称
+     * @param  string $mode     访问模式
+     */
+    public function __construct($filename, $mode = 'r')
+    {
+        parent::__construct($filename, $mode);
+        $this->filename = $this->getRealPath() ?: $this->getPathname();
+    }
+
+    /**
+     * 设置是否是单元测试
+     * @access public
+     * @param  bool $test 是否是测试
+     * @return $this
+     */
+    public function isTest($test = false)
+    {
+        $this->isTest = $test;
+
+        return $this;
+    }
+
+    /**
+     * 设置上传信息
+     * @access public
+     * @param  array $info 上传文件信息
+     * @return $this
+     */
+    public function setUploadInfo($info)
+    {
+        $this->info = $info;
+
+        return $this;
+    }
+
+    /**
+     * 获取上传文件的信息
+     * @access public
+     * @param  string $name 信息名称
+     * @return array|string
+     */
+    public function getInfo($name = '')
+    {
+        return isset($this->info[$name]) ? $this->info[$name] : $this->info;
+    }
+
+    /**
+     * 获取上传文件的文件名
+     * @access public
+     * @return string
+     */
+    public function getSaveName()
+    {
+        return $this->saveName;
+    }
+
+    /**
+     * 设置上传文件的保存文件名
+     * @access public
+     * @param  string $saveName 保存名称
+     * @return $this
+     */
+    public function setSaveName($saveName)
+    {
+        $this->saveName = $saveName;
+
+        return $this;
+    }
+
+    /**
+     * 获取文件的哈希散列值
+     * @access public
+     * @param  string $type 类型
+     * @return string
+     */
+    public function hash($type = 'sha1')
+    {
+        if (!isset($this->hash[$type])) {
+            $this->hash[$type] = hash_file($type, $this->filename);
+        }
+
+        return $this->hash[$type];
+    }
+
+    /**
+     * 检查目录是否可写
+     * @access protected
+     * @param  string $path 目录
+     * @return boolean
+     */
+    protected function checkPath($path)
+    {
+        if (is_dir($path) || mkdir($path, 0755, true)) {
+            return true;
+        }
+
+        $this->error = ['directory {:path} creation failed', ['path' => $path]];
+
+        return false;
+    }
+
+    /**
+     * 获取文件类型信息
+     * @access public
+     * @return string
+     */
+    public function getMime()
+    {
+        $finfo = finfo_open(FILEINFO_MIME_TYPE);
+
+        return finfo_file($finfo, $this->filename);
+    }
+
+    /**
+     * 设置文件的命名规则
+     * @access public
+     * @param  string $rule 文件命名规则
+     * @return $this
+     */
+    public function rule($rule)
+    {
+        $this->rule = $rule;
+
+        return $this;
+    }
+
+    /**
+     * 设置上传文件的验证规则
+     * @access public
+     * @param  array $rule 验证规则
+     * @return $this
+     */
+    public function validate(array $rule = [])
+    {
+        $this->validate = $rule;
+
+        return $this;
+    }
+
+    /**
+     * 检测是否合法的上传文件
+     * @access public
+     * @return bool
+     */
+    public function isValid()
+    {
+        return $this->isTest ? is_file($this->filename) : is_uploaded_file($this->filename);
+    }
+
+    /**
+     * 检测上传文件
+     * @access public
+     * @param  array $rule 验证规则
+     * @return bool
+     */
+    public function check($rule = [])
+    {
+        $rule = $rule ?: $this->validate;
+
+        /* 检查文件大小 */
+        if (isset($rule['size']) && !$this->checkSize($rule['size'])) {
+            $this->error = 'filesize not match';
+            return false;
+        }
+
+        /* 检查文件 Mime 类型 */
+        if (isset($rule['type']) && !$this->checkMime($rule['type'])) {
+            $this->error = 'mimetype to upload is not allowed';
+            return false;
+        }
+
+        /* 检查文件后缀 */
+        if (isset($rule['ext']) && !$this->checkExt($rule['ext'])) {
+            $this->error = 'extensions to upload is not allowed';
+            return false;
+        }
+
+        /* 检查图像文件 */
+        if (!$this->checkImg()) {
+            $this->error = 'illegal image files';
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * 检测上传文件后缀
+     * @access public
+     * @param  array|string $ext 允许后缀
+     * @return bool
+     */
+    public function checkExt($ext)
+    {
+        if (is_string($ext)) {
+            $ext = explode(',', $ext);
+        }
+
+        $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION));
+
+        return in_array($extension, $ext);
+    }
+
+    /**
+     * 检测图像文件
+     * @access public
+     * @return bool
+     */
+    public function checkImg()
+    {
+        $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION));
+
+        // 如果上传的不是图片,或者是图片而且后缀确实符合图片类型则返回 true
+        return !in_array($extension, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']) || in_array($this->getImageType($this->filename), [1, 2, 3, 4, 6, 13]);
+    }
+
+    /**
+     * 判断图像类型
+     * @access protected
+     * @param  string $image 图片名称
+     * @return bool|int
+     */
+    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  integer $size 最大大小
+     * @return bool
+     */
+    public function checkSize($size)
+    {
+        return $this->getSize() <= $size;
+    }
+
+    /**
+     * 检测上传文件类型
+     * @access public
+     * @param  array|string $mime 允许类型
+     * @return bool
+     */
+    public function checkMime($mime)
+    {
+        $mime = is_string($mime) ? explode(',', $mime) : $mime;
+
+        return in_array(strtolower($this->getMime()), $mime);
+    }
+
+    /**
+     * 移动文件
+     * @access public
+     * @param  string      $path     保存路径
+     * @param  string|bool $savename 保存的文件名 默认自动生成
+     * @param  boolean     $replace  同名文件是否覆盖
+     * @return false|File
+     */
+    public function move($path, $savename = true, $replace = true)
+    {
+        // 文件上传失败,捕获错误代码
+        if (!empty($this->info['error'])) {
+            $this->error($this->info['error']);
+            return false;
+        }
+
+        // 检测合法性
+        if (!$this->isValid()) {
+            $this->error = 'upload illegal files';
+            return false;
+        }
+
+        // 验证上传
+        if (!$this->check()) {
+            return false;
+        }
+
+        $path = rtrim($path, DS) . DS;
+        // 文件保存命名规则
+        $saveName = $this->buildSaveName($savename);
+        $filename = $path . $saveName;
+
+        // 检测目录
+        if (false === $this->checkPath(dirname($filename))) {
+            return false;
+        }
+
+        // 不覆盖同名文件
+        if (!$replace && is_file($filename)) {
+            $this->error = ['has the same filename: {:filename}', ['filename' => $filename]];
+            return false;
+        }
+
+        /* 移动文件 */
+        if ($this->isTest) {
+            rename($this->filename, $filename);
+        } elseif (!move_uploaded_file($this->filename, $filename)) {
+            $this->error = 'upload write error';
+            return false;
+        }
+
+        // 返回 File 对象实例
+        $file = new self($filename);
+        $file->setSaveName($saveName)->setUploadInfo($this->info);
+
+        return $file;
+    }
+
+    /**
+     * 获取保存文件名
+     * @access protected
+     * @param  string|bool $savename 保存的文件名 默认自动生成
+     * @return string
+     */
+    protected function buildSaveName($savename)
+    {
+        // 自动生成文件名
+        if (true === $savename) {
+            if ($this->rule instanceof \Closure) {
+                $savename = call_user_func_array($this->rule, [$this]);
+            } else {
+                switch ($this->rule) {
+                    case 'date':
+                        $savename = date('Ymd') . DS . md5(microtime(true));
+                        break;
+                    default:
+                        if (in_array($this->rule, hash_algos())) {
+                            $hash     = $this->hash($this->rule);
+                            $savename = substr($hash, 0, 2) . DS . substr($hash, 2);
+                        } elseif (is_callable($this->rule)) {
+                            $savename = call_user_func($this->rule);
+                        } else {
+                            $savename = date('Ymd') . DS . md5(microtime(true));
+                        }
+                }
+            }
+        } elseif ('' === $savename || false === $savename) {
+            $savename = $this->getInfo('name');
+        }
+
+        if (!strpos($savename, '.')) {
+            $savename .= '.' . pathinfo($this->getInfo('name'), PATHINFO_EXTENSION);
+        }
+
+        return $savename;
+    }
+
+    /**
+     * 获取错误代码信息
+     * @access private
+     * @param  int $errorNo 错误号
+     * @return $this
+     */
+    private function error($errorNo)
+    {
+        switch ($errorNo) {
+            case 1:
+            case 2:
+                $this->error = 'upload File size exceeds the maximum value';
+                break;
+            case 3:
+                $this->error = 'only the portion of file is uploaded';
+                break;
+            case 4:
+                $this->error = 'no file to uploaded';
+                break;
+            case 6:
+                $this->error = 'upload temp dir not found';
+                break;
+            case 7:
+                $this->error = 'file write error';
+                break;
+            default:
+                $this->error = 'unknown upload error';
+        }
+
+        return $this;
+    }
+
+    /**
+     * 获取错误信息(支持多语言)
+     * @access public
+     * @return string
+     */
+    public function getError()
+    {
+        if (is_array($this->error)) {
+            list($msg, $vars) = $this->error;
+        } else {
+            $msg  = $this->error;
+            $vars = [];
+        }
+
+        return Lang::has($msg) ? Lang::get($msg, $vars) : $msg;
+    }
+
+    /**
+     * 魔法方法,获取文件的 hash 值
+     * @access public
+     * @param  string $method 方法名
+     * @param  mixed  $args   调用参数
+     * @return string
+     */
+    public function __call($method, $args)
+    {
+        return $this->hash($method);
+    }
+}

+ 148 - 0
thinkphp/library/think/Hook.php

@@ -0,0 +1,148 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+class Hook
+{
+    /**
+     * @var array 标签
+     */
+    private static $tags = [];
+
+    /**
+     * 动态添加行为扩展到某个标签
+     * @access public
+     * @param  string $tag      标签名称
+     * @param  mixed  $behavior 行为名称
+     * @param  bool   $first    是否放到开头执行
+     * @return void
+     */
+    public static function add($tag, $behavior, $first = false)
+    {
+        isset(self::$tags[$tag]) || self::$tags[$tag] = [];
+
+        if (is_array($behavior) && !is_callable($behavior)) {
+            if (!array_key_exists('_overlay', $behavior) || !$behavior['_overlay']) {
+                unset($behavior['_overlay']);
+                self::$tags[$tag] = array_merge(self::$tags[$tag], $behavior);
+            } else {
+                unset($behavior['_overlay']);
+                self::$tags[$tag] = $behavior;
+            }
+        } elseif ($first) {
+            array_unshift(self::$tags[$tag], $behavior);
+        } else {
+            self::$tags[$tag][] = $behavior;
+        }
+    }
+
+    /**
+     * 批量导入插件
+     * @access public
+     * @param  array   $tags      插件信息
+     * @param  boolean $recursive 是否递归合并
+     * @return void
+     */
+    public static function import(array $tags, $recursive = true)
+    {
+        if ($recursive) {
+            foreach ($tags as $tag => $behavior) {
+                self::add($tag, $behavior);
+            }
+        } else {
+            self::$tags = $tags + self::$tags;
+        }
+    }
+
+    /**
+     * 获取插件信息
+     * @access public
+     * @param  string $tag 插件位置(留空获取全部)
+     * @return array
+     */
+    public static function get($tag = '')
+    {
+        if (empty($tag)) {
+            return self::$tags;
+        }
+
+        return array_key_exists($tag, self::$tags) ? self::$tags[$tag] : [];
+    }
+
+    /**
+     * 监听标签的行为
+     * @access public
+     * @param  string $tag    标签名称
+     * @param  mixed  $params 传入参数
+     * @param  mixed  $extra  额外参数
+     * @param  bool   $once   只获取一个有效返回值
+     * @return mixed
+     */
+    public static function listen($tag, &$params = null, $extra = null, $once = false)
+    {
+        $results = [];
+
+        foreach (static::get($tag) as $key => $name) {
+            $results[$key] = self::exec($name, $tag, $params, $extra);
+
+            // 如果返回 false,或者仅获取一个有效返回则中断行为执行
+            if (false === $results[$key] || (!is_null($results[$key]) && $once)) {
+                break;
+            }
+        }
+
+        return $once ? end($results) : $results;
+    }
+
+    /**
+     * 执行某个行为
+     * @access public
+     * @param  mixed  $class  要执行的行为
+     * @param  string $tag    方法名(标签名)
+     * @param  mixed  $params 传人的参数
+     * @param  mixed  $extra  额外参数
+     * @return mixed
+     */
+    public static function exec($class, $tag = '', &$params = null, $extra = null)
+    {
+        App::$debug && Debug::remark('behavior_start', 'time');
+
+        $method = Loader::parseName($tag, 1, false);
+
+        if ($class instanceof \Closure) {
+            $result = call_user_func_array($class, [ & $params, $extra]);
+            $class  = 'Closure';
+        } elseif (is_array($class)) {
+            list($class, $method) = $class;
+
+            $result = (new $class())->$method($params, $extra);
+            $class  = $class . '->' . $method;
+        } elseif (is_object($class)) {
+            $result = $class->$method($params, $extra);
+            $class  = get_class($class);
+        } elseif (strpos($class, '::')) {
+            $result = call_user_func_array($class, [ & $params, $extra]);
+        } else {
+            $obj    = new $class();
+            $method = ($tag && is_callable([$obj, $method])) ? $method : 'run';
+            $result = $obj->$method($params, $extra);
+        }
+
+        if (App::$debug) {
+            Debug::remark('behavior_end', 'time');
+            Log::record('[ BEHAVIOR ] Run ' . $class . ' @' . $tag . ' [ RunTime:' . Debug::getRangeTime('behavior_start', 'behavior_end') . 's ]', 'info');
+        }
+
+        return $result;
+    }
+
+}

+ 265 - 0
thinkphp/library/think/Lang.php

@@ -0,0 +1,265 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+class Lang
+{
+    /**
+     * @var array 语言数据
+     */
+    private static $lang = [];
+
+    /**
+     * @var string 语言作用域
+     */
+    private static $range = 'zh-cn';
+
+    /**
+     * @var string 语言自动侦测的变量
+     */
+    protected static $langDetectVar = 'lang';
+
+    /**
+     * @var string 语言 Cookie 变量
+     */
+    protected static $langCookieVar = 'think_var';
+
+    /**
+     * @var int 语言 Cookie 的过期时间
+     */
+    protected static $langCookieExpire = 3600;
+
+    /**
+     * @var array 允许语言列表
+     */
+    protected static $allowLangList = [];
+
+    /**
+     * @var array Accept-Language 转义为对应语言包名称 系统默认配置
+     */
+    protected static $acceptLanguage = ['zh-hans-cn' => 'zh-cn'];
+
+    /**
+     * 设定当前的语言
+     * @access public
+     * @param  string $range 语言作用域
+     * @return string
+     */
+    public static function range($range = '')
+    {
+        if ($range) {
+            self::$range = $range;
+        }
+
+        return self::$range;
+    }
+
+    /**
+     * 设置语言定义(不区分大小写)
+     * @access public
+     * @param  string|array  $name  语言变量
+     * @param  string        $value 语言值
+     * @param  string        $range 语言作用域
+     * @return mixed
+     */
+    public static function set($name, $value = null, $range = '')
+    {
+        $range = $range ?: self::$range;
+
+        if (!isset(self::$lang[$range])) {
+            self::$lang[$range] = [];
+        }
+
+        if (is_array($name)) {
+            return self::$lang[$range] = array_change_key_case($name) + self::$lang[$range];
+        }
+
+        return self::$lang[$range][strtolower($name)] = $value;
+    }
+
+    /**
+     * 加载语言定义(不区分大小写)
+     * @access public
+     * @param  array|string $file 语言文件
+     * @param  string $range      语言作用域
+     * @return mixed
+     */
+    public static function load($file, $range = '')
+    {
+        $range = $range ?: self::$range;
+        $file  = is_string($file) ? [$file] : $file;
+
+        if (!isset(self::$lang[$range])) {
+            self::$lang[$range] = [];
+        }
+
+        $lang = [];
+
+        foreach ($file as $_file) {
+            if (is_file($_file)) {
+                // 记录加载信息
+                App::$debug && Log::record('[ LANG ] ' . $_file, 'info');
+
+                $_lang = include $_file;
+
+                if (is_array($_lang)) {
+                    $lang = array_change_key_case($_lang) + $lang;
+                }
+            }
+        }
+
+        if (!empty($lang)) {
+            self::$lang[$range] = $lang + self::$lang[$range];
+        }
+
+        return self::$lang[$range];
+    }
+
+    /**
+     * 获取语言定义(不区分大小写)
+     * @access public
+     * @param  string|null $name  语言变量
+     * @param  string      $range 语言作用域
+     * @return mixed
+     */
+    public static function has($name, $range = '')
+    {
+        $range = $range ?: self::$range;
+
+        return isset(self::$lang[$range][strtolower($name)]);
+    }
+
+    /**
+     * 获取语言定义(不区分大小写)
+     * @access public
+     * @param  string|null $name  语言变量
+     * @param  array       $vars  变量替换
+     * @param  string      $range 语言作用域
+     * @return mixed
+     */
+    public static function get($name = null, $vars = [], $range = '')
+    {
+        $range = $range ?: self::$range;
+
+        // 空参数返回所有定义
+        if (empty($name)) {
+            return self::$lang[$range];
+        }
+
+        $key   = strtolower($name);
+        $value = isset(self::$lang[$range][$key]) ? self::$lang[$range][$key] : $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
+     * @return string
+     */
+    public static function detect()
+    {
+        $langSet = '';
+
+        if (isset($_GET[self::$langDetectVar])) {
+            // url 中设置了语言变量
+            $langSet = strtolower($_GET[self::$langDetectVar]);
+        } elseif (isset($_COOKIE[self::$langCookieVar])) {
+            // Cookie 中设置了语言变量
+            $langSet = strtolower($_COOKIE[self::$langCookieVar]);
+        } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
+            // 自动侦测浏览器语言
+            preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches);
+            $langSet     = strtolower($matches[1]);
+            $acceptLangs = Config::get('header_accept_lang');
+
+            if (isset($acceptLangs[$langSet])) {
+                $langSet = $acceptLangs[$langSet];
+            } elseif (isset(self::$acceptLanguage[$langSet])) {
+                $langSet = self::$acceptLanguage[$langSet];
+            }
+        }
+
+        // 合法的语言
+        if (empty(self::$allowLangList) || in_array($langSet, self::$allowLangList)) {
+            self::$range = $langSet ?: self::$range;
+        }
+
+        return self::$range;
+    }
+
+    /**
+     * 设置语言自动侦测的变量
+     * @access public
+     * @param  string $var 变量名称
+     * @return void
+     */
+    public static function setLangDetectVar($var)
+    {
+        self::$langDetectVar = $var;
+    }
+
+    /**
+     * 设置语言的 cookie 保存变量
+     * @access public
+     * @param  string $var 变量名称
+     * @return void
+     */
+    public static function setLangCookieVar($var)
+    {
+        self::$langCookieVar = $var;
+    }
+
+    /**
+     * 设置语言的 cookie 的过期时间
+     * @access public
+     * @param  string $expire 过期时间
+     * @return void
+     */
+    public static function setLangCookieExpire($expire)
+    {
+        self::$langCookieExpire = $expire;
+    }
+
+    /**
+     * 设置允许的语言列表
+     * @access public
+     * @param  array $list 语言列表
+     * @return void
+     */
+    public static function setAllowLangList($list)
+    {
+        self::$allowLangList = $list;
+    }
+}

+ 677 - 0
thinkphp/library/think/Loader.php

@@ -0,0 +1,677 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\exception\ClassNotFoundException;
+
+class Loader
+{
+    /**
+     * @var array 实例数组
+     */
+    protected static $instance = [];
+
+    /**
+     * @var array 类名映射
+     */
+    protected static $classMap = [];
+
+    /**
+     * @var array 命名空间别名
+     */
+    protected static $namespaceAlias = [];
+
+    /**
+     * @var array PSR-4 命名空间前缀长度映射
+     */
+    private static $prefixLengthsPsr4 = [];
+
+    /**
+     * @var array PSR-4 的加载目录
+     */
+    private static $prefixDirsPsr4 = [];
+
+    /**
+     * @var array PSR-4 加载失败的回退目录
+     */
+    private static $fallbackDirsPsr4 = [];
+
+    /**
+     * @var array PSR-0 命名空间前缀映射
+     */
+    private static $prefixesPsr0 = [];
+
+    /**
+     * @var array PSR-0 加载失败的回退目录
+     */
+    private static $fallbackDirsPsr0 = [];
+
+    /**
+     * @var array 需要加载的文件
+     */
+    private static $files = [];
+
+    /**
+     * 自动加载
+     * @access public
+     * @param  string $class 类名
+     * @return bool
+     */
+    public static function autoload($class)
+    {
+        // 检测命名空间别名
+        if (!empty(self::$namespaceAlias)) {
+            $namespace = dirname($class);
+            if (isset(self::$namespaceAlias[$namespace])) {
+                $original = self::$namespaceAlias[$namespace] . '\\' . basename($class);
+                if (class_exists($original)) {
+                    return class_alias($original, $class, false);
+                }
+            }
+        }
+
+        if ($file = self::findFile($class)) {
+            // 非 Win 环境不严格区分大小写
+            if (!IS_WIN || pathinfo($file, PATHINFO_FILENAME) == pathinfo(realpath($file), PATHINFO_FILENAME)) {
+                __include_file($file);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 查找文件
+     * @access private
+     * @param  string $class 类名
+     * @return bool|string
+     */
+    private static function findFile($class)
+    {
+        // 类库映射
+        if (!empty(self::$classMap[$class])) {
+            return self::$classMap[$class];
+        }
+
+        // 查找 PSR-4
+        $logicalPathPsr4 = strtr($class, '\\', DS) . EXT;
+        $first           = $class[0];
+
+        if (isset(self::$prefixLengthsPsr4[$first])) {
+            foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) {
+                if (0 === strpos($class, $prefix)) {
+                    foreach (self::$prefixDirsPsr4[$prefix] as $dir) {
+                        if (is_file($file = $dir . DS . substr($logicalPathPsr4, $length))) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // 查找 PSR-4 fallback dirs
+        foreach (self::$fallbackDirsPsr4 as $dir) {
+            if (is_file($file = $dir . DS . $logicalPathPsr4)) {
+                return $file;
+            }
+        }
+
+        // 查找 PSR-0
+        if (false !== $pos = strrpos($class, '\\')) {
+            // namespace class name
+            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+            . strtr(substr($logicalPathPsr4, $pos + 1), '_', DS);
+        } else {
+            // PEAR-like class name
+            $logicalPathPsr0 = strtr($class, '_', DS) . EXT;
+        }
+
+        if (isset(self::$prefixesPsr0[$first])) {
+            foreach (self::$prefixesPsr0[$first] as $prefix => $dirs) {
+                if (0 === strpos($class, $prefix)) {
+                    foreach ($dirs as $dir) {
+                        if (is_file($file = $dir . DS . $logicalPathPsr0)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // 查找 PSR-0 fallback dirs
+        foreach (self::$fallbackDirsPsr0 as $dir) {
+            if (is_file($file = $dir . DS . $logicalPathPsr0)) {
+                return $file;
+            }
+        }
+
+        // 找不到则设置映射为 false 并返回
+        return self::$classMap[$class] = false;
+    }
+
+    /**
+     * 注册 classmap
+     * @access public
+     * @param  string|array $class 类名
+     * @param  string       $map   映射
+     * @return void
+     */
+    public static function addClassMap($class, $map = '')
+    {
+        if (is_array($class)) {
+            self::$classMap = array_merge(self::$classMap, $class);
+        } else {
+            self::$classMap[$class] = $map;
+        }
+    }
+
+    /**
+     * 注册命名空间
+     * @access public
+     * @param  string|array $namespace 命名空间
+     * @param  string       $path      路径
+     * @return void
+     */
+    public static function addNamespace($namespace, $path = '')
+    {
+        if (is_array($namespace)) {
+            foreach ($namespace as $prefix => $paths) {
+                self::addPsr4($prefix . '\\', rtrim($paths, DS), true);
+            }
+        } else {
+            self::addPsr4($namespace . '\\', rtrim($path, DS), true);
+        }
+    }
+
+    /**
+     * 添加 PSR-0 命名空间
+     * @access private
+     * @param  array|string $prefix  空间前缀
+     * @param  array        $paths   路径
+     * @param  bool         $prepend 预先设置的优先级更高
+     * @return void
+     */
+    private static function addPsr0($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            self::$fallbackDirsPsr0 = $prepend ?
+            array_merge((array) $paths, self::$fallbackDirsPsr0) :
+            array_merge(self::$fallbackDirsPsr0, (array) $paths);
+        } else {
+            $first = $prefix[0];
+
+            if (!isset(self::$prefixesPsr0[$first][$prefix])) {
+                self::$prefixesPsr0[$first][$prefix] = (array) $paths;
+            } else {
+                self::$prefixesPsr0[$first][$prefix] = $prepend ?
+                array_merge((array) $paths, self::$prefixesPsr0[$first][$prefix]) :
+                array_merge(self::$prefixesPsr0[$first][$prefix], (array) $paths);
+            }
+        }
+    }
+
+    /**
+     * 添加 PSR-4 空间
+     * @access private
+     * @param  array|string $prefix  空间前缀
+     * @param  string       $paths   路径
+     * @param  bool         $prepend 预先设置的优先级更高
+     * @return void
+     */
+    private static function addPsr4($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            // Register directories for the root namespace.
+            self::$fallbackDirsPsr4 = $prepend ?
+            array_merge((array) $paths, self::$fallbackDirsPsr4) :
+            array_merge(self::$fallbackDirsPsr4, (array) $paths);
+
+        } elseif (!isset(self::$prefixDirsPsr4[$prefix])) {
+            // Register directories for a new namespace.
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException(
+                    "A non-empty PSR-4 prefix must end with a namespace separator."
+                );
+            }
+
+            self::$prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            self::$prefixDirsPsr4[$prefix]                = (array) $paths;
+
+        } else {
+            self::$prefixDirsPsr4[$prefix] = $prepend ?
+            // Prepend directories for an already registered namespace.
+            array_merge((array) $paths, self::$prefixDirsPsr4[$prefix]) :
+            // Append directories for an already registered namespace.
+            array_merge(self::$prefixDirsPsr4[$prefix], (array) $paths);
+        }
+    }
+
+    /**
+     * 注册命名空间别名
+     * @access public
+     * @param  array|string $namespace 命名空间
+     * @param  string       $original  源文件
+     * @return void
+     */
+    public static function addNamespaceAlias($namespace, $original = '')
+    {
+        if (is_array($namespace)) {
+            self::$namespaceAlias = array_merge(self::$namespaceAlias, $namespace);
+        } else {
+            self::$namespaceAlias[$namespace] = $original;
+        }
+    }
+
+    /**
+     * 注册自动加载机制
+     * @access public
+     * @param  callable $autoload 自动加载处理方法
+     * @return void
+     */
+    public static function register($autoload = null)
+    {
+        // 注册系统自动加载
+        spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);
+
+        // Composer 自动加载支持
+        if (is_dir(VENDOR_PATH . 'composer')) {
+            if (PHP_VERSION_ID >= 50600 && is_file(VENDOR_PATH . 'composer' . DS . 'autoload_static.php')) {
+                require VENDOR_PATH . 'composer' . DS . 'autoload_static.php';
+
+                $declaredClass = get_declared_classes();
+                $composerClass = array_pop($declaredClass);
+
+                foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) {
+                    if (property_exists($composerClass, $attr)) {
+                        self::${$attr} = $composerClass::${$attr};
+                    }
+                }
+            } else {
+                self::registerComposerLoader();
+            }
+        }
+
+        // 注册命名空间定义
+        self::addNamespace([
+            'think'    => LIB_PATH . 'think' . DS,
+            'behavior' => LIB_PATH . 'behavior' . DS,
+            'traits'   => LIB_PATH . 'traits' . DS,
+        ]);
+
+        // 加载类库映射文件
+        if (is_file(RUNTIME_PATH . 'classmap' . EXT)) {
+            self::addClassMap(__include_file(RUNTIME_PATH . 'classmap' . EXT));
+        }
+
+        self::loadComposerAutoloadFiles();
+
+        // 自动加载 extend 目录
+        self::$fallbackDirsPsr4[] = rtrim(EXTEND_PATH, DS);
+    }
+
+    /**
+     * 注册 composer 自动加载
+     * @access private
+     * @return void
+     */
+    private static function registerComposerLoader()
+    {
+        if (is_file(VENDOR_PATH . 'composer/autoload_namespaces.php')) {
+            $map = require VENDOR_PATH . 'composer/autoload_namespaces.php';
+            foreach ($map as $namespace => $path) {
+                self::addPsr0($namespace, $path);
+            }
+        }
+
+        if (is_file(VENDOR_PATH . 'composer/autoload_psr4.php')) {
+            $map = require VENDOR_PATH . 'composer/autoload_psr4.php';
+            foreach ($map as $namespace => $path) {
+                self::addPsr4($namespace, $path);
+            }
+        }
+
+        if (is_file(VENDOR_PATH . 'composer/autoload_classmap.php')) {
+            $classMap = require VENDOR_PATH . 'composer/autoload_classmap.php';
+            if ($classMap) {
+                self::addClassMap($classMap);
+            }
+        }
+
+        if (is_file(VENDOR_PATH . 'composer/autoload_files.php')) {
+            self::$files = require VENDOR_PATH . 'composer/autoload_files.php';
+        }
+    }
+
+    // 加载composer autofile文件
+    public static function loadComposerAutoloadFiles()
+    {
+        foreach (self::$files as $fileIdentifier => $file) {
+            if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+                __require_file($file);
+
+                $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+            }
+        }
+    }
+
+    /**
+     * 导入所需的类库 同 Java 的 Import 本函数有缓存功能
+     * @access public
+     * @param  string $class   类库命名空间字符串
+     * @param  string $baseUrl 起始路径
+     * @param  string $ext     导入的文件扩展名
+     * @return bool
+     */
+    public static function import($class, $baseUrl = '', $ext = EXT)
+    {
+        static $_file = [];
+        $key          = $class . $baseUrl;
+        $class        = str_replace(['.', '#'], [DS, '.'], $class);
+
+        if (isset($_file[$key])) {
+            return true;
+        }
+
+        if (empty($baseUrl)) {
+            list($name, $class) = explode(DS, $class, 2);
+
+            if (isset(self::$prefixDirsPsr4[$name . '\\'])) {
+                // 注册的命名空间
+                $baseUrl = self::$prefixDirsPsr4[$name . '\\'];
+            } elseif ('@' == $name) {
+                // 加载当前模块应用类库
+                $baseUrl = App::$modulePath;
+            } elseif (is_dir(EXTEND_PATH . $name)) {
+                $baseUrl = EXTEND_PATH . $name . DS;
+            } else {
+                // 加载其它模块的类库
+                $baseUrl = APP_PATH . $name . DS;
+            }
+        } elseif (substr($baseUrl, -1) != DS) {
+            $baseUrl .= DS;
+        }
+
+        // 如果类存在则导入类库文件
+        if (is_array($baseUrl)) {
+            foreach ($baseUrl as $path) {
+                if (is_file($filename = $path . DS . $class . $ext)) {
+                    break;
+                }
+            }
+        } else {
+            $filename = $baseUrl . $class . $ext;
+        }
+
+        if (!empty($filename) &&
+            is_file($filename) &&
+            (!IS_WIN || pathinfo($filename, PATHINFO_FILENAME) == pathinfo(realpath($filename), PATHINFO_FILENAME))
+        ) {
+            __include_file($filename);
+            $_file[$key] = true;
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * 实例化(分层)模型
+     * @access public
+     * @param  string $name         Model名称
+     * @param  string $layer        业务层名称
+     * @param  bool   $appendSuffix 是否添加类名后缀
+     * @param  string $common       公共模块名
+     * @return object
+     * @throws ClassNotFoundException
+     */
+    public static function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common')
+    {
+        $uid = $name . $layer;
+
+        if (isset(self::$instance[$uid])) {
+            return self::$instance[$uid];
+        }
+
+        list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix);
+
+        if (class_exists($class)) {
+            $model = new $class();
+        } else {
+            $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class);
+
+            if (class_exists($class)) {
+                $model = new $class();
+            } else {
+                throw new ClassNotFoundException('class not exists:' . $class, $class);
+            }
+        }
+
+        return self::$instance[$uid] = $model;
+    }
+
+    /**
+     * 实例化(分层)控制器 格式:[模块名/]控制器名
+     * @access public
+     * @param  string $name         资源地址
+     * @param  string $layer        控制层名称
+     * @param  bool   $appendSuffix 是否添加类名后缀
+     * @param  string $empty        空控制器名称
+     * @return object
+     * @throws ClassNotFoundException
+     */
+    public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')
+    {
+        list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix);
+
+        if (class_exists($class)) {
+            return App::invokeClass($class);
+        }
+
+        if ($empty) {
+            $emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix);
+
+            if (class_exists($emptyClass)) {
+                return new $emptyClass(Request::instance());
+            }
+        }
+
+        throw new ClassNotFoundException('class not exists:' . $class, $class);
+    }
+
+    /**
+     * 实例化验证类 格式:[模块名/]验证器名
+     * @access public
+     * @param  string $name         资源地址
+     * @param  string $layer        验证层名称
+     * @param  bool   $appendSuffix 是否添加类名后缀
+     * @param  string $common       公共模块名
+     * @return object|false
+     * @throws ClassNotFoundException
+     */
+    public static function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common')
+    {
+        $name = $name ?: Config::get('default_validate');
+
+        if (empty($name)) {
+            return new Validate;
+        }
+
+        $uid = $name . $layer;
+        if (isset(self::$instance[$uid])) {
+            return self::$instance[$uid];
+        }
+
+        list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix);
+
+        if (class_exists($class)) {
+            $validate = new $class;
+        } else {
+            $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class);
+
+            if (class_exists($class)) {
+                $validate = new $class;
+            } else {
+                throw new ClassNotFoundException('class not exists:' . $class, $class);
+            }
+        }
+
+        return self::$instance[$uid] = $validate;
+    }
+
+    /**
+     * 解析模块和类名
+     * @access protected
+     * @param  string $name         资源地址
+     * @param  string $layer        验证层名称
+     * @param  bool   $appendSuffix 是否添加类名后缀
+     * @return array
+     */
+    protected static function getModuleAndClass($name, $layer, $appendSuffix)
+    {
+        if (false !== strpos($name, '\\')) {
+            $module = Request::instance()->module();
+            $class  = $name;
+        } else {
+            if (strpos($name, '/')) {
+                list($module, $name) = explode('/', $name, 2);
+            } else {
+                $module = Request::instance()->module();
+            }
+
+            $class = self::parseClass($module, $layer, $name, $appendSuffix);
+        }
+
+        return [$module, $class];
+    }
+
+    /**
+     * 数据库初始化 并取得数据库类实例
+     * @access public
+     * @param  mixed       $config 数据库配置
+     * @param  bool|string $name   连接标识 true 强制重新连接
+     * @return \think\db\Connection
+     */
+    public static function db($config = [], $name = false)
+    {
+        return Db::connect($config, $name);
+    }
+
+    /**
+     * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作
+     * @access public
+     * @param  string       $url          调用地址
+     * @param  string|array $vars         调用参数 支持字符串和数组
+     * @param  string       $layer        要调用的控制层名称
+     * @param  bool         $appendSuffix 是否添加类名后缀
+     * @return mixed
+     */
+    public static function action($url, $vars = [], $layer = 'controller', $appendSuffix = false)
+    {
+        $info   = pathinfo($url);
+        $action = $info['basename'];
+        $module = '.' != $info['dirname'] ? $info['dirname'] : Request::instance()->controller();
+        $class  = self::controller($module, $layer, $appendSuffix);
+
+        if ($class) {
+            if (is_scalar($vars)) {
+                if (strpos($vars, '=')) {
+                    parse_str($vars, $vars);
+                } else {
+                    $vars = [$vars];
+                }
+            }
+
+            return App::invokeMethod([$class, $action . Config::get('action_suffix')], $vars);
+        }
+
+        return false;
+    }
+
+    /**
+     * 字符串命名风格转换
+     * type 0 将 Java 风格转换为 C 的风格 1 将 C 风格转换为 Java 的风格
+     * @access public
+     * @param  string  $name    字符串
+     * @param  integer $type    转换类型
+     * @param  bool    $ucfirst 首字母是否大写(驼峰规则)
+     * @return string
+     */
+    public static function parseName($name, $type = 0, $ucfirst = true)
+    {
+        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), "_"));
+    }
+
+    /**
+     * 解析应用类的类名
+     * @access public
+     * @param  string $module       模块名
+     * @param  string $layer        层名 controller model ...
+     * @param  string $name         类名
+     * @param  bool   $appendSuffix 是否添加类名后缀
+     * @return string
+     */
+    public static function parseClass($module, $layer, $name, $appendSuffix = false)
+    {
+
+        $array = explode('\\', str_replace(['/', '.'], '\\', $name));
+        $class = self::parseName(array_pop($array), 1);
+        $class = $class . (App::$suffix || $appendSuffix ? ucfirst($layer) : '');
+        $path  = $array ? implode('\\', $array) . '\\' : '';
+
+        return App::$namespace . '\\' .
+            ($module ? $module . '\\' : '') .
+            $layer . '\\' . $path . $class;
+    }
+
+    /**
+     * 初始化类的实例
+     * @access public
+     * @return void
+     */
+    public static function clearInstance()
+    {
+        self::$instance = [];
+    }
+}
+
+// 作用范围隔离
+
+/**
+ * include
+ * @param  string $file 文件路径
+ * @return mixed
+ */
+function __include_file($file)
+{
+    return include $file;
+}
+
+/**
+ * require
+ * @param  string $file 文件路径
+ * @return mixed
+ */
+function __require_file($file)
+{
+    return require $file;
+}

+ 237 - 0
thinkphp/library/think/Log.php

@@ -0,0 +1,237 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\exception\ClassNotFoundException;
+
+/**
+ * Class Log
+ * @package think
+ *
+ * @method void log($msg) static 记录一般日志
+ * @method void error($msg) static 记录错误日志
+ * @method void info($msg) static 记录一般信息日志
+ * @method void sql($msg) static 记录 SQL 查询日志
+ * @method void notice($msg) static 记录提示日志
+ * @method void alert($msg) static 记录报警日志
+ */
+class Log
+{
+    const LOG    = 'log';
+    const ERROR  = 'error';
+    const INFO   = 'info';
+    const SQL    = 'sql';
+    const NOTICE = 'notice';
+    const ALERT  = 'alert';
+    const DEBUG  = 'debug';
+
+    /**
+     * @var array 日志信息
+     */
+    protected static $log = [];
+
+    /**
+     * @var array 配置参数
+     */
+    protected static $config = [];
+
+    /**
+     * @var array 日志类型
+     */
+    protected static $type = ['log', 'error', 'info', 'sql', 'notice', 'alert', 'debug'];
+
+    /**
+     * @var log\driver\File|log\driver\Test|log\driver\Socket 日志写入驱动
+     */
+    protected static $driver;
+
+    /**
+     * @var string 当前日志授权 key
+     */
+    protected static $key;
+
+    /**
+     * 日志初始化
+     * @access public
+     * @param  array $config 配置参数
+     * @return void
+     */
+    public static function init($config = [])
+    {
+        $type  = isset($config['type']) ? $config['type'] : 'File';
+        $class = false !== strpos($type, '\\') ? $type : '\\think\\log\\driver\\' . ucwords($type);
+
+        self::$config = $config;
+        unset($config['type']);
+
+        if (class_exists($class)) {
+            self::$driver = new $class($config);
+        } else {
+            throw new ClassNotFoundException('class not exists:' . $class, $class);
+        }
+
+        // 记录初始化信息
+        App::$debug && Log::record('[ LOG ] INIT ' . $type, 'info');
+    }
+
+    /**
+     * 获取日志信息
+     * @access public
+     * @param  string $type 信息类型
+     * @return array|string
+     */
+    public static function getLog($type = '')
+    {
+        return $type ? self::$log[$type] : self::$log;
+    }
+
+    /**
+     * 记录调试信息
+     * @access public
+     * @param  mixed  $msg  调试信息
+     * @param  string $type 信息类型
+     * @return void
+     */
+    public static function record($msg, $type = 'log')
+    {
+        self::$log[$type][] = $msg;
+
+        // 命令行下面日志写入改进
+        IS_CLI && self::save();
+    }
+
+    /**
+     * 清空日志信息
+     * @access public
+     * @return void
+     */
+    public static function clear()
+    {
+        self::$log = [];
+    }
+
+    /**
+     * 设置当前日志记录的授权 key
+     * @access public
+     * @param  string $key 授权 key
+     * @return void
+     */
+    public static function key($key)
+    {
+        self::$key = $key;
+    }
+
+    /**
+     * 检查日志写入权限
+     * @access public
+     * @param  array $config 当前日志配置参数
+     * @return bool
+     */
+    public static function check($config)
+    {
+        return !self::$key || empty($config['allow_key']) || in_array(self::$key, $config['allow_key']);
+    }
+
+    /**
+     * 保存调试信息
+     * @access public
+     * @return bool
+     */
+    public static function save()
+    {
+        // 没有需要保存的记录则直接返回
+        if (empty(self::$log)) {
+            return true;
+        }
+
+        is_null(self::$driver) && self::init(Config::get('log'));
+
+        // 检测日志写入权限
+        if (!self::check(self::$config)) {
+            return false;
+        }
+
+        if (empty(self::$config['level'])) {
+            // 获取全部日志
+            $log = self::$log;
+            if (!App::$debug && isset($log['debug'])) {
+                unset($log['debug']);
+            }
+        } else {
+            // 记录允许级别
+            $log = [];
+            foreach (self::$config['level'] as $level) {
+                if (isset(self::$log[$level])) {
+                    $log[$level] = self::$log[$level];
+                }
+            }
+        }
+
+        if ($result = self::$driver->save($log, true)) {
+            self::$log = [];
+        }
+
+        Hook::listen('log_write_done', $log);
+
+        return $result;
+    }
+
+    /**
+     * 实时写入日志信息 并支持行为
+     * @access public
+     * @param  mixed  $msg   调试信息
+     * @param  string $type  信息类型
+     * @param  bool   $force 是否强制写入
+     * @return bool
+     */
+    public static function write($msg, $type = 'log', $force = false)
+    {
+        $log = self::$log;
+
+        // 如果不是强制写入,而且信息类型不在可记录的类别中则直接返回 false 不做记录
+        if (true !== $force && !empty(self::$config['level']) && !in_array($type, self::$config['level'])) {
+            return false;
+        }
+
+        // 封装日志信息
+        $log[$type][] = $msg;
+
+        // 监听 log_write
+        Hook::listen('log_write', $log);
+
+        is_null(self::$driver) && self::init(Config::get('log'));
+
+        // 写入日志
+        if ($result = self::$driver->save($log, false)) {
+            self::$log = [];
+        }
+
+        return $result;
+    }
+
+    /**
+     * 静态方法调用
+     * @access public
+     * @param  string $method 调用方法
+     * @param  mixed  $args   参数
+     * @return void
+     */
+    public static function __callStatic($method, $args)
+    {
+        if (in_array($method, self::$type)) {
+            array_push($args, $method);
+
+            call_user_func_array('\\think\\Log::record', $args);
+        }
+    }
+
+}

+ 2350 - 0
thinkphp/library/think/Model.php

@@ -0,0 +1,2350 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use BadMethodCallException;
+use InvalidArgumentException;
+use think\db\Query;
+use think\exception\ValidateException;
+use think\model\Collection as ModelCollection;
+use think\model\Relation;
+use think\model\relation\BelongsTo;
+use think\model\relation\BelongsToMany;
+use think\model\relation\HasMany;
+use think\model\relation\HasManyThrough;
+use think\model\relation\HasOne;
+use think\model\relation\MorphMany;
+use think\model\relation\MorphOne;
+use think\model\relation\MorphTo;
+
+/**
+ * Class Model
+ * @package think
+ * @mixin Query
+ */
+abstract class Model implements \JsonSerializable, \ArrayAccess
+{
+    // 数据库查询对象池
+    protected static $links = [];
+    // 数据库配置
+    protected $connection = [];
+    // 父关联模型对象
+    protected $parent;
+    // 数据库查询对象
+    protected $query;
+    // 当前模型名称
+    protected $name;
+    // 数据表名称
+    protected $table;
+    // 当前类名称
+    protected $class;
+    // 回调事件
+    private static $event = [];
+    // 错误信息
+    protected $error;
+    // 字段验证规则
+    protected $validate;
+    // 数据表主键 复合主键使用数组定义 不设置则自动获取
+    protected $pk;
+    // 数据表字段信息 留空则自动获取
+    protected $field = [];
+    // 数据排除字段
+    protected $except = [];
+    // 数据废弃字段
+    protected $disuse = [];
+    // 只读字段
+    protected $readonly = [];
+    // 显示属性
+    protected $visible = [];
+    // 隐藏属性
+    protected $hidden = [];
+    // 追加属性
+    protected $append = [];
+    // 数据信息
+    protected $data = [];
+    // 原始数据
+    protected $origin = [];
+    // 关联模型
+    protected $relation = [];
+
+    // 保存自动完成列表
+    protected $auto = [];
+    // 新增自动完成列表
+    protected $insert = [];
+    // 更新自动完成列表
+    protected $update = [];
+    // 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型
+    protected $autoWriteTimestamp;
+    // 创建时间字段
+    protected $createTime = 'createtime';
+    // 更新时间字段
+    protected $updateTime = 'updatetime';
+    // 时间字段取出后的默认时间格式
+    protected $dateFormat;
+    // 字段类型或者格式转换
+    protected $type = [];
+    // 是否为更新数据
+    protected $isUpdate = false;
+    // 是否使用Replace
+    protected $replace = false;
+    // 是否强制更新所有数据
+    protected $force = false;
+    // 更新条件
+    protected $updateWhere;
+    // 验证失败是否抛出异常
+    protected $failException = false;
+    // 全局查询范围
+    protected $useGlobalScope = true;
+    // 是否采用批量验证
+    protected $batchValidate = false;
+    // 查询数据集对象
+    protected $resultSetType;
+    // 关联自动写入
+    protected $relationWrite;
+
+    /**
+     * 初始化过的模型.
+     *
+     * @var array
+     */
+    protected static $initialized = [];
+
+    /**
+     * 是否从主库读取(主从分布式有效)
+     * @var array
+     */
+    protected static $readMaster;
+
+    /**
+     * 构造方法
+     * @access public
+     * @param array|object $data 数据
+     */
+    public function __construct($data = [])
+    {
+        if (is_object($data)) {
+            $this->data = get_object_vars($data);
+        } else {
+            $this->data = $data;
+        }
+
+        if ($this->disuse) {
+            // 废弃字段
+            foreach ((array) $this->disuse as $key) {
+                if (array_key_exists($key, $this->data)) {
+                    unset($this->data[$key]);
+                }
+            }
+        }
+
+        // 记录原始数据
+        $this->origin = $this->data;
+
+        // 当前类名
+        $this->class = get_called_class();
+
+        if (empty($this->name)) {
+            // 当前模型名
+            $name       = str_replace('\\', '/', $this->class);
+            $this->name = basename($name);
+            if (Config::get('class_suffix')) {
+                $suffix     = basename(dirname($name));
+                $this->name = substr($this->name, 0, -strlen($suffix));
+            }
+        }
+
+        if (is_null($this->autoWriteTimestamp)) {
+            // 自动写入时间戳
+            $this->autoWriteTimestamp = $this->getQuery()->getConfig('auto_timestamp');
+        }
+
+        if (is_null($this->dateFormat)) {
+            // 设置时间戳格式
+            $this->dateFormat = $this->getQuery()->getConfig('datetime_format');
+        }
+
+        if (is_null($this->resultSetType)) {
+            $this->resultSetType = $this->getQuery()->getConfig('resultset_type');
+        }
+        // 执行初始化操作
+        $this->initialize();
+    }
+
+    /**
+     * 是否从主库读取数据(主从分布有效)
+     * @access public
+     * @param  bool     $all 是否所有模型生效
+     * @return $this
+     */
+    public function readMaster($all = false)
+    {
+        $model = $all ? '*' : $this->class;
+
+        static::$readMaster[$model] = true;
+        return $this;
+    }
+
+    /**
+     * 创建模型的查询对象
+     * @access protected
+     * @return Query
+     */
+    protected function buildQuery()
+    {
+        // 合并数据库配置
+        if (!empty($this->connection)) {
+            if (is_array($this->connection)) {
+                $connection = array_merge(Config::get('database'), $this->connection);
+            } else {
+                $connection = $this->connection;
+            }
+        } else {
+            $connection = [];
+        }
+
+        $con = Db::connect($connection);
+        // 设置当前模型 确保查询返回模型对象
+        $queryClass = $this->query ?: $con->getConfig('query');
+        $query      = new $queryClass($con, $this);
+
+        if (isset(static::$readMaster['*']) || isset(static::$readMaster[$this->class])) {
+            $query->master(true);
+        }
+
+        // 设置当前数据表和模型名
+        if (!empty($this->table)) {
+            $query->setTable($this->table);
+        } else {
+            $query->name($this->name);
+        }
+
+        if (!empty($this->pk)) {
+            $query->pk($this->pk);
+        }
+
+        return $query;
+    }
+
+    /**
+     * 创建新的模型实例
+     * @access public
+     * @param  array|object $data 数据
+     * @param  bool         $isUpdate 是否为更新
+     * @param  mixed        $where 更新条件
+     * @return Model
+     */
+    public function newInstance($data = [], $isUpdate = false, $where = null)
+    {
+        return (new static($data))->isUpdate($isUpdate, $where);
+    }
+
+    /**
+     * 获取当前模型的查询对象
+     * @access public
+     * @param bool      $buildNewQuery  创建新的查询对象
+     * @return Query
+     */
+    public function getQuery($buildNewQuery = false)
+    {
+        if ($buildNewQuery) {
+            return $this->buildQuery();
+        } elseif (!isset(self::$links[$this->class])) {
+            // 创建模型查询对象
+            self::$links[$this->class] = $this->buildQuery();
+        }
+
+        return self::$links[$this->class];
+    }
+
+    /**
+     * 获取当前模型的数据库查询对象
+     * @access public
+     * @param bool $useBaseQuery 是否调用全局查询范围
+     * @param bool $buildNewQuery 创建新的查询对象
+     * @return Query
+     */
+    public function db($useBaseQuery = true, $buildNewQuery = true)
+    {
+        $query = $this->getQuery($buildNewQuery);
+
+        // 全局作用域
+        if ($useBaseQuery && method_exists($this, 'base')) {
+            call_user_func_array([$this, 'base'], [ & $query]);
+        }
+
+        // 返回当前模型的数据库查询对象
+        return $query;
+    }
+
+    /**
+     *  初始化模型
+     * @access protected
+     * @return void
+     */
+    protected function initialize()
+    {
+        $class = get_class($this);
+        if (!isset(static::$initialized[$class])) {
+            static::$initialized[$class] = true;
+            static::init();
+        }
+    }
+
+    /**
+     * 初始化处理
+     * @access protected
+     * @return void
+     */
+    protected static function init()
+    {
+    }
+
+    /**
+     * 设置父关联对象
+     * @access public
+     * @param Model $model  模型对象
+     * @return $this
+     */
+    public function setParent($model)
+    {
+        $this->parent = $model;
+        return $this;
+    }
+
+    /**
+     * 获取父关联对象
+     * @access public
+     * @return Model
+     */
+    public function getParent()
+    {
+        return $this->parent;
+    }
+
+    /**
+     * 设置数据对象值
+     * @access public
+     * @param mixed $data  数据或者属性名
+     * @param mixed $value 值
+     * @return $this
+     */
+    public function data($data, $value = null)
+    {
+        if (is_string($data)) {
+            $this->data[$data] = $value;
+        } else {
+            // 清空数据
+            $this->data = [];
+            if (is_object($data)) {
+                $data = get_object_vars($data);
+            }
+            if (true === $value) {
+                // 数据对象赋值
+                foreach ($data as $key => $value) {
+                    $this->setAttr($key, $value, $data);
+                }
+            } else {
+                $this->data = $data;
+            }
+        }
+        return $this;
+    }
+
+    /**
+     * 获取对象原始数据 如果不存在指定字段返回false
+     * @access public
+     * @param string $name 字段名 留空获取全部
+     * @return mixed
+     * @throws InvalidArgumentException
+     */
+    public function getData($name = null)
+    {
+        if (is_null($name)) {
+            return $this->data;
+        } elseif (array_key_exists($name, $this->data)) {
+            return $this->data[$name];
+        } elseif (array_key_exists($name, $this->relation)) {
+            return $this->relation[$name];
+        } else {
+            throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name);
+        }
+    }
+
+    /**
+     * 是否需要自动写入时间字段
+     * @access public
+     * @param bool $auto
+     * @return $this
+     */
+    public function isAutoWriteTimestamp($auto)
+    {
+        $this->autoWriteTimestamp = $auto;
+        return $this;
+    }
+
+    /**
+     * 更新是否强制写入数据 而不做比较
+     * @access public
+     * @param bool $force
+     * @return $this
+     */
+    public function force($force = true)
+    {
+        $this->force = $force;
+        return $this;
+    }
+
+    /**
+     * 修改器 设置数据对象值
+     * @access public
+     * @param string $name  属性名
+     * @param mixed  $value 属性值
+     * @param array  $data  数据
+     * @return $this
+     */
+    public function setAttr($name, $value, $data = [])
+    {
+        if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) {
+            // 自动写入的时间戳字段
+            $value = $this->autoWriteTimestamp($name);
+        } else {
+            // 检测修改器
+            $method = 'set' . Loader::parseName($name, 1) . 'Attr';
+            if (method_exists($this, $method)) {
+                $value = $this->$method($value, array_merge($this->data, $data), $this->relation);
+            } elseif (isset($this->type[$name])) {
+                // 类型转换
+                $value = $this->writeTransform($value, $this->type[$name]);
+            }
+        }
+
+        // 设置数据对象属性
+        $this->data[$name] = $value;
+        return $this;
+    }
+
+    /**
+     * 获取当前模型的关联模型数据
+     * @access public
+     * @param string $name 关联方法名
+     * @return mixed
+     */
+    public function getRelation($name = null)
+    {
+        if (is_null($name)) {
+            return $this->relation;
+        } elseif (array_key_exists($name, $this->relation)) {
+            return $this->relation[$name];
+        } else {
+            return;
+        }
+    }
+
+    /**
+     * 设置关联数据对象值
+     * @access public
+     * @param string $name  属性名
+     * @param mixed  $value 属性值
+     * @return $this
+     */
+    public function setRelation($name, $value)
+    {
+        $this->relation[$name] = $value;
+        return $this;
+    }
+
+    /**
+     * 自动写入时间戳
+     * @access public
+     * @param string $name 时间戳字段
+     * @return mixed
+     */
+    protected function autoWriteTimestamp($name)
+    {
+        if (isset($this->type[$name])) {
+            $type = $this->type[$name];
+            if (strpos($type, ':')) {
+                list($type, $param) = explode(':', $type, 2);
+            }
+            switch ($type) {
+                case 'datetime':
+                case 'date':
+                    $format = !empty($param) ? $param : $this->dateFormat;
+                    $value  = $this->formatDateTime(time(), $format);
+                    break;
+                case 'timestamp':
+                case 'integer':
+                default:
+                    $value = time();
+                    break;
+            }
+        } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [
+            'datetime',
+            'date',
+            'timestamp',
+        ])
+        ) {
+            $value = $this->formatDateTime(time(), $this->dateFormat);
+        } else {
+            $value = $this->formatDateTime(time(), $this->dateFormat, true);
+        }
+        return $value;
+    }
+
+    /**
+     * 时间日期字段格式化处理
+     * @access public
+     * @param mixed $time      时间日期表达式
+     * @param mixed $format    日期格式
+     * @param bool  $timestamp 是否进行时间戳转换
+     * @return mixed
+     */
+    protected function formatDateTime($time, $format, $timestamp = false)
+    {
+        if (false !== strpos($format, '\\')) {
+            $time = new $format($time);
+        } elseif (!$timestamp && false !== $format) {
+            $time = date($format, $time);
+        }
+        return $time;
+    }
+
+    /**
+     * 数据写入 类型转换
+     * @access public
+     * @param mixed        $value 值
+     * @param string|array $type  要转换的类型
+     * @return mixed
+     */
+    protected function writeTransform($value, $type)
+    {
+        if (is_null($value)) {
+            return;
+        }
+
+        if (is_array($type)) {
+            list($type, $param) = $type;
+        } elseif (strpos($type, ':')) {
+            list($type, $param) = explode(':', $type, 2);
+        }
+        switch ($type) {
+            case 'integer':
+                $value = (int) $value;
+                break;
+            case 'float':
+                if (empty($param)) {
+                    $value = (float) $value;
+                } else {
+                    $value = (float) number_format($value, $param, '.', '');
+                }
+                break;
+            case 'boolean':
+                $value = (bool) $value;
+                break;
+            case 'timestamp':
+                if (!is_numeric($value)) {
+                    $value = strtotime($value);
+                }
+                break;
+            case 'datetime':
+                $format = !empty($param) ? $param : $this->dateFormat;
+                $value  = is_numeric($value) ? $value : strtotime($value);
+                $value  = $this->formatDateTime($value, $format);
+                break;
+            case 'object':
+                if (is_object($value)) {
+                    $value = json_encode($value, JSON_FORCE_OBJECT);
+                }
+                break;
+            case 'array':
+                $value = (array) $value;
+            case 'json':
+                $option = !empty($param) ? (int) $param : JSON_UNESCAPED_UNICODE;
+                $value  = json_encode($value, $option);
+                break;
+            case 'serialize':
+                $value = serialize($value);
+                break;
+
+        }
+        return $value;
+    }
+
+    /**
+     * 获取器 获取数据对象的值
+     * @access public
+     * @param string $name 名称
+     * @return mixed
+     * @throws InvalidArgumentException
+     */
+    public function getAttr($name)
+    {
+        try {
+            $notFound = false;
+            $value    = $this->getData($name);
+        } catch (InvalidArgumentException $e) {
+            $notFound = true;
+            $value    = null;
+        }
+
+        // 检测属性获取器
+        $method = 'get' . Loader::parseName($name, 1) . 'Attr';
+        if (method_exists($this, $method)) {
+            $value = $this->$method($value, $this->data, $this->relation);
+        } elseif (isset($this->type[$name])) {
+            // 类型转换
+            $value = $this->readTransform($value, $this->type[$name]);
+        } elseif (in_array($name, [$this->createTime, $this->updateTime])) {
+            if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [
+                'datetime',
+                'date',
+                'timestamp',
+            ])
+            ) {
+                $value = $this->formatDateTime(strtotime($value), $this->dateFormat);
+            } else {
+                $value = $this->formatDateTime($value, $this->dateFormat);
+            }
+        } elseif ($notFound) {
+            $relation = Loader::parseName($name, 1, false);
+            if (method_exists($this, $relation)) {
+                $modelRelation = $this->$relation();
+                // 不存在该字段 获取关联数据
+                $value = $this->getRelationData($modelRelation);
+                // 保存关联对象值
+                $this->relation[$name] = $value;
+            } else {
+                throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name);
+            }
+        }
+        return $value;
+    }
+
+    /**
+     * 获取关联模型数据
+     * @access public
+     * @param Relation        $modelRelation 模型关联对象
+     * @return mixed
+     * @throws BadMethodCallException
+     */
+    protected function getRelationData(Relation $modelRelation)
+    {
+        if ($this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)) {
+            $value = $this->parent;
+        } else {
+            // 首先获取关联数据
+            if (method_exists($modelRelation, 'getRelation')) {
+                $value = $modelRelation->getRelation();
+            } else {
+                throw new BadMethodCallException('method not exists:' . get_class($modelRelation) . '-> getRelation');
+            }
+        }
+        return $value;
+    }
+
+    /**
+     * 数据读取 类型转换
+     * @access public
+     * @param mixed        $value 值
+     * @param string|array $type  要转换的类型
+     * @return mixed
+     */
+    protected function readTransform($value, $type)
+    {
+        if (is_null($value)) {
+            return;
+        }
+
+        if (is_array($type)) {
+            list($type, $param) = $type;
+        } elseif (strpos($type, ':')) {
+            list($type, $param) = explode(':', $type, 2);
+        }
+        switch ($type) {
+            case 'integer':
+                $value = (int) $value;
+                break;
+            case 'float':
+                if (empty($param)) {
+                    $value = (float) $value;
+                } else {
+                    $value = (float) number_format($value, $param, '.', '');
+                }
+                break;
+            case 'boolean':
+                $value = (bool) $value;
+                break;
+            case 'timestamp':
+                if (!is_null($value)) {
+                    $format = !empty($param) ? $param : $this->dateFormat;
+                    $value  = $this->formatDateTime($value, $format);
+                }
+                break;
+            case 'datetime':
+                if (!is_null($value)) {
+                    $format = !empty($param) ? $param : $this->dateFormat;
+                    $value  = $this->formatDateTime(strtotime($value), $format);
+                }
+                break;
+            case 'json':
+                $value = json_decode($value, true);
+                break;
+            case 'array':
+                $value = empty($value) ? [] : json_decode($value, true);
+                break;
+            case 'object':
+                $value = empty($value) ? new \stdClass() : json_decode($value);
+                break;
+            case 'serialize':
+                try {
+                    $value = unserialize($value);
+                } catch (\Exception $e) {
+                    $value = null;
+                }
+                break;
+            default:
+                if (false !== strpos($type, '\\')) {
+                    // 对象类型
+                    $value = new $type($value);
+                }
+        }
+        return $value;
+    }
+
+    /**
+     * 设置需要追加的输出属性
+     * @access public
+     * @param array $append   属性列表
+     * @param bool  $override 是否覆盖
+     * @return $this
+     */
+    public function append($append = [], $override = false)
+    {
+        $this->append = $override ? $append : array_merge($this->append, $append);
+        return $this;
+    }
+
+    /**
+     * 设置附加关联对象的属性
+     * @access public
+     * @param string       $relation 关联方法
+     * @param string|array $append   追加属性名
+     * @return $this
+     * @throws Exception
+     */
+    public function appendRelationAttr($relation, $append)
+    {
+        if (is_string($append)) {
+            $append = explode(',', $append);
+        }
+
+        $relation = Loader::parseName($relation, 1, false);
+
+        // 获取关联数据
+        if (isset($this->relation[$relation])) {
+            $model = $this->relation[$relation];
+        } else {
+            $model = $this->getRelationData($this->$relation());
+        }
+
+        if ($model instanceof Model) {
+            foreach ($append as $key => $attr) {
+                $key = is_numeric($key) ? $attr : $key;
+                if (isset($this->data[$key])) {
+                    throw new Exception('bind attr has exists:' . $key);
+                } else {
+                    $this->data[$key] = $model->getAttr($attr);
+                }
+            }
+        }
+        return $this;
+    }
+
+    /**
+     * 设置需要隐藏的输出属性
+     * @access public
+     * @param array $hidden   属性列表
+     * @param bool  $override 是否覆盖
+     * @return $this
+     */
+    public function hidden($hidden = [], $override = false)
+    {
+        $this->hidden = $override ? $hidden : array_merge($this->hidden, $hidden);
+        return $this;
+    }
+
+    /**
+     * 设置需要输出的属性
+     * @access public
+     * @param array $visible
+     * @param bool  $override 是否覆盖
+     * @return $this
+     */
+    public function visible($visible = [], $override = false)
+    {
+        $this->visible = $override ? $visible : array_merge($this->visible, $visible);
+        return $this;
+    }
+
+    /**
+     * 解析隐藏及显示属性
+     * @access protected
+     * @param array $attrs  属性
+     * @param array $result 结果集
+     * @param bool  $visible
+     * @return array
+     */
+    protected function parseAttr($attrs, &$result, $visible = true)
+    {
+        $array = [];
+        foreach ($attrs as $key => $val) {
+            if (is_array($val)) {
+                if ($visible) {
+                    $array[] = $key;
+                }
+                $result[$key] = $val;
+            } elseif (strpos($val, '.')) {
+                list($key, $name) = explode('.', $val);
+                if ($visible) {
+                    $array[] = $key;
+                }
+                $result[$key][] = $name;
+            } else {
+                $array[] = $val;
+            }
+        }
+        return $array;
+    }
+
+    /**
+     * 转换子模型对象
+     * @access protected
+     * @param Model|ModelCollection $model
+     * @param                  $visible
+     * @param                  $hidden
+     * @param                  $key
+     * @return array
+     */
+    protected function subToArray($model, $visible, $hidden, $key)
+    {
+        // 关联模型对象
+        if (isset($visible[$key])) {
+            $model->visible($visible[$key]);
+        } elseif (isset($hidden[$key])) {
+            $model->hidden($hidden[$key]);
+        }
+        return $model->toArray();
+    }
+
+    /**
+     * 转换当前模型对象为数组
+     * @access public
+     * @return array
+     */
+    public function toArray()
+    {
+        $item    = [];
+        $visible = [];
+        $hidden  = [];
+
+        $data = array_merge($this->data, $this->relation);
+
+        // 过滤属性
+        if (!empty($this->visible)) {
+            $array = $this->parseAttr($this->visible, $visible);
+            $data  = array_intersect_key($data, array_flip($array));
+        } elseif (!empty($this->hidden)) {
+            $array = $this->parseAttr($this->hidden, $hidden, false);
+            $data  = array_diff_key($data, array_flip($array));
+        }
+
+        foreach ($data as $key => $val) {
+            if ($val instanceof Model || $val instanceof ModelCollection) {
+                // 关联模型对象
+                $item[$key] = $this->subToArray($val, $visible, $hidden, $key);
+            } elseif (is_array($val) && reset($val) instanceof Model) {
+                // 关联模型数据集
+                $arr = [];
+                foreach ($val as $k => $value) {
+                    $arr[$k] = $this->subToArray($value, $visible, $hidden, $key);
+                }
+                $item[$key] = $arr;
+            } else {
+                // 模型属性
+                $item[$key] = $this->getAttr($key);
+            }
+        }
+        // 追加属性(必须定义获取器)
+        if (!empty($this->append)) {
+            foreach ($this->append as $key => $name) {
+                if (is_array($name)) {
+                    // 追加关联对象属性
+                    $relation   = $this->getAttr($key);
+                    $item[$key] = $relation->append($name)->toArray();
+                } elseif (strpos($name, '.')) {
+                    list($key, $attr) = explode('.', $name);
+                    // 追加关联对象属性
+                    $relation   = $this->getAttr($key);
+                    $item[$key] = $relation->append([$attr])->toArray();
+                } else {
+                    $relation = Loader::parseName($name, 1, false);
+                    if (method_exists($this, $relation)) {
+                        $modelRelation = $this->$relation();
+                        $value         = $this->getRelationData($modelRelation);
+
+                        if (method_exists($modelRelation, 'getBindAttr')) {
+                            $bindAttr = $modelRelation->getBindAttr();
+                            if ($bindAttr) {
+                                foreach ($bindAttr as $key => $attr) {
+                                    $key = is_numeric($key) ? $attr : $key;
+                                    if (isset($this->data[$key])) {
+                                        throw new Exception('bind attr has exists:' . $key);
+                                    } else {
+                                        $item[$key] = $value ? $value->getAttr($attr) : null;
+                                    }
+                                }
+                                continue;
+                            }
+                        }
+                        $item[$name] = $value;
+                    } else {
+                        $item[$name] = $this->getAttr($name);
+                    }
+                }
+            }
+        }
+        return !empty($item) ? $item : [];
+    }
+
+    /**
+     * 转换当前模型对象为JSON字符串
+     * @access public
+     * @param integer $options json参数
+     * @return string
+     */
+    public function toJson($options = JSON_UNESCAPED_UNICODE)
+    {
+        return json_encode($this->toArray(), $options);
+    }
+
+    /**
+     * 移除当前模型的关联属性
+     * @access public
+     * @return $this
+     */
+    public function removeRelation()
+    {
+        $this->relation = [];
+        return $this;
+    }
+
+    /**
+     * 转换当前模型数据集为数据集对象
+     * @access public
+     * @param array|\think\Collection $collection 数据集
+     * @return \think\Collection
+     */
+    public function toCollection($collection)
+    {
+        if ($this->resultSetType) {
+            if ('collection' == $this->resultSetType) {
+                $collection = new ModelCollection($collection);
+            } elseif (false !== strpos($this->resultSetType, '\\')) {
+                $class      = $this->resultSetType;
+                $collection = new $class($collection);
+            }
+        }
+        return $collection;
+    }
+
+    /**
+     * 关联数据一起更新
+     * @access public
+     * @param mixed $relation 关联
+     * @return $this
+     */
+    public function together($relation)
+    {
+        if (is_string($relation)) {
+            $relation = explode(',', $relation);
+        }
+        $this->relationWrite = $relation;
+        return $this;
+    }
+
+    /**
+     * 获取模型对象的主键
+     * @access public
+     * @param string $name 模型名
+     * @return mixed
+     */
+    public function getPk($name = '')
+    {
+        if (!empty($name)) {
+            $table = $this->getQuery()->getTable($name);
+            return $this->getQuery()->getPk($table);
+        } elseif (empty($this->pk)) {
+            $this->pk = $this->getQuery()->getPk();
+        }
+        return $this->pk;
+    }
+
+    /**
+     * 判断一个字段名是否为主键字段
+     * @access public
+     * @param string $key 名称
+     * @return bool
+     */
+    protected function isPk($key)
+    {
+        $pk = $this->getPk();
+        if (is_string($pk) && $pk == $key) {
+            return true;
+        } elseif (is_array($pk) && in_array($key, $pk)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 新增数据是否使用Replace
+     * @access public
+     * @param  bool $replace
+     * @return $this
+     */
+    public function replace($replace = true)
+    {
+        $this->replace = $replace;
+        return $this;
+    }
+
+    /**
+     * 保存当前数据对象
+     * @access public
+     * @param array  $data     数据
+     * @param array  $where    更新条件
+     * @param string $sequence 自增序列名
+     * @return integer|false
+     */
+    public function save($data = [], $where = [], $sequence = null)
+    {
+        if (is_string($data)) {
+            $sequence = $data;
+            $data     = [];
+        }
+
+        // 数据自动验证
+        if (!empty($data)) {
+            if (!$this->validateData($data)) {
+                return false;
+            }
+
+            // 数据对象赋值
+            foreach ($data as $key => $value) {
+                $this->setAttr($key, $value, $data);
+            }
+        }
+
+        if (!empty($where)) {
+            $this->isUpdate    = true;
+            $this->updateWhere = $where;
+        }
+
+        // 自动关联写入
+        if (!empty($this->relationWrite)) {
+            $relation = [];
+            foreach ($this->relationWrite as $key => $name) {
+                if (is_array($name)) {
+                    if (key($name) === 0) {
+                        $relation[$key] = [];
+                        foreach ($name as $val) {
+                            if (isset($this->data[$val])) {
+                                $relation[$key][$val] = $this->data[$val];
+                                unset($this->data[$val]);
+                            }
+                        }
+                    } else {
+                        $relation[$key] = $name;
+                    }
+                } elseif (isset($this->relation[$name])) {
+                    $relation[$name] = $this->relation[$name];
+                } elseif (isset($this->data[$name])) {
+                    $relation[$name] = $this->data[$name];
+                    unset($this->data[$name]);
+                }
+            }
+        }
+
+        // 数据自动完成
+        $this->autoCompleteData($this->auto);
+
+        // 事件回调
+        if (false === $this->trigger('before_write', $this)) {
+            return false;
+        }
+        $pk = $this->getPk();
+        if ($this->isUpdate) {
+            // 自动更新
+            $this->autoCompleteData($this->update);
+
+            // 事件回调
+            if (false === $this->trigger('before_update', $this)) {
+                return false;
+            }
+
+            // 获取有更新的数据
+            $data = $this->getChangedData();
+
+            if (empty($data) || (count($data) == 1 && is_string($pk) && isset($data[$pk]))) {
+                // 关联更新
+                if (isset($relation)) {
+                    $this->autoRelationUpdate($relation);
+                }
+                return 0;
+            } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) {
+                // 自动写入更新时间
+                $data[$this->updateTime]       = $this->autoWriteTimestamp($this->updateTime);
+                $this->data[$this->updateTime] = $data[$this->updateTime];
+            }
+
+            if (empty($where) && !empty($this->updateWhere)) {
+                $where = $this->updateWhere;
+            }
+
+            // 保留主键数据
+            foreach ($this->data as $key => $val) {
+                if ($this->isPk($key)) {
+                    $data[$key] = $val;
+                }
+            }
+
+            $array = [];
+
+            foreach ((array) $pk as $key) {
+                if (isset($data[$key])) {
+                    $array[$key] = $data[$key];
+                    unset($data[$key]);
+                }
+            }
+
+            if (!empty($array)) {
+                $where = $array;
+            }
+
+            // 检测字段
+            $allowFields = $this->checkAllowField(array_merge($this->auto, $this->update));
+
+            // 模型更新
+            if (!empty($allowFields)) {
+                $result = $this->getQuery()->where($where)->strict(false)->field($allowFields)->update($data);
+            } else {
+                $result = $this->getQuery()->where($where)->update($data);
+            }
+
+            // 关联更新
+            if (isset($relation)) {
+                $this->autoRelationUpdate($relation);
+            }
+
+            // 更新回调
+            $this->trigger('after_update', $this);
+
+        } else {
+            // 自动写入
+            $this->autoCompleteData($this->insert);
+
+            // 自动写入创建时间和更新时间
+            if ($this->autoWriteTimestamp) {
+                if ($this->createTime && !isset($this->data[$this->createTime])) {
+                    $this->data[$this->createTime] = $this->autoWriteTimestamp($this->createTime);
+                }
+                if ($this->updateTime && !isset($this->data[$this->updateTime])) {
+                    $this->data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime);
+                }
+            }
+
+            if (false === $this->trigger('before_insert', $this)) {
+                return false;
+            }
+
+            // 检测字段
+            $allowFields = $this->checkAllowField(array_merge($this->auto, $this->insert));
+            if (!empty($allowFields)) {
+                $result = $this->getQuery()->strict(false)->field($allowFields)->insert($this->data, $this->replace, false, $sequence);
+            } else {
+                $result = $this->getQuery()->insert($this->data, $this->replace, false, $sequence);
+            }
+
+            // 获取自动增长主键
+            if ($result && $insertId = $this->getQuery()->getLastInsID($sequence)) {
+                foreach ((array) $pk as $key) {
+                    if (!isset($this->data[$key]) || '' == $this->data[$key]) {
+                        $this->data[$key] = $insertId;
+                    }
+                }
+            }
+
+            // 关联写入
+            if (isset($relation)) {
+                foreach ($relation as $name => $val) {
+                    $method = Loader::parseName($name, 1, false);
+                    $this->$method()->save($val);
+                }
+            }
+
+            // 标记为更新
+            $this->isUpdate = true;
+
+            // 新增回调
+            $this->trigger('after_insert', $this);
+        }
+        // 写入回调
+        $this->trigger('after_write', $this);
+
+        // 重新记录原始数据
+        $this->origin = $this->data;
+
+        return $result;
+    }
+
+    protected function checkAllowField($auto = [])
+    {
+        if (true === $this->field) {
+            $this->field = $this->getQuery()->getTableInfo('', 'fields');
+            $field       = $this->field;
+        } elseif (!empty($this->field)) {
+            $field = array_merge($this->field, $auto);
+            if ($this->autoWriteTimestamp) {
+                array_push($field, $this->createTime, $this->updateTime);
+            }
+        } elseif (!empty($this->except)) {
+            $fields      = $this->getQuery()->getTableInfo('', 'fields');
+            $field       = array_diff($fields, (array) $this->except);
+            $this->field = $field;
+        } else {
+            $field = [];
+        }
+
+        if ($this->disuse) {
+            // 废弃字段
+            $field = array_diff($field, (array) $this->disuse);
+        }
+        return $field;
+    }
+
+    protected function autoRelationUpdate($relation)
+    {
+        foreach ($relation as $name => $val) {
+            if ($val instanceof Model) {
+                $val->save();
+            } else {
+                unset($this->data[$name]);
+                $model = $this->getAttr($name);
+                if ($model instanceof Model) {
+                    $model->save($val);
+                }
+            }
+        }
+    }
+
+    /**
+     * 获取变化的数据 并排除只读数据
+     * @access public
+     * @return array
+     */
+    public function getChangedData()
+    {
+        if ($this->force) {
+            $data = $this->data;
+        } else {
+            $data = array_udiff_assoc($this->data, $this->origin, function ($a, $b) {
+                if ((empty($a) || empty($b)) && $a !== $b) {
+                    return 1;
+                }
+                return is_object($a) || $a != $b ? 1 : 0;
+            });
+        }
+
+        if (!empty($this->readonly)) {
+            // 只读字段不允许更新
+            foreach ($this->readonly as $key => $field) {
+                if (isset($data[$field])) {
+                    unset($data[$field]);
+                }
+            }
+        }
+
+        return $data;
+    }
+
+    /**
+     * 字段值(延迟)增长
+     * @access public
+     * @param string  $field    字段名
+     * @param integer $step     增长值
+     * @param integer $lazyTime 延时时间(s)
+     * @return integer|true
+     * @throws Exception
+     */
+    public function setInc($field, $step = 1, $lazyTime = 0)
+    {
+        // 更新条件
+        $where = $this->getWhere();
+
+        $result = $this->getQuery()->where($where)->setInc($field, $step, $lazyTime);
+        if (true !== $result) {
+            $this->data[$field] += $step;
+        }
+
+        return $result;
+    }
+
+    /**
+     * 字段值(延迟)增长
+     * @access public
+     * @param string  $field    字段名
+     * @param integer $step     增长值
+     * @param integer $lazyTime 延时时间(s)
+     * @return integer|true
+     * @throws Exception
+     */
+    public function setDec($field, $step = 1, $lazyTime = 0)
+    {
+        // 更新条件
+        $where  = $this->getWhere();
+        $result = $this->getQuery()->where($where)->setDec($field, $step, $lazyTime);
+        if (true !== $result) {
+            $this->data[$field] -= $step;
+        }
+
+        return $result;
+    }
+
+    /**
+     * 获取更新条件
+     * @access protected
+     * @return mixed
+     */
+    protected function getWhere()
+    {
+        // 删除条件
+        $pk = $this->getPk();
+
+        if (is_string($pk) && isset($this->data[$pk])) {
+            $where = [$pk => $this->data[$pk]];
+        } elseif (!empty($this->updateWhere)) {
+            $where = $this->updateWhere;
+        } else {
+            $where = null;
+        }
+        return $where;
+    }
+
+    /**
+     * 保存多个数据到当前数据对象
+     * @access public
+     * @param array   $dataSet 数据
+     * @param boolean $replace 是否自动识别更新和写入
+     * @return array|false
+     * @throws \Exception
+     */
+    public function saveAll($dataSet, $replace = true)
+    {
+        if ($this->validate) {
+            // 数据批量验证
+            $validate = $this->validate;
+            foreach ($dataSet as $data) {
+                if (!$this->validateData($data, $validate)) {
+                    return false;
+                }
+            }
+        }
+
+        $result = [];
+        $db     = $this->getQuery();
+        $db->startTrans();
+        try {
+            $pk = $this->getPk();
+            if (is_string($pk) && $replace) {
+                $auto = true;
+            }
+            foreach ($dataSet as $key => $data) {
+                if ($this->isUpdate || (!empty($auto) && isset($data[$pk]))) {
+                    $result[$key] = self::update($data, [], $this->field);
+                } else {
+                    $result[$key] = self::create($data, $this->field);
+                }
+            }
+            $db->commit();
+            return $this->toCollection($result);
+        } catch (\Exception $e) {
+            $db->rollback();
+            throw $e;
+        }
+    }
+
+    /**
+     * 设置允许写入的字段
+     * @access public
+     * @param string|array $field 允许写入的字段 如果为true只允许写入数据表字段
+     * @return $this
+     */
+    public function allowField($field)
+    {
+        if (is_string($field)) {
+            $field = explode(',', $field);
+        }
+        $this->field = $field;
+        return $this;
+    }
+
+    /**
+     * 设置排除写入的字段
+     * @access public
+     * @param string|array $field 排除允许写入的字段
+     * @return $this
+     */
+    public function except($field)
+    {
+        if (is_string($field)) {
+            $field = explode(',', $field);
+        }
+        $this->except = $field;
+        return $this;
+    }
+
+    /**
+     * 设置只读字段
+     * @access public
+     * @param mixed $field 只读字段
+     * @return $this
+     */
+    public function readonly($field)
+    {
+        if (is_string($field)) {
+            $field = explode(',', $field);
+        }
+        $this->readonly = $field;
+        return $this;
+    }
+
+    /**
+     * 是否为更新数据
+     * @access public
+     * @param bool  $update
+     * @param mixed $where
+     * @return $this
+     */
+    public function isUpdate($update = true, $where = null)
+    {
+        $this->isUpdate = $update;
+        if (!empty($where)) {
+            $this->updateWhere = $where;
+        }
+        return $this;
+    }
+
+    /**
+     * 数据自动完成
+     * @access public
+     * @param array $auto 要自动更新的字段列表
+     * @return void
+     */
+    protected function autoCompleteData($auto = [])
+    {
+        foreach ($auto as $field => $value) {
+            if (is_integer($field)) {
+                $field = $value;
+                $value = null;
+            }
+
+            if (!isset($this->data[$field])) {
+                $default = null;
+            } else {
+                $default = $this->data[$field];
+            }
+
+            $this->setAttr($field, !is_null($value) ? $value : $default);
+        }
+    }
+
+    /**
+     * 删除当前的记录
+     * @access public
+     * @return integer
+     */
+    public function delete()
+    {
+        if (false === $this->trigger('before_delete', $this)) {
+            return false;
+        }
+
+        // 删除条件
+        $where = $this->getWhere();
+
+        // 删除当前模型数据
+        $result = $this->getQuery()->where($where)->delete();
+
+        // 关联删除
+        if (!empty($this->relationWrite)) {
+            foreach ($this->relationWrite as $key => $name) {
+                $name  = is_numeric($key) ? $name : $key;
+                $model = $this->getAttr($name);
+                if ($model instanceof Model) {
+                    $model->delete();
+                }
+            }
+        }
+
+        $this->trigger('after_delete', $this);
+        // 清空原始数据
+        $this->origin = [];
+
+        return $result;
+    }
+
+    /**
+     * 设置自动完成的字段( 规则通过修改器定义)
+     * @access public
+     * @param array $fields 需要自动完成的字段
+     * @return $this
+     */
+    public function auto($fields)
+    {
+        $this->auto = $fields;
+        return $this;
+    }
+
+    /**
+     * 设置字段验证
+     * @access public
+     * @param array|string|bool $rule  验证规则 true表示自动读取验证器类
+     * @param array             $msg   提示信息
+     * @param bool              $batch 批量验证
+     * @return $this
+     */
+    public function validate($rule = true, $msg = [], $batch = false)
+    {
+        if (is_array($rule)) {
+            $this->validate = [
+                'rule' => $rule,
+                'msg'  => $msg,
+            ];
+        } else {
+            $this->validate = true === $rule ? $this->name : $rule;
+        }
+        $this->batchValidate = $batch;
+        return $this;
+    }
+
+    /**
+     * 设置验证失败后是否抛出异常
+     * @access public
+     * @param bool $fail 是否抛出异常
+     * @return $this
+     */
+    public function validateFailException($fail = true)
+    {
+        $this->failException = $fail;
+        return $this;
+    }
+
+    /**
+     * 自动验证数据
+     * @access protected
+     * @param array $data  验证数据
+     * @param mixed $rule  验证规则
+     * @param bool  $batch 批量验证
+     * @return bool
+     */
+    protected function validateData($data, $rule = null, $batch = null)
+    {
+        $info = is_null($rule) ? $this->validate : $rule;
+
+        if (!empty($info)) {
+            if (is_array($info)) {
+                $validate = Loader::validate();
+                $validate->rule($info['rule']);
+                $validate->message($info['msg']);
+            } else {
+                $name = is_string($info) ? $info : $this->name;
+                if (strpos($name, '.')) {
+                    list($name, $scene) = explode('.', $name);
+                }
+                $validate = Loader::validate($name);
+                if (!empty($scene)) {
+                    $validate->scene($scene);
+                }
+            }
+            $batch = is_null($batch) ? $this->batchValidate : $batch;
+
+            if (!$validate->batch($batch)->check($data)) {
+                $this->error = $validate->getError();
+                if ($this->failException) {
+                    throw new ValidateException($this->error);
+                } else {
+                    return false;
+                }
+            }
+            $this->validate = null;
+        }
+        return true;
+    }
+
+    /**
+     * 返回模型的错误信息
+     * @access public
+     * @return string|array
+     */
+    public function getError()
+    {
+        return $this->error;
+    }
+
+    /**
+     * 注册回调方法
+     * @access public
+     * @param string   $event    事件名
+     * @param callable $callback 回调方法
+     * @param bool     $override 是否覆盖
+     * @return void
+     */
+    public static function event($event, $callback, $override = false)
+    {
+        $class = get_called_class();
+        if ($override) {
+            self::$event[$class][$event] = [];
+        }
+        self::$event[$class][$event][] = $callback;
+    }
+
+    /**
+     * 触发事件
+     * @access protected
+     * @param string $event  事件名
+     * @param mixed  $params 传入参数(引用)
+     * @return bool
+     */
+    protected function trigger($event, &$params)
+    {
+        if (isset(self::$event[$this->class][$event])) {
+            foreach (self::$event[$this->class][$event] as $callback) {
+                if (is_callable($callback)) {
+                    $result = call_user_func_array($callback, [ & $params]);
+                    if (false === $result) {
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 写入数据
+     * @access public
+     * @param array      $data  数据数组
+     * @param array|true $field 允许字段
+     * @return $this
+     */
+    public static function create($data = [], $field = null)
+    {
+        $model = new static();
+        if (!empty($field)) {
+            $model->allowField($field);
+        }
+        $model->isUpdate(false)->save($data, []);
+        return $model;
+    }
+
+    /**
+     * 更新数据
+     * @access public
+     * @param array      $data  数据数组
+     * @param array      $where 更新条件
+     * @param array|true $field 允许字段
+     * @return $this
+     */
+    public static function update($data = [], $where = [], $field = null)
+    {
+        $model = new static();
+        if (!empty($field)) {
+            $model->allowField($field);
+        }
+        $result = $model->isUpdate(true)->save($data, $where);
+        return $model;
+    }
+
+    /**
+     * 查找单条记录
+     * @access public
+     * @param mixed        $data  主键值或者查询条件(闭包)
+     * @param array|string $with  关联预查询
+     * @param bool         $cache 是否缓存
+     * @return static|null
+     * @throws exception\DbException
+     */
+    public static function get($data, $with = [], $cache = false)
+    {
+        if (is_null($data)) {
+            return;
+        }
+
+        if (true === $with || is_int($with)) {
+            $cache = $with;
+            $with  = [];
+        }
+        $query = static::parseQuery($data, $with, $cache);
+        return $query->find($data);
+    }
+
+    /**
+     * 查找所有记录
+     * @access public
+     * @param mixed        $data  主键列表或者查询条件(闭包)
+     * @param array|string $with  关联预查询
+     * @param bool         $cache 是否缓存
+     * @return static[]|false
+     * @throws exception\DbException
+     */
+    public static function all($data = null, $with = [], $cache = false)
+    {
+        if (true === $with || is_int($with)) {
+            $cache = $with;
+            $with  = [];
+        }
+        $query = static::parseQuery($data, $with, $cache);
+        return $query->select($data);
+    }
+
+    /**
+     * 分析查询表达式
+     * @access public
+     * @param mixed  $data  主键列表或者查询条件(闭包)
+     * @param string $with  关联预查询
+     * @param bool   $cache 是否缓存
+     * @return Query
+     */
+    protected static function parseQuery(&$data, $with, $cache)
+    {
+        $result = self::with($with)->cache($cache);
+        if (is_array($data) && key($data) !== 0) {
+            $result = $result->where($data);
+            $data   = null;
+        } elseif ($data instanceof \Closure) {
+            call_user_func_array($data, [ & $result]);
+            $data = null;
+        } elseif ($data instanceof Query) {
+            $result = $data->with($with)->cache($cache);
+            $data   = null;
+        }
+        return $result;
+    }
+
+    /**
+     * 删除记录
+     * @access public
+     * @param mixed $data 主键列表 支持闭包查询条件
+     * @return integer 成功删除的记录数
+     */
+    public static function destroy($data)
+    {
+        $model = new static();
+        $query = $model->db();
+        if (empty($data) && 0 !== $data) {
+            return 0;
+        } elseif (is_array($data) && key($data) !== 0) {
+            $query->where($data);
+            $data = null;
+        } elseif ($data instanceof \Closure) {
+            call_user_func_array($data, [ & $query]);
+            $data = null;
+        }
+        $resultSet = $query->select($data);
+        $count     = 0;
+        if ($resultSet) {
+            foreach ($resultSet as $data) {
+                $result = $data->delete();
+                $count += $result;
+            }
+        }
+        return $count;
+    }
+
+    /**
+     * 命名范围
+     * @access public
+     * @param string|array|\Closure $name 命名范围名称 逗号分隔
+     * @internal  mixed                 ...$params 参数调用
+     * @return Query
+     */
+    public static function scope($name)
+    {
+        $model  = new static();
+        $query  = $model->db();
+        $params = func_get_args();
+        array_shift($params);
+        array_unshift($params, $query);
+        if ($name instanceof \Closure) {
+            call_user_func_array($name, $params);
+        } elseif (is_string($name)) {
+            $name = explode(',', $name);
+        }
+        if (is_array($name)) {
+            foreach ($name as $scope) {
+                $method = 'scope' . trim($scope);
+                if (method_exists($model, $method)) {
+                    call_user_func_array([$model, $method], $params);
+                }
+            }
+        }
+        return $query;
+    }
+
+    /**
+     * 设置是否使用全局查询范围
+     * @param bool $use 是否启用全局查询范围
+     * @access public
+     * @return Query
+     */
+    public static function useGlobalScope($use)
+    {
+        $model = new static();
+        return $model->db($use);
+    }
+
+    /**
+     * 根据关联条件查询当前模型
+     * @access public
+     * @param string  $relation 关联方法名
+     * @param mixed   $operator 比较操作符
+     * @param integer $count    个数
+     * @param string  $id       关联表的统计字段
+     * @return Relation|Query
+     */
+    public static function has($relation, $operator = '>=', $count = 1, $id = '*')
+    {
+        $relation = (new static())->$relation();
+        if (is_array($operator) || $operator instanceof \Closure) {
+            return $relation->hasWhere($operator);
+        }
+        return $relation->has($operator, $count, $id);
+    }
+
+    /**
+     * 根据关联条件查询当前模型
+     * @access public
+     * @param  string $relation 关联方法名
+     * @param  mixed  $where    查询条件(数组或者闭包)
+     * @param  mixed  $fields   字段
+     * @return Relation|Query
+     */
+    public static function hasWhere($relation, $where = [], $fields = null)
+    {
+        return (new static())->$relation()->hasWhere($where, $fields);
+    }
+
+    /**
+     * 解析模型的完整命名空间
+     * @access public
+     * @param string $model 模型名(或者完整类名)
+     * @return string
+     */
+    protected function parseModel($model)
+    {
+        if (false === strpos($model, '\\')) {
+            $path = explode('\\', get_called_class());
+            array_pop($path);
+            array_push($path, Loader::parseName($model, 1));
+            $model = implode('\\', $path);
+        }
+        return $model;
+    }
+
+    /**
+     * 查询当前模型的关联数据
+     * @access public
+     * @param string|array $relations 关联名
+     * @return $this
+     */
+    public function relationQuery($relations)
+    {
+        if (is_string($relations)) {
+            $relations = explode(',', $relations);
+        }
+
+        foreach ($relations as $key => $relation) {
+            $subRelation = '';
+            $closure     = null;
+            if ($relation instanceof \Closure) {
+                // 支持闭包查询过滤关联条件
+                $closure  = $relation;
+                $relation = $key;
+            }
+            if (is_array($relation)) {
+                $subRelation = $relation;
+                $relation    = $key;
+            } elseif (strpos($relation, '.')) {
+                list($relation, $subRelation) = explode('.', $relation, 2);
+            }
+            $method                = Loader::parseName($relation, 1, false);
+            $this->data[$relation] = $this->$method()->getRelation($subRelation, $closure);
+        }
+        return $this;
+    }
+
+    /**
+     * 预载入关联查询 返回数据集
+     * @access public
+     * @param array  $resultSet 数据集
+     * @param string $relation  关联名
+     * @return array
+     */
+    public function eagerlyResultSet(&$resultSet, $relation)
+    {
+        $relations = is_string($relation) ? explode(',', $relation) : $relation;
+        foreach ($relations as $key => $relation) {
+            $subRelation = '';
+            $closure     = false;
+            if ($relation instanceof \Closure) {
+                $closure  = $relation;
+                $relation = $key;
+            }
+            if (is_array($relation)) {
+                $subRelation = $relation;
+                $relation    = $key;
+            } elseif (strpos($relation, '.')) {
+                list($relation, $subRelation) = explode('.', $relation, 2);
+            }
+            $relation = Loader::parseName($relation, 1, false);
+            $this->$relation()->eagerlyResultSet($resultSet, $relation, $subRelation, $closure);
+        }
+    }
+
+    /**
+     * 预载入关联查询 返回模型对象
+     * @access public
+     * @param Model  $result   数据对象
+     * @param string $relation 关联名
+     * @return Model
+     */
+    public function eagerlyResult(&$result, $relation)
+    {
+        $relations = is_string($relation) ? explode(',', $relation) : $relation;
+
+        foreach ($relations as $key => $relation) {
+            $subRelation = '';
+            $closure     = false;
+            if ($relation instanceof \Closure) {
+                $closure  = $relation;
+                $relation = $key;
+            }
+            if (is_array($relation)) {
+                $subRelation = $relation;
+                $relation    = $key;
+            } elseif (strpos($relation, '.')) {
+                list($relation, $subRelation) = explode('.', $relation, 2);
+            }
+            $relation = Loader::parseName($relation, 1, false);
+            $this->$relation()->eagerlyResult($result, $relation, $subRelation, $closure);
+        }
+    }
+
+    /**
+     * 关联统计
+     * @access public
+     * @param Model        $result   数据对象
+     * @param string|array $relation 关联名
+     * @return void
+     */
+    public function relationCount(&$result, $relation)
+    {
+        $relations = is_string($relation) ? explode(',', $relation) : $relation;
+
+        foreach ($relations as $key => $relation) {
+            $closure = false;
+            if ($relation instanceof \Closure) {
+                $closure  = $relation;
+                $relation = $key;
+            } elseif (is_string($key)) {
+                $name     = $relation;
+                $relation = $key;
+            }
+            $relation = Loader::parseName($relation, 1, false);
+            $count    = $this->$relation()->relationCount($result, $closure);
+            if (!isset($name)) {
+                $name = Loader::parseName($relation) . '_count';
+            }
+            $result->setAttr($name, $count);
+        }
+    }
+
+    /**
+     * 获取模型的默认外键名
+     * @access public
+     * @param string $name 模型名
+     * @return string
+     */
+    protected function getForeignKey($name)
+    {
+        if (strpos($name, '\\')) {
+            $name = basename(str_replace('\\', '/', $name));
+        }
+        return Loader::parseName($name) . '_id';
+    }
+
+    /**
+     * HAS ONE 关联定义
+     * @access public
+     * @param string $model      模型名
+     * @param string $foreignKey 关联外键
+     * @param string $localKey   当前模型主键
+     * @param array  $alias      别名定义(已经废弃)
+     * @param string $joinType   JOIN类型
+     * @return HasOne
+     */
+    public function hasOne($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER')
+    {
+        // 记录当前关联信息
+        $model      = $this->parseModel($model);
+        $localKey   = $localKey ?: $this->getPk();
+        $foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
+        return new HasOne($this, $model, $foreignKey, $localKey, $joinType);
+    }
+
+    /**
+     * BELONGS TO 关联定义
+     * @access public
+     * @param string $model      模型名
+     * @param string $foreignKey 关联外键
+     * @param string $localKey   关联主键
+     * @param array  $alias      别名定义(已经废弃)
+     * @param string $joinType   JOIN类型
+     * @return BelongsTo
+     */
+    public function belongsTo($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER')
+    {
+        // 记录当前关联信息
+        $model      = $this->parseModel($model);
+        $foreignKey = $foreignKey ?: $this->getForeignKey($model);
+        $localKey   = $localKey ?: (new $model)->getPk();
+        $trace      = debug_backtrace(false, 2);
+        $relation   = Loader::parseName($trace[1]['function']);
+        return new BelongsTo($this, $model, $foreignKey, $localKey, $joinType, $relation);
+    }
+
+    /**
+     * HAS MANY 关联定义
+     * @access public
+     * @param string $model      模型名
+     * @param string $foreignKey 关联外键
+     * @param string $localKey   当前模型主键
+     * @return HasMany
+     */
+    public function hasMany($model, $foreignKey = '', $localKey = '')
+    {
+        // 记录当前关联信息
+        $model      = $this->parseModel($model);
+        $localKey   = $localKey ?: $this->getPk();
+        $foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
+        return new HasMany($this, $model, $foreignKey, $localKey);
+    }
+
+    /**
+     * HAS MANY 远程关联定义
+     * @access public
+     * @param string $model      模型名
+     * @param string $through    中间模型名
+     * @param string $foreignKey 关联外键
+     * @param string $throughKey 关联外键
+     * @param string $localKey   当前模型主键
+     * @return HasManyThrough
+     */
+    public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '')
+    {
+        // 记录当前关联信息
+        $model      = $this->parseModel($model);
+        $through    = $this->parseModel($through);
+        $localKey   = $localKey ?: $this->getPk();
+        $foreignKey = $foreignKey ?: $this->getForeignKey($this->name);
+        $throughKey = $throughKey ?: $this->getForeignKey($through);
+        return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey);
+    }
+
+    /**
+     * BELONGS TO MANY 关联定义
+     * @access public
+     * @param string $model      模型名
+     * @param string $table      中间表名
+     * @param string $foreignKey 关联外键
+     * @param string $localKey   当前模型关联键
+     * @return BelongsToMany
+     */
+    public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '')
+    {
+        // 记录当前关联信息
+        $model      = $this->parseModel($model);
+        $name       = Loader::parseName(basename(str_replace('\\', '/', $model)));
+        $table      = $table ?: Loader::parseName($this->name) . '_' . $name;
+        $foreignKey = $foreignKey ?: $name . '_id';
+        $localKey   = $localKey ?: $this->getForeignKey($this->name);
+        return new BelongsToMany($this, $model, $table, $foreignKey, $localKey);
+    }
+
+    /**
+     * MORPH  MANY 关联定义
+     * @access public
+     * @param string       $model 模型名
+     * @param string|array $morph 多态字段信息
+     * @param string       $type  多态类型
+     * @return MorphMany
+     */
+    public function morphMany($model, $morph = null, $type = '')
+    {
+        // 记录当前关联信息
+        $model = $this->parseModel($model);
+        if (is_null($morph)) {
+            $trace = debug_backtrace(false, 2);
+            $morph = Loader::parseName($trace[1]['function']);
+        }
+        $type = $type ?: get_class($this);
+        if (is_array($morph)) {
+            list($morphType, $foreignKey) = $morph;
+        } else {
+            $morphType  = $morph . '_type';
+            $foreignKey = $morph . '_id';
+        }
+        return new MorphMany($this, $model, $foreignKey, $morphType, $type);
+    }
+
+    /**
+     * MORPH  One 关联定义
+     * @access public
+     * @param string       $model 模型名
+     * @param string|array $morph 多态字段信息
+     * @param string       $type  多态类型
+     * @return MorphOne
+     */
+    public function morphOne($model, $morph = null, $type = '')
+    {
+        // 记录当前关联信息
+        $model = $this->parseModel($model);
+        if (is_null($morph)) {
+            $trace = debug_backtrace(false, 2);
+            $morph = Loader::parseName($trace[1]['function']);
+        }
+        $type = $type ?: get_class($this);
+        if (is_array($morph)) {
+            list($morphType, $foreignKey) = $morph;
+        } else {
+            $morphType  = $morph . '_type';
+            $foreignKey = $morph . '_id';
+        }
+        return new MorphOne($this, $model, $foreignKey, $morphType, $type);
+    }
+
+    /**
+     * MORPH TO 关联定义
+     * @access public
+     * @param string|array $morph 多态字段信息
+     * @param array        $alias 多态别名定义
+     * @return MorphTo
+     */
+    public function morphTo($morph = null, $alias = [])
+    {
+        $trace    = debug_backtrace(false, 2);
+        $relation = Loader::parseName($trace[1]['function']);
+
+        if (is_null($morph)) {
+            $morph = $relation;
+        }
+        // 记录当前关联信息
+        if (is_array($morph)) {
+            list($morphType, $foreignKey) = $morph;
+        } else {
+            $morphType  = $morph . '_type';
+            $foreignKey = $morph . '_id';
+        }
+        return new MorphTo($this, $morphType, $foreignKey, $alias, $relation);
+    }
+
+    public function __call($method, $args)
+    {
+        $query = $this->db(true, false);
+        if (method_exists($this, 'scope' . $method)) {
+            // 动态调用命名范围
+            $method = 'scope' . $method;
+            array_unshift($args, $query);
+            call_user_func_array([$this, $method], $args);
+            return $this;
+        } else {
+            return call_user_func_array([$query, $method], $args);
+        }
+    }
+
+    public static function __callStatic($method, $args)
+    {
+        $model = new static();
+        $query = $model->db();
+        if (method_exists($model, 'scope' . $method)) {
+            // 动态调用命名范围
+            $method = 'scope' . $method;
+            array_unshift($args, $query);
+
+            call_user_func_array([$model, $method], $args);
+            return $query;
+        } else {
+            return call_user_func_array([$query, $method], $args);
+        }
+    }
+
+    /**
+     * 修改器 设置数据对象的值
+     * @access public
+     * @param string $name  名称
+     * @param mixed  $value 值
+     * @return void
+     */
+    public function __set($name, $value)
+    {
+        $this->setAttr($name, $value);
+    }
+
+    /**
+     * 获取器 获取数据对象的值
+     * @access public
+     * @param string $name 名称
+     * @return mixed
+     */
+    public function __get($name)
+    {
+        return $this->getAttr($name);
+    }
+
+    /**
+     * 检测数据对象的值
+     * @access public
+     * @param string $name 名称
+     * @return boolean
+     */
+    public function __isset($name)
+    {
+        try {
+            if (array_key_exists($name, $this->data) || array_key_exists($name, $this->relation)) {
+                return true;
+            } else {
+                $this->getAttr($name);
+                return true;
+            }
+        } catch (InvalidArgumentException $e) {
+            return false;
+        }
+
+    }
+
+    /**
+     * 销毁数据对象的值
+     * @access public
+     * @param string $name 名称
+     * @return void
+     */
+    public function __unset($name)
+    {
+        unset($this->data[$name], $this->relation[$name]);
+    }
+
+    public function __toString()
+    {
+        return $this->toJson();
+    }
+
+    // JsonSerializable
+    public function jsonSerialize()
+    {
+        return $this->toArray();
+    }
+
+    // ArrayAccess
+    public function offsetSet($name, $value)
+    {
+        $this->setAttr($name, $value);
+    }
+
+    public function offsetExists($name)
+    {
+        return $this->__isset($name);
+    }
+
+    public function offsetUnset($name)
+    {
+        $this->__unset($name);
+    }
+
+    public function offsetGet($name)
+    {
+        return $this->getAttr($name);
+    }
+
+    /**
+     * 解序列化后处理
+     */
+    public function __wakeup()
+    {
+        $this->initialize();
+    }
+
+    /**
+     * 模型事件快捷方法
+     * @param      $callback
+     * @param bool $override
+     */
+    protected static function beforeInsert($callback, $override = false)
+    {
+        self::event('before_insert', $callback, $override);
+    }
+
+    protected static function afterInsert($callback, $override = false)
+    {
+        self::event('after_insert', $callback, $override);
+    }
+
+    protected static function beforeUpdate($callback, $override = false)
+    {
+        self::event('before_update', $callback, $override);
+    }
+
+    protected static function afterUpdate($callback, $override = false)
+    {
+        self::event('after_update', $callback, $override);
+    }
+
+    protected static function beforeWrite($callback, $override = false)
+    {
+        self::event('before_write', $callback, $override);
+    }
+
+    protected static function afterWrite($callback, $override = false)
+    {
+        self::event('after_write', $callback, $override);
+    }
+
+    protected static function beforeDelete($callback, $override = false)
+    {
+        self::event('before_delete', $callback, $override);
+    }
+
+    protected static function afterDelete($callback, $override = false)
+    {
+        self::event('after_delete', $callback, $override);
+    }
+
+}

+ 409 - 0
thinkphp/library/think/Paginator.php

@@ -0,0 +1,409 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: zhangyajun <448901948@qq.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use ArrayAccess;
+use ArrayIterator;
+use Countable;
+use IteratorAggregate;
+use JsonSerializable;
+use Traversable;
+
+abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable
+{
+    /** @var bool 是否为简洁模式 */
+    protected $simple = false;
+
+    /** @var Collection 数据集 */
+    protected $items;
+
+    /** @var integer 当前页 */
+    protected $currentPage;
+
+    /** @var  integer 最后一页 */
+    protected $lastPage;
+
+    /** @var integer|null 数据总数 */
+    protected $total;
+
+    /** @var  integer 每页的数量 */
+    protected $listRows;
+
+    /** @var bool 是否有下一页 */
+    protected $hasMore;
+
+    /** @var array 一些配置 */
+    protected $options = [
+        'var_page' => 'page',
+        'path'     => '/',
+        'query'    => [],
+        'fragment' => '',
+    ];
+
+    /** @var mixed simple模式下的下个元素 */
+    protected $nextItem;
+
+    public function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = [])
+    {
+        $this->options = array_merge($this->options, $options);
+
+        $this->options['path'] = '/' != $this->options['path'] ? rtrim($this->options['path'], '/') : $this->options['path'];
+
+        $this->simple   = $simple;
+        $this->listRows = $listRows;
+
+        if (!$items instanceof Collection) {
+            $items = Collection::make($items);
+        }
+
+        if ($simple) {
+            $this->currentPage = $this->setCurrentPage($currentPage);
+            $this->hasMore     = count($items) > ($this->listRows);
+            if ($this->hasMore) {
+                $this->nextItem = $items->slice($this->listRows, 1);
+            }
+            $items = $items->slice(0, $this->listRows);
+        } else {
+            $this->total       = $total;
+            $this->lastPage    = (int) ceil($total / $listRows);
+            $this->currentPage = $this->setCurrentPage($currentPage);
+            $this->hasMore     = $this->currentPage < $this->lastPage;
+        }
+        $this->items = $items;
+    }
+
+    /**
+     * @param       $items
+     * @param       $listRows
+     * @param null  $currentPage
+     * @param bool  $simple
+     * @param null  $total
+     * @param array $options
+     * @return Paginator
+     */
+    public static function make($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = [])
+    {
+        return new static($items, $listRows, $currentPage, $total, $simple, $options);
+    }
+
+    protected function setCurrentPage($currentPage)
+    {
+        if (!$this->simple && $currentPage > $this->lastPage) {
+            return $this->lastPage > 0 ? $this->lastPage : 1;
+        }
+
+        return $currentPage;
+    }
+
+    /**
+     * 获取页码对应的链接
+     *
+     * @param $page
+     * @return string
+     */
+    protected function url($page)
+    {
+        if ($page <= 0) {
+            $page = 1;
+        }
+
+        if (strpos($this->options['path'], '[PAGE]') === false) {
+            $parameters = [$this->options['var_page'] => $page];
+            $path       = $this->options['path'];
+        } else {
+            $parameters = [];
+            $path       = str_replace('[PAGE]', $page, $this->options['path']);
+        }
+        if (count($this->options['query']) > 0) {
+            $parameters = array_merge($this->options['query'], $parameters);
+        }
+        $url = $path;
+        if (!empty($parameters)) {
+            $url .= '?' . http_build_query($parameters, null, '&');
+        }
+        return $url . $this->buildFragment();
+    }
+
+    /**
+     * 自动获取当前页码
+     * @param string $varPage
+     * @param int    $default
+     * @return int
+     */
+    public static function getCurrentPage($varPage = 'page', $default = 1)
+    {
+        $page = (int) Request::instance()->param($varPage);
+
+        if (filter_var($page, FILTER_VALIDATE_INT) !== false && $page >= 1) {
+            return $page;
+        }
+
+        return $default;
+    }
+
+    /**
+     * 自动获取当前的path
+     * @return string
+     */
+    public static function getCurrentPath()
+    {
+        return Request::instance()->baseUrl();
+    }
+
+    public function total()
+    {
+        if ($this->simple) {
+            throw new \DomainException('not support total');
+        }
+        return $this->total;
+    }
+
+    public function listRows()
+    {
+        return $this->listRows;
+    }
+
+    public function currentPage()
+    {
+        return $this->currentPage;
+    }
+
+    public function lastPage()
+    {
+        if ($this->simple) {
+            throw new \DomainException('not support last');
+        }
+        return $this->lastPage;
+    }
+
+    /**
+     * 数据是否足够分页
+     * @return boolean
+     */
+    public function hasPages()
+    {
+        return !(1 == $this->currentPage && !$this->hasMore);
+    }
+
+    /**
+     * 创建一组分页链接
+     *
+     * @param  int $start
+     * @param  int $end
+     * @return array
+     */
+    public function getUrlRange($start, $end)
+    {
+        $urls = [];
+
+        for ($page = $start; $page <= $end; $page++) {
+            $urls[$page] = $this->url($page);
+        }
+
+        return $urls;
+    }
+
+    /**
+     * 设置URL锚点
+     *
+     * @param  string|null $fragment
+     * @return $this
+     */
+    public function fragment($fragment)
+    {
+        $this->options['fragment'] = $fragment;
+        return $this;
+    }
+
+    /**
+     * 添加URL参数
+     *
+     * @param  array|string $key
+     * @param  string|null  $value
+     * @return $this
+     */
+    public function appends($key, $value = null)
+    {
+        if (!is_array($key)) {
+            $queries = [$key => $value];
+        } else {
+            $queries = $key;
+        }
+
+        foreach ($queries as $k => $v) {
+            if ($k !== $this->options['var_page']) {
+                $this->options['query'][$k] = $v;
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * 构造锚点字符串
+     *
+     * @return string
+     */
+    protected function buildFragment()
+    {
+        return $this->options['fragment'] ? '#' . $this->options['fragment'] : '';
+    }
+
+    /**
+     * 渲染分页html
+     * @return mixed
+     */
+    abstract public function render();
+
+    public function items()
+    {
+        return $this->items->all();
+    }
+
+    public function getCollection()
+    {
+        return $this->items;
+    }
+
+    public function isEmpty()
+    {
+        return $this->items->isEmpty();
+    }
+
+    /**
+     * 给每个元素执行个回调
+     *
+     * @param  callable $callback
+     * @return $this
+     */
+    public function each(callable $callback)
+    {
+        foreach ($this->items as $key => $item) {
+            $result = $callback($item, $key);
+            if (false === $result) {
+                break;
+            } elseif (!is_object($item)) {
+                $this->items[$key] = $result;
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * Retrieve an external iterator
+     * @return Traversable An instance of an object implementing <b>Iterator</b> or
+     * <b>Traversable</b>
+     */
+    public function getIterator()
+    {
+        return new ArrayIterator($this->items->all());
+    }
+
+    /**
+     * Whether a offset exists
+     * @param mixed $offset
+     * @return bool
+     */
+    public function offsetExists($offset)
+    {
+        return $this->items->offsetExists($offset);
+    }
+
+    /**
+     * Offset to retrieve
+     * @param mixed $offset
+     * @return mixed
+     */
+    public function offsetGet($offset)
+    {
+        return $this->items->offsetGet($offset);
+    }
+
+    /**
+     * Offset to set
+     * @param mixed $offset
+     * @param mixed $value
+     */
+    public function offsetSet($offset, $value)
+    {
+        $this->items->offsetSet($offset, $value);
+    }
+
+    /**
+     * Offset to unset
+     * @param mixed $offset
+     * @return void
+     * @since 5.0.0
+     */
+    public function offsetUnset($offset)
+    {
+        $this->items->offsetUnset($offset);
+    }
+
+    /**
+     * Count elements of an object
+     */
+    public function count()
+    {
+        return $this->items->count();
+    }
+
+    public function __toString()
+    {
+        return (string) $this->render();
+    }
+
+    public function toArray()
+    {
+        if ($this->simple) {
+            return [
+                'per_page'     => $this->listRows,
+                'current_page' => $this->currentPage,
+                'has_more'     => $this->hasMore,
+                'next_item'    => $this->nextItem,
+                'data'         => $this->items->toArray(),
+            ];
+        } else {
+            return [
+                'total'        => $this->total,
+                'per_page'     => $this->listRows,
+                'current_page' => $this->currentPage,
+                'last_page'    => $this->lastPage,
+                'data'         => $this->items->toArray(),
+            ];
+        }
+
+    }
+
+    /**
+     * Specify data which should be serialized to JSON
+     */
+    public function jsonSerialize()
+    {
+        return $this->toArray();
+    }
+
+    public function __call($name, $arguments)
+    {
+        $collection = $this->getCollection();
+
+        $result = call_user_func_array([$collection, $name], $arguments);
+
+        if ($result === $collection) {
+            return $this;
+        }
+
+        return $result;
+    }
+
+}

+ 1205 - 0
thinkphp/library/think/Process.php

@@ -0,0 +1,1205 @@
+<?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;
+
+use think\process\exception\Failed as ProcessFailedException;
+use think\process\exception\Timeout as ProcessTimeoutException;
+use think\process\pipes\Pipes;
+use think\process\pipes\Unix as UnixPipes;
+use think\process\pipes\Windows as WindowsPipes;
+use think\process\Utils;
+
+class Process
+{
+
+    const ERR = 'err';
+    const OUT = 'out';
+
+    const STATUS_READY      = 'ready';
+    const STATUS_STARTED    = 'started';
+    const STATUS_TERMINATED = 'terminated';
+
+    const STDIN  = 0;
+    const STDOUT = 1;
+    const STDERR = 2;
+
+    const TIMEOUT_PRECISION = 0.2;
+
+    private $callback;
+    private $commandline;
+    private $cwd;
+    private $env;
+    private $input;
+    private $starttime;
+    private $lastOutputTime;
+    private $timeout;
+    private $idleTimeout;
+    private $options;
+    private $exitcode;
+    private $fallbackExitcode;
+    private $processInformation;
+    private $outputDisabled = false;
+    private $stdout;
+    private $stderr;
+    private $enhanceWindowsCompatibility = true;
+    private $enhanceSigchildCompatibility;
+    private $process;
+    private $status                       = self::STATUS_READY;
+    private $incrementalOutputOffset      = 0;
+    private $incrementalErrorOutputOffset = 0;
+    private $tty;
+    private $pty;
+
+    private $useFileHandles = false;
+
+    /** @var Pipes */
+    private $processPipes;
+
+    private $latestSignal;
+
+    private static $sigchild;
+
+    /**
+     * @var array
+     */
+    public static $exitCodes = [
+        0   => 'OK',
+        1   => 'General error',
+        2   => 'Misuse of shell builtins',
+        126 => 'Invoked command cannot execute',
+        127 => 'Command not found',
+        128 => 'Invalid exit argument',
+        // signals
+        129 => 'Hangup',
+        130 => 'Interrupt',
+        131 => 'Quit and dump core',
+        132 => 'Illegal instruction',
+        133 => 'Trace/breakpoint trap',
+        134 => 'Process aborted',
+        135 => 'Bus error: "access to undefined portion of memory object"',
+        136 => 'Floating point exception: "erroneous arithmetic operation"',
+        137 => 'Kill (terminate immediately)',
+        138 => 'User-defined 1',
+        139 => 'Segmentation violation',
+        140 => 'User-defined 2',
+        141 => 'Write to pipe with no one reading',
+        142 => 'Signal raised by alarm',
+        143 => 'Termination (request to terminate)',
+        // 144 - not defined
+        145 => 'Child process terminated, stopped (or continued*)',
+        146 => 'Continue if stopped',
+        147 => 'Stop executing temporarily',
+        148 => 'Terminal stop signal',
+        149 => 'Background process attempting to read from tty ("in")',
+        150 => 'Background process attempting to write to tty ("out")',
+        151 => 'Urgent data available on socket',
+        152 => 'CPU time limit exceeded',
+        153 => 'File size limit exceeded',
+        154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
+        155 => 'Profiling timer expired',
+        // 156 - not defined
+        157 => 'Pollable event',
+        // 158 - not defined
+        159 => 'Bad syscall',
+    ];
+
+    /**
+     * 构造方法
+     * @param string         $commandline 指令
+     * @param string|null    $cwd         工作目录
+     * @param array|null     $env         环境变量
+     * @param string|null    $input       输入
+     * @param int|float|null $timeout     超时时间
+     * @param array          $options     proc_open的选项
+     * @throws \RuntimeException
+     * @api
+     */
+    public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = [])
+    {
+        if (!function_exists('proc_open')) {
+            throw new \RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.');
+        }
+
+        $this->commandline = $commandline;
+        $this->cwd         = $cwd;
+
+        if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DS)) {
+            $this->cwd = getcwd();
+        }
+        if (null !== $env) {
+            $this->setEnv($env);
+        }
+
+        $this->input = $input;
+        $this->setTimeout($timeout);
+        $this->useFileHandles               = '\\' === DS;
+        $this->pty                          = false;
+        $this->enhanceWindowsCompatibility  = true;
+        $this->enhanceSigchildCompatibility = '\\' !== DS && $this->isSigchildEnabled();
+        $this->options                      = array_replace([
+            'suppress_errors' => true,
+            'binary_pipes'    => true,
+        ], $options);
+    }
+
+    public function __destruct()
+    {
+        $this->stop();
+    }
+
+    public function __clone()
+    {
+        $this->resetProcessData();
+    }
+
+    /**
+     * 运行指令
+     * @param callback|null $callback
+     * @return int
+     */
+    public function run($callback = null)
+    {
+        $this->start($callback);
+
+        return $this->wait();
+    }
+
+    /**
+     * 运行指令
+     * @param callable|null $callback
+     * @return self
+     * @throws \RuntimeException
+     * @throws ProcessFailedException
+     */
+    public function mustRun($callback = null)
+    {
+        if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) {
+            throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
+        }
+
+        if (0 !== $this->run($callback)) {
+            throw new ProcessFailedException($this);
+        }
+
+        return $this;
+    }
+
+    /**
+     * 启动进程并写到 STDIN 输入后返回。
+     * @param callable|null $callback
+     * @throws \RuntimeException
+     * @throws \RuntimeException
+     * @throws \LogicException
+     */
+    public function start($callback = null)
+    {
+        if ($this->isRunning()) {
+            throw new \RuntimeException('Process is already running');
+        }
+        if ($this->outputDisabled && null !== $callback) {
+            throw new \LogicException('Output has been disabled, enable it to allow the use of a callback.');
+        }
+
+        $this->resetProcessData();
+        $this->starttime = $this->lastOutputTime = microtime(true);
+        $this->callback  = $this->buildCallback($callback);
+        $descriptors     = $this->getDescriptors();
+
+        $commandline = $this->commandline;
+
+        if ('\\' === DS && $this->enhanceWindowsCompatibility) {
+            $commandline = 'cmd /V:ON /E:ON /C "(' . $commandline . ')';
+            foreach ($this->processPipes->getFiles() as $offset => $filename) {
+                $commandline .= ' ' . $offset . '>' . Utils::escapeArgument($filename);
+            }
+            $commandline .= '"';
+
+            if (!isset($this->options['bypass_shell'])) {
+                $this->options['bypass_shell'] = true;
+            }
+        }
+
+        $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options);
+
+        if (!is_resource($this->process)) {
+            throw new \RuntimeException('Unable to launch a new process.');
+        }
+        $this->status = self::STATUS_STARTED;
+
+        if ($this->tty) {
+            return;
+        }
+
+        $this->updateStatus(false);
+        $this->checkTimeout();
+    }
+
+    /**
+     * 重启进程
+     * @param callable|null $callback
+     * @return Process
+     * @throws \RuntimeException
+     * @throws \RuntimeException
+     */
+    public function restart($callback = null)
+    {
+        if ($this->isRunning()) {
+            throw new \RuntimeException('Process is already running');
+        }
+
+        $process = clone $this;
+        $process->start($callback);
+
+        return $process;
+    }
+
+    /**
+     * 等待要终止的进程
+     * @param callable|null $callback
+     * @return int
+     */
+    public function wait($callback = null)
+    {
+        $this->requireProcessIsStarted(__FUNCTION__);
+
+        $this->updateStatus(false);
+        if (null !== $callback) {
+            $this->callback = $this->buildCallback($callback);
+        }
+
+        do {
+            $this->checkTimeout();
+            $running = '\\' === DS ? $this->isRunning() : $this->processPipes->areOpen();
+            $close   = '\\' !== DS || !$running;
+            $this->readPipes(true, $close);
+        } while ($running);
+
+        while ($this->isRunning()) {
+            usleep(1000);
+        }
+
+        if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
+            throw new \RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig']));
+        }
+
+        return $this->exitcode;
+    }
+
+    /**
+     * 获取PID
+     * @return int|null
+     * @throws \RuntimeException
+     */
+    public function getPid()
+    {
+        if ($this->isSigchildEnabled()) {
+            throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.');
+        }
+
+        $this->updateStatus(false);
+
+        return $this->isRunning() ? $this->processInformation['pid'] : null;
+    }
+
+    /**
+     * 将一个 POSIX 信号发送到进程中
+     * @param int $signal
+     * @return Process
+     */
+    public function signal($signal)
+    {
+        $this->doSignal($signal, true);
+
+        return $this;
+    }
+
+    /**
+     * 禁用从底层过程获取输出和错误输出。
+     * @return Process
+     */
+    public function disableOutput()
+    {
+        if ($this->isRunning()) {
+            throw new \RuntimeException('Disabling output while the process is running is not possible.');
+        }
+        if (null !== $this->idleTimeout) {
+            throw new \LogicException('Output can not be disabled while an idle timeout is set.');
+        }
+
+        $this->outputDisabled = true;
+
+        return $this;
+    }
+
+    /**
+     * 开启从底层过程获取输出和错误输出。
+     * @return Process
+     * @throws \RuntimeException
+     */
+    public function enableOutput()
+    {
+        if ($this->isRunning()) {
+            throw new \RuntimeException('Enabling output while the process is running is not possible.');
+        }
+
+        $this->outputDisabled = false;
+
+        return $this;
+    }
+
+    /**
+     * 输出是否禁用
+     * @return bool
+     */
+    public function isOutputDisabled()
+    {
+        return $this->outputDisabled;
+    }
+
+    /**
+     * 获取当前的输出管道
+     * @return string
+     * @throws \LogicException
+     * @throws \LogicException
+     * @api
+     */
+    public function getOutput()
+    {
+        if ($this->outputDisabled) {
+            throw new \LogicException('Output has been disabled.');
+        }
+
+        $this->requireProcessIsStarted(__FUNCTION__);
+
+        $this->readPipes(false, '\\' === DS ? !$this->processInformation['running'] : true);
+
+        return $this->stdout;
+    }
+
+    /**
+     * 以增量方式返回的输出结果。
+     * @return string
+     */
+    public function getIncrementalOutput()
+    {
+        $this->requireProcessIsStarted(__FUNCTION__);
+
+        $data = $this->getOutput();
+
+        $latest = substr($data, $this->incrementalOutputOffset);
+
+        if (false === $latest) {
+            return '';
+        }
+
+        $this->incrementalOutputOffset = strlen($data);
+
+        return $latest;
+    }
+
+    /**
+     * 清空输出
+     * @return Process
+     */
+    public function clearOutput()
+    {
+        $this->stdout                  = '';
+        $this->incrementalOutputOffset = 0;
+
+        return $this;
+    }
+
+    /**
+     * 返回当前的错误输出的过程 (STDERR)。
+     * @return string
+     */
+    public function getErrorOutput()
+    {
+        if ($this->outputDisabled) {
+            throw new \LogicException('Output has been disabled.');
+        }
+
+        $this->requireProcessIsStarted(__FUNCTION__);
+
+        $this->readPipes(false, '\\' === DS ? !$this->processInformation['running'] : true);
+
+        return $this->stderr;
+    }
+
+    /**
+     * 以增量方式返回 errorOutput
+     * @return string
+     */
+    public function getIncrementalErrorOutput()
+    {
+        $this->requireProcessIsStarted(__FUNCTION__);
+
+        $data = $this->getErrorOutput();
+
+        $latest = substr($data, $this->incrementalErrorOutputOffset);
+
+        if (false === $latest) {
+            return '';
+        }
+
+        $this->incrementalErrorOutputOffset = strlen($data);
+
+        return $latest;
+    }
+
+    /**
+     * 清空 errorOutput
+     * @return Process
+     */
+    public function clearErrorOutput()
+    {
+        $this->stderr                       = '';
+        $this->incrementalErrorOutputOffset = 0;
+
+        return $this;
+    }
+
+    /**
+     * 获取退出码
+     * @return null|int
+     */
+    public function getExitCode()
+    {
+        if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) {
+            throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
+        }
+
+        $this->updateStatus(false);
+
+        return $this->exitcode;
+    }
+
+    /**
+     * 获取退出文本
+     * @return null|string
+     */
+    public function getExitCodeText()
+    {
+        if (null === $exitcode = $this->getExitCode()) {
+            return;
+        }
+
+        return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
+    }
+
+    /**
+     * 检查是否成功
+     * @return bool
+     */
+    public function isSuccessful()
+    {
+        return 0 === $this->getExitCode();
+    }
+
+    /**
+     * 是否未捕获的信号已被终止子进程
+     * @return bool
+     */
+    public function hasBeenSignaled()
+    {
+        $this->requireProcessIsTerminated(__FUNCTION__);
+
+        if ($this->isSigchildEnabled()) {
+            throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
+        }
+
+        $this->updateStatus(false);
+
+        return $this->processInformation['signaled'];
+    }
+
+    /**
+     * 返回导致子进程终止其执行的数。
+     * @return int
+     */
+    public function getTermSignal()
+    {
+        $this->requireProcessIsTerminated(__FUNCTION__);
+
+        if ($this->isSigchildEnabled()) {
+            throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
+        }
+
+        $this->updateStatus(false);
+
+        return $this->processInformation['termsig'];
+    }
+
+    /**
+     * 检查子进程信号是否已停止
+     * @return bool
+     */
+    public function hasBeenStopped()
+    {
+        $this->requireProcessIsTerminated(__FUNCTION__);
+
+        $this->updateStatus(false);
+
+        return $this->processInformation['stopped'];
+    }
+
+    /**
+     * 返回导致子进程停止其执行的数。
+     * @return int
+     */
+    public function getStopSignal()
+    {
+        $this->requireProcessIsTerminated(__FUNCTION__);
+
+        $this->updateStatus(false);
+
+        return $this->processInformation['stopsig'];
+    }
+
+    /**
+     * 检查是否正在运行
+     * @return bool
+     */
+    public function isRunning()
+    {
+        if (self::STATUS_STARTED !== $this->status) {
+            return false;
+        }
+
+        $this->updateStatus(false);
+
+        return $this->processInformation['running'];
+    }
+
+    /**
+     * 检查是否已开始
+     * @return bool
+     */
+    public function isStarted()
+    {
+        return self::STATUS_READY != $this->status;
+    }
+
+    /**
+     * 检查是否已终止
+     * @return bool
+     */
+    public function isTerminated()
+    {
+        $this->updateStatus(false);
+
+        return self::STATUS_TERMINATED == $this->status;
+    }
+
+    /**
+     * 获取当前的状态
+     * @return string
+     */
+    public function getStatus()
+    {
+        $this->updateStatus(false);
+
+        return $this->status;
+    }
+
+    /**
+     * 终止进程
+     */
+    public function stop()
+    {
+        if ($this->isRunning()) {
+            if ('\\' === DS && !$this->isSigchildEnabled()) {
+                exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode);
+                if ($exitCode > 0) {
+                    throw new \RuntimeException('Unable to kill the process');
+                }
+            } else {
+                $pids = preg_split('/\s+/', `ps -o pid --no-heading --ppid {$this->getPid()}`);
+                foreach ($pids as $pid) {
+                    if (is_numeric($pid)) {
+                        posix_kill($pid, 9);
+                    }
+                }
+            }
+        }
+
+        $this->updateStatus(false);
+        if ($this->processInformation['running']) {
+            $this->close();
+        }
+
+        return $this->exitcode;
+    }
+
+    /**
+     * 添加一行输出
+     * @param string $line
+     */
+    public function addOutput($line)
+{
+        $this->lastOutputTime = microtime(true);
+        $this->stdout .= $line;
+    }
+
+    /**
+     * 添加一行错误输出
+     * @param string $line
+     */
+    public function addErrorOutput($line)
+{
+        $this->lastOutputTime = microtime(true);
+        $this->stderr .= $line;
+    }
+
+    /**
+     * 获取被执行的指令
+     * @return string
+     */
+    public function getCommandLine()
+{
+        return $this->commandline;
+    }
+
+    /**
+     * 设置指令
+     * @param string $commandline
+     * @return self
+     */
+    public function setCommandLine($commandline)
+{
+        $this->commandline = $commandline;
+
+        return $this;
+    }
+
+    /**
+     * 获取超时时间
+     * @return float|null
+     */
+    public function getTimeout()
+{
+        return $this->timeout;
+    }
+
+    /**
+     * 获取idle超时时间
+     * @return float|null
+     */
+    public function getIdleTimeout()
+{
+        return $this->idleTimeout;
+    }
+
+    /**
+     * 设置超时时间
+     * @param int|float|null $timeout
+     * @return self
+     */
+    public function setTimeout($timeout)
+{
+        $this->timeout = $this->validateTimeout($timeout);
+
+        return $this;
+    }
+
+    /**
+     * 设置idle超时时间
+     * @param int|float|null $timeout
+     * @return self
+     */
+    public function setIdleTimeout($timeout)
+{
+        if (null !== $timeout && $this->outputDisabled) {
+            throw new \LogicException('Idle timeout can not be set while the output is disabled.');
+        }
+
+        $this->idleTimeout = $this->validateTimeout($timeout);
+
+        return $this;
+    }
+
+    /**
+     * 设置TTY
+     * @param bool $tty
+     * @return self
+     */
+    public function setTty($tty)
+{
+        if ('\\' === DS && $tty) {
+            throw new \RuntimeException('TTY mode is not supported on Windows platform.');
+        }
+        if ($tty && (!file_exists('/dev/tty') || !is_readable('/dev/tty'))) {
+            throw new \RuntimeException('TTY mode requires /dev/tty to be readable.');
+        }
+
+        $this->tty = (bool) $tty;
+
+        return $this;
+    }
+
+    /**
+     * 检查是否是tty模式
+     * @return bool
+     */
+    public function isTty()
+{
+        return $this->tty;
+    }
+
+    /**
+     * 设置pty模式
+     * @param bool $bool
+     * @return self
+     */
+    public function setPty($bool)
+{
+        $this->pty = (bool) $bool;
+
+        return $this;
+    }
+
+    /**
+     * 是否是pty模式
+     * @return bool
+     */
+    public function isPty()
+{
+        return $this->pty;
+    }
+
+    /**
+     * 获取工作目录
+     * @return string|null
+     */
+    public function getWorkingDirectory()
+{
+        if (null === $this->cwd) {
+            return getcwd() ?: null;
+        }
+
+        return $this->cwd;
+    }
+
+    /**
+     * 设置工作目录
+     * @param string $cwd
+     * @return self
+     */
+    public function setWorkingDirectory($cwd)
+{
+        $this->cwd = $cwd;
+
+        return $this;
+    }
+
+    /**
+     * 获取环境变量
+     * @return array
+     */
+    public function getEnv()
+{
+        return $this->env;
+    }
+
+    /**
+     * 设置环境变量
+     * @param array $env
+     * @return self
+     */
+    public function setEnv(array $env)
+{
+        $env = array_filter($env, function ($value) {
+            return !is_array($value);
+        });
+
+        $this->env = [];
+        foreach ($env as $key => $value) {
+            $this->env[(binary) $key] = (binary) $value;
+        }
+
+        return $this;
+    }
+
+    /**
+     * 获取输入
+     * @return null|string
+     */
+    public function getInput()
+{
+        return $this->input;
+    }
+
+    /**
+     * 设置输入
+     * @param mixed $input
+     * @return self
+     */
+    public function setInput($input)
+{
+        if ($this->isRunning()) {
+            throw new \LogicException('Input can not be set while the process is running.');
+        }
+
+        $this->input = Utils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input);
+
+        return $this;
+    }
+
+    /**
+     * 获取proc_open的选项
+     * @return array
+     */
+    public function getOptions()
+{
+        return $this->options;
+    }
+
+    /**
+     * 设置proc_open的选项
+     * @param array $options
+     * @return self
+     */
+    public function setOptions(array $options)
+{
+        $this->options = $options;
+
+        return $this;
+    }
+
+    /**
+     * 是否兼容windows
+     * @return bool
+     */
+    public function getEnhanceWindowsCompatibility()
+{
+        return $this->enhanceWindowsCompatibility;
+    }
+
+    /**
+     * 设置是否兼容windows
+     * @param bool $enhance
+     * @return self
+     */
+    public function setEnhanceWindowsCompatibility($enhance)
+{
+        $this->enhanceWindowsCompatibility = (bool) $enhance;
+
+        return $this;
+    }
+
+    /**
+     * 返回是否 sigchild 兼容模式激活
+     * @return bool
+     */
+    public function getEnhanceSigchildCompatibility()
+{
+        return $this->enhanceSigchildCompatibility;
+    }
+
+    /**
+     * 激活 sigchild 兼容性模式。
+     * @param bool $enhance
+     * @return self
+     */
+    public function setEnhanceSigchildCompatibility($enhance)
+{
+        $this->enhanceSigchildCompatibility = (bool) $enhance;
+
+        return $this;
+    }
+
+    /**
+     * 是否超时
+     */
+    public function checkTimeout()
+{
+        if (self::STATUS_STARTED !== $this->status) {
+            return;
+        }
+
+        if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
+            $this->stop();
+
+            throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_GENERAL);
+        }
+
+        if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
+            $this->stop();
+
+            throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_IDLE);
+        }
+    }
+
+    /**
+     * 是否支持pty
+     * @return bool
+     */
+    public static function isPtySupported()
+{
+        static $result;
+
+        if (null !== $result) {
+            return $result;
+        }
+
+        if ('\\' === DS) {
+            return $result = false;
+        }
+
+        $proc = @proc_open('echo 1', [['pty'], ['pty'], ['pty']], $pipes);
+        if (is_resource($proc)) {
+            proc_close($proc);
+
+            return $result = true;
+        }
+
+        return $result = false;
+    }
+
+    /**
+     * 创建所需的 proc_open 的描述符
+     * @return array
+     */
+    private function getDescriptors()
+{
+        if ('\\' === DS) {
+            $this->processPipes = WindowsPipes::create($this, $this->input);
+        } else {
+            $this->processPipes = UnixPipes::create($this, $this->input);
+        }
+        $descriptors = $this->processPipes->getDescriptors($this->outputDisabled);
+
+        if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
+
+            $descriptors = array_merge($descriptors, [['pipe', 'w']]);
+
+            $this->commandline = '(' . $this->commandline . ') 3>/dev/null; code=$?; echo $code >&3; exit $code';
+        }
+
+        return $descriptors;
+    }
+
+    /**
+     * 建立 wait () 使用的回调。
+     * @param callable|null $callback
+     * @return callable
+     */
+    protected function buildCallback($callback)
+{
+        $out      = self::OUT;
+        $callback = function ($type, $data) use ($callback, $out) {
+            if ($out == $type) {
+                $this->addOutput($data);
+            } else {
+                $this->addErrorOutput($data);
+            }
+
+            if (null !== $callback) {
+                call_user_func($callback, $type, $data);
+            }
+        };
+
+        return $callback;
+    }
+
+    /**
+     * 更新状态
+     * @param bool $blocking
+     */
+    protected function updateStatus($blocking)
+{
+        if (self::STATUS_STARTED !== $this->status) {
+            return;
+        }
+
+        $this->processInformation = proc_get_status($this->process);
+        $this->captureExitCode();
+
+        $this->readPipes($blocking, '\\' === DS ? !$this->processInformation['running'] : true);
+
+        if (!$this->processInformation['running']) {
+            $this->close();
+        }
+    }
+
+    /**
+     * 是否开启 '--enable-sigchild'
+     * @return bool
+     */
+    protected function isSigchildEnabled()
+{
+        if (null !== self::$sigchild) {
+            return self::$sigchild;
+        }
+
+        if (!function_exists('phpinfo')) {
+            return self::$sigchild = false;
+        }
+
+        ob_start();
+        phpinfo(INFO_GENERAL);
+
+        return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
+    }
+
+    /**
+     * 验证是否超时
+     * @param int|float|null $timeout
+     * @return float|null
+     */
+    private function validateTimeout($timeout)
+{
+        $timeout = (float) $timeout;
+
+        if (0.0 === $timeout) {
+            $timeout = null;
+        } elseif ($timeout < 0) {
+            throw new \InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
+        }
+
+        return $timeout;
+    }
+
+    /**
+     * 读取pipes
+     * @param bool $blocking
+     * @param bool $close
+     */
+    private function readPipes($blocking, $close)
+{
+        $result = $this->processPipes->readAndWrite($blocking, $close);
+
+        $callback = $this->callback;
+        foreach ($result as $type => $data) {
+            if (3 == $type) {
+                $this->fallbackExitcode = (int) $data;
+            } else {
+                $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
+            }
+        }
+    }
+
+    /**
+     * 捕获退出码
+     */
+    private function captureExitCode()
+{
+        if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) {
+            $this->exitcode = $this->processInformation['exitcode'];
+        }
+    }
+
+    /**
+     * 关闭资源
+     * @return int 退出码
+     */
+    private function close()
+{
+        $this->processPipes->close();
+        if (is_resource($this->process)) {
+            $exitcode = proc_close($this->process);
+        } else {
+            $exitcode = -1;
+        }
+
+        $this->exitcode = -1 !== $exitcode ? $exitcode : (null !== $this->exitcode ? $this->exitcode : -1);
+        $this->status   = self::STATUS_TERMINATED;
+
+        if (-1 === $this->exitcode && null !== $this->fallbackExitcode) {
+            $this->exitcode = $this->fallbackExitcode;
+        } elseif (-1 === $this->exitcode && $this->processInformation['signaled']
+            && 0 < $this->processInformation['termsig']
+        ) {
+            $this->exitcode = 128 + $this->processInformation['termsig'];
+        }
+
+        return $this->exitcode;
+    }
+
+    /**
+     * 重置数据
+     */
+    private function resetProcessData()
+{
+        $this->starttime                    = null;
+        $this->callback                     = null;
+        $this->exitcode                     = null;
+        $this->fallbackExitcode             = null;
+        $this->processInformation           = null;
+        $this->stdout                       = null;
+        $this->stderr                       = null;
+        $this->process                      = null;
+        $this->latestSignal                 = null;
+        $this->status                       = self::STATUS_READY;
+        $this->incrementalOutputOffset      = 0;
+        $this->incrementalErrorOutputOffset = 0;
+    }
+
+    /**
+     * 将一个 POSIX 信号发送到进程中。
+     * @param int  $signal
+     * @param bool $throwException
+     * @return bool
+     */
+    private function doSignal($signal, $throwException)
+{
+        if (!$this->isRunning()) {
+            if ($throwException) {
+                throw new \LogicException('Can not send signal on a non running process.');
+            }
+
+            return false;
+        }
+
+        if ($this->isSigchildEnabled()) {
+            if ($throwException) {
+                throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
+            }
+
+            return false;
+        }
+
+        if (true !== @proc_terminate($this->process, $signal)) {
+            if ($throwException) {
+                throw new \RuntimeException(sprintf('Error while sending signal `%s`.', $signal));
+            }
+
+            return false;
+        }
+
+        $this->latestSignal = $signal;
+
+        return true;
+    }
+
+    /**
+     * 确保进程已经开启
+     * @param string $functionName
+     */
+    private function requireProcessIsStarted($functionName)
+{
+        if (!$this->isStarted()) {
+            throw new \LogicException(sprintf('Process must be started before calling %s.', $functionName));
+        }
+    }
+
+    /**
+     * 确保进程已经终止
+     * @param string $functionName
+     */
+    private function requireProcessIsTerminated($functionName)
+{
+        if (!$this->isTerminated()) {
+            throw new \LogicException(sprintf('Process must be terminated before calling %s.', $functionName));
+        }
+    }
+}

+ 1690 - 0
thinkphp/library/think/Request.php

@@ -0,0 +1,1690 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+class Request
+{
+    /**
+     * @var object 对象实例
+     */
+    protected static $instance;
+
+    protected $method;
+    /**
+     * @var string 域名(含协议和端口)
+     */
+    protected $domain;
+
+    /**
+     * @var string URL地址
+     */
+    protected $url;
+
+    /**
+     * @var string 基础URL
+     */
+    protected $baseUrl;
+
+    /**
+     * @var string 当前执行的文件
+     */
+    protected $baseFile;
+
+    /**
+     * @var string 访问的ROOT地址
+     */
+    protected $root;
+
+    /**
+     * @var string pathinfo
+     */
+    protected $pathinfo;
+
+    /**
+     * @var string pathinfo(不含后缀)
+     */
+    protected $path;
+
+    /**
+     * @var array 当前路由信息
+     */
+    protected $routeInfo = [];
+
+    /**
+     * @var array 环境变量
+     */
+    protected $env;
+
+    /**
+     * @var array 当前调度信息
+     */
+    protected $dispatch = [];
+    protected $module;
+    protected $controller;
+    protected $action;
+    // 当前语言集
+    protected $langset;
+
+    /**
+     * @var array 请求参数
+     */
+    protected $param   = [];
+    protected $get     = [];
+    protected $post    = [];
+    protected $request = [];
+    protected $route   = [];
+    protected $put;
+    protected $session = [];
+    protected $file    = [];
+    protected $cookie  = [];
+    protected $server  = [];
+    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,*/*',
+    ];
+
+    protected $content;
+
+    // 全局过滤规则
+    protected $filter;
+    // Hook扩展方法
+    protected static $hook = [];
+    // 绑定的属性
+    protected $bind = [];
+    // php://input
+    protected $input;
+    // 请求缓存
+    protected $cache;
+    // 缓存是否检查
+    protected $isCheckCache;
+    /**
+     * 是否合并Param
+     * @var bool
+     */
+    protected $mergeParam = false;
+
+    /**
+     * 构造函数
+     * @access protected
+     * @param array $options 参数
+     */
+    protected function __construct($options = [])
+    {
+        foreach ($options as $name => $item) {
+            if (property_exists($this, $name)) {
+                $this->$name = $item;
+            }
+        }
+        if (is_null($this->filter)) {
+            $this->filter = Config::get('default_filter');
+        }
+
+        // 保存 php://input
+        $this->input = file_get_contents('php://input');
+    }
+
+    public function __call($method, $args)
+    {
+        if (array_key_exists($method, self::$hook)) {
+            array_unshift($args, $this);
+            return call_user_func_array(self::$hook[$method], $args);
+        } else {
+            throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
+        }
+    }
+
+    /**
+     * Hook 方法注入
+     * @access public
+     * @param string|array $method   方法名
+     * @param mixed        $callback callable
+     * @return void
+     */
+    public static function hook($method, $callback = null)
+    {
+        if (is_array($method)) {
+            self::$hook = array_merge(self::$hook, $method);
+        } else {
+            self::$hook[$method] = $callback;
+        }
+    }
+
+    /**
+     * 初始化
+     * @access public
+     * @param array $options 参数
+     * @return \think\Request
+     */
+    public static function instance($options = [])
+    {
+        if (is_null(self::$instance)) {
+            self::$instance = new static($options);
+        }
+        return self::$instance;
+    }
+
+    /**
+     * 销毁当前请求对象
+     * @access public
+     * @return void
+     */
+    public static function destroy()
+    {
+        if (!is_null(self::$instance)) {
+            self::$instance = null;
+        }
+    }
+
+    /**
+     * 创建一个URL请求
+     * @access public
+     * @param string $uri    URL地址
+     * @param string $method 请求类型
+     * @param array  $params 请求参数
+     * @param array  $cookie
+     * @param array  $files
+     * @param array  $server
+     * @param string $content
+     * @return \think\Request
+     */
+    public static function create($uri, $method = 'GET', $params = [], $cookie = [], $files = [], $server = [], $content = null)
+    {
+        $server['PATH_INFO']      = '';
+        $server['REQUEST_METHOD'] = strtoupper($method);
+        $info                     = parse_url($uri);
+        if (isset($info['host'])) {
+            $server['SERVER_NAME'] = $info['host'];
+            $server['HTTP_HOST']   = $info['host'];
+        }
+        if (isset($info['scheme'])) {
+            if ('https' === $info['scheme']) {
+                $server['HTTPS']       = 'on';
+                $server['SERVER_PORT'] = 443;
+            } else {
+                unset($server['HTTPS']);
+                $server['SERVER_PORT'] = 80;
+            }
+        }
+        if (isset($info['port'])) {
+            $server['SERVER_PORT'] = $info['port'];
+            $server['HTTP_HOST']   = $server['HTTP_HOST'] . ':' . $info['port'];
+        }
+        if (isset($info['user'])) {
+            $server['PHP_AUTH_USER'] = $info['user'];
+        }
+        if (isset($info['pass'])) {
+            $server['PHP_AUTH_PW'] = $info['pass'];
+        }
+        if (!isset($info['path'])) {
+            $info['path'] = '/';
+        }
+        $options                      = [];
+        $options[strtolower($method)] = $params;
+        $queryString                  = '';
+        if (isset($info['query'])) {
+            parse_str(html_entity_decode($info['query']), $query);
+            if (!empty($params)) {
+                $params      = array_replace($query, $params);
+                $queryString = http_build_query($params, '', '&');
+            } else {
+                $params      = $query;
+                $queryString = $info['query'];
+            }
+        } elseif (!empty($params)) {
+            $queryString = http_build_query($params, '', '&');
+        }
+        if ($queryString) {
+            parse_str($queryString, $get);
+            $options['get'] = isset($options['get']) ? array_merge($get, $options['get']) : $get;
+        }
+
+        $server['REQUEST_URI']  = $info['path'] . ('' !== $queryString ? '?' . $queryString : '');
+        $server['QUERY_STRING'] = $queryString;
+        $options['cookie']      = $cookie;
+        $options['param']       = $params;
+        $options['file']        = $files;
+        $options['server']      = $server;
+        $options['url']         = $server['REQUEST_URI'];
+        $options['baseUrl']     = $info['path'];
+        $options['pathinfo']    = '/' == $info['path'] ? '/' : ltrim($info['path'], '/');
+        $options['method']      = $server['REQUEST_METHOD'];
+        $options['domain']      = isset($info['scheme']) ? $info['scheme'] . '://' . $server['HTTP_HOST'] : '';
+        $options['content']     = $content;
+        self::$instance         = new self($options);
+        return self::$instance;
+    }
+
+    /**
+     * 设置或获取当前包含协议的域名
+     * @access public
+     * @param string $domain 域名
+     * @return string
+     */
+    public function domain($domain = null)
+    {
+        if (!is_null($domain)) {
+            $this->domain = $domain;
+            return $this;
+        } elseif (!$this->domain) {
+            $this->domain = $this->scheme() . '://' . $this->host();
+        }
+        return $this->domain;
+    }
+
+    /**
+     * 设置或获取当前完整URL 包括QUERY_STRING
+     * @access public
+     * @param string|true $url URL地址 true 带域名获取
+     * @return string
+     */
+    public function url($url = null)
+    {
+        if (!is_null($url) && true !== $url) {
+            $this->url = $url;
+            return $this;
+        } elseif (!$this->url) {
+            if (IS_CLI) {
+                $this->url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
+            } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) {
+                $this->url = $_SERVER['HTTP_X_REWRITE_URL'];
+            } elseif (isset($_SERVER['REQUEST_URI'])) {
+                $this->url = $_SERVER['REQUEST_URI'];
+            } elseif (isset($_SERVER['ORIG_PATH_INFO'])) {
+                $this->url = $_SERVER['ORIG_PATH_INFO'] . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '');
+            } else {
+                $this->url = '';
+            }
+        }
+        return true === $url ? $this->domain() . $this->url : $this->url;
+    }
+
+    /**
+     * 设置或获取当前URL 不含QUERY_STRING
+     * @access public
+     * @param string $url URL地址
+     * @return string
+     */
+    public function baseUrl($url = null)
+    {
+        if (!is_null($url) && true !== $url) {
+            $this->baseUrl = $url;
+            return $this;
+        } elseif (!$this->baseUrl) {
+            $str           = $this->url();
+            $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str;
+        }
+        return true === $url ? $this->domain() . $this->baseUrl : $this->baseUrl;
+    }
+
+    /**
+     * 设置或获取当前执行的文件 SCRIPT_NAME
+     * @access public
+     * @param string $file 当前执行的文件
+     * @return string
+     */
+    public function baseFile($file = null)
+    {
+        if (!is_null($file) && true !== $file) {
+            $this->baseFile = $file;
+            return $this;
+        } elseif (!$this->baseFile) {
+            $url = '';
+            if (!IS_CLI) {
+                $script_name = basename($_SERVER['SCRIPT_FILENAME']);
+                if (basename($_SERVER['SCRIPT_NAME']) === $script_name) {
+                    $url = $_SERVER['SCRIPT_NAME'];
+                } elseif (basename($_SERVER['PHP_SELF']) === $script_name) {
+                    $url = $_SERVER['PHP_SELF'];
+                } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $script_name) {
+                    $url = $_SERVER['ORIG_SCRIPT_NAME'];
+                } elseif (($pos = strpos($_SERVER['PHP_SELF'], '/' . $script_name)) !== false) {
+                    $url = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $script_name;
+                } elseif (isset($_SERVER['DOCUMENT_ROOT']) && strpos($_SERVER['SCRIPT_FILENAME'], $_SERVER['DOCUMENT_ROOT']) === 0) {
+                    $url = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $_SERVER['SCRIPT_FILENAME']));
+                }
+            }
+            $this->baseFile = $url;
+        }
+        return true === $file ? $this->domain() . $this->baseFile : $this->baseFile;
+    }
+
+    /**
+     * 设置或获取URL访问根地址
+     * @access public
+     * @param string $url URL地址
+     * @return string
+     */
+    public function root($url = null)
+    {
+        if (!is_null($url) && true !== $url) {
+            $this->root = $url;
+            return $this;
+        } elseif (!$this->root) {
+            $file = $this->baseFile();
+            if ($file && 0 !== strpos($this->url(), $file)) {
+                $file = str_replace('\\', '/', dirname($file));
+            }
+            $this->root = rtrim($file, '/');
+        }
+        return true === $url ? $this->domain() . $this->root : $this->root;
+    }
+
+    /**
+     * 获取当前请求URL的pathinfo信息(含URL后缀)
+     * @access public
+     * @return string
+     */
+    public function pathinfo()
+    {
+        if (is_null($this->pathinfo)) {
+            if (isset($_GET[Config::get('var_pathinfo')])) {
+                // 判断URL里面是否有兼容模式参数
+                $_SERVER['PATH_INFO'] = $_GET[Config::get('var_pathinfo')];
+                unset($_GET[Config::get('var_pathinfo')]);
+            } elseif (IS_CLI) {
+                // CLI模式下 index.php module/controller/action/params/...
+                $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
+            }
+
+            // 分析PATHINFO信息
+            if (!isset($_SERVER['PATH_INFO'])) {
+                foreach (Config::get('pathinfo_fetch') as $type) {
+                    if (!empty($_SERVER[$type])) {
+                        $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ?
+                        substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type];
+                        break;
+                    }
+                }
+            }
+            $this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/');
+        }
+        return $this->pathinfo;
+    }
+
+    /**
+     * 获取当前请求URL的pathinfo信息(不含URL后缀)
+     * @access public
+     * @return string
+     */
+    public function path()
+    {
+        if (is_null($this->path)) {
+            $suffix   = Config::get('url_html_suffix');
+            $pathinfo = $this->pathinfo();
+            if (false === $suffix) {
+                // 禁止伪静态访问
+                $this->path = $pathinfo;
+            } elseif ($suffix) {
+                // 去除正常的URL后缀
+                $this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo);
+            } else {
+                // 允许任何后缀访问
+                $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo);
+            }
+        }
+        return $this->path;
+    }
+
+    /**
+     * 当前URL的访问后缀
+     * @access public
+     * @return string
+     */
+    public function ext()
+    {
+        return pathinfo($this->pathinfo(), PATHINFO_EXTENSION);
+    }
+
+    /**
+     * 获取当前请求的时间
+     * @access public
+     * @param bool $float 是否使用浮点类型
+     * @return integer|float
+     */
+    public function time($float = false)
+    {
+        return $float ? $_SERVER['REQUEST_TIME_FLOAT'] : $_SERVER['REQUEST_TIME'];
+    }
+
+    /**
+     * 当前请求的资源类型
+     * @access public
+     * @return false|string
+     */
+    public function type()
+    {
+        $accept = $this->server('HTTP_ACCEPT');
+        if (empty($accept)) {
+            return false;
+        }
+
+        foreach ($this->mimeType as $key => $val) {
+            $array = explode(',', $val);
+            foreach ($array as $k => $v) {
+                if (stristr($accept, $v)) {
+                    return $key;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 设置资源类型
+     * @access public
+     * @param string|array $type 资源类型名
+     * @param string       $val  资源类型
+     * @return void
+     */
+    public function mimeType($type, $val = '')
+    {
+        if (is_array($type)) {
+            $this->mimeType = array_merge($this->mimeType, $type);
+        } else {
+            $this->mimeType[$type] = $val;
+        }
+    }
+
+    /**
+     * 当前的请求类型
+     * @access public
+     * @param bool $method true 获取原始请求类型
+     * @return string
+     */
+    public function method($method = false)
+    {
+        if (true === $method) {
+            // 获取原始请求类型
+            return $this->server('REQUEST_METHOD') ?: 'GET';
+        } elseif (!$this->method) {
+            if (isset($_POST[Config::get('var_method')])) {
+                $method = strtoupper($_POST[Config::get('var_method')]);
+                if (in_array($method, ['GET', 'POST', 'DELETE', 'PUT', 'PATCH'])) {
+                    $this->method = $method;
+                    $this->{$this->method}($_POST);
+                } else {
+                    $this->method = 'POST';
+                }
+                unset($_POST[Config::get('var_method')]);
+            } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
+                $this->method = strtoupper($_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()
+    {
+        return $this->method() == 'GET';
+    }
+
+    /**
+     * 是否为POST请求
+     * @access public
+     * @return bool
+     */
+    public function isPost()
+    {
+        return $this->method() == 'POST';
+    }
+
+    /**
+     * 是否为PUT请求
+     * @access public
+     * @return bool
+     */
+    public function isPut()
+    {
+        return $this->method() == 'PUT';
+    }
+
+    /**
+     * 是否为DELTE请求
+     * @access public
+     * @return bool
+     */
+    public function isDelete()
+    {
+        return $this->method() == 'DELETE';
+    }
+
+    /**
+     * 是否为HEAD请求
+     * @access public
+     * @return bool
+     */
+    public function isHead()
+    {
+        return $this->method() == 'HEAD';
+    }
+
+    /**
+     * 是否为PATCH请求
+     * @access public
+     * @return bool
+     */
+    public function isPatch()
+    {
+        return $this->method() == 'PATCH';
+    }
+
+    /**
+     * 是否为OPTIONS请求
+     * @access public
+     * @return bool
+     */
+    public function isOptions()
+    {
+        return $this->method() == 'OPTIONS';
+    }
+
+    /**
+     * 是否为cli
+     * @access public
+     * @return bool
+     */
+    public function isCli()
+    {
+        return PHP_SAPI == 'cli';
+    }
+
+    /**
+     * 是否为cgi
+     * @access public
+     * @return bool
+     */
+    public function isCgi()
+    {
+        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 (true === $name) {
+            // 获取包含文件上传信息的数组
+            $file = $this->file();
+            $data = is_array($file) ? array_merge($this->param, $file) : $this->param;
+            return $this->input($data, '', $default, $filter);
+        }
+        return $this->input($this->param, $name, $default, $filter);
+    }
+
+    /**
+     * 设置获取路由参数
+     * @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)) {
+            $this->param        = [];
+            $this->mergeParam   = false;
+            return $this->route = array_merge($this->route, $name);
+        }
+        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 (empty($this->get)) {
+            $this->get = $_GET;
+        }
+        if (is_array($name)) {
+            $this->param      = [];
+            $this->mergeParam = false;
+            return $this->get = array_merge($this->get, $name);
+        }
+        return $this->input($this->get, $name, $default, $filter);
+    }
+
+    /**
+     * 设置获取POST参数
+     * @access public
+     * @param string       $name    变量名
+     * @param mixed        $default 默认值
+     * @param string|array $filter  过滤方法
+     * @return mixed
+     */
+    public function post($name = '', $default = null, $filter = '')
+    {
+        if (empty($this->post)) {
+            $content = $this->input;
+            if (empty($_POST) && false !== strpos($this->contentType(), 'application/json')) {
+                $this->post = (array) json_decode($content, true);
+            } else {
+                $this->post = $_POST;
+            }
+        }
+        if (is_array($name)) {
+            $this->param       = [];
+            $this->mergeParam  = false;
+            return $this->post = array_merge($this->post, $name);
+        }
+        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_null($this->put)) {
+            $content = $this->input;
+            if (false !== strpos($this->contentType(), 'application/json')) {
+                $this->put = (array) json_decode($content, true);
+            } else {
+                parse_str($content, $this->put);
+            }
+        }
+        if (is_array($name)) {
+            $this->param      = [];
+            $this->mergeParam = false;
+            return $this->put = is_null($this->put) ? $name : array_merge($this->put, $name);
+        }
+
+        return $this->input($this->put, $name, $default, $filter);
+    }
+
+    /**
+     * 设置获取DELETE参数
+     * @access public
+     * @param string|array $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 string|array $name    变量名
+     * @param mixed        $default 默认值
+     * @param string|array $filter  过滤方法
+     * @return mixed
+     */
+    public function patch($name = '', $default = null, $filter = '')
+    {
+        return $this->put($name, $default, $filter);
+    }
+
+    /**
+     * 获取request变量
+     * @param string       $name    数据名称
+     * @param string       $default 默认值
+     * @param string|array $filter  过滤方法
+     * @return mixed
+     */
+    public function request($name = '', $default = null, $filter = '')
+    {
+        if (empty($this->request)) {
+            $this->request = $_REQUEST;
+        }
+        if (is_array($name)) {
+            $this->param          = [];
+            $this->mergeParam     = false;
+            return $this->request = array_merge($this->request, $name);
+        }
+        return $this->input($this->request, $name, $default, $filter);
+    }
+
+    /**
+     * 获取session数据
+     * @access public
+     * @param string|array $name    数据名称
+     * @param string       $default 默认值
+     * @param string|array $filter  过滤方法
+     * @return mixed
+     */
+    public function session($name = '', $default = null, $filter = '')
+    {
+        if (empty($this->session)) {
+            $this->session = Session::get();
+        }
+        if (is_array($name)) {
+            return $this->session = array_merge($this->session, $name);
+        }
+        return $this->input($this->session, $name, $default, $filter);
+    }
+
+    /**
+     * 获取cookie参数
+     * @access public
+     * @param string|array $name    数据名称
+     * @param string       $default 默认值
+     * @param string|array $filter  过滤方法
+     * @return mixed
+     */
+    public function cookie($name = '', $default = null, $filter = '')
+    {
+        if (empty($this->cookie)) {
+            $this->cookie = Cookie::get();
+        }
+        if (is_array($name)) {
+            return $this->cookie = array_merge($this->cookie, $name);
+        } elseif (!empty($name)) {
+            $data = Cookie::has($name) ? Cookie::get($name) : $default;
+        } else {
+            $data = $this->cookie;
+        }
+
+        // 解析过滤器
+        $filter = $this->getFilter($filter, $default);
+
+        if (is_array($data)) {
+            array_walk_recursive($data, [$this, 'filterValue'], $filter);
+            reset($data);
+        } else {
+            $this->filterValue($data, $name, $filter);
+        }
+        return $data;
+    }
+
+    /**
+     * 获取server参数
+     * @access public
+     * @param string|array $name    数据名称
+     * @param string       $default 默认值
+     * @param string|array $filter  过滤方法
+     * @return mixed
+     */
+    public function server($name = '', $default = null, $filter = '')
+    {
+        if (empty($this->server)) {
+            $this->server = $_SERVER;
+        }
+        if (is_array($name)) {
+            return $this->server = array_merge($this->server, $name);
+        }
+        return $this->input($this->server, false === $name ? false : strtoupper($name), $default, $filter);
+    }
+
+    /**
+     * 获取上传的文件信息
+     * @access public
+     * @param string|array $name 名称
+     * @return null|array|\think\File
+     */
+    public function file($name = '')
+    {
+        if (empty($this->file)) {
+            $this->file = isset($_FILES) ? $_FILES : [];
+        }
+        if (is_array($name)) {
+            return $this->file = array_merge($this->file, $name);
+        }
+        $files = $this->file;
+        if (!empty($files)) {
+            // 处理上传文件
+            $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 (empty($file['tmp_name'][$i]) || !is_file($file['tmp_name'][$i])) {
+                            continue;
+                        }
+                        $temp['key'] = $key;
+                        foreach ($keys as $_key) {
+                            $temp[$_key] = $file[$_key][$i];
+                        }
+                        $item[] = (new File($temp['tmp_name']))->setUploadInfo($temp);
+                    }
+                    $array[$key] = $item;
+                } else {
+                    if ($file instanceof File) {
+                        $array[$key] = $file;
+                    } else {
+                        if (empty($file['tmp_name']) || !is_file($file['tmp_name'])) {
+                            continue;
+                        }
+                        $array[$key] = (new File($file['tmp_name']))->setUploadInfo($file);
+                    }
+                }
+            }
+            if (strpos($name, '.')) {
+                list($name, $sub) = explode('.', $name);
+            }
+            if ('' === $name) {
+                // 获取全部文件
+                return $array;
+            } elseif (isset($sub) && isset($array[$name][$sub])) {
+                return $array[$name][$sub];
+            } elseif (isset($array[$name])) {
+                return $array[$name];
+            }
+        }
+        return;
+    }
+
+    /**
+     * 获取环境变量
+     * @param string|array $name    数据名称
+     * @param string       $default 默认值
+     * @param string|array $filter  过滤方法
+     * @return mixed
+     */
+    public function env($name = '', $default = null, $filter = '')
+    {
+        if (empty($this->env)) {
+            $this->env = $_ENV;
+        }
+        if (is_array($name)) {
+            return $this->env = array_merge($this->env, $name);
+        }
+        return $this->input($this->env, false === $name ? false : strtoupper($name), $default, $filter);
+    }
+
+    /**
+     * 设置或者获取当前的Header
+     * @access public
+     * @param string|array $name    header名称
+     * @param string       $default 默认值
+     * @return string
+     */
+    public function header($name = '', $default = null)
+    {
+        if (empty($this->header)) {
+            $header = [];
+            if (function_exists('apache_request_headers') && $result = apache_request_headers()) {
+                $header = $result;
+            } else {
+                $server = $this->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'];
+                }
+            }
+            $this->header = array_change_key_case($header);
+        }
+        if (is_array($name)) {
+            return $this->header = array_merge($this->header, $name);
+        }
+        if ('' === $name) {
+            return $this->header;
+        }
+        $name = str_replace('_', '-', strtolower($name));
+        return isset($this->header[$name]) ? $this->header[$name] : $default;
+    }
+
+    /**
+     * 获取变量 支持过滤和默认值
+     * @param array        $data    数据源
+     * @param string|false $name    字段名
+     * @param mixed        $default 默认值
+     * @param string|array $filter  过滤函数
+     * @return mixed
+     */
+    public function input($data = [], $name = '', $default = null, $filter = '')
+    {
+        if (false === $name) {
+            // 获取原始数据
+            return $data;
+        }
+        $name = (string) $name;
+        if ('' != $name) {
+            // 解析name
+            if (strpos($name, '/')) {
+                list($name, $type) = explode('/', $name);
+            } else {
+                $type = 's';
+            }
+            // 按.拆分成多维数组进行判断
+            foreach (explode('.', $name) as $val) {
+                if (isset($data[$val])) {
+                    $data = $data[$val];
+                } else {
+                    // 无输入数据,返回默认值
+                    return $default;
+                }
+            }
+            if (is_object($data)) {
+                return $data;
+            }
+        }
+
+        // 解析过滤器
+        $filter = $this->getFilter($filter, $default);
+
+        if (is_array($data)) {
+            array_walk_recursive($data, [$this, 'filterValue'], $filter);
+            reset($data);
+        } else {
+            $this->filterValue($data, $name, $filter);
+        }
+
+        if (isset($type) && $data !== $default) {
+            // 强制类型转换
+            $this->typeCast($data, $type);
+        }
+        return $data;
+    }
+
+    /**
+     * 设置或获取当前的过滤规则
+     * @param mixed $filter 过滤规则
+     * @return mixed
+     */
+    public function filter($filter = null)
+    {
+        if (is_null($filter)) {
+            return $this->filter;
+        } else {
+            $this->filter = $filter;
+        }
+    }
+
+    protected function getFilter($filter, $default)
+    {
+        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;
+    }
+
+    /**
+     * 递归过滤给定的值
+     * @param mixed $value   键值
+     * @param mixed $key     键名
+     * @param array $filters 过滤方法+默认值
+     * @return mixed
+     */
+    private 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 (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 $this->filterExp($value);
+    }
+
+    /**
+     * 过滤表单中的表达式
+     * @param string $value
+     * @return void
+     */
+    public function filterExp(&$value)
+    {
+        // 过滤查询特殊字符
+        if (is_string($value) && preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT LIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOT EXISTS|NOTEXISTS|EXISTS|NOT NULL|NOTNULL|NULL|BETWEEN TIME|NOT BETWEEN TIME|NOTBETWEEN TIME|NOTIN|NOT IN|IN)$/i', $value)) {
+            $value .= ' ';
+        }
+        // TODO 其他安全过滤
+    }
+
+    /**
+     * 强制类型转换
+     * @param string $data
+     * @param string $type
+     * @return mixed
+     */
+    private function typeCast(&$data, $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':
+            default:
+                if (is_scalar($data)) {
+                    $data = (string) $data;
+                } else {
+                    throw new \InvalidArgumentException('variable type error:' . gettype($data));
+                }
+        }
+    }
+
+    /**
+     * 是否存在某个请求参数
+     * @access public
+     * @param string $name       变量名
+     * @param string $type       变量类型
+     * @param bool   $checkEmpty 是否检测空值
+     * @return mixed
+     */
+    public function has($name, $type = 'param', $checkEmpty = false)
+    {
+        if (empty($this->$type)) {
+            $param = $this->$type();
+        } else {
+            $param = $this->$type;
+        }
+        // 按.拆分成多维数组进行判断
+        foreach (explode('.', $name) as $val) {
+            if (isset($param[$val])) {
+                $param = $param[$val];
+            } else {
+                return false;
+            }
+        }
+        return ($checkEmpty && '' === $param) ? false : true;
+    }
+
+    /**
+     * 获取指定的参数
+     * @access public
+     * @param string|array $name 变量名
+     * @param string       $type 变量类型
+     * @return mixed
+     */
+    public function only($name, $type = 'param')
+    {
+        $param = $this->$type();
+        if (is_string($name)) {
+            $name = explode(',', $name);
+        }
+        $item = [];
+        foreach ($name as $key) {
+            if (isset($param[$key])) {
+                $item[$key] = $param[$key];
+            }
+        }
+        return $item;
+    }
+
+    /**
+     * 排除指定参数获取
+     * @access public
+     * @param string|array $name 变量名
+     * @param string       $type 变量类型
+     * @return mixed
+     */
+    public function except($name, $type = 'param')
+    {
+        $param = $this->$type();
+        if (is_string($name)) {
+            $name = explode(',', $name);
+        }
+        foreach ($name as $key) {
+            if (isset($param[$key])) {
+                unset($param[$key]);
+            }
+        }
+        return $param;
+    }
+
+    /**
+     * 当前是否ssl
+     * @access public
+     * @return bool
+     */
+    public function isSsl()
+    {
+        $server = array_merge($_SERVER, $this->server);
+        if (isset($server['HTTPS']) && ('1' == $server['HTTPS'] || 'on' == strtolower($server['HTTPS']))) {
+            return true;
+        } elseif (isset($server['REQUEST_SCHEME']) && 'https' == $server['REQUEST_SCHEME']) {
+            return true;
+        } elseif (isset($server['SERVER_PORT']) && ('443' == $server['SERVER_PORT'])) {
+            return true;
+        } elseif (isset($server['HTTP_X_FORWARDED_PROTO']) && 'https' == $server['HTTP_X_FORWARDED_PROTO']) {
+            return true;
+        } elseif (Config::get('https_agent_name') && isset($server[Config::get('https_agent_name')])) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 当前是否Ajax请求
+     * @access public
+     * @param bool $ajax true 获取原始ajax请求
+     * @return bool
+     */
+    public function isAjax($ajax = false)
+    {
+        $value  = $this->server('HTTP_X_REQUESTED_WITH', '', 'strtolower');
+        $result = ('xmlhttprequest' == $value) ? true : false;
+        if (true === $ajax) {
+            return $result;
+        } else {
+            $result           = $this->param(Config::get('var_ajax')) ? true : $result;
+            $this->mergeParam = false;
+            return $result;
+        }
+    }
+
+    /**
+     * 当前是否Pjax请求
+     * @access public
+     * @param bool $pjax true 获取原始pjax请求
+     * @return bool
+     */
+    public function isPjax($pjax = false)
+    {
+        $result = !is_null($this->server('HTTP_X_PJAX')) ? true : false;
+        if (true === $pjax) {
+            return $result;
+        } else {
+            $result           = $this->param(Config::get('var_pjax')) ? true : $result;
+            $this->mergeParam = false;
+            return $result;
+        }
+    }
+
+    /**
+     * 获取客户端IP地址
+     * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字
+     * @param boolean $adv  是否进行高级模式获取(有可能被伪装)
+     * @return mixed
+     */
+    public function ip($type = 0, $adv = true)
+    {
+        $type      = $type ? 1 : 0;
+        static $ip = null;
+        if (null !== $ip) {
+            return $ip[$type];
+        }
+
+        $httpAgentIp = Config::get('http_agent_ip');
+
+        if ($httpAgentIp && isset($_SERVER[$httpAgentIp])) {
+            $ip = $_SERVER[$httpAgentIp];
+        } elseif ($adv) {
+            if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+                $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
+                $pos = array_search('unknown', $arr);
+                if (false !== $pos) {
+                    unset($arr[$pos]);
+                }
+                $ip = trim(current($arr));
+            } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
+                $ip = $_SERVER['HTTP_CLIENT_IP'];
+            } elseif (isset($_SERVER['REMOTE_ADDR'])) {
+                $ip = $_SERVER['REMOTE_ADDR'];
+            }
+        } elseif (isset($_SERVER['REMOTE_ADDR'])) {
+            $ip = $_SERVER['REMOTE_ADDR'];
+        }
+        // IP地址合法验证
+        $long = sprintf("%u", ip2long($ip));
+        $ip   = $long ? [$ip, $long] : ['0.0.0.0', 0];
+        return $ip[$type];
+    }
+
+    /**
+     * 检测是否使用手机访问
+     * @access public
+     * @return bool
+     */
+    public function isMobile()
+    {
+        if (isset($_SERVER['HTTP_VIA']) && stristr($_SERVER['HTTP_VIA'], "wap")) {
+            return true;
+        } elseif (isset($_SERVER['HTTP_ACCEPT']) && strpos(strtoupper($_SERVER['HTTP_ACCEPT']), "VND.WAP.WML")) {
+            return true;
+        } elseif (isset($_SERVER['HTTP_X_WAP_PROFILE']) || isset($_SERVER['HTTP_PROFILE'])) {
+            return true;
+        } elseif (isset($_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', $_SERVER['HTTP_USER_AGENT'])) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 当前URL地址中的scheme参数
+     * @access public
+     * @return string
+     */
+    public function scheme()
+    {
+        return $this->isSsl() ? 'https' : 'http';
+    }
+
+    /**
+     * 当前请求URL地址中的query参数
+     * @access public
+     * @return string
+     */
+    public function query()
+    {
+        return $this->server('QUERY_STRING');
+    }
+
+    /**
+     * 当前请求的host
+     * @access public
+     * @param bool $strict true 仅仅获取HOST
+     * @return string
+     */
+    public function host($strict = false)
+    {
+        if (isset($_SERVER['HTTP_X_REAL_HOST'])) {
+            $host = $_SERVER['HTTP_X_REAL_HOST'];
+        } else {
+            $host = $this->server('HTTP_HOST');
+        }
+
+        return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host;
+    }
+
+    /**
+     * 当前请求URL地址中的port参数
+     * @access public
+     * @return integer
+     */
+    public function port()
+    {
+        return $this->server('SERVER_PORT');
+    }
+
+    /**
+     * 当前请求 SERVER_PROTOCOL
+     * @access public
+     * @return integer
+     */
+    public function protocol()
+    {
+        return $this->server('SERVER_PROTOCOL');
+    }
+
+    /**
+     * 当前请求 REMOTE_PORT
+     * @access public
+     * @return integer
+     */
+    public function remotePort()
+    {
+        return $this->server('REMOTE_PORT');
+    }
+
+    /**
+     * 当前请求 HTTP_CONTENT_TYPE
+     * @access public
+     * @return string
+     */
+    public function contentType()
+    {
+        $contentType = $this->server('CONTENT_TYPE');
+        if ($contentType) {
+            if (strpos($contentType, ';')) {
+                list($type) = explode(';', $contentType);
+            } else {
+                $type = $contentType;
+            }
+            return trim($type);
+        }
+        return '';
+    }
+
+    /**
+     * 获取当前请求的路由信息
+     * @access public
+     * @param array $route 路由名称
+     * @return array
+     */
+    public function routeInfo($route = [])
+    {
+        if (!empty($route)) {
+            $this->routeInfo = $route;
+        } else {
+            return $this->routeInfo;
+        }
+    }
+
+    /**
+     * 设置或者获取当前请求的调度信息
+     * @access public
+     * @param array $dispatch 调度信息
+     * @return array
+     */
+    public function dispatch($dispatch = null)
+    {
+        if (!is_null($dispatch)) {
+            $this->dispatch = $dispatch;
+        }
+        return $this->dispatch;
+    }
+
+    /**
+     * 设置或者获取当前的模块名
+     * @access public
+     * @param string $module 模块名
+     * @return string|Request
+     */
+    public function module($module = null)
+    {
+        if (!is_null($module)) {
+            $this->module = $module;
+            return $this;
+        } else {
+            return $this->module ?: '';
+        }
+    }
+
+    /**
+     * 设置或者获取当前的控制器名
+     * @access public
+     * @param string $controller 控制器名
+     * @return string|Request
+     */
+    public function controller($controller = null)
+    {
+        if (!is_null($controller)) {
+            $this->controller = $controller;
+            return $this;
+        } else {
+            return $this->controller ?: '';
+        }
+    }
+
+    /**
+     * 设置或者获取当前的操作名
+     * @access public
+     * @param string $action 操作名
+     * @return string|Request
+     */
+    public function action($action = null)
+    {
+        if (!is_null($action) && !is_bool($action)) {
+            $this->action = $action;
+            return $this;
+        } else {
+            $name = $this->action ?: '';
+            return true === $action ? $name : strtolower($name);
+        }
+    }
+
+    /**
+     * 设置或者获取当前的语言
+     * @access public
+     * @param string $lang 语言名
+     * @return string|Request
+     */
+    public function langset($lang = null)
+    {
+        if (!is_null($lang)) {
+            $this->langset = $lang;
+            return $this;
+        } else {
+            return $this->langset ?: '';
+        }
+    }
+
+    /**
+     * 设置或者获取当前请求的content
+     * @access public
+     * @return string
+     */
+    public function getContent()
+    {
+        if (is_null($this->content)) {
+            $this->content = $this->input;
+        }
+        return $this->content;
+    }
+
+    /**
+     * 获取当前请求的php://input
+     * @access public
+     * @return string
+     */
+    public function getInput()
+    {
+        return $this->input;
+    }
+
+    /**
+     * 生成请求令牌
+     * @access public
+     * @param string $name 令牌名称
+     * @param mixed  $type 令牌生成方法
+     * @return string
+     */
+    public function token($name = '__token__', $type = 'md5')
+    {
+        $type  = is_callable($type) ? $type : 'md5';
+        $token = call_user_func($type, $_SERVER['REQUEST_TIME_FLOAT']);
+        if ($this->isAjax()) {
+            header($name . ': ' . $token);
+        }
+        Session::set($name, $token);
+        return $token;
+    }
+
+    /**
+     * 设置当前地址的请求缓存
+     * @access public
+     * @param string $key    缓存标识,支持变量规则 ,例如 item/:name/:id
+     * @param mixed  $expire 缓存有效期
+     * @param array  $except 缓存排除
+     * @param string $tag    缓存标签
+     * @return void
+     */
+    public function cache($key, $expire = null, $except = [], $tag = null)
+    {
+        if (!is_array($except)) {
+            $tag    = $except;
+            $except = [];
+        }
+
+        if (false !== $key && $this->isGet() && !$this->isCheckCache) {
+            // 标记请求缓存检查
+            $this->isCheckCache = true;
+            if (false === $expire) {
+                // 关闭当前缓存
+                return;
+            }
+            if ($key instanceof \Closure) {
+                $key = call_user_func_array($key, [$this]);
+            } elseif (true === $key) {
+                foreach ($except as $rule) {
+                    if (0 === stripos($this->url(), $rule)) {
+                        return;
+                    }
+                }
+                // 自动缓存功能
+                $key = '__URL__';
+            } elseif (strpos($key, '|')) {
+                list($key, $fun) = explode('|', $key);
+            }
+            // 特殊规则替换
+            if (false !== strpos($key, '__')) {
+                $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__', ''], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key);
+            }
+
+            if (false !== strpos($key, ':')) {
+                $param = $this->param();
+                foreach ($param as $item => $val) {
+                    if (is_string($val) && false !== strpos($key, ':' . $item)) {
+                        $key = str_replace(':' . $item, $val, $key);
+                    }
+                }
+            } elseif (strpos($key, ']')) {
+                if ('[' . $this->ext() . ']' == $key) {
+                    // 缓存某个后缀的请求
+                    $key = md5($this->url());
+                } else {
+                    return;
+                }
+            }
+            if (isset($fun)) {
+                $key = $fun($key);
+            }
+
+            if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) {
+                // 读取缓存
+                $response = Response::create()->code(304);
+                throw new \think\exception\HttpResponseException($response);
+            } elseif (Cache::has($key)) {
+                list($content, $header) = Cache::get($key);
+                $response               = Response::create($content)->header($header);
+                throw new \think\exception\HttpResponseException($response);
+            } else {
+                $this->cache = [$key, $expire, $tag];
+            }
+        }
+    }
+
+    /**
+     * 读取请求缓存设置
+     * @access public
+     * @return array
+     */
+    public function getCache()
+    {
+        return $this->cache;
+    }
+
+    /**
+     * 设置当前请求绑定的对象实例
+     * @access public
+     * @param string|array $name 绑定的对象标识
+     * @param mixed        $obj  绑定的对象实例
+     * @return mixed
+     */
+    public function bind($name, $obj = null)
+    {
+        if (is_array($name)) {
+            $this->bind = array_merge($this->bind, $name);
+        } else {
+            $this->bind[$name] = $obj;
+        }
+    }
+
+    public function __set($name, $value)
+    {
+        $this->bind[$name] = $value;
+    }
+
+    public function __get($name)
+    {
+        return isset($this->bind[$name]) ? $this->bind[$name] : null;
+    }
+
+    public function __isset($name)
+    {
+        return isset($this->bind[$name]);
+    }
+}

+ 332 - 0
thinkphp/library/think/Response.php

@@ -0,0 +1,332 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\response\Json as JsonResponse;
+use think\response\Jsonp as JsonpResponse;
+use think\response\Redirect as RedirectResponse;
+use think\response\View as ViewResponse;
+use think\response\Xml as XmlResponse;
+
+class Response
+{
+    // 原始数据
+    protected $data;
+
+    // 当前的contentType
+    protected $contentType = 'text/html';
+
+    // 字符集
+    protected $charset = 'utf-8';
+
+    //状态
+    protected $code = 200;
+
+    // 输出参数
+    protected $options = [];
+    // header参数
+    protected $header = [];
+
+    protected $content = null;
+
+    /**
+     * 构造函数
+     * @access   public
+     * @param mixed $data    输出数据
+     * @param int   $code
+     * @param array $header
+     * @param array $options 输出参数
+     */
+    public function __construct($data = '', $code = 200, array $header = [], $options = [])
+    {
+        $this->data($data);
+        if (!empty($options)) {
+            $this->options = array_merge($this->options, $options);
+        }
+        $this->contentType($this->contentType, $this->charset);
+        $this->header = array_merge($this->header, $header);
+        $this->code   = $code;
+    }
+
+    /**
+     * 创建Response对象
+     * @access public
+     * @param mixed  $data    输出数据
+     * @param string $type    输出类型
+     * @param int    $code
+     * @param array  $header
+     * @param array  $options 输出参数
+     * @return Response|JsonResponse|ViewResponse|XmlResponse|RedirectResponse|JsonpResponse
+     */
+    public static function create($data = '', $type = '', $code = 200, array $header = [], $options = [])
+    {
+        $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type));
+        if (class_exists($class)) {
+            $response = new $class($data, $code, $header, $options);
+        } else {
+            $response = new static($data, $code, $header, $options);
+        }
+
+        return $response;
+    }
+
+    /**
+     * 发送数据到客户端
+     * @access public
+     * @return mixed
+     * @throws \InvalidArgumentException
+     */
+    public function send()
+    {
+        // 监听response_send
+        Hook::listen('response_send', $this);
+
+        // 处理输出数据
+        $data = $this->getContent();
+
+        // Trace调试注入
+        if (Env::get('app_trace', Config::get('app_trace'))) {
+            Debug::inject($this, $data);
+        }
+
+        if (200 == $this->code) {
+            $cache = Request::instance()->getCache();
+            if ($cache) {
+                $this->header['Cache-Control'] = 'max-age=' . $cache[1] . ',must-revalidate';
+                $this->header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT';
+                $this->header['Expires']       = gmdate('D, d M Y H:i:s', $_SERVER['REQUEST_TIME'] + $cache[1]) . ' GMT';
+                Cache::tag($cache[2])->set($cache[0], [$data, $this->header], $cache[1]);
+            }
+        }
+
+        if (!headers_sent() && !empty($this->header)) {
+            // 发送状态码
+            http_response_code($this->code);
+            // 发送头部信息
+            foreach ($this->header as $name => $val) {
+                if (is_null($val)) {
+                    header($name);
+                } else {
+                    header($name . ':' . $val);
+                }
+            }
+        }
+
+        echo $data;
+
+        if (function_exists('fastcgi_finish_request')) {
+            // 提高页面响应
+            fastcgi_finish_request();
+        }
+
+        // 监听response_end
+        Hook::listen('response_end', $this);
+
+        // 清空当次请求有效的数据
+        if (!($this instanceof RedirectResponse)) {
+            Session::flush();
+        }
+    }
+
+    /**
+     * 处理数据
+     * @access protected
+     * @param mixed $data 要处理的数据
+     * @return mixed
+     */
+    protected function output($data)
+    {
+        return $data;
+    }
+
+    /**
+     * 输出的参数
+     * @access public
+     * @param mixed $options 输出参数
+     * @return $this
+     */
+    public function options($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 string|array $name  参数名
+     * @param string       $value 参数值
+     * @return $this
+     */
+    public function header($name, $value = null)
+    {
+        if (is_array($name)) {
+            $this->header = array_merge($this->header, $name);
+        } else {
+            $this->header[$name] = $value;
+        }
+        return $this;
+    }
+
+    /**
+     * 设置页面输出内容
+     * @param $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状态
+     * @param integer $code 状态码
+     * @return $this
+     */
+    public function code($code)
+    {
+        $this->code = $code;
+        return $this;
+    }
+
+    /**
+     * LastModified
+     * @param string $time
+     * @return $this
+     */
+    public function lastModified($time)
+    {
+        $this->header['Last-Modified'] = $time;
+        return $this;
+    }
+
+    /**
+     * Expires
+     * @param string $time
+     * @return $this
+     */
+    public function expires($time)
+    {
+        $this->header['Expires'] = $time;
+        return $this;
+    }
+
+    /**
+     * ETag
+     * @param string $eTag
+     * @return $this
+     */
+    public function eTag($eTag)
+    {
+        $this->header['ETag'] = $eTag;
+        return $this;
+    }
+
+    /**
+     * 页面缓存控制
+     * @param string $cache 状态码
+     * @return $this
+     */
+    public function cacheControl($cache)
+    {
+        $this->header['Cache-control'] = $cache;
+        return $this;
+    }
+
+    /**
+     * 页面输出类型
+     * @param string $contentType 输出类型
+     * @param string $charset     输出编码
+     * @return $this
+     */
+    public function contentType($contentType, $charset = 'utf-8')
+    {
+        $this->header['Content-Type'] = $contentType . '; charset=' . $charset;
+        return $this;
+    }
+
+    /**
+     * 获取头部信息
+     * @param string $name 头部名称
+     * @return mixed
+     */
+    public function getHeader($name = '')
+    {
+        if (!empty($name)) {
+            return isset($this->header[$name]) ? $this->header[$name] : null;
+        } else {
+            return $this->header;
+        }
+    }
+
+    /**
+     * 获取原始数据
+     * @return mixed
+     */
+    public function getData()
+    {
+        return $this->data;
+    }
+
+    /**
+     * 获取输出数据
+     * @return mixed
+     */
+    public function getContent()
+    {
+        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;
+    }
+
+    /**
+     * 获取状态码
+     * @return integer
+     */
+    public function getCode()
+    {
+        return $this->code;
+    }
+}

+ 1645 - 0
thinkphp/library/think/Route.php

@@ -0,0 +1,1645 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\exception\HttpException;
+
+class Route
+{
+    // 路由规则
+    private static $rules = [
+        'get'     => [],
+        'post'    => [],
+        'put'     => [],
+        'delete'  => [],
+        'patch'   => [],
+        'head'    => [],
+        'options' => [],
+        '*'       => [],
+        'alias'   => [],
+        'domain'  => [],
+        'pattern' => [],
+        'name'    => [],
+    ];
+
+    // REST路由操作方法定义
+    private static $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'],
+    ];
+
+    // 不同请求类型的方法前缀
+    private static $methodPrefix = [
+        'get'    => 'get',
+        'post'   => 'post',
+        'put'    => 'put',
+        'delete' => 'delete',
+        'patch'  => 'patch',
+    ];
+
+    // 子域名
+    private static $subDomain = '';
+    // 域名绑定
+    private static $bind = [];
+    // 当前分组信息
+    private static $group = [];
+    // 当前子域名绑定
+    private static $domainBind;
+    private static $domainRule;
+    // 当前域名
+    private static $domain;
+    // 当前路由执行过程中的参数
+    private static $option = [];
+
+    /**
+     * 注册变量规则
+     * @access public
+     * @param string|array $name 变量名
+     * @param string       $rule 变量规则
+     * @return void
+     */
+    public static function pattern($name = null, $rule = '')
+    {
+        if (is_array($name)) {
+            self::$rules['pattern'] = array_merge(self::$rules['pattern'], $name);
+        } else {
+            self::$rules['pattern'][$name] = $rule;
+        }
+    }
+
+    /**
+     * 注册子域名部署规则
+     * @access public
+     * @param string|array $domain  子域名
+     * @param mixed        $rule    路由规则
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
+     * @return void
+     */
+    public static function domain($domain, $rule = '', $option = [], $pattern = [])
+    {
+        if (is_array($domain)) {
+            foreach ($domain as $key => $item) {
+                self::domain($key, $item, $option, $pattern);
+            }
+        } elseif ($rule instanceof \Closure) {
+            // 执行闭包
+            self::setDomain($domain);
+            call_user_func_array($rule, []);
+            self::setDomain(null);
+        } elseif (is_array($rule)) {
+            self::setDomain($domain);
+            self::group('', function () use ($rule) {
+                // 动态注册域名的路由规则
+                self::registerRules($rule);
+            }, $option, $pattern);
+            self::setDomain(null);
+        } else {
+            self::$rules['domain'][$domain]['[bind]'] = [$rule, $option, $pattern];
+        }
+    }
+
+    private static function setDomain($domain)
+    {
+        self::$domain = $domain;
+    }
+
+    /**
+     * 设置路由绑定
+     * @access public
+     * @param mixed  $bind 绑定信息
+     * @param string $type 绑定类型 默认为module 支持 namespace class controller
+     * @return mixed
+     */
+    public static function bind($bind, $type = 'module')
+    {
+        self::$bind = ['type' => $type, $type => $bind];
+    }
+
+    /**
+     * 设置或者获取路由标识
+     * @access public
+     * @param string|array $name  路由命名标识 数组表示批量设置
+     * @param array        $value 路由地址及变量信息
+     * @return array
+     */
+    public static function name($name = '', $value = null)
+    {
+        if (is_array($name)) {
+            return self::$rules['name'] = $name;
+        } elseif ('' === $name) {
+            return self::$rules['name'];
+        } elseif (!is_null($value)) {
+            self::$rules['name'][strtolower($name)][] = $value;
+        } else {
+            $name = strtolower($name);
+            return isset(self::$rules['name'][$name]) ? self::$rules['name'][$name] : null;
+        }
+    }
+
+    /**
+     * 读取路由绑定
+     * @access public
+     * @param string $type 绑定类型
+     * @return mixed
+     */
+    public static function getBind($type)
+    {
+        return isset(self::$bind[$type]) ? self::$bind[$type] : null;
+    }
+
+    /**
+     * 导入配置文件的路由规则
+     * @access public
+     * @param array  $rule 路由规则
+     * @param string $type 请求类型
+     * @return void
+     */
+    public static function import(array $rule, $type = '*')
+    {
+        // 检查域名部署
+        if (isset($rule['__domain__'])) {
+            self::domain($rule['__domain__']);
+            unset($rule['__domain__']);
+        }
+
+        // 检查变量规则
+        if (isset($rule['__pattern__'])) {
+            self::pattern($rule['__pattern__']);
+            unset($rule['__pattern__']);
+        }
+
+        // 检查路由别名
+        if (isset($rule['__alias__'])) {
+            self::alias($rule['__alias__']);
+            unset($rule['__alias__']);
+        }
+
+        // 检查资源路由
+        if (isset($rule['__rest__'])) {
+            self::resource($rule['__rest__']);
+            unset($rule['__rest__']);
+        }
+
+        self::registerRules($rule, strtolower($type));
+    }
+
+    // 批量注册路由
+    protected static function registerRules($rules, $type = '*')
+    {
+        foreach ($rules as $key => $val) {
+            if (is_numeric($key)) {
+                $key = array_shift($val);
+            }
+            if (empty($val)) {
+                continue;
+            }
+            if (is_string($key) && 0 === strpos($key, '[')) {
+                $key = substr($key, 1, -1);
+                self::group($key, $val);
+            } elseif (is_array($val)) {
+                self::setRule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []);
+            } else {
+                self::setRule($key, $val, $type);
+            }
+        }
+    }
+
+    /**
+     * 注册路由规则
+     * @access public
+     * @param string|array $rule    路由规则
+     * @param string       $route   路由地址
+     * @param string       $type    请求类型
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
+     * @return void
+     */
+    public static function rule($rule, $route = '', $type = '*', $option = [], $pattern = [])
+    {
+        $group = self::getGroup('name');
+
+        if (!is_null($group)) {
+            // 路由分组
+            $option  = array_merge(self::getGroup('option'), $option);
+            $pattern = array_merge(self::getGroup('pattern'), $pattern);
+        }
+
+        $type = strtolower($type);
+
+        if (strpos($type, '|')) {
+            $option['method'] = $type;
+            $type             = '*';
+        }
+        if (is_array($rule) && empty($route)) {
+            foreach ($rule as $key => $val) {
+                if (is_numeric($key)) {
+                    $key = array_shift($val);
+                }
+                if (is_array($val)) {
+                    $route    = $val[0];
+                    $option1  = array_merge($option, $val[1]);
+                    $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []);
+                } else {
+                    $option1  = null;
+                    $pattern1 = null;
+                    $route    = $val;
+                }
+                self::setRule($key, $route, $type, !is_null($option1) ? $option1 : $option, !is_null($pattern1) ? $pattern1 : $pattern, $group);
+            }
+        } else {
+            self::setRule($rule, $route, $type, $option, $pattern, $group);
+        }
+
+    }
+
+    /**
+     * 设置路由规则
+     * @access public
+     * @param string|array $rule    路由规则
+     * @param string       $route   路由地址
+     * @param string       $type    请求类型
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
+     * @param string       $group   所属分组
+     * @return void
+     */
+    protected static function setRule($rule, $route, $type = '*', $option = [], $pattern = [], $group = '')
+    {
+        if (is_array($rule)) {
+            $name = $rule[0];
+            $rule = $rule[1];
+        } elseif (is_string($route)) {
+            $name = $route;
+        }
+        if (!isset($option['complete_match'])) {
+            if (Config::get('route_complete_match')) {
+                $option['complete_match'] = true;
+            } elseif ('$' == substr($rule, -1, 1)) {
+                // 是否完整匹配
+                $option['complete_match'] = true;
+            }
+        } elseif (empty($option['complete_match']) && '$' == substr($rule, -1, 1)) {
+            // 是否完整匹配
+            $option['complete_match'] = true;
+        }
+
+        if ('$' == substr($rule, -1, 1)) {
+            $rule = substr($rule, 0, -1);
+        }
+
+        if ('/' != $rule || $group) {
+            $rule = trim($rule, '/');
+        }
+        $vars = self::parseVar($rule);
+        if (isset($name)) {
+            $key    = $group ? $group . ($rule ? '/' . $rule : '') : $rule;
+            $suffix = isset($option['ext']) ? $option['ext'] : null;
+            self::name($name, [$key, $vars, self::$domain, $suffix]);
+        }
+        if (isset($option['modular'])) {
+            $route = $option['modular'] . '/' . $route;
+        }
+        if ($group) {
+            if ('*' != $type) {
+                $option['method'] = $type;
+            }
+            if (self::$domain) {
+                self::$rules['domain'][self::$domain]['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
+            } else {
+                self::$rules['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
+            }
+        } else {
+            if ('*' != $type && isset(self::$rules['*'][$rule])) {
+                unset(self::$rules['*'][$rule]);
+            }
+            if (self::$domain) {
+                self::$rules['domain'][self::$domain][$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
+            } else {
+                self::$rules[$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern];
+            }
+            if ('*' == $type) {
+                // 注册路由快捷方式
+                foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) {
+                    if (self::$domain && !isset(self::$rules['domain'][self::$domain][$method][$rule])) {
+                        self::$rules['domain'][self::$domain][$method][$rule] = true;
+                    } elseif (!self::$domain && !isset(self::$rules[$method][$rule])) {
+                        self::$rules[$method][$rule] = true;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 设置当前执行的参数信息
+     * @access public
+     * @param array $options 参数信息
+     * @return mixed
+     */
+    protected static function setOption($options = [])
+    {
+        self::$option[] = $options;
+    }
+
+    /**
+     * 获取当前执行的所有参数信息
+     * @access public
+     * @return array
+     */
+    public static function getOption()
+    {
+        return self::$option;
+    }
+
+    /**
+     * 获取当前的分组信息
+     * @access public
+     * @param string $type 分组信息名称 name option pattern
+     * @return mixed
+     */
+    public static function getGroup($type)
+    {
+        if (isset(self::$group[$type])) {
+            return self::$group[$type];
+        } else {
+            return 'name' == $type ? null : [];
+        }
+    }
+
+    /**
+     * 设置当前的路由分组
+     * @access public
+     * @param string $name    分组名称
+     * @param array  $option  分组路由参数
+     * @param array  $pattern 分组变量规则
+     * @return void
+     */
+    public static function setGroup($name, $option = [], $pattern = [])
+    {
+        self::$group['name']    = $name;
+        self::$group['option']  = $option ?: [];
+        self::$group['pattern'] = $pattern ?: [];
+    }
+
+    /**
+     * 注册路由分组
+     * @access public
+     * @param string|array   $name    分组名称或者参数
+     * @param array|\Closure $routes  路由地址
+     * @param array          $option  路由参数
+     * @param array          $pattern 变量规则
+     * @return void
+     */
+    public static function group($name, $routes, $option = [], $pattern = [])
+    {
+        if (is_array($name)) {
+            $option = $name;
+            $name   = isset($option['name']) ? $option['name'] : '';
+        }
+        // 分组
+        $currentGroup = self::getGroup('name');
+        if ($currentGroup) {
+            $name = $currentGroup . ($name ? '/' . ltrim($name, '/') : '');
+        }
+        if (!empty($name)) {
+            if ($routes instanceof \Closure) {
+                $currentOption  = self::getGroup('option');
+                $currentPattern = self::getGroup('pattern');
+                self::setGroup($name, array_merge($currentOption, $option), array_merge($currentPattern, $pattern));
+                call_user_func_array($routes, []);
+                self::setGroup($currentGroup, $currentOption, $currentPattern);
+                if ($currentGroup != $name) {
+                    self::$rules['*'][$name]['route']   = '';
+                    self::$rules['*'][$name]['var']     = self::parseVar($name);
+                    self::$rules['*'][$name]['option']  = $option;
+                    self::$rules['*'][$name]['pattern'] = $pattern;
+                }
+            } else {
+                $item          = [];
+                $completeMatch = Config::get('route_complete_match');
+                foreach ($routes as $key => $val) {
+                    if (is_numeric($key)) {
+                        $key = array_shift($val);
+                    }
+                    if (is_array($val)) {
+                        $route    = $val[0];
+                        $option1  = array_merge($option, isset($val[1]) ? $val[1] : []);
+                        $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []);
+                    } else {
+                        $route = $val;
+                    }
+
+                    $options  = isset($option1) ? $option1 : $option;
+                    $patterns = isset($pattern1) ? $pattern1 : $pattern;
+                    if ('$' == substr($key, -1, 1)) {
+                        // 是否完整匹配
+                        $options['complete_match'] = true;
+                        $key                       = substr($key, 0, -1);
+                    } elseif ($completeMatch) {
+                        $options['complete_match'] = true;
+                    }
+                    $key    = trim($key, '/');
+                    $vars   = self::parseVar($key);
+                    $item[] = ['rule' => $key, 'route' => $route, 'var' => $vars, 'option' => $options, 'pattern' => $patterns];
+                    // 设置路由标识
+                    $suffix = isset($options['ext']) ? $options['ext'] : null;
+                    self::name($route, [$name . ($key ? '/' . $key : ''), $vars, self::$domain, $suffix]);
+                }
+                self::$rules['*'][$name] = ['rule' => $item, 'route' => '', 'var' => [], 'option' => $option, 'pattern' => $pattern];
+            }
+
+            foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) {
+                if (!isset(self::$rules[$method][$name])) {
+                    self::$rules[$method][$name] = true;
+                } elseif (is_array(self::$rules[$method][$name])) {
+                    self::$rules[$method][$name] = array_merge(self::$rules['*'][$name], self::$rules[$method][$name]);
+                }
+            }
+
+        } elseif ($routes instanceof \Closure) {
+            // 闭包注册
+            $currentOption  = self::getGroup('option');
+            $currentPattern = self::getGroup('pattern');
+            self::setGroup('', array_merge($currentOption, $option), array_merge($currentPattern, $pattern));
+            call_user_func_array($routes, []);
+            self::setGroup($currentGroup, $currentOption, $currentPattern);
+        } else {
+            // 批量注册路由
+            self::rule($routes, '', '*', $option, $pattern);
+        }
+    }
+
+    /**
+     * 注册路由
+     * @access public
+     * @param string|array $rule    路由规则
+     * @param string       $route   路由地址
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
+     * @return void
+     */
+    public static function any($rule, $route = '', $option = [], $pattern = [])
+    {
+        self::rule($rule, $route, '*', $option, $pattern);
+    }
+
+    /**
+     * 注册GET路由
+     * @access public
+     * @param string|array $rule    路由规则
+     * @param string       $route   路由地址
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
+     * @return void
+     */
+    public static function get($rule, $route = '', $option = [], $pattern = [])
+    {
+        self::rule($rule, $route, 'GET', $option, $pattern);
+    }
+
+    /**
+     * 注册POST路由
+     * @access public
+     * @param string|array $rule    路由规则
+     * @param string       $route   路由地址
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
+     * @return void
+     */
+    public static function post($rule, $route = '', $option = [], $pattern = [])
+    {
+        self::rule($rule, $route, 'POST', $option, $pattern);
+    }
+
+    /**
+     * 注册PUT路由
+     * @access public
+     * @param string|array $rule    路由规则
+     * @param string       $route   路由地址
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
+     * @return void
+     */
+    public static function put($rule, $route = '', $option = [], $pattern = [])
+    {
+        self::rule($rule, $route, 'PUT', $option, $pattern);
+    }
+
+    /**
+     * 注册DELETE路由
+     * @access public
+     * @param string|array $rule    路由规则
+     * @param string       $route   路由地址
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
+     * @return void
+     */
+    public static function delete($rule, $route = '', $option = [], $pattern = [])
+    {
+        self::rule($rule, $route, 'DELETE', $option, $pattern);
+    }
+
+    /**
+     * 注册PATCH路由
+     * @access public
+     * @param string|array $rule    路由规则
+     * @param string       $route   路由地址
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
+     * @return void
+     */
+    public static function patch($rule, $route = '', $option = [], $pattern = [])
+    {
+        self::rule($rule, $route, 'PATCH', $option, $pattern);
+    }
+
+    /**
+     * 注册资源路由
+     * @access public
+     * @param string|array $rule    路由规则
+     * @param string       $route   路由地址
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
+     * @return void
+     */
+    public static function resource($rule, $route = '', $option = [], $pattern = [])
+    {
+        if (is_array($rule)) {
+            foreach ($rule as $key => $val) {
+                if (is_array($val)) {
+                    list($val, $option, $pattern) = array_pad($val, 3, []);
+                }
+                self::resource($key, $val, $option, $pattern);
+            }
+        } else {
+            if (strpos($rule, '.')) {
+                // 注册嵌套资源路由
+                $array = explode('.', $rule);
+                $last  = array_pop($array);
+                $item  = [];
+                foreach ($array as $val) {
+                    $item[] = $val . '/:' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id');
+                }
+                $rule = implode('/', $item) . '/' . $last;
+            }
+            // 注册资源路由
+            foreach (self::$rest as $key => $val) {
+                if ((isset($option['only']) && !in_array($key, $option['only']))
+                    || (isset($option['except']) && in_array($key, $option['except']))) {
+                    continue;
+                }
+                if (isset($last) && strpos($val[1], ':id') && isset($option['var'][$last])) {
+                    $val[1] = str_replace(':id', ':' . $option['var'][$last], $val[1]);
+                } elseif (strpos($val[1], ':id') && isset($option['var'][$rule])) {
+                    $val[1] = str_replace(':id', ':' . $option['var'][$rule], $val[1]);
+                }
+                $item           = ltrim($rule . $val[1], '/');
+                $option['rest'] = $key;
+                self::rule($item . '$', $route . '/' . $val[2], $val[0], $option, $pattern);
+            }
+        }
+    }
+
+    /**
+     * 注册控制器路由 操作方法对应不同的请求后缀
+     * @access public
+     * @param string $rule    路由规则
+     * @param string $route   路由地址
+     * @param array  $option  路由参数
+     * @param array  $pattern 变量规则
+     * @return void
+     */
+    public static function controller($rule, $route = '', $option = [], $pattern = [])
+    {
+        foreach (self::$methodPrefix as $type => $val) {
+            self::$type($rule . '/:action', $route . '/' . $val . ':action', $option, $pattern);
+        }
+    }
+
+    /**
+     * 注册别名路由
+     * @access public
+     * @param string|array $rule   路由别名
+     * @param string       $route  路由地址
+     * @param array        $option 路由参数
+     * @return void
+     */
+    public static function alias($rule = null, $route = '', $option = [])
+    {
+        if (is_array($rule)) {
+            self::$rules['alias'] = array_merge(self::$rules['alias'], $rule);
+        } else {
+            self::$rules['alias'][$rule] = $option ? [$route, $option] : $route;
+        }
+    }
+
+    /**
+     * 设置不同请求类型下面的方法前缀
+     * @access public
+     * @param string $method 请求类型
+     * @param string $prefix 类型前缀
+     * @return void
+     */
+    public static function setMethodPrefix($method, $prefix = '')
+    {
+        if (is_array($method)) {
+            self::$methodPrefix = array_merge(self::$methodPrefix, array_change_key_case($method));
+        } else {
+            self::$methodPrefix[strtolower($method)] = $prefix;
+        }
+    }
+
+    /**
+     * rest方法定义和修改
+     * @access public
+     * @param string|array $name     方法名称
+     * @param array|bool   $resource 资源
+     * @return void
+     */
+    public static function rest($name, $resource = [])
+    {
+        if (is_array($name)) {
+            self::$rest = $resource ? $name : array_merge(self::$rest, $name);
+        } else {
+            self::$rest[$name] = $resource;
+        }
+    }
+
+    /**
+     * 注册未匹配路由规则后的处理
+     * @access public
+     * @param string $route  路由地址
+     * @param string $method 请求类型
+     * @param array  $option 路由参数
+     * @return void
+     */
+    public static function miss($route, $method = '*', $option = [])
+    {
+        self::rule('__miss__', $route, $method, $option, []);
+    }
+
+    /**
+     * 注册一个自动解析的URL路由
+     * @access public
+     * @param string $route 路由地址
+     * @return void
+     */
+    public static function auto($route)
+    {
+        self::rule('__auto__', $route, '*', [], []);
+    }
+
+    /**
+     * 获取或者批量设置路由定义
+     * @access public
+     * @param mixed $rules 请求类型或者路由定义数组
+     * @return array
+     */
+    public static function rules($rules = '')
+    {
+        if (is_array($rules)) {
+            self::$rules = $rules;
+        } elseif ($rules) {
+            return true === $rules ? self::$rules : self::$rules[strtolower($rules)];
+        } else {
+            $rules = self::$rules;
+            unset($rules['pattern'], $rules['alias'], $rules['domain'], $rules['name']);
+            return $rules;
+        }
+    }
+
+    /**
+     * 检测子域名部署
+     * @access public
+     * @param Request $request      Request请求对象
+     * @param array   $currentRules 当前路由规则
+     * @param string  $method       请求类型
+     * @return void
+     */
+    public static function checkDomain($request, &$currentRules, $method = 'get')
+    {
+        // 域名规则
+        $rules = self::$rules['domain'];
+        // 开启子域名部署 支持二级和三级域名
+        if (!empty($rules)) {
+            $host = $request->host(true);
+            if (isset($rules[$host])) {
+                // 完整域名或者IP配置
+                $item = $rules[$host];
+            } else {
+                $rootDomain = Config::get('url_domain_root');
+                if ($rootDomain) {
+                    // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置
+                    $domain = explode('.', rtrim(stristr($host, $rootDomain, true), '.'));
+                } else {
+                    $domain = explode('.', $host, -2);
+                }
+                // 子域名配置
+                if (!empty($domain)) {
+                    // 当前子域名
+                    $subDomain       = implode('.', $domain);
+                    self::$subDomain = $subDomain;
+                    $domain2         = array_pop($domain);
+                    if ($domain) {
+                        // 存在三级域名
+                        $domain3 = array_pop($domain);
+                    }
+                    if ($subDomain && isset($rules[$subDomain])) {
+                        // 子域名配置
+                        $item = $rules[$subDomain];
+                    } elseif (isset($rules['*.' . $domain2]) && !empty($domain3)) {
+                        // 泛三级域名
+                        $item      = $rules['*.' . $domain2];
+                        $panDomain = $domain3;
+                    } elseif (isset($rules['*']) && !empty($domain2)) {
+                        // 泛二级域名
+                        if ('www' != $domain2) {
+                            $item      = $rules['*'];
+                            $panDomain = $domain2;
+                        }
+                    }
+                }
+            }
+            if (!empty($item)) {
+                if (isset($panDomain)) {
+                    // 保存当前泛域名
+                    $request->route(['__domain__' => $panDomain]);
+                }
+                if (isset($item['[bind]'])) {
+                    // 解析子域名部署规则
+                    list($rule, $option, $pattern) = $item['[bind]'];
+                    if (!empty($option['https']) && !$request->isSsl()) {
+                        // https检测
+                        throw new HttpException(404, 'must use https request:' . $host);
+                    }
+
+                    if (strpos($rule, '?')) {
+                        // 传入其它参数
+                        $array  = parse_url($rule);
+                        $result = $array['path'];
+                        parse_str($array['query'], $params);
+                        if (isset($panDomain)) {
+                            $pos = array_search('*', $params);
+                            if (false !== $pos) {
+                                // 泛域名作为参数
+                                $params[$pos] = $panDomain;
+                            }
+                        }
+                        $_GET = array_merge($_GET, $params);
+                    } else {
+                        $result = $rule;
+                    }
+
+                    if (0 === strpos($result, '\\')) {
+                        // 绑定到命名空间 例如 \app\index\behavior
+                        self::$bind = ['type' => 'namespace', 'namespace' => $result];
+                    } elseif (0 === strpos($result, '@')) {
+                        // 绑定到类 例如 @app\index\controller\User
+                        self::$bind = ['type' => 'class', 'class' => substr($result, 1)];
+                    } else {
+                        // 绑定到模块/控制器 例如 index/user
+                        self::$bind = ['type' => 'module', 'module' => $result];
+                    }
+                    self::$domainBind = true;
+                } else {
+                    self::$domainRule = $item;
+                    $currentRules     = isset($item[$method]) ? $item[$method] : $item['*'];
+                }
+            }
+        }
+    }
+
+    /**
+     * 检测URL路由
+     * @access public
+     * @param Request $request     Request请求对象
+     * @param string  $url         URL地址
+     * @param string  $depr        URL分隔符
+     * @param bool    $checkDomain 是否检测域名规则
+     * @return false|array
+     */
+    public static function check($request, $url, $depr = '/', $checkDomain = false)
+    {
+        //检查解析缓存
+        if (!App::$debug && Config::get('route_check_cache')) {
+            $key = self::getCheckCacheKey($request);
+            if (Cache::has($key)) {
+                list($rule, $route, $pathinfo, $option, $matches) = Cache::get($key);
+                return self::parseRule($rule, $route, $pathinfo, $option, $matches, true);
+            }
+        }
+
+        // 分隔符替换 确保路由定义使用统一的分隔符
+        $url = str_replace($depr, '|', $url);
+
+        if (isset(self::$rules['alias'][$url]) || isset(self::$rules['alias'][strstr($url, '|', true)])) {
+            // 检测路由别名
+            $result = self::checkRouteAlias($request, $url, $depr);
+            if (false !== $result) {
+                return $result;
+            }
+        }
+        $method = strtolower($request->method());
+        // 获取当前请求类型的路由规则
+        $rules = isset(self::$rules[$method]) ? self::$rules[$method] : [];
+        // 检测域名部署
+        if ($checkDomain) {
+            self::checkDomain($request, $rules, $method);
+        }
+        // 检测URL绑定
+        $return = self::checkUrlBind($url, $rules, $depr);
+        if (false !== $return) {
+            return $return;
+        }
+        if ('|' != $url) {
+            $url = rtrim($url, '|');
+        }
+        $item = str_replace('|', '/', $url);
+        if (isset($rules[$item])) {
+            // 静态路由规则检测
+            $rule = $rules[$item];
+            if (true === $rule) {
+                $rule = self::getRouteExpress($item);
+            }
+            if (!empty($rule['route']) && self::checkOption($rule['option'], $request)) {
+                self::setOption($rule['option']);
+                return self::parseRule($item, $rule['route'], $url, $rule['option']);
+            }
+        }
+
+        // 路由规则检测
+        if (!empty($rules)) {
+            return self::checkRoute($request, $rules, $url, $depr);
+        }
+        return false;
+    }
+
+    private static function getRouteExpress($key)
+    {
+        return self::$domainRule ? self::$domainRule['*'][$key] : self::$rules['*'][$key];
+    }
+
+    /**
+     * 检测路由规则
+     * @access private
+     * @param Request $request
+     * @param array   $rules   路由规则
+     * @param string  $url     URL地址
+     * @param string  $depr    URL分割符
+     * @param string  $group   路由分组名
+     * @param array   $options 路由参数(分组)
+     * @return mixed
+     */
+    private static function checkRoute($request, $rules, $url, $depr = '/', $group = '', $options = [])
+    {
+        foreach ($rules as $key => $item) {
+            if (true === $item) {
+                $item = self::getRouteExpress($key);
+            }
+            if (!isset($item['rule'])) {
+                continue;
+            }
+            $rule    = $item['rule'];
+            $route   = $item['route'];
+            $vars    = $item['var'];
+            $option  = $item['option'];
+            $pattern = $item['pattern'];
+
+            // 检查参数有效性
+            if (!self::checkOption($option, $request)) {
+                continue;
+            }
+
+            if (isset($option['ext'])) {
+                // 路由ext参数 优先于系统配置的URL伪静态后缀参数
+                $url = preg_replace('/\.' . $request->ext() . '$/i', '', $url);
+            }
+
+            if (is_array($rule)) {
+                // 分组路由
+                $pos = strpos(str_replace('<', ':', $key), ':');
+                if (false !== $pos) {
+                    $str = substr($key, 0, $pos);
+                } else {
+                    $str = $key;
+                }
+                if (is_string($str) && $str && 0 !== stripos(str_replace('|', '/', $url), $str)) {
+                    continue;
+                }
+                self::setOption($option);
+                $result = self::checkRoute($request, $rule, $url, $depr, $key, $option);
+                if (false !== $result) {
+                    return $result;
+                }
+            } elseif ($route) {
+                if ('__miss__' == $rule || '__auto__' == $rule) {
+                    // 指定特殊路由
+                    $var    = trim($rule, '__');
+                    ${$var} = $item;
+                    continue;
+                }
+                if ($group) {
+                    $rule = $group . ($rule ? '/' . ltrim($rule, '/') : '');
+                }
+
+                self::setOption($option);
+                if (isset($options['bind_model']) && isset($option['bind_model'])) {
+                    $option['bind_model'] = array_merge($options['bind_model'], $option['bind_model']);
+                }
+                $result = self::checkRule($rule, $route, $url, $pattern, $option, $depr);
+                if (false !== $result) {
+                    return $result;
+                }
+            }
+        }
+        if (isset($auto)) {
+            // 自动解析URL地址
+            return self::parseUrl($auto['route'] . '/' . $url, $depr);
+        } elseif (isset($miss)) {
+            // 未匹配所有路由的路由规则处理
+            return self::parseRule('', $miss['route'], $url, $miss['option']);
+        }
+        return false;
+    }
+
+    /**
+     * 检测路由别名
+     * @access private
+     * @param Request $request
+     * @param string  $url  URL地址
+     * @param string  $depr URL分隔符
+     * @return mixed
+     */
+    private static function checkRouteAlias($request, $url, $depr)
+    {
+        $array = explode('|', $url);
+        $alias = array_shift($array);
+        $item  = self::$rules['alias'][$alias];
+
+        if (is_array($item)) {
+            list($rule, $option) = $item;
+            $action              = $array[0];
+            if (isset($option['allow']) && !in_array($action, explode(',', $option['allow']))) {
+                // 允许操作
+                return false;
+            } elseif (isset($option['except']) && in_array($action, explode(',', $option['except']))) {
+                // 排除操作
+                return false;
+            }
+            if (isset($option['method'][$action])) {
+                $option['method'] = $option['method'][$action];
+            }
+        } else {
+            $rule = $item;
+        }
+        $bind = implode('|', $array);
+        // 参数有效性检查
+        if (isset($option) && !self::checkOption($option, $request)) {
+            // 路由不匹配
+            return false;
+        } elseif (0 === strpos($rule, '\\')) {
+            // 路由到类
+            return self::bindToClass($bind, substr($rule, 1), $depr);
+        } elseif (0 === strpos($rule, '@')) {
+            // 路由到控制器类
+            return self::bindToController($bind, substr($rule, 1), $depr);
+        } else {
+            // 路由到模块/控制器
+            return self::bindToModule($bind, $rule, $depr);
+        }
+    }
+
+    /**
+     * 检测URL绑定
+     * @access private
+     * @param string $url   URL地址
+     * @param array  $rules 路由规则
+     * @param string $depr  URL分隔符
+     * @return mixed
+     */
+    private static function checkUrlBind(&$url, &$rules, $depr = '/')
+    {
+        if (!empty(self::$bind)) {
+            $type = self::$bind['type'];
+            $bind = self::$bind[$type];
+            // 记录绑定信息
+            App::$debug && Log::record('[ BIND ] ' . var_export($bind, true), 'info');
+            // 如果有URL绑定 则进行绑定检测
+            switch ($type) {
+                case 'class':
+                    // 绑定到类
+                    return self::bindToClass($url, $bind, $depr);
+                case 'controller':
+                    // 绑定到控制器类
+                    return self::bindToController($url, $bind, $depr);
+                case 'namespace':
+                    // 绑定到命名空间
+                    return self::bindToNamespace($url, $bind, $depr);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 绑定到类
+     * @access public
+     * @param string $url   URL地址
+     * @param string $class 类名(带命名空间)
+     * @param string $depr  URL分隔符
+     * @return array
+     */
+    public static function bindToClass($url, $class, $depr = '/')
+    {
+        $url    = str_replace($depr, '|', $url);
+        $array  = explode('|', $url, 2);
+        $action = !empty($array[0]) ? $array[0] : Config::get('default_action');
+        if (!empty($array[1])) {
+            self::parseUrlParams($array[1]);
+        }
+        return ['type' => 'method', 'method' => [$class, $action], 'var' => []];
+    }
+
+    /**
+     * 绑定到命名空间
+     * @access public
+     * @param string $url       URL地址
+     * @param string $namespace 命名空间
+     * @param string $depr      URL分隔符
+     * @return array
+     */
+    public static function bindToNamespace($url, $namespace, $depr = '/')
+    {
+        $url    = str_replace($depr, '|', $url);
+        $array  = explode('|', $url, 3);
+        $class  = !empty($array[0]) ? $array[0] : Config::get('default_controller');
+        $method = !empty($array[1]) ? $array[1] : Config::get('default_action');
+        if (!empty($array[2])) {
+            self::parseUrlParams($array[2]);
+        }
+        return ['type' => 'method', 'method' => [$namespace . '\\' . Loader::parseName($class, 1), $method], 'var' => []];
+    }
+
+    /**
+     * 绑定到控制器类
+     * @access public
+     * @param string $url        URL地址
+     * @param string $controller 控制器名 (支持带模块名 index/user )
+     * @param string $depr       URL分隔符
+     * @return array
+     */
+    public static function bindToController($url, $controller, $depr = '/')
+    {
+        $url    = str_replace($depr, '|', $url);
+        $array  = explode('|', $url, 2);
+        $action = !empty($array[0]) ? $array[0] : Config::get('default_action');
+        if (!empty($array[1])) {
+            self::parseUrlParams($array[1]);
+        }
+        return ['type' => 'controller', 'controller' => $controller . '/' . $action, 'var' => []];
+    }
+
+    /**
+     * 绑定到模块/控制器
+     * @access public
+     * @param string $url        URL地址
+     * @param string $controller 控制器类名(带命名空间)
+     * @param string $depr       URL分隔符
+     * @return array
+     */
+    public static function bindToModule($url, $controller, $depr = '/')
+    {
+        $url    = str_replace($depr, '|', $url);
+        $array  = explode('|', $url, 2);
+        $action = !empty($array[0]) ? $array[0] : Config::get('default_action');
+        if (!empty($array[1])) {
+            self::parseUrlParams($array[1]);
+        }
+        return ['type' => 'module', 'module' => $controller . '/' . $action];
+    }
+
+    /**
+     * 路由参数有效性检查
+     * @access private
+     * @param array   $option  路由参数
+     * @param Request $request Request对象
+     * @return bool
+     */
+    private static function checkOption($option, $request)
+    {
+        if ((isset($option['method']) && is_string($option['method']) && false === stripos($option['method'], $request->method()))
+            || (isset($option['ajax']) && $option['ajax'] && !$request->isAjax()) // Ajax检测
+             || (isset($option['ajax']) && !$option['ajax'] && $request->isAjax()) // 非Ajax检测
+             || (isset($option['pjax']) && $option['pjax'] && !$request->isPjax()) // Pjax检测
+             || (isset($option['pjax']) && !$option['pjax'] && $request->isPjax()) // 非Pjax检测
+             || (isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) // 伪静态后缀检测
+             || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|'))
+            || (isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], self::$subDomain])) // 域名检测
+             || (isset($option['https']) && $option['https'] && !$request->isSsl()) // https检测
+             || (isset($option['https']) && !$option['https'] && $request->isSsl()) // https检测
+             || (!empty($option['before_behavior']) && false === Hook::exec($option['before_behavior'])) // 行为检测
+             || (!empty($option['callback']) && is_callable($option['callback']) && false === call_user_func($option['callback'])) // 自定义检测
+        ) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 检测路由规则
+     * @access private
+     * @param string $rule    路由规则
+     * @param string $route   路由地址
+     * @param string $url     URL地址
+     * @param array  $pattern 变量规则
+     * @param array  $option  路由参数
+     * @param string $depr    URL分隔符(全局)
+     * @return array|false
+     */
+    private static function checkRule($rule, $route, $url, $pattern, $option, $depr)
+    {
+        // 检查完整规则定义
+        if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) {
+            return false;
+        }
+        // 检查路由的参数分隔符
+        if (isset($option['param_depr'])) {
+            $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url);
+        }
+
+        $len1 = substr_count($url, '|');
+        $len2 = substr_count($rule, '/');
+        // 多余参数是否合并
+        $merge = !empty($option['merge_extra_vars']);
+        if ($merge && $len1 > $len2) {
+            $url = str_replace('|', $depr, $url);
+            $url = implode('|', explode($depr, $url, $len2 + 1));
+        }
+
+        if ($len1 >= $len2 || strpos($rule, '[')) {
+            if (!empty($option['complete_match'])) {
+                // 完整匹配
+                if (!$merge && $len1 != $len2 && (false === strpos($rule, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($rule, '['))) {
+                    return false;
+                }
+            }
+            $pattern = array_merge(self::$rules['pattern'], $pattern);
+            if (false !== $match = self::match($url, $rule, $pattern)) {
+                // 匹配到路由规则
+                return self::parseRule($rule, $route, $url, $option, $match);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 解析模块的URL地址 [模块/控制器/操作?]参数1=值1&参数2=值2...
+     * @access public
+     * @param string $url        URL地址
+     * @param string $depr       URL分隔符
+     * @param bool   $autoSearch 是否自动深度搜索控制器
+     * @return array
+     */
+    public static function parseUrl($url, $depr = '/', $autoSearch = false)
+    {
+
+        if (isset(self::$bind['module'])) {
+            $bind = str_replace('/', $depr, self::$bind['module']);
+            // 如果有模块/控制器绑定
+            $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr);
+        }
+        $url              = str_replace($depr, '|', $url);
+        list($path, $var) = self::parseUrlPath($url);
+        $route            = [null, null, null];
+        if (isset($path)) {
+            // 解析模块
+            $module = Config::get('app_multi_module') ? array_shift($path) : null;
+            if ($autoSearch) {
+                // 自动搜索控制器
+                $dir    = APP_PATH . ($module ? $module . DS : '') . Config::get('url_controller_layer');
+                $suffix = App::$suffix || Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : '';
+                $item   = [];
+                $find   = false;
+                foreach ($path as $val) {
+                    $item[] = $val;
+                    $file   = $dir . DS . str_replace('.', DS, $val) . $suffix . EXT;
+                    $file   = pathinfo($file, PATHINFO_DIRNAME) . DS . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . EXT;
+                    if (is_file($file)) {
+                        $find = true;
+                        break;
+                    } else {
+                        $dir .= DS . Loader::parseName($val);
+                    }
+                }
+                if ($find) {
+                    $controller = implode('.', $item);
+                    $path       = array_slice($path, count($item));
+                } else {
+                    $controller = array_shift($path);
+                }
+            } else {
+                // 解析控制器
+                $controller = !empty($path) ? array_shift($path) : null;
+            }
+            // 解析操作
+            $action = !empty($path) ? array_shift($path) : null;
+            // 解析额外参数
+            self::parseUrlParams(empty($path) ? '' : implode('|', $path));
+            // 封装路由
+            $route = [$module, $controller, $action];
+            // 检查地址是否被定义过路由
+            $name  = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action);
+            $name2 = '';
+            if (empty($module) || isset($bind) && $module == $bind) {
+                $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action);
+            }
+
+            if (isset(self::$rules['name'][$name]) || isset(self::$rules['name'][$name2])) {
+                throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url));
+            }
+        }
+        return ['type' => 'module', 'module' => $route];
+    }
+
+    /**
+     * 解析URL的pathinfo参数和变量
+     * @access private
+     * @param string $url URL地址
+     * @return array
+     */
+    private static function parseUrlPath($url)
+    {
+        // 分隔符替换 确保路由定义使用统一的分隔符
+        $url = str_replace('|', '/', $url);
+        $url = trim($url, '/');
+        $var = [];
+        if (false !== strpos($url, '?')) {
+            // [模块/控制器/操作?]参数1=值1&参数2=值2...
+            $info = parse_url($url);
+            $path = explode('/', $info['path']);
+            parse_str($info['query'], $var);
+        } elseif (strpos($url, '/')) {
+            // [模块/控制器/操作]
+            $path = explode('/', $url);
+        } else {
+            $path = [$url];
+        }
+        return [$path, $var];
+    }
+
+    /**
+     * 检测URL和规则路由是否匹配
+     * @access private
+     * @param string $url     URL地址
+     * @param string $rule    路由规则
+     * @param array  $pattern 变量规则
+     * @return array|false
+     */
+    private static function match($url, $rule, $pattern)
+    {
+        $m2 = explode('/', $rule);
+        $m1 = explode('|', $url);
+
+        $var = [];
+        foreach ($m2 as $key => $val) {
+            // val中定义了多个变量 <id><name>
+            if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) {
+                $value   = [];
+                $replace = [];
+                foreach ($matches[1] as $name) {
+                    if (strpos($name, '?')) {
+                        $name      = substr($name, 0, -1);
+                        $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')?';
+                    } else {
+                        $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')';
+                    }
+                    $value[] = $name;
+                }
+                $val = str_replace($matches[0], $replace, $val);
+                if (preg_match('/^' . $val . '$/', isset($m1[$key]) ? $m1[$key] : '', $match)) {
+                    array_shift($match);
+                    foreach ($value as $k => $name) {
+                        if (isset($match[$k])) {
+                            $var[$name] = $match[$k];
+                        }
+                    }
+                    continue;
+                } else {
+                    return false;
+                }
+            }
+
+            if (0 === strpos($val, '[:')) {
+                // 可选参数
+                $val      = substr($val, 1, -1);
+                $optional = true;
+            } else {
+                $optional = false;
+            }
+            if (0 === strpos($val, ':')) {
+                // URL变量
+                $name = substr($val, 1);
+                if (!$optional && !isset($m1[$key])) {
+                    return false;
+                }
+                if (isset($m1[$key]) && isset($pattern[$name])) {
+                    // 检查变量规则
+                    if ($pattern[$name] instanceof \Closure) {
+                        $result = call_user_func_array($pattern[$name], [$m1[$key]]);
+                        if (false === $result) {
+                            return false;
+                        }
+                    } elseif (!preg_match(0 === strpos($pattern[$name], '/') ? $pattern[$name] : '/^' . $pattern[$name] . '$/', $m1[$key])) {
+                        return false;
+                    }
+                }
+                $var[$name] = isset($m1[$key]) ? $m1[$key] : '';
+            } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) {
+                return false;
+            }
+        }
+        // 成功匹配后返回URL中的动态变量数组
+        return $var;
+    }
+
+    /**
+     * 解析规则路由
+     * @access private
+     * @param string $rule      路由规则
+     * @param string $route     路由地址
+     * @param string $pathinfo  URL地址
+     * @param array  $option    路由参数
+     * @param array  $matches   匹配的变量
+     * @param bool   $fromCache 通过缓存解析
+     * @return array
+     */
+    private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = [], $fromCache = false)
+    {
+        $request = Request::instance();
+
+        //保存解析缓存
+        if (Config::get('route_check_cache') && !$fromCache) {
+            try {
+                $key = self::getCheckCacheKey($request);
+                Cache::tag('route_check')->set($key, [$rule, $route, $pathinfo, $option, $matches]);
+            } catch (\Exception $e) {
+
+            }
+        }
+
+        // 解析路由规则
+        if ($rule) {
+            $rule = explode('/', $rule);
+            // 获取URL地址中的参数
+            $paths = explode('|', $pathinfo);
+            foreach ($rule as $item) {
+                $fun = '';
+                if (0 === strpos($item, '[:')) {
+                    $item = substr($item, 1, -1);
+                }
+                if (0 === strpos($item, ':')) {
+                    $var           = substr($item, 1);
+                    $matches[$var] = array_shift($paths);
+                } else {
+                    // 过滤URL中的静态变量
+                    array_shift($paths);
+                }
+            }
+        } else {
+            $paths = explode('|', $pathinfo);
+        }
+
+        // 获取路由地址规则
+        if (is_string($route) && isset($option['prefix'])) {
+            // 路由地址前缀
+            $route = $option['prefix'] . $route;
+        }
+        // 替换路由地址中的变量
+        if (is_string($route) && !empty($matches)) {
+            foreach ($matches as $key => $val) {
+                if (false !== strpos($route, ':' . $key)) {
+                    $route = str_replace(':' . $key, $val, $route);
+                }
+            }
+        }
+
+        // 绑定模型数据
+        if (isset($option['bind_model'])) {
+            $bind = [];
+            foreach ($option['bind_model'] as $key => $val) {
+                if ($val instanceof \Closure) {
+                    $result = call_user_func_array($val, [$matches]);
+                } else {
+                    if (is_array($val)) {
+                        $fields    = explode('&', $val[1]);
+                        $model     = $val[0];
+                        $exception = isset($val[2]) ? $val[2] : true;
+                    } else {
+                        $fields    = ['id'];
+                        $model     = $val;
+                        $exception = true;
+                    }
+                    $where = [];
+                    $match = true;
+                    foreach ($fields as $field) {
+                        if (!isset($matches[$field])) {
+                            $match = false;
+                            break;
+                        } else {
+                            $where[$field] = $matches[$field];
+                        }
+                    }
+                    if ($match) {
+                        $query  = strpos($model, '\\') ? $model::where($where) : Loader::model($model)->where($where);
+                        $result = $query->failException($exception)->find();
+                    }
+                }
+                if (!empty($result)) {
+                    $bind[$key] = $result;
+                }
+            }
+            $request->bind($bind);
+        }
+
+        if (!empty($option['response'])) {
+            Hook::add('response_send', $option['response']);
+        }
+
+        // 解析额外参数
+        self::parseUrlParams(empty($paths) ? '' : implode('|', $paths), $matches);
+        // 记录匹配的路由信息
+        $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]);
+
+        // 检测路由after行为
+        if (!empty($option['after_behavior'])) {
+            if ($option['after_behavior'] instanceof \Closure) {
+                $result = call_user_func_array($option['after_behavior'], []);
+            } else {
+                foreach ((array) $option['after_behavior'] as $behavior) {
+                    $result = Hook::exec($behavior, '');
+                    if (!is_null($result)) {
+                        break;
+                    }
+                }
+            }
+            // 路由规则重定向
+            if ($result instanceof Response) {
+                return ['type' => 'response', 'response' => $result];
+            } elseif (is_array($result)) {
+                return $result;
+            }
+        }
+
+        if ($route instanceof \Closure) {
+            // 执行闭包
+            $result = ['type' => 'function', 'function' => $route];
+        } elseif (0 === strpos($route, '/') || strpos($route, '://')) {
+            // 路由到重定向地址
+            $result = ['type' => 'redirect', 'url' => $route, 'status' => isset($option['status']) ? $option['status'] : 301];
+        } elseif (false !== strpos($route, '\\')) {
+            // 路由到方法
+            list($path, $var) = self::parseUrlPath($route);
+            $route            = str_replace('/', '@', implode('/', $path));
+            $method           = strpos($route, '@') ? explode('@', $route) : $route;
+            $result           = ['type' => 'method', 'method' => $method, 'var' => $var];
+        } elseif (0 === strpos($route, '@')) {
+            // 路由到控制器
+            $route             = substr($route, 1);
+            list($route, $var) = self::parseUrlPath($route);
+            $result            = ['type' => 'controller', 'controller' => implode('/', $route), 'var' => $var];
+            $request->action(array_pop($route));
+            $request->controller($route ? array_pop($route) : Config::get('default_controller'));
+            $request->module($route ? array_pop($route) : Config::get('default_module'));
+            App::$modulePath = APP_PATH . (Config::get('app_multi_module') ? $request->module() . DS : '');
+        } else {
+            // 路由到模块/控制器/操作
+            $result = self::parseModule($route, isset($option['convert']) ? $option['convert'] : false);
+        }
+        // 开启请求缓存
+        if ($request->isGet() && isset($option['cache'])) {
+            $cache = $option['cache'];
+            if (is_array($cache)) {
+                list($key, $expire, $tag) = array_pad($cache, 3, null);
+            } else {
+                $key    = str_replace('|', '/', $pathinfo);
+                $expire = $cache;
+                $tag    = null;
+            }
+            $request->cache($key, $expire, $tag);
+        }
+        return $result;
+    }
+
+    /**
+     * 解析URL地址为 模块/控制器/操作
+     * @access private
+     * @param string $url     URL地址
+     * @param bool   $convert 是否自动转换URL地址
+     * @return array
+     */
+    private static function parseModule($url, $convert = false)
+    {
+        list($path, $var) = self::parseUrlPath($url);
+        $action           = array_pop($path);
+        $controller       = !empty($path) ? array_pop($path) : null;
+        $module           = Config::get('app_multi_module') && !empty($path) ? array_pop($path) : null;
+        $method           = Request::instance()->method();
+        if (Config::get('use_action_prefix') && !empty(self::$methodPrefix[$method])) {
+            // 操作方法前缀支持
+            $action = 0 !== strpos($action, self::$methodPrefix[$method]) ? self::$methodPrefix[$method] . $action : $action;
+        }
+        // 设置当前请求的路由变量
+        Request::instance()->route($var);
+        // 路由到模块/控制器/操作
+        return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => $convert];
+    }
+
+    /**
+     * 解析URL地址中的参数Request对象
+     * @access private
+     * @param string $url 路由规则
+     * @param array  $var 变量
+     * @return void
+     */
+    private static function parseUrlParams($url, &$var = [])
+    {
+        if ($url) {
+            if (Config::get('url_param_type')) {
+                $var += explode('|', $url);
+            } else {
+                preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) {
+                    $var[$match[1]] = strip_tags($match[2]);
+                }, $url);
+            }
+        }
+        // 设置当前请求的参数
+        Request::instance()->route($var);
+    }
+
+    // 分析路由规则中的变量
+    private static function parseVar($rule)
+    {
+        // 提取路由规则中的变量
+        $var = [];
+        foreach (explode('/', $rule) as $val) {
+            $optional = false;
+            if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) {
+                foreach ($matches[1] as $name) {
+                    if (strpos($name, '?')) {
+                        $name     = substr($name, 0, -1);
+                        $optional = true;
+                    } else {
+                        $optional = false;
+                    }
+                    $var[$name] = $optional ? 2 : 1;
+                }
+            }
+
+            if (0 === strpos($val, '[:')) {
+                // 可选参数
+                $optional = true;
+                $val      = substr($val, 1, -1);
+            }
+            if (0 === strpos($val, ':')) {
+                // URL变量
+                $name       = substr($val, 1);
+                $var[$name] = $optional ? 2 : 1;
+            }
+        }
+        return $var;
+    }
+
+    /**
+     * 获取路由解析缓存的key
+     * @param Request $request
+     * @return string
+     */
+    private static function getCheckCacheKey(Request $request)
+    {
+        static $key;
+
+        if (empty($key)) {
+            if ($callback = Config::get('route_check_cache_key')) {
+                $key = call_user_func($callback, $request);
+            } else {
+                $key = "{$request->host(true)}|{$request->method()}|{$request->path()}";
+            }
+        }
+
+        return $key;
+    }
+}

+ 366 - 0
thinkphp/library/think/Session.php

@@ -0,0 +1,366 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\exception\ClassNotFoundException;
+
+class Session
+{
+    protected static $prefix = '';
+    protected static $init   = null;
+
+    /**
+     * 设置或者获取session作用域(前缀)
+     * @param string $prefix
+     * @return string|void
+     */
+    public static function prefix($prefix = '')
+    {
+        empty(self::$init) && self::boot();
+        if (empty($prefix) && null !== $prefix) {
+            return self::$prefix;
+        } else {
+            self::$prefix = $prefix;
+        }
+    }
+
+    /**
+     * session初始化
+     * @param array $config
+     * @return void
+     * @throws \think\Exception
+     */
+    public static function init(array $config = [])
+    {
+        if (empty($config)) {
+            $config = Config::get('session');
+        }
+        // 记录初始化信息
+        App::$debug && Log::record('[ SESSION ] INIT ' . var_export($config, true), 'info');
+        $isDoStart = false;
+        if (isset($config['use_trans_sid'])) {
+            ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0);
+        }
+
+        // 启动session
+        if (!empty($config['auto_start']) && PHP_SESSION_ACTIVE != session_status()) {
+            ini_set('session.auto_start', 0);
+            $isDoStart = true;
+        }
+
+        if (isset($config['prefix']) && ('' === self::$prefix || null === self::$prefix)) {
+            self::$prefix = $config['prefix'];
+        }
+        if (isset($config['var_session_id']) && isset($_REQUEST[$config['var_session_id']])) {
+            session_id($_REQUEST[$config['var_session_id']]);
+        } elseif (isset($config['id']) && !empty($config['id'])) {
+            session_id($config['id']);
+        }
+        if (isset($config['name'])) {
+            session_name($config['name']);
+        }
+        if (isset($config['path'])) {
+            session_save_path($config['path']);
+        }
+        if (isset($config['domain'])) {
+            ini_set('session.cookie_domain', $config['domain']);
+        }
+        if (isset($config['expire'])) {
+            ini_set('session.gc_maxlifetime', $config['expire']);
+            ini_set('session.cookie_lifetime', $config['expire']);
+        }
+        if (isset($config['secure'])) {
+            ini_set('session.cookie_secure', $config['secure']);
+        }
+        if (isset($config['httponly'])) {
+            ini_set('session.cookie_httponly', $config['httponly']);
+        }
+        if (isset($config['use_cookies'])) {
+            ini_set('session.use_cookies', $config['use_cookies'] ? 1 : 0);
+        }
+        if (isset($config['cache_limiter'])) {
+            session_cache_limiter($config['cache_limiter']);
+        }
+        if (isset($config['cache_expire'])) {
+            session_cache_expire($config['cache_expire']);
+        }
+        if (!empty($config['type'])) {
+            // 读取session驱动
+            $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']);
+
+            // 检查驱动类
+            if (!class_exists($class) || !session_set_save_handler(new $class($config))) {
+                throw new ClassNotFoundException('error session handler:' . $class, $class);
+            }
+        }
+        if ($isDoStart) {
+            session_start();
+            self::$init = true;
+        } else {
+            self::$init = false;
+        }
+    }
+
+    /**
+     * session自动启动或者初始化
+     * @return void
+     */
+    public static function boot()
+    {
+        if (is_null(self::$init)) {
+            self::init();
+        } elseif (false === self::$init) {
+            if (PHP_SESSION_ACTIVE != session_status()) {
+                session_start();
+            }
+            self::$init = true;
+        }
+    }
+
+    /**
+     * session设置
+     * @param string        $name session名称
+     * @param mixed         $value session值
+     * @param string|null   $prefix 作用域(前缀)
+     * @return void
+     */
+    public static function set($name, $value = '', $prefix = null)
+    {
+        empty(self::$init) && self::boot();
+
+        $prefix = !is_null($prefix) ? $prefix : self::$prefix;
+        if (strpos($name, '.')) {
+            // 二维数组赋值
+            list($name1, $name2) = explode('.', $name);
+            if ($prefix) {
+                $_SESSION[$prefix][$name1][$name2] = $value;
+            } else {
+                $_SESSION[$name1][$name2] = $value;
+            }
+        } elseif ($prefix) {
+            $_SESSION[$prefix][$name] = $value;
+        } else {
+            $_SESSION[$name] = $value;
+        }
+    }
+
+    /**
+     * session获取
+     * @param string        $name session名称
+     * @param string|null   $prefix 作用域(前缀)
+     * @return mixed
+     */
+    public static function get($name = '', $prefix = null)
+    {
+        empty(self::$init) && self::boot();
+        $prefix = !is_null($prefix) ? $prefix : self::$prefix;
+        if ('' == $name) {
+            // 获取全部的session
+            $value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION;
+        } elseif ($prefix) {
+            // 获取session
+            if (strpos($name, '.')) {
+                list($name1, $name2) = explode('.', $name);
+                $value               = isset($_SESSION[$prefix][$name1][$name2]) ? $_SESSION[$prefix][$name1][$name2] : null;
+            } else {
+                $value = isset($_SESSION[$prefix][$name]) ? $_SESSION[$prefix][$name] : null;
+            }
+        } else {
+            if (strpos($name, '.')) {
+                list($name1, $name2) = explode('.', $name);
+                $value               = isset($_SESSION[$name1][$name2]) ? $_SESSION[$name1][$name2] : null;
+            } else {
+                $value = isset($_SESSION[$name]) ? $_SESSION[$name] : null;
+            }
+        }
+        return $value;
+    }
+
+    /**
+     * session获取并删除
+     * @param string        $name session名称
+     * @param string|null   $prefix 作用域(前缀)
+     * @return mixed
+     */
+    public static function pull($name, $prefix = null)
+    {
+        $result = self::get($name, $prefix);
+        if ($result) {
+            self::delete($name, $prefix);
+            return $result;
+        } else {
+            return;
+        }
+    }
+
+    /**
+     * session设置 下一次请求有效
+     * @param string        $name session名称
+     * @param mixed         $value session值
+     * @param string|null   $prefix 作用域(前缀)
+     * @return void
+     */
+    public static function flash($name, $value)
+    {
+        self::set($name, $value);
+        if (!self::has('__flash__.__time__')) {
+            self::set('__flash__.__time__', $_SERVER['REQUEST_TIME_FLOAT']);
+        }
+        self::push('__flash__', $name);
+    }
+
+    /**
+     * 清空当前请求的session数据
+     * @return void
+     */
+    public static function flush()
+    {
+        if (self::$init) {
+            $item = self::get('__flash__');
+
+            if (!empty($item)) {
+                $time = $item['__time__'];
+                if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) {
+                    unset($item['__time__']);
+                    self::delete($item);
+                    self::set('__flash__', []);
+                }
+            }
+        }
+    }
+
+    /**
+     * 删除session数据
+     * @param string|array  $name session名称
+     * @param string|null   $prefix 作用域(前缀)
+     * @return void
+     */
+    public static function delete($name, $prefix = null)
+    {
+        empty(self::$init) && self::boot();
+        $prefix = !is_null($prefix) ? $prefix : self::$prefix;
+        if (is_array($name)) {
+            foreach ($name as $key) {
+                self::delete($key, $prefix);
+            }
+        } elseif (strpos($name, '.')) {
+            list($name1, $name2) = explode('.', $name);
+            if ($prefix) {
+                unset($_SESSION[$prefix][$name1][$name2]);
+            } else {
+                unset($_SESSION[$name1][$name2]);
+            }
+        } else {
+            if ($prefix) {
+                unset($_SESSION[$prefix][$name]);
+            } else {
+                unset($_SESSION[$name]);
+            }
+        }
+    }
+
+    /**
+     * 清空session数据
+     * @param string|null   $prefix 作用域(前缀)
+     * @return void
+     */
+    public static function clear($prefix = null)
+    {
+        empty(self::$init) && self::boot();
+        $prefix = !is_null($prefix) ? $prefix : self::$prefix;
+        if ($prefix) {
+            unset($_SESSION[$prefix]);
+        } else {
+            $_SESSION = [];
+        }
+    }
+
+    /**
+     * 判断session数据
+     * @param string        $name session名称
+     * @param string|null   $prefix
+     * @return bool
+     */
+    public static function has($name, $prefix = null)
+    {
+        empty(self::$init) && self::boot();
+        $prefix = !is_null($prefix) ? $prefix : self::$prefix;
+        if (strpos($name, '.')) {
+            // 支持数组
+            list($name1, $name2) = explode('.', $name);
+            return $prefix ? isset($_SESSION[$prefix][$name1][$name2]) : isset($_SESSION[$name1][$name2]);
+        } else {
+            return $prefix ? isset($_SESSION[$prefix][$name]) : isset($_SESSION[$name]);
+        }
+    }
+
+    /**
+     * 添加数据到一个session数组
+     * @param  string  $key
+     * @param  mixed   $value
+     * @return void
+     */
+    public static function push($key, $value)
+    {
+        $array = self::get($key);
+        if (is_null($array)) {
+            $array = [];
+        }
+        $array[] = $value;
+        self::set($key, $array);
+    }
+
+    /**
+     * 启动session
+     * @return void
+     */
+    public static function start()
+    {
+        session_start();
+        self::$init = true;
+    }
+
+    /**
+     * 销毁session
+     * @return void
+     */
+    public static function destroy()
+    {
+        if (!empty($_SESSION)) {
+            $_SESSION = [];
+        }
+        session_unset();
+        session_destroy();
+        self::$init = null;
+    }
+
+    /**
+     * 重新生成session_id
+     * @param bool $delete 是否删除关联会话文件
+     * @return void
+     */
+    public static function regenerate($delete = false)
+    {
+        session_regenerate_id($delete);
+    }
+
+    /**
+     * 暂停session
+     * @return void
+     */
+    public static function pause()
+    {
+        // 暂停session
+        session_write_close();
+        self::$init = false;
+    }
+}

+ 1139 - 0
thinkphp/library/think/Template.php

@@ -0,0 +1,1139 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\exception\TemplateNotFoundException;
+use think\template\TagLib;
+
+/**
+ * ThinkPHP分离出来的模板引擎
+ * 支持XML标签和普通标签的模板解析
+ * 编译型模板引擎 支持动态缓存
+ */
+class Template
+{
+    // 模板变量
+    protected $data = [];
+    // 引擎配置
+    protected $config = [
+        'view_path'          => '', // 模板路径
+        'view_base'          => '',
+        'view_suffix'        => 'html', // 默认模板文件后缀
+        'view_depr'          => DS,
+        'cache_suffix'       => 'php', // 默认模板缓存后缀
+        'tpl_deny_func_list' => 'echo,exit', // 模板引擎禁用函数
+        'tpl_deny_php'       => false, // 默认模板引擎是否禁用PHP原生代码
+        'tpl_begin'          => '{', // 模板引擎普通标签开始标记
+        'tpl_end'            => '}', // 模板引擎普通标签结束标记
+        'strip_space'        => false, // 是否去除模板文件里面的html空格与换行
+        'tpl_cache'          => true, // 是否开启模板编译缓存,设为false则每次都会重新编译
+        'compile_type'       => 'file', // 模板编译类型
+        'cache_prefix'       => '', // 模板缓存前缀标识,可以动态改变
+        'cache_time'         => 0, // 模板缓存有效期 0 为永久,(以数字为值,单位:秒)
+        'layout_on'          => false, // 布局模板开关
+        'layout_name'        => 'layout', // 布局模板入口文件
+        'layout_item'        => '{__CONTENT__}', // 布局模板的内容替换标识
+        'taglib_begin'       => '{', // 标签库标签开始标记
+        'taglib_end'         => '}', // 标签库标签结束标记
+        'taglib_load'        => true, // 是否使用内置标签库之外的其它标签库,默认自动检测
+        'taglib_build_in'    => 'cx', // 内置标签库名称(标签使用不必指定标签库名称),以逗号分隔 注意解析顺序
+        'taglib_pre_load'    => '', // 需要额外加载的标签库(须指定标签库名称),多个以逗号分隔
+        'display_cache'      => false, // 模板渲染缓存
+        'cache_id'           => '', // 模板缓存ID
+        'tpl_replace_string' => [],
+        'tpl_var_identify'   => 'array', // .语法变量识别,array|object|'', 为空时自动识别
+    ];
+
+    private $literal     = [];
+    private $includeFile = []; // 记录所有模板包含的文件路径及更新时间
+    protected $storage;
+
+    /**
+     * 构造函数
+     * @access public
+     * @param array $config
+     */
+    public function __construct(array $config = [])
+    {
+        $this->config['cache_path'] = TEMP_PATH;
+        $this->config               = array_merge($this->config, $config);
+
+        $this->config['taglib_begin_origin'] = $this->config['taglib_begin'];
+        $this->config['taglib_end_origin']   = $this->config['taglib_end'];
+
+        $this->config['taglib_begin'] = preg_quote($this->config['taglib_begin'], '/');
+        $this->config['taglib_end']   = preg_quote($this->config['taglib_end'], '/');
+        $this->config['tpl_begin']    = preg_quote($this->config['tpl_begin'], '/');
+        $this->config['tpl_end']      = preg_quote($this->config['tpl_end'], '/');
+
+        // 初始化模板编译存储器
+        $type          = $this->config['compile_type'] ? $this->config['compile_type'] : 'File';
+        $class         = false !== strpos($type, '\\') ? $type : '\\think\\template\\driver\\' . ucwords($type);
+        $this->storage = new $class();
+    }
+
+    /**
+     * 模板变量赋值
+     * @access public
+     * @param mixed $name
+     * @param mixed $value
+     * @return void
+     */
+    public function assign($name, $value = '')
+    {
+        if (is_array($name)) {
+            $this->data = array_merge($this->data, $name);
+        } else {
+            $this->data[$name] = $value;
+        }
+    }
+
+    /**
+     * 模板引擎参数赋值
+     * @access public
+     * @param mixed $name
+     * @param mixed $value
+     */
+    public function __set($name, $value)
+    {
+        $this->config[$name] = $value;
+    }
+
+    /**
+     * 模板引擎配置项
+     * @access public
+     * @param array|string $config
+     * @return string|void|array
+     */
+    public function config($config)
+    {
+        if (is_array($config)) {
+            $this->config = array_merge($this->config, $config);
+        } elseif (isset($this->config[$config])) {
+            return $this->config[$config];
+        } else {
+            return;
+        }
+    }
+
+    /**
+     * 模板变量获取
+     * @access public
+     * @param  string $name 变量名
+     * @return mixed
+     */
+    public function get($name = '')
+    {
+        if ('' == $name) {
+            return $this->data;
+        } else {
+            $data = $this->data;
+            foreach (explode('.', $name) as $key => $val) {
+                if (isset($data[$val])) {
+                    $data = $data[$val];
+                } else {
+                    $data = null;
+                    break;
+                }
+            }
+            return $data;
+        }
+    }
+
+    /**
+     * 渲染模板文件
+     * @access public
+     * @param string    $template 模板文件
+     * @param array     $vars 模板变量
+     * @param array     $config 模板参数
+     * @return void
+     */
+    public function fetch($template, $vars = [], $config = [])
+    {
+        if ($vars) {
+            $this->data = $vars;
+        }
+        if ($config) {
+            $this->config($config);
+        }
+        if (!empty($this->config['cache_id']) && $this->config['display_cache']) {
+            // 读取渲染缓存
+            $cacheContent = Cache::get($this->config['cache_id']);
+            if (false !== $cacheContent) {
+                echo $cacheContent;
+                return;
+            }
+        }
+        $template = $this->parseTemplateFile($template);
+        if ($template) {
+            $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.');
+            if (!$this->checkCache($cacheFile)) {
+                // 缓存无效 重新模板编译
+                $content = file_get_contents($template);
+                $this->compiler($content, $cacheFile);
+            }
+            // 页面缓存
+            ob_start();
+            ob_implicit_flush(0);
+            // 读取编译存储
+            $this->storage->read($cacheFile, $this->data);
+            // 获取并清空缓存
+            $content = ob_get_clean();
+            if (!empty($this->config['cache_id']) && $this->config['display_cache']) {
+                // 缓存页面输出
+                Cache::set($this->config['cache_id'], $content, $this->config['cache_time']);
+            }
+            echo $content;
+        }
+    }
+
+    /**
+     * 渲染模板内容
+     * @access public
+     * @param string    $content 模板内容
+     * @param array     $vars 模板变量
+     * @param array     $config 模板参数
+     * @return void
+     */
+    public function display($content, $vars = [], $config = [])
+    {
+        if ($vars) {
+            $this->data = $vars;
+        }
+        if ($config) {
+            $this->config($config);
+        }
+        $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($content) . '.' . ltrim($this->config['cache_suffix'], '.');
+        if (!$this->checkCache($cacheFile)) {
+            // 缓存无效 模板编译
+            $this->compiler($content, $cacheFile);
+        }
+        // 读取编译存储
+        $this->storage->read($cacheFile, $this->data);
+    }
+
+    /**
+     * 设置布局
+     * @access public
+     * @param mixed     $name 布局模板名称 false 则关闭布局
+     * @param string    $replace 布局模板内容替换标识
+     * @return Template
+     */
+    public function layout($name, $replace = '')
+    {
+        if (false === $name) {
+            // 关闭布局
+            $this->config['layout_on'] = false;
+        } else {
+            // 开启布局
+            $this->config['layout_on'] = true;
+            // 名称必须为字符串
+            if (is_string($name)) {
+                $this->config['layout_name'] = $name;
+            }
+            if (!empty($replace)) {
+                $this->config['layout_item'] = $replace;
+            }
+        }
+        return $this;
+    }
+
+    /**
+     * 检查编译缓存是否有效
+     * 如果无效则需要重新编译
+     * @access private
+     * @param string $cacheFile 缓存文件名
+     * @return boolean
+     */
+    private function checkCache($cacheFile)
+    {
+        // 未开启缓存功能
+        if (!$this->config['tpl_cache']) {
+            return false;
+        }
+        // 缓存文件不存在
+        if (!is_file($cacheFile)) {
+            return false;
+        }
+        // 读取缓存文件失败
+        if (!$handle = @fopen($cacheFile, "r")) {
+            return false;
+        }
+        // 读取第一行
+        preg_match('/\/\*(.+?)\*\//', fgets($handle), $matches);
+        if (!isset($matches[1])) {
+            return false;
+        }
+        $includeFile = unserialize($matches[1]);
+        if (!is_array($includeFile)) {
+            return false;
+        }
+        // 检查模板文件是否有更新
+        foreach ($includeFile as $path => $time) {
+            if (is_file($path) && filemtime($path) > $time) {
+                // 模板文件如果有更新则缓存需要更新
+                return false;
+            }
+        }
+        // 检查编译存储是否有效
+        return $this->storage->check($cacheFile, $this->config['cache_time']);
+    }
+
+    /**
+     * 检查编译缓存是否存在
+     * @access public
+     * @param string $cacheId 缓存的id
+     * @return boolean
+     */
+    public function isCache($cacheId)
+    {
+        if ($cacheId && $this->config['display_cache']) {
+            // 缓存页面输出
+            return Cache::has($cacheId);
+        }
+        return false;
+    }
+
+    /**
+     * 编译模板文件内容
+     * @access private
+     * @param string    $content 模板内容
+     * @param string    $cacheFile 缓存文件名
+     * @return void
+     */
+    private function compiler(&$content, $cacheFile)
+    {
+        // 判断是否启用布局
+        if ($this->config['layout_on']) {
+            if (false !== strpos($content, '{__NOLAYOUT__}')) {
+                // 可以单独定义不使用布局
+                $content = str_replace('{__NOLAYOUT__}', '', $content);
+            } else {
+                // 读取布局模板
+                $layoutFile = $this->parseTemplateFile($this->config['layout_name']);
+                if ($layoutFile) {
+                    // 替换布局的主体内容
+                    $content = str_replace($this->config['layout_item'], $content, file_get_contents($layoutFile));
+                }
+            }
+        } else {
+            $content = str_replace('{__NOLAYOUT__}', '', $content);
+        }
+
+        // 模板解析
+        $this->parse($content);
+        if ($this->config['strip_space']) {
+            /* 去除html空格与换行 */
+            $find    = ['~>\s+<~', '~>(\s+\n|\r)~'];
+            $replace = ['><', '>'];
+            $content = preg_replace($find, $replace, $content);
+        }
+        // 优化生成的php代码
+        $content = preg_replace('/\?>\s*<\?php\s(?!echo\b)/s', '', $content);
+        // 模板过滤输出
+        $replace = $this->config['tpl_replace_string'];
+        $content = str_replace(array_keys($replace), array_values($replace), $content);
+        // 添加安全代码及模板引用记录
+        $content = '<?php if (!defined(\'THINK_PATH\')) exit(); /*' . serialize($this->includeFile) . '*/ ?>' . "\n" . $content;
+        // 编译存储
+        $this->storage->write($cacheFile, $content);
+        $this->includeFile = [];
+        return;
+    }
+
+    /**
+     * 模板解析入口
+     * 支持普通标签和TagLib解析 支持自定义标签库
+     * @access public
+     * @param string $content 要解析的模板内容
+     * @return void
+     */
+    public function parse(&$content)
+    {
+        // 内容为空不解析
+        if (empty($content)) {
+            return;
+        }
+        // 替换literal标签内容
+        $this->parseLiteral($content);
+        // 解析继承
+        $this->parseExtend($content);
+        // 解析布局
+        $this->parseLayout($content);
+        // 检查include语法
+        $this->parseInclude($content);
+        // 替换包含文件中literal标签内容
+        $this->parseLiteral($content);
+        // 检查PHP语法
+        $this->parsePhp($content);
+
+        // 获取需要引入的标签库列表
+        // 标签库只需要定义一次,允许引入多个一次
+        // 一般放在文件的最前面
+        // 格式:<taglib name="html,mytag..." />
+        // 当TAGLIB_LOAD配置为true时才会进行检测
+        if ($this->config['taglib_load']) {
+            $tagLibs = $this->getIncludeTagLib($content);
+            if (!empty($tagLibs)) {
+                // 对导入的TagLib进行解析
+                foreach ($tagLibs as $tagLibName) {
+                    $this->parseTagLib($tagLibName, $content);
+                }
+            }
+        }
+        // 预先加载的标签库 无需在每个模板中使用taglib标签加载 但必须使用标签库XML前缀
+        if ($this->config['taglib_pre_load']) {
+            $tagLibs = explode(',', $this->config['taglib_pre_load']);
+            foreach ($tagLibs as $tag) {
+                $this->parseTagLib($tag, $content);
+            }
+        }
+        // 内置标签库 无需使用taglib标签导入就可以使用 并且不需使用标签库XML前缀
+        $tagLibs = explode(',', $this->config['taglib_build_in']);
+        foreach ($tagLibs as $tag) {
+            $this->parseTagLib($tag, $content, true);
+        }
+        // 解析普通模板标签 {$tagName}
+        $this->parseTag($content);
+
+        // 还原被替换的Literal标签
+        $this->parseLiteral($content, true);
+        return;
+    }
+
+    /**
+     * 检查PHP语法
+     * @access private
+     * @param string $content 要解析的模板内容
+     * @return void
+     * @throws \think\Exception
+     */
+    private function parsePhp(&$content)
+    {
+        // 短标签的情况要将<?标签用echo方式输出 否则无法正常输出xml标识
+        $content = preg_replace('/(<\?(?!php|=|$))/i', '<?php echo \'\\1\'; ?>' . "\n", $content);
+        // PHP语法检查
+        if ($this->config['tpl_deny_php'] && false !== strpos($content, '<?php')) {
+            throw new Exception('not allow php tag', 11600);
+        }
+        return;
+    }
+
+    /**
+     * 解析模板中的布局标签
+     * @access private
+     * @param string $content 要解析的模板内容
+     * @return void
+     */
+    private function parseLayout(&$content)
+    {
+        // 读取模板中的布局标签
+        if (preg_match($this->getRegex('layout'), $content, $matches)) {
+            // 替换Layout标签
+            $content = str_replace($matches[0], '', $content);
+            // 解析Layout标签
+            $array = $this->parseAttr($matches[0]);
+            if (!$this->config['layout_on'] || $this->config['layout_name'] != $array['name']) {
+                // 读取布局模板
+                $layoutFile = $this->parseTemplateFile($array['name']);
+                if ($layoutFile) {
+                    $replace = isset($array['replace']) ? $array['replace'] : $this->config['layout_item'];
+                    // 替换布局的主体内容
+                    $content = str_replace($replace, $content, file_get_contents($layoutFile));
+                }
+            }
+        } else {
+            $content = str_replace('{__NOLAYOUT__}', '', $content);
+        }
+        return;
+    }
+
+    /**
+     * 解析模板中的include标签
+     * @access private
+     * @param  string $content 要解析的模板内容
+     * @return void
+     */
+    private function parseInclude(&$content)
+    {
+        $regex = $this->getRegex('include');
+        $func  = function ($template) use (&$func, &$regex, &$content) {
+            if (preg_match_all($regex, $template, $matches, PREG_SET_ORDER)) {
+                foreach ($matches as $match) {
+                    $array = $this->parseAttr($match[0]);
+                    $file  = $array['file'];
+                    unset($array['file']);
+                    // 分析模板文件名并读取内容
+                    $parseStr = $this->parseTemplateName($file);
+                    foreach ($array as $k => $v) {
+                        // 以$开头字符串转换成模板变量
+                        if (0 === strpos($v, '$')) {
+                            $v = $this->get(substr($v, 1));
+                        }
+                        $parseStr = str_replace('[' . $k . ']', $v, $parseStr);
+                    }
+                    $content = str_replace($match[0], $parseStr, $content);
+                    // 再次对包含文件进行模板分析
+                    $func($parseStr);
+                }
+                unset($matches);
+            }
+        };
+        // 替换模板中的include标签
+        $func($content);
+        return;
+    }
+
+    /**
+     * 解析模板中的extend标签
+     * @access private
+     * @param  string $content 要解析的模板内容
+     * @return void
+     */
+    private function parseExtend(&$content)
+    {
+        $regex  = $this->getRegex('extend');
+        $array  = $blocks  = $baseBlocks  = [];
+        $extend = '';
+        $func   = function ($template) use (&$func, &$regex, &$array, &$extend, &$blocks, &$baseBlocks) {
+            if (preg_match($regex, $template, $matches)) {
+                if (!isset($array[$matches['name']])) {
+                    $array[$matches['name']] = 1;
+                    // 读取继承模板
+                    $extend = $this->parseTemplateName($matches['name']);
+                    // 递归检查继承
+                    $func($extend);
+                    // 取得block标签内容
+                    $blocks = array_merge($blocks, $this->parseBlock($template));
+                    return;
+                }
+            } else {
+                // 取得顶层模板block标签内容
+                $baseBlocks = $this->parseBlock($template, true);
+                if (empty($extend)) {
+                    // 无extend标签但有block标签的情况
+                    $extend = $template;
+                }
+            }
+        };
+
+        $func($content);
+        if (!empty($extend)) {
+            if ($baseBlocks) {
+                $children = [];
+                foreach ($baseBlocks as $name => $val) {
+                    $replace = $val['content'];
+                    if (!empty($children[$name])) {
+                        // 如果包含有子block标签
+                        foreach ($children[$name] as $key) {
+                            $replace = str_replace($baseBlocks[$key]['begin'] . $baseBlocks[$key]['content'] . $baseBlocks[$key]['end'], $blocks[$key]['content'], $replace);
+                        }
+                    }
+                    if (isset($blocks[$name])) {
+                        // 带有{__block__}表示与所继承模板的相应标签合并,而不是覆盖
+                        $replace = str_replace(['{__BLOCK__}', '{__block__}'], $replace, $blocks[$name]['content']);
+                        if (!empty($val['parent'])) {
+                            // 如果不是最顶层的block标签
+                            $parent = $val['parent'];
+                            if (isset($blocks[$parent])) {
+                                $blocks[$parent]['content'] = str_replace($blocks[$name]['begin'] . $blocks[$name]['content'] . $blocks[$name]['end'], $replace, $blocks[$parent]['content']);
+                            }
+                            $blocks[$name]['content'] = $replace;
+                            $children[$parent][]      = $name;
+                            continue;
+                        }
+                    } elseif (!empty($val['parent'])) {
+                        // 如果子标签没有被继承则用原值
+                        $children[$val['parent']][] = $name;
+                        $blocks[$name]              = $val;
+                    }
+                    if (!$val['parent']) {
+                        // 替换模板中的顶级block标签
+                        $extend = str_replace($val['begin'] . $val['content'] . $val['end'], $replace, $extend);
+                    }
+                }
+            }
+            $content = $extend;
+            unset($blocks, $baseBlocks);
+        }
+        return;
+    }
+
+    /**
+     * 替换页面中的literal标签
+     * @access private
+     * @param  string   $content 模板内容
+     * @param  boolean  $restore 是否为还原
+     * @return void
+     */
+    private function parseLiteral(&$content, $restore = false)
+    {
+        $regex = $this->getRegex($restore ? 'restoreliteral' : 'literal');
+        if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
+            if (!$restore) {
+                $count = count($this->literal);
+                // 替换literal标签
+                foreach ($matches as $match) {
+                    $this->literal[] = substr($match[0], strlen($match[1]), -strlen($match[2]));
+                    $content         = str_replace($match[0], "<!--###literal{$count}###-->", $content);
+                    $count++;
+                }
+            } else {
+                // 还原literal标签
+                foreach ($matches as $match) {
+                    $content = str_replace($match[0], $this->literal[$match[1]], $content);
+                }
+                // 清空literal记录
+                $this->literal = [];
+            }
+            unset($matches);
+        }
+        return;
+    }
+
+    /**
+     * 获取模板中的block标签
+     * @access private
+     * @param  string   $content 模板内容
+     * @param  boolean  $sort 是否排序
+     * @return array
+     */
+    private function parseBlock(&$content, $sort = false)
+    {
+        $regex  = $this->getRegex('block');
+        $result = [];
+        if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
+            $right = $keys = [];
+            foreach ($matches as $match) {
+                if (empty($match['name'][0])) {
+                    if (count($right) > 0) {
+                        $tag                  = array_pop($right);
+                        $start                = $tag['offset'] + strlen($tag['tag']);
+                        $length               = $match[0][1] - $start;
+                        $result[$tag['name']] = [
+                            'begin'   => $tag['tag'],
+                            'content' => substr($content, $start, $length),
+                            'end'     => $match[0][0],
+                            'parent'  => count($right) ? end($right)['name'] : '',
+                        ];
+                        $keys[$tag['name']] = $match[0][1];
+                    }
+                } else {
+                    // 标签头压入栈
+                    $right[] = [
+                        'name'   => $match[2][0],
+                        'offset' => $match[0][1],
+                        'tag'    => $match[0][0],
+                    ];
+                }
+            }
+            unset($right, $matches);
+            if ($sort) {
+                // 按block标签结束符在模板中的位置排序
+                array_multisort($keys, $result);
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * 搜索模板页面中包含的TagLib库
+     * 并返回列表
+     * @access private
+     * @param  string $content 模板内容
+     * @return array|null
+     */
+    private function getIncludeTagLib(&$content)
+    {
+        // 搜索是否有TagLib标签
+        if (preg_match($this->getRegex('taglib'), $content, $matches)) {
+            // 替换TagLib标签
+            $content = str_replace($matches[0], '', $content);
+            return explode(',', $matches['name']);
+        }
+        return;
+    }
+
+    /**
+     * TagLib库解析
+     * @access public
+     * @param  string   $tagLib 要解析的标签库
+     * @param  string   $content 要解析的模板内容
+     * @param  boolean  $hide 是否隐藏标签库前缀
+     * @return void
+     */
+    public function parseTagLib($tagLib, &$content, $hide = false)
+    {
+        if (false !== strpos($tagLib, '\\')) {
+            // 支持指定标签库的命名空间
+            $className = $tagLib;
+            $tagLib    = substr($tagLib, strrpos($tagLib, '\\') + 1);
+        } else {
+            $className = '\\think\\template\\taglib\\' . ucwords($tagLib);
+        }
+        /** @var Taglib $tLib */
+        $tLib = new $className($this);
+        $tLib->parseTag($content, $hide ? '' : $tagLib);
+        return;
+    }
+
+    /**
+     * 分析标签属性
+     * @access public
+     * @param  string   $str 属性字符串
+     * @param  string   $name 不为空时返回指定的属性名
+     * @return array
+     */
+    public function parseAttr($str, $name = null)
+    {
+        $regex = '/\s+(?>(?P<name>[\w-]+)\s*)=(?>\s*)([\"\'])(?P<value>(?:(?!\\2).)*)\\2/is';
+        $array = [];
+        if (preg_match_all($regex, $str, $matches, PREG_SET_ORDER)) {
+            foreach ($matches as $match) {
+                $array[$match['name']] = $match['value'];
+            }
+            unset($matches);
+        }
+        if (!empty($name) && isset($array[$name])) {
+            return $array[$name];
+        } else {
+            return $array;
+        }
+    }
+
+    /**
+     * 模板标签解析
+     * 格式: {TagName:args [|content] }
+     * @access private
+     * @param  string $content 要解析的模板内容
+     * @return void
+     */
+    private function parseTag(&$content)
+    {
+        $regex = $this->getRegex('tag');
+        if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
+            foreach ($matches as $match) {
+                $str  = stripslashes($match[1]);
+                $flag = substr($str, 0, 1);
+                switch ($flag) {
+                    case '$':
+                        // 解析模板变量 格式 {$varName}
+                        // 是否带有?号
+                        if (false !== $pos = strpos($str, '?')) {
+                            $array = preg_split('/([!=]={1,2}|(?<!-)[><]={0,1})/', substr($str, 0, $pos), 2, PREG_SPLIT_DELIM_CAPTURE);
+                            $name  = $array[0];
+                            $this->parseVar($name);
+                            $this->parseVarFunction($name);
+
+                            $str = trim(substr($str, $pos + 1));
+                            $this->parseVar($str);
+                            $first = substr($str, 0, 1);
+                            if (strpos($name, ')')) {
+                                // $name为对象或是自动识别,或者含有函数
+                                if (isset($array[1])) {
+                                    $this->parseVar($array[2]);
+                                    $name .= $array[1] . $array[2];
+                                }
+                                switch ($first) {
+                                    case '?':
+                                        $str = '<?php echo (' . $name . ') ? ' . $name . ' : ' . substr($str, 1) . '; ?>';
+                                        break;
+                                    case '=':
+                                        $str = '<?php if(' . $name . ') echo ' . substr($str, 1) . '; ?>';
+                                        break;
+                                    default:
+                                        $str = '<?php echo ' . $name . '?' . $str . '; ?>';
+                                }
+                            } else {
+                                if (isset($array[1])) {
+                                    $this->parseVar($array[2]);
+                                    $express = $name . $array[1] . $array[2];
+                                } else {
+                                    $express = false;
+                                }
+                                // $name为数组
+                                switch ($first) {
+                                    case '?':
+                                        // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx
+                                        $str = '<?php echo ' . ($express ?: 'isset(' . $name . ')') . '?' . $name . ':' . substr($str, 1) . '; ?>';
+                                        break;
+                                    case '=':
+                                        // {$varname?='xxx'} $varname为真时才输出xxx
+                                        $str = '<?php if(' . ($express ?: '!empty(' . $name . ')') . ') echo ' . substr($str, 1) . '; ?>';
+                                        break;
+                                    case ':':
+                                        // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx
+                                        $str = '<?php echo ' . ($express ?: '!empty(' . $name . ')') . '?' . $name . $str . '; ?>';
+                                        break;
+                                    default:
+                                        $str = '<?php echo ' . ($express ?: '!empty(' . $name . ')') . '?' . $str . '; ?>';
+                                }
+                            }
+                        } else {
+                            $this->parseVar($str);
+                            $this->parseVarFunction($str);
+                            $str = '<?php echo ' . $str . '; ?>';
+                        }
+                        break;
+                    case ':':
+                        // 输出某个函数的结果
+                        $str = substr($str, 1);
+                        $this->parseVar($str);
+                        $str = '<?php echo ' . $str . '; ?>';
+                        break;
+                    case '~':
+                        // 执行某个函数
+                        $str = substr($str, 1);
+                        $this->parseVar($str);
+                        $str = '<?php ' . $str . '; ?>';
+                        break;
+                    case '-':
+                    case '+':
+                        // 输出计算
+                        $this->parseVar($str);
+                        $str = '<?php echo ' . $str . '; ?>';
+                        break;
+                    case '/':
+                        // 注释标签
+                        $flag2 = substr($str, 1, 1);
+                        if ('/' == $flag2 || ('*' == $flag2 && substr(rtrim($str), -2) == '*/')) {
+                            $str = '';
+                        }
+                        break;
+                    default:
+                        // 未识别的标签直接返回
+                        $str = $this->config['tpl_begin'] . $str . $this->config['tpl_end'];
+                        break;
+                }
+                $content = str_replace($match[0], $str, $content);
+            }
+            unset($matches);
+        }
+        return;
+    }
+
+    /**
+     * 模板变量解析,支持使用函数
+     * 格式: {$varname|function1|function2=arg1,arg2}
+     * @access public
+     * @param  string $varStr 变量数据
+     * @return void
+     */
+    public function parseVar(&$varStr)
+    {
+        $varStr = trim($varStr);
+        if (preg_match_all('/\$[a-zA-Z_](?>\w*)(?:[:\.][0-9a-zA-Z_](?>\w*))+/', $varStr, $matches, PREG_OFFSET_CAPTURE)) {
+            static $_varParseList = [];
+            while ($matches[0]) {
+                $match = array_pop($matches[0]);
+                //如果已经解析过该变量字串,则直接返回变量值
+                if (isset($_varParseList[$match[0]])) {
+                    $parseStr = $_varParseList[$match[0]];
+                } else {
+                    if (strpos($match[0], '.')) {
+                        $vars  = explode('.', $match[0]);
+                        $first = array_shift($vars);
+                        if ('$Think' == $first) {
+                            // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出
+                            $parseStr = $this->parseThinkVar($vars);
+                        } elseif ('$Request' == $first) {
+                            // 获取Request请求对象参数
+                            $method = array_shift($vars);
+                            if (!empty($vars)) {
+                                $params = implode('.', $vars);
+                                if ('true' != $params) {
+                                    $params = '\'' . $params . '\'';
+                                }
+                            } else {
+                                $params = '';
+                            }
+                            $parseStr = '\think\Request::instance()->' . $method . '(' . $params . ')';
+                        } else {
+                            switch ($this->config['tpl_var_identify']) {
+                                case 'array': // 识别为数组
+                                    $parseStr = $first . '[\'' . implode('\'][\'', $vars) . '\']';
+                                    break;
+                                case 'obj': // 识别为对象
+                                    $parseStr = $first . '->' . implode('->', $vars);
+                                    break;
+                                default: // 自动判断数组或对象
+                                    $parseStr = '(is_array(' . $first . ')?' . $first . '[\'' . implode('\'][\'', $vars) . '\']:' . $first . '->' . implode('->', $vars) . ')';
+                            }
+                        }
+                    } else {
+                        $parseStr = str_replace(':', '->', $match[0]);
+                    }
+                    $_varParseList[$match[0]] = $parseStr;
+                }
+                $varStr = substr_replace($varStr, $parseStr, $match[1], strlen($match[0]));
+            }
+            unset($matches);
+        }
+        return;
+    }
+
+    /**
+     * 对模板中使用了函数的变量进行解析
+     * 格式 {$varname|function1|function2=arg1,arg2}
+     * @access public
+     * @param  string $varStr 变量字符串
+     * @return void
+     */
+    public function parseVarFunction(&$varStr)
+    {
+        if (false == strpos($varStr, '|')) {
+            return;
+        }
+        static $_varFunctionList = [];
+        $_key                    = md5($varStr);
+        //如果已经解析过该变量字串,则直接返回变量值
+        if (isset($_varFunctionList[$_key])) {
+            $varStr = $_varFunctionList[$_key];
+        } else {
+            $varArray = explode('|', $varStr);
+            // 取得变量名称
+            $name = array_shift($varArray);
+            // 对变量使用函数
+            $length = count($varArray);
+            // 取得模板禁止使用函数列表
+            $template_deny_funs = explode(',', $this->config['tpl_deny_func_list']);
+            for ($i = 0; $i < $length; $i++) {
+                $args = explode('=', $varArray[$i], 2);
+                // 模板函数过滤
+                $fun = trim($args[0]);
+                switch ($fun) {
+                    case 'default': // 特殊模板函数
+                        if (false === strpos($name, '(')) {
+                            $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')';
+                        } else {
+                            $name = '(' . $name . ' ?: ' . $args[1] . ')';
+                        }
+                        break;
+                    default: // 通用模板函数
+                        if (!in_array($fun, $template_deny_funs)) {
+                            if (isset($args[1])) {
+                                if (strstr($args[1], '###')) {
+                                    $args[1] = str_replace('###', $name, $args[1]);
+                                    $name    = "$fun($args[1])";
+                                } else {
+                                    $name = "$fun($name,$args[1])";
+                                }
+                            } else {
+                                if (!empty($args[0])) {
+                                    $name = "$fun($name)";
+                                }
+                            }
+                        }
+                }
+            }
+            $_varFunctionList[$_key] = $name;
+            $varStr                  = $name;
+        }
+        return;
+    }
+
+    /**
+     * 特殊模板变量解析
+     * 格式 以 $Think. 打头的变量属于特殊模板变量
+     * @access public
+     * @param  array $vars 变量数组
+     * @return string
+     */
+    public function parseThinkVar($vars)
+    {
+        $type  = strtoupper(trim(array_shift($vars)));
+        $param = implode('.', $vars);
+        if ($vars) {
+            switch ($type) {
+                case 'SERVER':
+                    $parseStr = '\\think\\Request::instance()->server(\'' . $param . '\')';
+                    break;
+                case 'GET':
+                    $parseStr = '\\think\\Request::instance()->get(\'' . $param . '\')';
+                    break;
+                case 'POST':
+                    $parseStr = '\\think\\Request::instance()->post(\'' . $param . '\')';
+                    break;
+                case 'COOKIE':
+                    $parseStr = '\\think\\Cookie::get(\'' . $param . '\')';
+                    break;
+                case 'SESSION':
+                    $parseStr = '\\think\\Session::get(\'' . $param . '\')';
+                    break;
+                case 'ENV':
+                    $parseStr = '\\think\\Request::instance()->env(\'' . $param . '\')';
+                    break;
+                case 'REQUEST':
+                    $parseStr = '\\think\\Request::instance()->request(\'' . $param . '\')';
+                    break;
+                case 'CONST':
+                    $parseStr = strtoupper($param);
+                    break;
+                case 'LANG':
+                    $parseStr = '\\think\\Lang::get(\'' . $param . '\')';
+                    break;
+                case 'CONFIG':
+                    $parseStr = '\\think\\Config::get(\'' . $param . '\')';
+                    break;
+                default:
+                    $parseStr = '\'\'';
+                    break;
+            }
+        } else {
+            switch ($type) {
+                case 'NOW':
+                    $parseStr = "date('Y-m-d g:i a',time())";
+                    break;
+                case 'VERSION':
+                    $parseStr = 'THINK_VERSION';
+                    break;
+                case 'LDELIM':
+                    $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\'';
+                    break;
+                case 'RDELIM':
+                    $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\'';
+                    break;
+                default:
+                    if (defined($type)) {
+                        $parseStr = $type;
+                    } else {
+                        $parseStr = '';
+                    }
+            }
+        }
+        return $parseStr;
+    }
+
+    /**
+     * 分析加载的模板文件并读取内容 支持多个模板文件读取
+     * @access private
+     * @param  string $templateName 模板文件名
+     * @return string
+     */
+    private function parseTemplateName($templateName)
+    {
+        $array    = explode(',', $templateName);
+        $parseStr = '';
+        foreach ($array as $templateName) {
+            if (empty($templateName)) {
+                continue;
+            }
+            if (0 === strpos($templateName, '$')) {
+                //支持加载变量文件名
+                $templateName = $this->get(substr($templateName, 1));
+            }
+            $template = $this->parseTemplateFile($templateName);
+            if ($template) {
+                // 获取模板文件内容
+                $parseStr .= file_get_contents($template);
+            }
+        }
+        return $parseStr;
+    }
+
+    /**
+     * 解析模板文件名
+     * @access private
+     * @param  string $template 文件名
+     * @return string|false
+     */
+    private function parseTemplateFile($template)
+    {
+        if ('' == pathinfo($template, PATHINFO_EXTENSION)) {
+            if (strpos($template, '@')) {
+                list($module, $template) = explode('@', $template);
+            }
+            if (0 !== strpos($template, '/')) {
+                $template = str_replace(['/', ':'], $this->config['view_depr'], $template);
+            } else {
+                $template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1));
+            }
+            if ($this->config['view_base']) {
+                $module = isset($module) ? $module : Request::instance()->module();
+                $path   = $this->config['view_base'] . ($module ? $module . DS : '');
+            } else {
+                $path = isset($module) ? APP_PATH . $module . DS . basename($this->config['view_path']) . DS : $this->config['view_path'];
+            }
+            $template = realpath($path . $template . '.' . ltrim($this->config['view_suffix'], '.'));
+        }
+
+        if (is_file($template)) {
+            // 记录模板文件的更新时间
+            $this->includeFile[$template] = filemtime($template);
+            return $template;
+        } else {
+            throw new TemplateNotFoundException('template not exists:' . $template, $template);
+        }
+    }
+
+    /**
+     * 按标签生成正则
+     * @access private
+     * @param  string $tagName 标签名
+     * @return string
+     */
+    private function getRegex($tagName)
+    {
+        $regex = '';
+        if ('tag' == $tagName) {
+            $begin = $this->config['tpl_begin'];
+            $end   = $this->config['tpl_end'];
+            if (strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1) {
+                $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>[^' . $end . ']*))' . $end;
+            } else {
+                $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>(?:(?!' . $end . ').)*))' . $end;
+            }
+        } else {
+            $begin  = $this->config['taglib_begin'];
+            $end    = $this->config['taglib_end'];
+            $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false;
+            switch ($tagName) {
+                case 'block':
+                    if ($single) {
+                        $regex = $begin . '(?:' . $tagName . '\b(?>(?:(?!name=).)*)\bname=([\'\"])(?P<name>[\$\w\-\/\.]+)\\1(?>[^' . $end . ']*)|\/' . $tagName . ')' . $end;
+                    } else {
+                        $regex = $begin . '(?:' . $tagName . '\b(?>(?:(?!name=).)*)\bname=([\'\"])(?P<name>[\$\w\-\/\.]+)\\1(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end;
+                    }
+                    break;
+                case 'literal':
+                    if ($single) {
+                        $regex = '(' . $begin . $tagName . '\b(?>[^' . $end . ']*)' . $end . ')';
+                        $regex .= '(?:(?>[^' . $begin . ']*)(?>(?!' . $begin . '(?>' . $tagName . '\b[^' . $end . ']*|\/' . $tagName . ')' . $end . ')' . $begin . '[^' . $begin . ']*)*)';
+                        $regex .= '(' . $begin . '\/' . $tagName . $end . ')';
+                    } else {
+                        $regex = '(' . $begin . $tagName . '\b(?>(?:(?!' . $end . ').)*)' . $end . ')';
+                        $regex .= '(?:(?>(?:(?!' . $begin . ').)*)(?>(?!' . $begin . '(?>' . $tagName . '\b(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end . ')' . $begin . '(?>(?:(?!' . $begin . ').)*))*)';
+                        $regex .= '(' . $begin . '\/' . $tagName . $end . ')';
+                    }
+                    break;
+                case 'restoreliteral':
+                    $regex = '<!--###literal(\d+)###-->';
+                    break;
+                case 'include':
+                    $name = 'file';
+                case 'taglib':
+                case 'layout':
+                case 'extend':
+                    if (empty($name)) {
+                        $name = 'name';
+                    }
+                    if ($single) {
+                        $regex = $begin . $tagName . '\b(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P<name>[\$\w\-\/\.\:@,\\\\]+)\\1(?>[^' . $end . ']*)' . $end;
+                    } else {
+                        $regex = $begin . $tagName . '\b(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P<name>[\$\w\-\/\.\:@,\\\\]+)\\1(?>(?:(?!' . $end . ').)*)' . $end;
+                    }
+                    break;
+            }
+        }
+        return '/' . $regex . '/is';
+    }
+}

+ 333 - 0
thinkphp/library/think/Url.php

@@ -0,0 +1,333 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+class Url
+{
+    // 生成URL地址的root
+    protected static $root;
+    protected static $bindCheck;
+
+    /**
+     * URL生成 支持路由反射
+     * @param string            $url 路由地址
+     * @param string|array      $vars 参数(支持数组和字符串)a=val&b=val2... ['a'=>'val1', 'b'=>'val2']
+     * @param string|bool       $suffix 伪静态后缀,默认为true表示获取配置值
+     * @param boolean|string    $domain 是否显示域名 或者直接传入域名
+     * @return string
+     */
+    public static function build($url = '', $vars = '', $suffix = true, $domain = false)
+    {
+        if (false === $domain && Route::rules('domain')) {
+            $domain = true;
+        }
+        // 解析URL
+        if (0 === strpos($url, '[') && $pos = strpos($url, ']')) {
+            // [name] 表示使用路由命名标识生成URL
+            $name = substr($url, 1, $pos - 1);
+            $url  = 'name' . substr($url, $pos + 1);
+        }
+        if (false === strpos($url, '://') && 0 !== strpos($url, '/')) {
+            $info = parse_url($url);
+            $url  = !empty($info['path']) ? $info['path'] : '';
+            if (isset($info['fragment'])) {
+                // 解析锚点
+                $anchor = $info['fragment'];
+                if (false !== strpos($anchor, '?')) {
+                    // 解析参数
+                    list($anchor, $info['query']) = explode('?', $anchor, 2);
+                }
+                if (false !== strpos($anchor, '@')) {
+                    // 解析域名
+                    list($anchor, $domain) = explode('@', $anchor, 2);
+                }
+            } elseif (strpos($url, '@') && false === strpos($url, '\\')) {
+                // 解析域名
+                list($url, $domain) = explode('@', $url, 2);
+            }
+        }
+
+        // 解析参数
+        if (is_string($vars)) {
+            // aaa=1&bbb=2 转换成数组
+            parse_str($vars, $vars);
+        }
+
+        if ($url) {
+            $rule = Route::name(isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : ''));
+            if (is_null($rule) && isset($info['query'])) {
+                $rule = Route::name($url);
+                // 解析地址里面参数 合并到vars
+                parse_str($info['query'], $params);
+                $vars = array_merge($params, $vars);
+                unset($info['query']);
+            }
+        }
+        if (!empty($rule) && $match = self::getRuleUrl($rule, $vars)) {
+            // 匹配路由命名标识
+            $url = $match[0];
+            // 替换可选分隔符
+            $url = preg_replace(['/(\W)\?$/', '/(\W)\?/'], ['', '\1'], $url);
+            if (!empty($match[1])) {
+                $domain = $match[1];
+            }
+            if (!is_null($match[2])) {
+                $suffix = $match[2];
+            }
+        } elseif (!empty($rule) && isset($name)) {
+            throw new \InvalidArgumentException('route name not exists:' . $name);
+        } else {
+            // 检查别名路由
+            $alias      = Route::rules('alias');
+            $matchAlias = false;
+            if ($alias) {
+                // 别名路由解析
+                foreach ($alias as $key => $val) {
+                    if (is_array($val)) {
+                        $val = $val[0];
+                    }
+                    if (0 === strpos($url, $val)) {
+                        $url        = $key . substr($url, strlen($val));
+                        $matchAlias = true;
+                        break;
+                    }
+                }
+            }
+            if (!$matchAlias) {
+                // 路由标识不存在 直接解析
+                $url = self::parseUrl($url, $domain);
+            }
+            if (isset($info['query'])) {
+                // 解析地址里面参数 合并到vars
+                parse_str($info['query'], $params);
+                $vars = array_merge($params, $vars);
+            }
+        }
+
+        // 检测URL绑定
+        if (!self::$bindCheck) {
+            $type = Route::getBind('type');
+            if ($type) {
+                $bind = Route::getBind($type);
+                if ($bind && 0 === strpos($url, $bind)) {
+                    $url = substr($url, strlen($bind) + 1);
+                }
+            }
+        }
+        // 还原URL分隔符
+        $depr = Config::get('pathinfo_depr');
+        $url  = str_replace('/', $depr, $url);
+
+        // URL后缀
+        $suffix = in_array($url, ['/', '']) ? '' : self::parseSuffix($suffix);
+        // 锚点
+        $anchor = !empty($anchor) ? '#' . $anchor : '';
+        // 参数组装
+        if (!empty($vars)) {
+            // 添加参数
+            if (Config::get('url_common_param')) {
+                $vars = http_build_query($vars);
+                $url .= $suffix . '?' . $vars . $anchor;
+            } else {
+                $paramType = Config::get('url_param_type');
+                foreach ($vars as $var => $val) {
+                    if ('' !== trim($val)) {
+                        if ($paramType) {
+                            $url .= $depr . urlencode($val);
+                        } else {
+                            $url .= $depr . $var . $depr . urlencode($val);
+                        }
+                    }
+                }
+                $url .= $suffix . $anchor;
+            }
+        } else {
+            $url .= $suffix . $anchor;
+        }
+        // 检测域名
+        $domain = self::parseDomain($url, $domain);
+        // URL组装
+        $url = $domain . rtrim(self::$root ?: Request::instance()->root(), '/') . '/' . ltrim($url, '/');
+
+        self::$bindCheck = false;
+        return $url;
+    }
+
+    // 直接解析URL地址
+    protected static function parseUrl($url, &$domain)
+    {
+        $request = Request::instance();
+        if (0 === strpos($url, '/')) {
+            // 直接作为路由地址解析
+            $url = substr($url, 1);
+        } elseif (false !== strpos($url, '\\')) {
+            // 解析到类
+            $url = ltrim(str_replace('\\', '/', $url), '/');
+        } elseif (0 === strpos($url, '@')) {
+            // 解析到控制器
+            $url = substr($url, 1);
+        } else {
+            // 解析到 模块/控制器/操作
+            $module  = $request->module();
+            $domains = Route::rules('domain');
+            if (true === $domain && 2 == substr_count($url, '/')) {
+                $current = $request->host();
+                $match   = [];
+                $pos     = [];
+                foreach ($domains as $key => $item) {
+                    if (isset($item['[bind]']) && 0 === strpos($url, $item['[bind]'][0])) {
+                        $pos[$key] = strlen($item['[bind]'][0]) + 1;
+                        $match[]   = $key;
+                        $module    = '';
+                    }
+                }
+                if ($match) {
+                    $domain = current($match);
+                    foreach ($match as $item) {
+                        if (0 === strpos($current, $item)) {
+                            $domain = $item;
+                        }
+                    }
+                    self::$bindCheck = true;
+                    $url             = substr($url, $pos[$domain]);
+                }
+            } elseif ($domain) {
+                if (isset($domains[$domain]['[bind]'][0])) {
+                    $bindModule = $domains[$domain]['[bind]'][0];
+                    if ($bindModule && !in_array($bindModule[0], ['\\', '@'])) {
+                        $module = '';
+                    }
+                }
+            }
+            $module = $module ? $module . '/' : '';
+
+            $controller = $request->controller();
+            if ('' == $url) {
+                // 空字符串输出当前的 模块/控制器/操作
+                $action = $request->action();
+            } else {
+                $path       = explode('/', $url);
+                $action     = array_pop($path);
+                $controller = empty($path) ? $controller : array_pop($path);
+                $module     = empty($path) ? $module : array_pop($path) . '/';
+            }
+            if (Config::get('url_convert')) {
+                $action     = strtolower($action);
+                $controller = Loader::parseName($controller);
+            }
+            $url = $module . $controller . '/' . $action;
+        }
+        return $url;
+    }
+
+    // 检测域名
+    protected static function parseDomain(&$url, $domain)
+    {
+        if (!$domain) {
+            return '';
+        }
+        $request    = Request::instance();
+        $rootDomain = Config::get('url_domain_root');
+        if (true === $domain) {
+            // 自动判断域名
+            $domain = Config::get('app_host') ?: $request->host();
+
+            $domains = Route::rules('domain');
+            if ($domains) {
+                $route_domain = array_keys($domains);
+                foreach ($route_domain as $domain_prefix) {
+                    if (0 === strpos($domain_prefix, '*.') && strpos($domain, ltrim($domain_prefix, '*.')) !== false) {
+                        foreach ($domains as $key => $rule) {
+                            $rule = is_array($rule) ? $rule[0] : $rule;
+                            if (is_string($rule) && false === strpos($key, '*') && 0 === strpos($url, $rule)) {
+                                $url    = ltrim($url, $rule);
+                                $domain = $key;
+                                // 生成对应子域名
+                                if (!empty($rootDomain)) {
+                                    $domain .= $rootDomain;
+                                }
+                                break;
+                            } elseif (false !== strpos($key, '*')) {
+                                if (!empty($rootDomain)) {
+                                    $domain .= $rootDomain;
+                                }
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+
+        } else {
+            if (empty($rootDomain)) {
+                $host       = Config::get('app_host') ?: $request->host();
+                $rootDomain = substr_count($host, '.') > 1 ? substr(strstr($host, '.'), 1) : $host;
+            }
+            if (substr_count($domain, '.') < 2 && !strpos($domain, $rootDomain)) {
+                $domain .= '.' . $rootDomain;
+            }
+        }
+        if (false !== strpos($domain, '://')) {
+            $scheme = '';
+        } else {
+            $scheme = $request->isSsl() || Config::get('is_https') ? 'https://' : 'http://';
+        }
+        return $scheme . $domain;
+    }
+
+    // 解析URL后缀
+    protected static function parseSuffix($suffix)
+    {
+        if ($suffix) {
+            $suffix = true === $suffix ? Config::get('url_html_suffix') : $suffix;
+            if ($pos = strpos($suffix, '|')) {
+                $suffix = substr($suffix, 0, $pos);
+            }
+        }
+        return (empty($suffix) || 0 === strpos($suffix, '.')) ? $suffix : '.' . $suffix;
+    }
+
+    // 匹配路由地址
+    public static function getRuleUrl($rule, &$vars = [])
+    {
+        foreach ($rule as $item) {
+            list($url, $pattern, $domain, $suffix) = $item;
+            if (empty($pattern)) {
+                return [rtrim($url, '$'), $domain, $suffix];
+            }
+            $type = Config::get('url_common_param');
+            foreach ($pattern as $key => $val) {
+                if (isset($vars[$key])) {
+                    $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key . '', '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url);
+                    unset($vars[$key]);
+                    $result = [$url, $domain, $suffix];
+                } elseif (2 == $val) {
+                    $url    = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url);
+                    $result = [$url, $domain, $suffix];
+                } else {
+                    break;
+                }
+            }
+            if (isset($result)) {
+                return $result;
+            }
+        }
+        return false;
+    }
+
+    // 指定当前生成URL地址的root
+    public static function root($root)
+    {
+        self::$root = $root;
+        Request::instance()->root($root);
+    }
+}

+ 1371 - 0
thinkphp/library/think/Validate.php

@@ -0,0 +1,1371 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+use think\exception\ClassNotFoundException;
+
+class Validate
+{
+    // 实例
+    protected static $instance;
+
+    // 自定义的验证类型
+    protected static $type = [];
+
+    // 验证类型别名
+    protected $alias = [
+        '>' => 'gt', '>=' => 'egt', '<' => 'lt', '<=' => 'elt', '=' => 'eq', 'same' => 'eq',
+    ];
+
+    // 当前验证的规则
+    protected $rule = [];
+
+    // 验证提示信息
+    protected $message = [];
+    // 验证字段描述
+    protected $field = [];
+
+    // 验证规则默认提示信息
+    protected static $typeMsg = [
+        'require'     => ':attribute require',
+        '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',
+        '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',
+        'afterWith'   => ':attribute cannot be less than :rule',
+        'beforeWith'  => ':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',
+    ];
+
+    // 当前验证场景
+    protected $currentScene = null;
+
+    // 正则表达式 regex = ['zip'=>'\d{6}',...]
+    protected $regex = [];
+
+    // 验证场景 scene = ['edit'=>'name1,name2,...']
+    protected $scene = [];
+
+    // 验证失败错误信息
+    protected $error = [];
+
+    // 批量验证
+    protected $batch = false;
+
+    /**
+     * 构造函数
+     * @access public
+     * @param array $rules 验证规则
+     * @param array $message 验证提示信息
+     * @param array $field 验证字段描述信息
+     */
+    public function __construct(array $rules = [], $message = [], $field = [])
+    {
+        $this->rule    = array_merge($this->rule, $rules);
+        $this->message = array_merge($this->message, $message);
+        $this->field   = array_merge($this->field, $field);
+    }
+
+    /**
+     * 实例化验证
+     * @access public
+     * @param array     $rules 验证规则
+     * @param array     $message 验证提示信息
+     * @param array     $field 验证字段描述信息
+     * @return Validate
+     */
+    public static function make($rules = [], $message = [], $field = [])
+    {
+        if (is_null(self::$instance)) {
+            self::$instance = new self($rules, $message, $field);
+        }
+        return self::$instance;
+    }
+
+    /**
+     * 添加字段验证规则
+     * @access protected
+     * @param string|array  $name  字段名称或者规则数组
+     * @param mixed         $rule  验证规则
+     * @return Validate
+     */
+    public function rule($name, $rule = '')
+    {
+        if (is_array($name)) {
+            $this->rule = array_merge($this->rule, $name);
+        } else {
+            $this->rule[$name] = $rule;
+        }
+        return $this;
+    }
+
+    /**
+     * 注册验证(类型)规则
+     * @access public
+     * @param string    $type  验证规则类型
+     * @param mixed     $callback callback方法(或闭包)
+     * @return void
+     */
+    public static function extend($type, $callback = null)
+    {
+        if (is_array($type)) {
+            self::$type = array_merge(self::$type, $type);
+        } else {
+            self::$type[$type] = $callback;
+        }
+    }
+
+    /**
+     * 设置验证规则的默认提示信息
+     * @access protected
+     * @param string|array  $type  验证规则类型名称或者数组
+     * @param string        $msg  验证提示信息
+     * @return void
+     */
+    public static function setTypeMsg($type, $msg = null)
+    {
+        if (is_array($type)) {
+            self::$typeMsg = array_merge(self::$typeMsg, $type);
+        } else {
+            self::$typeMsg[$type] = $msg;
+        }
+    }
+
+    /**
+     * 设置提示信息
+     * @access public
+     * @param string|array  $name  字段名称
+     * @param string        $message 提示信息
+     * @return Validate
+     */
+    public function message($name, $message = '')
+    {
+        if (is_array($name)) {
+            $this->message = array_merge($this->message, $name);
+        } else {
+            $this->message[$name] = $message;
+        }
+        return $this;
+    }
+
+    /**
+     * 设置验证场景
+     * @access public
+     * @param string|array  $name  场景名或者场景设置数组
+     * @param mixed         $fields 要验证的字段
+     * @return Validate
+     */
+    public function scene($name, $fields = null)
+    {
+        if (is_array($name)) {
+            $this->scene = array_merge($this->scene, $name);
+        }if (is_null($fields)) {
+            // 设置当前场景
+            $this->currentScene = $name;
+        } else {
+            // 设置验证场景
+            $this->scene[$name] = $fields;
+        }
+        return $this;
+    }
+
+    /**
+     * 判断是否存在某个验证场景
+     * @access public
+     * @param string $name 场景名
+     * @return bool
+     */
+    public function hasScene($name)
+    {
+        return isset($this->scene[$name]);
+    }
+
+    /**
+     * 设置批量验证
+     * @access public
+     * @param bool $batch  是否批量验证
+     * @return Validate
+     */
+    public function batch($batch = true)
+    {
+        $this->batch = $batch;
+        return $this;
+    }
+
+    /**
+     * 数据自动验证
+     * @access public
+     * @param array     $data  数据
+     * @param mixed     $rules  验证规则
+     * @param string    $scene 验证场景
+     * @return bool
+     */
+    public function check($data, $rules = [], $scene = '')
+    {
+        $this->error = [];
+
+        if (empty($rules)) {
+            // 读取验证规则
+            $rules = $this->rule;
+        }
+
+        // 分析验证规则
+        $scene = $this->getScene($scene);
+        if (is_array($scene)) {
+            // 处理场景验证字段
+            $change = [];
+            $array  = [];
+            foreach ($scene as $k => $val) {
+                if (is_numeric($k)) {
+                    $array[] = $val;
+                } else {
+                    $array[]    = $k;
+                    $change[$k] = $val;
+                }
+            }
+        }
+
+        foreach ($rules as $key => $item) {
+            // field => rule1|rule2... field=>['rule1','rule2',...]
+            if (is_numeric($key)) {
+                // [field,rule1|rule2,msg1|msg2]
+                $key  = $item[0];
+                $rule = $item[1];
+                if (isset($item[2])) {
+                    $msg = is_string($item[2]) ? explode('|', $item[2]) : $item[2];
+                } else {
+                    $msg = [];
+                }
+            } else {
+                $rule = $item;
+                $msg  = [];
+            }
+            if (strpos($key, '|')) {
+                // 字段|描述 用于指定属性名称
+                list($key, $title) = explode('|', $key);
+            } else {
+                $title = isset($this->field[$key]) ? $this->field[$key] : $key;
+            }
+
+            // 场景检测
+            if (!empty($scene)) {
+                if ($scene instanceof \Closure && !call_user_func_array($scene, [$key, $data])) {
+                    continue;
+                } elseif (is_array($scene)) {
+                    if (!in_array($key, $array)) {
+                        continue;
+                    } elseif (isset($change[$key])) {
+                        // 重载某个验证规则
+                        $rule = $change[$key];
+                    }
+                }
+            }
+
+            // 获取数据 支持二维数组
+            $value = $this->getDataValue($data, $key);
+
+            // 字段验证
+            if ($rule instanceof \Closure) {
+                // 匿名函数验证 支持传入当前字段和所有字段两个数据
+                $result = call_user_func_array($rule, [$value, $data]);
+            } else {
+                $result = $this->checkItem($key, $value, $rule, $data, $title, $msg);
+            }
+
+            if (true !== $result) {
+                // 没有返回true 则表示验证失败
+                if (!empty($this->batch)) {
+                    // 批量验证
+                    if (is_array($result)) {
+                        $this->error = array_merge($this->error, $result);
+                    } else {
+                        $this->error[$key] = $result;
+                    }
+                } else {
+                    $this->error = $result;
+                    return false;
+                }
+            }
+        }
+        return !empty($this->error) ? false : true;
+    }
+
+    /**
+     * 根据验证规则验证数据
+     * @access protected
+     * @param  mixed     $value 字段值
+     * @param  mixed     $rules 验证规则
+     * @return bool
+     */
+    protected function checkRule($value, $rules)
+    {
+        if ($rules instanceof \Closure) {
+            return call_user_func_array($rules, [$value]);
+        } elseif (is_string($rules)) {
+            $rules = explode('|', $rules);
+        }
+
+        foreach ($rules as $key => $rule) {
+            if ($rule instanceof \Closure) {
+                $result = call_user_func_array($rule, [$value]);
+            } else {
+                // 判断验证类型
+                list($type, $rule) = $this->getValidateType($key, $rule);
+
+                $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type];
+
+                $result = call_user_func_array($callback, [$value, $rule]);
+            }
+
+            if (true !== $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($field, $value, $rules, $data, $title = '', $msg = [])
+    {
+        // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...]
+        if (is_string($rules)) {
+            $rules = explode('|', $rules);
+        }
+        $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 {
+                // 判断验证类型
+                list($type, $rule, $info) = $this->getValidateType($key, $rule);
+
+                // 如果不是require 有数据才会行验证
+                if (0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) {
+                    // 验证类型
+                    $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type];
+                    // 验证数据
+                    $result = call_user_func_array($callback, [$value, $rule, $data, $field, $title]);
+                } else {
+                    $result = true;
+                }
+            }
+
+            if (false === $result) {
+                // 验证失败 返回错误信息
+                if (isset($msg[$i])) {
+                    $message = $msg[$i];
+                    if (is_string($message) && strpos($message, '{%') === 0) {
+                        $message = 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', ':rule'], [$title, (string) $rule], $result);
+                }
+                return $result;
+            }
+            $i++;
+        }
+        return $result;
+    }
+
+    /**
+     * 获取当前验证类型及规则
+     * @access public
+     * @param  mixed     $key
+     * @param  mixed     $rule
+     * @return array
+     */
+    protected function getValidateType($key, $rule)
+    {
+        // 判断验证类型
+        if (!is_numeric($key)) {
+            return [$key, $rule, $key];
+        }
+
+        if (strpos($rule, ':')) {
+            list($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 protected
+     * @param mixed     $value 字段值
+     * @param mixed     $rule  验证规则
+     * @param array     $data  数据
+     * @param string    $field 字段名
+     * @return bool
+     */
+    protected function confirm($value, $rule, $data, $field = '')
+    {
+        if ('' == $rule) {
+            if (strpos($field, '_confirm')) {
+                $rule = strstr($field, '_confirm', true);
+            } else {
+                $rule = $field . '_confirm';
+            }
+        }
+        return $this->getDataValue($data, $rule) === $value;
+    }
+
+    /**
+     * 验证是否和某个字段的值是否不同
+     * @access protected
+     * @param mixed $value 字段值
+     * @param mixed $rule  验证规则
+     * @param array $data  数据
+     * @return bool
+     */
+    protected function different($value, $rule, $data)
+    {
+        return $this->getDataValue($data, $rule) != $value;
+    }
+
+    /**
+     * 验证是否大于等于某个值
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @param array     $data  数据
+     * @return bool
+     */
+    protected function egt($value, $rule, $data)
+    {
+        $val = $this->getDataValue($data, $rule);
+        return !is_null($val) && $value >= $val;
+    }
+
+    /**
+     * 验证是否大于某个值
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @param array     $data  数据
+     * @return bool
+     */
+    protected function gt($value, $rule, $data)
+    {
+        $val = $this->getDataValue($data, $rule);
+        return !is_null($val) && $value > $val;
+    }
+
+    /**
+     * 验证是否小于等于某个值
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @param array     $data  数据
+     * @return bool
+     */
+    protected function elt($value, $rule, $data)
+    {
+        $val = $this->getDataValue($data, $rule);
+        return !is_null($val) && $value <= $val;
+    }
+
+    /**
+     * 验证是否小于某个值
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @param array     $data  数据
+     * @return bool
+     */
+    protected function lt($value, $rule, $data)
+    {
+        $val = $this->getDataValue($data, $rule);
+        return !is_null($val) && $value < $val;
+    }
+
+    /**
+     * 验证是否等于某个值
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @return bool
+     */
+    protected function eq($value, $rule)
+    {
+        return $value == $rule;
+    }
+
+    /**
+     * 验证字段值是否为有效格式
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param string    $rule  验证规则
+     * @param array     $data  验证数据
+     * @return bool
+     */
+    protected function is($value, $rule, $data = [])
+    {
+        switch ($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 'alpha':
+                // 只允许字母
+                $result = $this->regex($value, '/^[A-Za-z]+$/');
+                break;
+            case 'alphaNum':
+                // 只允许字母和数字
+                $result = $this->regex($value, '/^[A-Za-z0-9]+$/');
+                break;
+            case 'alphaDash':
+                // 只允许字母、数字和下划线 破折号
+                $result = $this->regex($value, '/^[A-Za-z0-9\-\_]+$/');
+                break;
+            case 'chs':
+                // 只允许汉字
+                $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}]+$/u');
+                break;
+            case 'chsAlpha':
+                // 只允许汉字、字母
+                $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u');
+                break;
+            case 'chsAlphaNum':
+                // 只允许汉字、字母和数字
+                $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u');
+                break;
+            case 'chsDash':
+                // 只允许汉字、字母、数字和下划线_及破折号-
+                $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u');
+                break;
+            case 'activeUrl':
+                // 是否为有效的网址
+                $result = checkdnsrr($value);
+                break;
+            case 'ip':
+                // 是否为IP地址
+                $result = $this->filter($value, [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6]);
+                break;
+            case 'url':
+                // 是否为一个URL地址
+                $result = $this->filter($value, FILTER_VALIDATE_URL);
+                break;
+            case 'float':
+                // 是否为float
+                $result = $this->filter($value, FILTER_VALIDATE_FLOAT);
+                break;
+            case 'number':
+                $result = is_numeric($value);
+                break;
+            case 'integer':
+                // 是否为整型
+                $result = $this->filter($value, FILTER_VALIDATE_INT);
+                break;
+            case 'email':
+                // 是否为邮箱地址
+                $result = $this->filter($value, FILTER_VALIDATE_EMAIL);
+                break;
+            case 'boolean':
+                // 是否为布尔值
+                $result = in_array($value, [true, false, 0, 1, '0', '1'], true);
+                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(self::$type[$rule])) {
+                    // 注册的验证规则
+                    $result = call_user_func_array(self::$type[$rule], [$value]);
+                } else {
+                    // 正则验证
+                    $result = $this->regex($value, $rule);
+                }
+        }
+        return $result;
+    }
+
+    // 判断图像类型
+    protected function getImageType($image)
+    {
+        if (function_exists('exif_imagetype')) {
+            return exif_imagetype($image);
+        } else {
+            try {
+                $info = getimagesize($image);
+                return $info ? $info[2] : false;
+            } catch (\Exception $e) {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @return bool
+     */
+    protected function activeUrl($value, $rule)
+    {
+        if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) {
+            $rule = 'MX';
+        }
+        return checkdnsrr($value, $rule);
+    }
+
+    /**
+     * 验证是否有效IP
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则 ipv4 ipv6
+     * @return bool
+     */
+    protected function ip($value, $rule)
+    {
+        if (!in_array($rule, ['ipv4', 'ipv6'])) {
+            $rule = 'ipv4';
+        }
+        return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]);
+    }
+
+    /**
+     * 验证上传文件后缀
+     * @access protected
+     * @param mixed     $file  上传文件
+     * @param mixed     $rule  验证规则
+     * @return bool
+     */
+    protected function fileExt($file, $rule)
+    {
+        if (is_array($file)) {
+            foreach ($file as $item) {
+                if (!($item instanceof File) || !$item->checkExt($rule)) {
+                    return false;
+                }
+            }
+            return true;
+        } elseif ($file instanceof File) {
+            return $file->checkExt($rule);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 验证上传文件类型
+     * @access protected
+     * @param mixed     $file  上传文件
+     * @param mixed     $rule  验证规则
+     * @return bool
+     */
+    protected function fileMime($file, $rule)
+    {
+        if (is_array($file)) {
+            foreach ($file as $item) {
+                if (!($item instanceof File) || !$item->checkMime($rule)) {
+                    return false;
+                }
+            }
+            return true;
+        } elseif ($file instanceof File) {
+            return $file->checkMime($rule);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 验证上传文件大小
+     * @access protected
+     * @param mixed     $file  上传文件
+     * @param mixed     $rule  验证规则
+     * @return bool
+     */
+    protected function fileSize($file, $rule)
+    {
+        if (is_array($file)) {
+            foreach ($file as $item) {
+                if (!($item instanceof File) || !$item->checkSize($rule)) {
+                    return false;
+                }
+            }
+            return true;
+        } elseif ($file instanceof File) {
+            return $file->checkSize($rule);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 验证图片的宽高及类型
+     * @access protected
+     * @param mixed     $file  上传文件
+     * @param mixed     $rule  验证规则
+     * @return bool
+     */
+    protected function image($file, $rule)
+    {
+        if (!($file instanceof File)) {
+            return false;
+        }
+        if ($rule) {
+            $rule                        = explode(',', $rule);
+            list($width, $height, $type) = getimagesize($file->getRealPath());
+            if (isset($rule[2])) {
+                $imageType = strtolower($rule[2]);
+                if ('jpeg' == $imageType) {
+                    $imageType = 'jpg';
+                }
+                if (image_type_to_extension($type, false) != $imageType) {
+                    return false;
+                }
+            }
+
+            list($w, $h) = $rule;
+            return $w == $width && $h == $height;
+        } else {
+            return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]);
+        }
+    }
+
+    /**
+     * 验证请求类型
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @return bool
+     */
+    protected function method($value, $rule)
+    {
+        $method = Request::instance()->method();
+        return strtoupper($rule) == $method;
+    }
+
+    /**
+     * 验证时间和日期是否符合指定格式
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @return bool
+     */
+    protected function dateFormat($value, $rule)
+    {
+        $info = date_parse_from_format($rule, $value);
+        return 0 == $info['warning_count'] && 0 == $info['error_count'];
+    }
+
+    /**
+     * 验证是否唯一
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则 格式:数据表,字段名,排除ID,主键名
+     * @param array     $data  数据
+     * @param string    $field  验证字段名
+     * @return bool
+     */
+    protected function unique($value, $rule, $data, $field)
+    {
+        if (is_string($rule)) {
+            $rule = explode(',', $rule);
+        }
+        if (false !== strpos($rule[0], '\\')) {
+            // 指定模型类
+            $db = new $rule[0];
+        } else {
+            try {
+                $db = Loader::model($rule[0]);
+            } catch (ClassNotFoundException $e) {
+                $db = Db::name($rule[0]);
+            }
+        }
+        $key = isset($rule[1]) ? $rule[1] : $field;
+
+        if (strpos($key, '^')) {
+            // 支持多个字段验证
+            $fields = explode('^', $key);
+            foreach ($fields as $key) {
+                if (isset($data[$key])) {
+                    $map[$key] = $data[$key];
+                }
+            }
+        } elseif (strpos($key, '=')) {
+            parse_str($key, $map);
+        } elseif (isset($data[$field])) {
+            $map[$key] = $data[$field];
+        } else {
+            $map = [];
+        }
+
+        $pk = isset($rule[3]) ? $rule[3] : $db->getPk();
+        if (is_string($pk)) {
+            if (isset($rule[2])) {
+                $map[$pk] = ['neq', $rule[2]];
+            } elseif (isset($data[$pk])) {
+                $map[$pk] = ['neq', $data[$pk]];
+            }
+        }
+        if ($db->where($map)->field($pk)->find()) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 使用行为类验证
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @param array     $data  数据
+     * @return mixed
+     */
+    protected function behavior($value, $rule, $data)
+    {
+        return Hook::exec($rule, '', $data);
+    }
+
+    /**
+     * 使用filter_var方式验证
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @return bool
+     */
+    protected function filter($value, $rule)
+    {
+        if (is_string($rule) && strpos($rule, ',')) {
+            list($rule, $param) = explode(',', $rule);
+        } elseif (is_array($rule)) {
+            $param = isset($rule[1]) ? $rule[1] : null;
+            $rule  = $rule[0];
+        } else {
+            $param = null;
+        }
+        return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param);
+    }
+
+    /**
+     * 验证某个字段等于某个值的时候必须
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @param array     $data  数据
+     * @return bool
+     */
+    protected function requireIf($value, $rule, $data)
+    {
+        list($field, $val) = explode(',', $rule);
+        if ($this->getDataValue($data, $field) == $val) {
+            return !empty($value) || '0' == $value;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * 通过回调方法验证某个字段是否必须
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @param array     $data  数据
+     * @return bool
+     */
+    protected function requireCallback($value, $rule, $data)
+    {
+        $result = call_user_func_array($rule, [$value, $data]);
+        if ($result) {
+            return !empty($value) || '0' == $value;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * 验证某个字段有值的情况下必须
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @param array     $data  数据
+     * @return bool
+     */
+    protected function requireWith($value, $rule, $data)
+    {
+        $val = $this->getDataValue($data, $rule);
+        if (!empty($val)) {
+            return !empty($value) || '0' == $value;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * 验证是否在范围内
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @return bool
+     */
+    protected function in($value, $rule)
+    {
+        return in_array($value, is_array($rule) ? $rule : explode(',', $rule));
+    }
+
+    /**
+     * 验证是否不在某个范围
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @return bool
+     */
+    protected function notIn($value, $rule)
+    {
+        return !in_array($value, is_array($rule) ? $rule : explode(',', $rule));
+    }
+
+    /**
+     * between验证数据
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @return bool
+     */
+    protected function between($value, $rule)
+    {
+        if (is_string($rule)) {
+            $rule = explode(',', $rule);
+        }
+        list($min, $max) = $rule;
+        return $value >= $min && $value <= $max;
+    }
+
+    /**
+     * 使用notbetween验证数据
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @return bool
+     */
+    protected function notBetween($value, $rule)
+    {
+        if (is_string($rule)) {
+            $rule = explode(',', $rule);
+        }
+        list($min, $max) = $rule;
+        return $value < $min || $value > $max;
+    }
+
+    /**
+     * 验证数据长度
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @return bool
+     */
+    protected function length($value, $rule)
+    {
+        if (is_array($value)) {
+            $length = count($value);
+        } elseif ($value instanceof File) {
+            $length = $value->getSize();
+        } else {
+            $length = mb_strlen((string) $value);
+        }
+
+        if (strpos($rule, ',')) {
+            // 长度区间
+            list($min, $max) = explode(',', $rule);
+            return $length >= $min && $length <= $max;
+        } else {
+            // 指定长度
+            return $length == $rule;
+        }
+    }
+
+    /**
+     * 验证数据最大长度
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @return bool
+     */
+    protected function max($value, $rule)
+    {
+        if (is_array($value)) {
+            $length = count($value);
+        } elseif ($value instanceof File) {
+            $length = $value->getSize();
+        } else {
+            $length = mb_strlen((string) $value);
+        }
+        return $length <= $rule;
+    }
+
+    /**
+     * 验证数据最小长度
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @return bool
+     */
+    protected function min($value, $rule)
+    {
+        if (is_array($value)) {
+            $length = count($value);
+        } elseif ($value instanceof File) {
+            $length = $value->getSize();
+        } else {
+            $length = mb_strlen((string) $value);
+        }
+        return $length >= $rule;
+    }
+
+    /**
+     * 验证日期
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @param array     $data  数据
+     * @return bool
+     */
+    protected function after($value, $rule, $data)
+    {
+        return strtotime($value) >= strtotime($rule);
+    }
+
+    /**
+     * 验证日期
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @param array     $data  数据
+     * @return bool
+     */
+    protected function before($value, $rule, $data)
+    {
+        return strtotime($value) <= strtotime($rule);
+    }
+
+    /**
+     * 验证日期字段
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @param array     $data  数据
+     * @return bool
+     */
+    protected function afterWith($value, $rule, $data)
+    {
+        $rule = $this->getDataValue($data, $rule);
+        return !is_null($rule) && strtotime($value) >= strtotime($rule);
+    }
+
+    /**
+     * 验证日期字段
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @param array     $data  数据
+     * @return bool
+     */
+    protected function beforeWith($value, $rule, $data)
+    {
+        $rule = $this->getDataValue($data, $rule);
+        return !is_null($rule) && strtotime($value) <= strtotime($rule);
+    }
+
+    /**
+     * 验证有效期
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @return bool
+     */
+    protected function expire($value, $rule)
+    {
+        if (is_string($rule)) {
+            $rule = explode(',', $rule);
+        }
+        list($start, $end) = $rule;
+        if (!is_numeric($start)) {
+            $start = strtotime($start);
+        }
+
+        if (!is_numeric($end)) {
+            $end = strtotime($end);
+        }
+        return $_SERVER['REQUEST_TIME'] >= $start && $_SERVER['REQUEST_TIME'] <= $end;
+    }
+
+    /**
+     * 验证IP许可
+     * @access protected
+     * @param string    $value  字段值
+     * @param mixed     $rule  验证规则
+     * @return mixed
+     */
+    protected function allowIp($value, $rule)
+    {
+        return in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule));
+    }
+
+    /**
+     * 验证IP禁用
+     * @access protected
+     * @param string    $value  字段值
+     * @param mixed     $rule  验证规则
+     * @return mixed
+     */
+    protected function denyIp($value, $rule)
+    {
+        return !in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule));
+    }
+
+    /**
+     * 使用正则验证数据
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则 正则规则或者预定义正则名
+     * @return mixed
+     */
+    protected function regex($value, $rule)
+    {
+        if (isset($this->regex[$rule])) {
+            $rule = $this->regex[$rule];
+        }
+        if (0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) {
+            // 不是正则表达式则两端补上/
+            $rule = '/^' . $rule . '$/';
+        }
+        return is_scalar($value) && 1 === preg_match($rule, (string) $value);
+    }
+
+    /**
+     * 验证表单令牌
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @param array     $data  数据
+     * @return bool
+     */
+    protected function token($value, $rule, $data)
+    {
+        $rule = !empty($rule) ? $rule : '__token__';
+        if (!isset($data[$rule]) || !Session::has($rule)) {
+            // 令牌数据无效
+            return false;
+        }
+
+        // 令牌验证
+        if (isset($data[$rule]) && Session::get($rule) === $data[$rule]) {
+            // 防止重复提交
+            Session::delete($rule); // 验证完成销毁session
+            return true;
+        }
+        // 开启TOKEN重置
+        Session::delete($rule);
+        return false;
+    }
+
+    // 获取错误信息
+    public function getError()
+    {
+        return $this->error;
+    }
+
+    /**
+     * 获取数据值
+     * @access protected
+     * @param array $data 数据
+     * @param string $key 数据标识 支持二维
+     * @return mixed
+     */
+    protected function getDataValue($data, $key)
+    {
+        if (is_numeric($key)) {
+            $value = $key;
+        } elseif (strpos($key, '.')) {
+            // 支持二维数组验证
+            list($name1, $name2) = explode('.', $key);
+            $value               = isset($data[$name1][$name2]) ? $data[$name1][$name2] : null;
+        } else {
+            $value = isset($data[$key]) ? $data[$key] : null;
+        }
+        return $value;
+    }
+
+    /**
+     * 获取验证规则的错误提示信息
+     * @access protected
+     * @param string    $attribute  字段英文名
+     * @param string    $title  字段描述名
+     * @param string    $type  验证规则名称
+     * @param mixed     $rule  验证规则数据
+     * @return string
+     */
+    protected function getRuleMsg($attribute, $title, $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(self::$typeMsg[$type])) {
+            $msg = self::$typeMsg[$type];
+        } elseif (0 === strpos($type, 'require')) {
+            $msg = self::$typeMsg['require'];
+        } else {
+            $msg = $title . Lang::get('not conform to the rules');
+        }
+
+        if (is_string($msg) && 0 === strpos($msg, '{%')) {
+            $msg = Lang::get(substr($msg, 2, -1));
+        } elseif (Lang::has($msg)) {
+            $msg = Lang::get($msg);
+        }
+
+        if (is_string($msg) && 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', ':rule', ':1', ':2', ':3'],
+                [$title, (string) $rule, $array[0], $array[1], $array[2]],
+                $msg);
+        }
+        return $msg;
+    }
+
+    /**
+     * 获取数据验证的场景
+     * @access protected
+     * @param string $scene  验证场景
+     * @return array
+     */
+    protected function getScene($scene = '')
+    {
+        if (empty($scene)) {
+            // 读取指定场景
+            $scene = $this->currentScene;
+        }
+
+        if (!empty($scene) && isset($this->scene[$scene])) {
+            // 如果设置了验证适用场景
+            $scene = $this->scene[$scene];
+            if (is_string($scene)) {
+                $scene = explode(',', $scene);
+            }
+        } else {
+            $scene = [];
+        }
+        return $scene;
+    }
+
+    public static function __callStatic($method, $params)
+    {
+        $class = self::make();
+        if (method_exists($class, $method)) {
+            return call_user_func_array([$class, $method], $params);
+        } else {
+            throw new \BadMethodCallException('method not exists:' . __CLASS__ . '->' . $method);
+        }
+    }
+}

+ 239 - 0
thinkphp/library/think/View.php

@@ -0,0 +1,239 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think;
+
+class View
+{
+    // 视图实例
+    protected static $instance;
+    // 模板引擎实例
+    public $engine;
+    // 模板变量
+    protected $data = [];
+    // 用于静态赋值的模板变量
+    protected static $var = [];
+    // 视图输出替换
+    protected $replace = [];
+
+    /**
+     * 构造函数
+     * @access public
+     * @param array $engine  模板引擎参数
+     * @param array $replace  字符串替换参数
+     */
+    public function __construct($engine = [], $replace = [])
+    {
+        // 初始化模板引擎
+        $this->engine($engine);
+        // 基础替换字符串
+        $request = Request::instance();
+        $base    = $request->root();
+        $root    = strpos($base, '.') ? ltrim(dirname($base), DS) : $base;
+        if ('' != $root) {
+            $root = '/' . ltrim($root, '/');
+        }
+        $baseReplace = [
+            '__ROOT__'   => $root,
+            '__URL__'    => $base . '/' . $request->module() . '/' . Loader::parseName($request->controller()),
+            '__STATIC__' => $root . '/static',
+            '__CSS__'    => $root . '/static/css',
+            '__JS__'     => $root . '/static/js',
+        ];
+        $this->replace = array_merge($baseReplace, (array) $replace);
+    }
+
+    /**
+     * 初始化视图
+     * @access public
+     * @param array $engine  模板引擎参数
+     * @param array $replace  字符串替换参数
+     * @return object
+     */
+    public static function instance($engine = [], $replace = [])
+    {
+        if (is_null(self::$instance)) {
+            self::$instance = new self($engine, $replace);
+        }
+        return self::$instance;
+    }
+
+    /**
+     * 模板变量静态赋值
+     * @access public
+     * @param mixed $name  变量名
+     * @param mixed $value 变量值
+     * @return void
+     */
+    public static function share($name, $value = '')
+    {
+        if (is_array($name)) {
+            self::$var = array_merge(self::$var, $name);
+        } else {
+            self::$var[$name] = $value;
+        }
+    }
+
+    /**
+     * 模板变量赋值
+     * @access public
+     * @param mixed $name  变量名
+     * @param mixed $value 变量值
+     * @return $this
+     */
+    public function assign($name, $value = '')
+    {
+        if (is_array($name)) {
+            $this->data = array_merge($this->data, $name);
+        } else {
+            $this->data[$name] = $value;
+        }
+        return $this;
+    }
+
+    /**
+     * 设置当前模板解析的引擎
+     * @access public
+     * @param array|string $options 引擎参数
+     * @return $this
+     */
+    public function engine($options = [])
+    {
+        if (is_string($options)) {
+            $type    = $options;
+            $options = [];
+        } else {
+            $type = !empty($options['type']) ? $options['type'] : 'Think';
+        }
+
+        $class = false !== strpos($type, '\\') ? $type : '\\think\\view\\driver\\' . ucfirst($type);
+        if (isset($options['type'])) {
+            unset($options['type']);
+        }
+        $this->engine = new $class($options);
+        return $this;
+    }
+
+    /**
+     * 配置模板引擎
+     * @access private
+     * @param string|array  $name 参数名
+     * @param mixed         $value 参数值
+     * @return $this
+     */
+    public function config($name, $value = null)
+    {
+        $this->engine->config($name, $value);
+        return $this;
+    }
+
+    /**
+     * 解析和获取模板内容 用于输出
+     * @param string    $template 模板文件名或者内容
+     * @param array     $vars     模板输出变量
+     * @param array     $replace 替换内容
+     * @param array     $config     模板参数
+     * @param bool      $renderContent     是否渲染内容
+     * @return string
+     * @throws Exception
+     */
+    public function fetch($template = '', $vars = [], $replace = [], $config = [], $renderContent = false)
+    {
+        // 模板变量
+        $vars = array_merge(self::$var, $this->data, $vars);
+
+        // 页面缓存
+        ob_start();
+        ob_implicit_flush(0);
+
+        // 渲染输出
+        try {
+            $method = $renderContent ? 'display' : 'fetch';
+            // 允许用户自定义模板的字符串替换
+            $replace = array_merge($this->replace, $replace, (array) $this->engine->config('tpl_replace_string'));
+            $this->engine->config('tpl_replace_string', $replace);
+            $this->engine->$method($template, $vars, $config);
+        } catch (\Exception $e) {
+            ob_end_clean();
+            throw $e;
+        }
+
+        // 获取并清空缓存
+        $content = ob_get_clean();
+        // 内容过滤标签
+        Hook::listen('view_filter', $content);
+        return $content;
+    }
+
+    /**
+     * 视图内容替换
+     * @access public
+     * @param string|array  $content 被替换内容(支持批量替换)
+     * @param string        $replace    替换内容
+     * @return $this
+     */
+    public function replace($content, $replace = '')
+    {
+        if (is_array($content)) {
+            $this->replace = array_merge($this->replace, $content);
+        } else {
+            $this->replace[$content] = $replace;
+        }
+        return $this;
+    }
+
+    /**
+     * 渲染内容输出
+     * @access public
+     * @param string $content 内容
+     * @param array  $vars    模板输出变量
+     * @param array  $replace 替换内容
+     * @param array  $config     模板参数
+     * @return mixed
+     */
+    public function display($content, $vars = [], $replace = [], $config = [])
+    {
+        return $this->fetch($content, $vars, $replace, $config, true);
+    }
+
+    /**
+     * 模板变量赋值
+     * @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]);
+    }
+}

+ 231 - 0
thinkphp/library/think/cache/Driver.php

@@ -0,0 +1,231 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\cache;
+
+/**
+ * 缓存基础类
+ */
+abstract class Driver
+{
+    protected $handler = null;
+    protected $options = [];
+    protected $tag;
+
+    /**
+     * 判断缓存是否存在
+     * @access public
+     * @param string $name 缓存变量名
+     * @return bool
+     */
+    abstract public function has($name);
+
+    /**
+     * 读取缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @param mixed  $default 默认值
+     * @return mixed
+     */
+    abstract public function get($name, $default = false);
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param string    $name 缓存变量名
+     * @param mixed     $value  存储数据
+     * @param int       $expire  有效时间 0为永久
+     * @return boolean
+     */
+    abstract public function set($name, $value, $expire = null);
+
+    /**
+     * 自增缓存(针对数值缓存)
+     * @access public
+     * @param string    $name 缓存变量名
+     * @param int       $step 步长
+     * @return false|int
+     */
+    abstract public function inc($name, $step = 1);
+
+    /**
+     * 自减缓存(针对数值缓存)
+     * @access public
+     * @param string    $name 缓存变量名
+     * @param int       $step 步长
+     * @return false|int
+     */
+    abstract public function dec($name, $step = 1);
+
+    /**
+     * 删除缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @return boolean
+     */
+    abstract public function rm($name);
+
+    /**
+     * 清除缓存
+     * @access public
+     * @param string $tag 标签名
+     * @return boolean
+     */
+    abstract public function clear($tag = null);
+
+    /**
+     * 获取实际的缓存标识
+     * @access public
+     * @param string $name 缓存名
+     * @return string
+     */
+    protected function getCacheKey($name)
+    {
+        return $this->options['prefix'] . $name;
+    }
+
+    /**
+     * 读取缓存并删除
+     * @access public
+     * @param string $name 缓存变量名
+     * @return mixed
+     */
+    public function pull($name)
+    {
+        $result = $this->get($name, false);
+        if ($result) {
+            $this->rm($name);
+            return $result;
+        } else {
+            return;
+        }
+    }
+
+    /**
+     * 如果不存在则写入缓存
+     * @access public
+     * @param string    $name 缓存变量名
+     * @param mixed     $value  存储数据
+     * @param int       $expire  有效时间 0为永久
+     * @return mixed
+     */
+    public function remember($name, $value, $expire = null)
+    {
+        if (!$this->has($name)) {
+            $time = time();
+            while ($time + 5 > time() && $this->has($name . '_lock')) {
+                // 存在锁定则等待
+                usleep(200000);
+            }
+
+            try {
+                // 锁定
+                $this->set($name . '_lock', true);
+                if ($value instanceof \Closure) {
+                    $value = call_user_func($value);
+                }
+                $this->set($name, $value, $expire);
+                // 解锁
+                $this->rm($name . '_lock');
+            } catch (\Exception $e) {
+                // 解锁
+                $this->rm($name . '_lock');
+                throw $e;
+            } catch (\throwable $e) {
+                $this->rm($name . '_lock');
+                throw $e;
+            }
+        } else {
+            $value = $this->get($name);
+        }
+        return $value;
+    }
+
+    /**
+     * 缓存标签
+     * @access public
+     * @param string        $name 标签名
+     * @param string|array  $keys 缓存标识
+     * @param bool          $overlay 是否覆盖
+     * @return $this
+     */
+    public function tag($name, $keys = null, $overlay = false)
+    {
+        if (is_null($name)) {
+
+        } elseif (is_null($keys)) {
+            $this->tag = $name;
+        } else {
+            $key = 'tag_' . md5($name);
+            if (is_string($keys)) {
+                $keys = explode(',', $keys);
+            }
+            $keys = array_map([$this, 'getCacheKey'], $keys);
+            if ($overlay) {
+                $value = $keys;
+            } else {
+                $value = array_unique(array_merge($this->getTagItem($name), $keys));
+            }
+            $this->set($key, implode(',', $value), 0);
+        }
+        return $this;
+    }
+
+    /**
+     * 更新标签
+     * @access public
+     * @param string $name 缓存标识
+     * @return void
+     */
+    protected function setTagItem($name)
+    {
+        if ($this->tag) {
+            $key       = 'tag_' . md5($this->tag);
+            $this->tag = null;
+            if ($this->has($key)) {
+                $value   = explode(',', $this->get($key));
+                $value[] = $name;
+                $value   = implode(',', array_unique($value));
+            } else {
+                $value = $name;
+            }
+            $this->set($key, $value, 0);
+        }
+    }
+
+    /**
+     * 获取标签包含的缓存标识
+     * @access public
+     * @param string $tag 缓存标签
+     * @return array
+     */
+    protected function getTagItem($tag)
+    {
+        $key   = 'tag_' . md5($tag);
+        $value = $this->get($key);
+        if ($value) {
+            return array_filter(explode(',', $value));
+        } else {
+            return [];
+        }
+    }
+
+    /**
+     * 返回句柄对象,可执行其它高级方法
+     *
+     * @access public
+     * @return object
+     */
+    public function handler()
+    {
+        return $this->handler;
+    }
+}

+ 268 - 0
thinkphp/library/think/cache/driver/File.php

@@ -0,0 +1,268 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\cache\driver;
+
+use think\cache\Driver;
+
+/**
+ * 文件类型缓存类
+ * @author    liu21st <liu21st@gmail.com>
+ */
+class File extends Driver
+{
+    protected $options = [
+        'expire'        => 0,
+        'cache_subdir'  => true,
+        'prefix'        => '',
+        'path'          => CACHE_PATH,
+        'data_compress' => false,
+    ];
+
+    protected $expire;
+
+    /**
+     * 构造函数
+     * @param array $options
+     */
+    public function __construct($options = [])
+    {
+        if (!empty($options)) {
+            $this->options = array_merge($this->options, $options);
+        }
+        if (substr($this->options['path'], -1) != DS) {
+            $this->options['path'] .= DS;
+        }
+        $this->init();
+    }
+
+    /**
+     * 初始化检查
+     * @access private
+     * @return boolean
+     */
+    private function init()
+    {
+        // 创建项目缓存目录
+        if (!is_dir($this->options['path'])) {
+            if (mkdir($this->options['path'], 0755, true)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 取得变量的存储文件名
+     * @access protected
+     * @param  string $name 缓存变量名
+     * @param  bool   $auto 是否自动创建目录
+     * @return string
+     */
+    protected function getCacheKey($name, $auto = false)
+    {
+        $name = md5($name);
+        if ($this->options['cache_subdir']) {
+            // 使用子目录
+            $name = substr($name, 0, 2) . DS . substr($name, 2);
+        }
+        if ($this->options['prefix']) {
+            $name = $this->options['prefix'] . DS . $name;
+        }
+        $filename = $this->options['path'] . $name . '.php';
+        $dir      = dirname($filename);
+
+        if ($auto && !is_dir($dir)) {
+            mkdir($dir, 0755, true);
+        }
+        return $filename;
+    }
+
+    /**
+     * 判断缓存是否存在
+     * @access public
+     * @param string $name 缓存变量名
+     * @return bool
+     */
+    public function has($name)
+    {
+        return $this->get($name) ? true : false;
+    }
+
+    /**
+     * 读取缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @param mixed  $default 默认值
+     * @return mixed
+     */
+    public function get($name, $default = false)
+    {
+        $filename = $this->getCacheKey($name);
+        if (!is_file($filename)) {
+            return $default;
+        }
+        $content      = file_get_contents($filename);
+        $this->expire = null;
+        if (false !== $content) {
+            $expire = (int) substr($content, 8, 12);
+            if (0 != $expire && time() > filemtime($filename) + $expire) {
+                return $default;
+            }
+            $this->expire = $expire;
+            $content      = substr($content, 32);
+            if ($this->options['data_compress'] && function_exists('gzcompress')) {
+                //启用数据压缩
+                $content = gzuncompress($content);
+            }
+            $content = unserialize($content);
+            return $content;
+        } else {
+            return $default;
+        }
+    }
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param string            $name 缓存变量名
+     * @param mixed             $value  存储数据
+     * @param integer|\DateTime $expire  有效时间(秒)
+     * @return boolean
+     */
+    public function set($name, $value, $expire = null)
+    {
+        if (is_null($expire)) {
+            $expire = $this->options['expire'];
+        }
+        if ($expire instanceof \DateTime) {
+            $expire = $expire->getTimestamp() - time();
+        }
+        $filename = $this->getCacheKey($name, true);
+        if ($this->tag && !is_file($filename)) {
+            $first = true;
+        }
+        $data = 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) {
+            isset($first) && $this->setTagItem($filename);
+            clearstatcache();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 自增缓存(针对数值缓存)
+     * @access public
+     * @param string    $name 缓存变量名
+     * @param int       $step 步长
+     * @return false|int
+     */
+    public function inc($name, $step = 1)
+    {
+        if ($this->has($name)) {
+            $value  = $this->get($name) + $step;
+            $expire = $this->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($name, $step = 1)
+    {
+        if ($this->has($name)) {
+            $value  = $this->get($name) - $step;
+            $expire = $this->expire;
+        } else {
+            $value  = -$step;
+            $expire = 0;
+        }
+
+        return $this->set($name, $value, $expire) ? $value : false;
+    }
+
+    /**
+     * 删除缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @return boolean
+     */
+    public function rm($name)
+    {
+        $filename = $this->getCacheKey($name);
+        try {
+            return $this->unlink($filename);
+        } catch (\Exception $e) {
+        }
+    }
+
+    /**
+     * 清除缓存
+     * @access public
+     * @param string $tag 标签名
+     * @return boolean
+     */
+    public function clear($tag = null)
+    {
+        if ($tag) {
+            // 指定标签清除
+            $keys = $this->getTagItem($tag);
+            foreach ($keys as $key) {
+                $this->unlink($key);
+            }
+            $this->rm('tag_' . md5($tag));
+            return true;
+        }
+        $files = (array) glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*');
+        foreach ($files as $path) {
+            if (is_dir($path)) {
+                $matches = glob($path . '/*.php');
+                if (is_array($matches)) {
+                    array_map('unlink', $matches);
+                }
+                rmdir($path);
+            } else {
+                unlink($path);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 判断文件是否存在后,删除
+     * @param $path
+     * @return bool
+     * @author byron sampson <xiaobo.sun@qq.com>
+     * @return boolean
+     */
+    private function unlink($path)
+    {
+        return is_file($path) && unlink($path);
+    }
+
+}

+ 187 - 0
thinkphp/library/think/cache/driver/Lite.php

@@ -0,0 +1,187 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\cache\driver;
+
+use think\cache\Driver;
+
+/**
+ * 文件类型缓存类
+ * @author    liu21st <liu21st@gmail.com>
+ */
+class Lite extends Driver
+{
+    protected $options = [
+        'prefix' => '',
+        'path'   => '',
+        'expire' => 0, // 等于 10*365*24*3600(10年)
+    ];
+
+    /**
+     * 构造函数
+     * @access public
+     *
+     * @param array $options
+     */
+    public function __construct($options = [])
+    {
+        if (!empty($options)) {
+            $this->options = array_merge($this->options, $options);
+        }
+        if (substr($this->options['path'], -1) != DS) {
+            $this->options['path'] .= DS;
+        }
+
+    }
+
+    /**
+     * 取得变量的存储文件名
+     * @access protected
+     * @param string $name 缓存变量名
+     * @return string
+     */
+    protected function getCacheKey($name)
+    {
+        return $this->options['path'] . $this->options['prefix'] . md5($name) . '.php';
+    }
+
+    /**
+     * 判断缓存是否存在
+     * @access public
+     * @param string $name 缓存变量名
+     * @return mixed
+     */
+    public function has($name)
+    {
+        return $this->get($name) ? true : false;
+    }
+
+    /**
+     * 读取缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @param mixed  $default 默认值
+     * @return mixed
+     */
+    public function get($name, $default = false)
+    {
+        $filename = $this->getCacheKey($name);
+        if (is_file($filename)) {
+            // 判断是否过期
+            $mtime = filemtime($filename);
+            if ($mtime < time()) {
+                // 清除已经过期的文件
+                unlink($filename);
+                return $default;
+            }
+            return include $filename;
+        } else {
+            return $default;
+        }
+    }
+
+    /**
+     * 写入缓存
+     * @access   public
+     * @param string            $name 缓存变量名
+     * @param mixed             $value  存储数据
+     * @param integer|\DateTime $expire  有效时间(秒)
+     * @return bool
+     */
+    public function set($name, $value, $expire = null)
+    {
+        if (is_null($expire)) {
+            $expire = $this->options['expire'];
+        }
+        if ($expire instanceof \DateTime) {
+            $expire = $expire->getTimestamp();
+        } else {
+            $expire = 0 === $expire ? 10 * 365 * 24 * 3600 : $expire;
+            $expire = time() + $expire;
+        }
+        $filename = $this->getCacheKey($name);
+        if ($this->tag && !is_file($filename)) {
+            $first = true;
+        }
+        $ret = file_put_contents($filename, ("<?php return " . var_export($value, true) . ";"));
+        // 通过设置修改时间实现有效期
+        if ($ret) {
+            isset($first) && $this->setTagItem($filename);
+            touch($filename, $expire);
+        }
+        return $ret;
+    }
+
+    /**
+     * 自增缓存(针对数值缓存)
+     * @access public
+     * @param string    $name 缓存变量名
+     * @param int       $step 步长
+     * @return false|int
+     */
+    public function inc($name, $step = 1)
+    {
+        if ($this->has($name)) {
+            $value = $this->get($name) + $step;
+        } else {
+            $value = $step;
+        }
+        return $this->set($name, $value, 0) ? $value : false;
+    }
+
+    /**
+     * 自减缓存(针对数值缓存)
+     * @access public
+     * @param string    $name 缓存变量名
+     * @param int       $step 步长
+     * @return false|int
+     */
+    public function dec($name, $step = 1)
+    {
+        if ($this->has($name)) {
+            $value = $this->get($name) - $step;
+        } else {
+            $value = -$step;
+        }
+        return $this->set($name, $value, 0) ? $value : false;
+    }
+
+    /**
+     * 删除缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @return boolean
+     */
+    public function rm($name)
+    {
+        return unlink($this->getCacheKey($name));
+    }
+
+    /**
+     * 清除缓存
+     * @access   public
+     * @param string $tag 标签名
+     * @return bool
+     */
+    public function clear($tag = null)
+    {
+        if ($tag) {
+            // 指定标签清除
+            $keys = $this->getTagItem($tag);
+            foreach ($keys as $key) {
+                unlink($key);
+            }
+            $this->rm('tag_' . md5($tag));
+            return true;
+        }
+        array_map("unlink", glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*.php'));
+    }
+}

+ 177 - 0
thinkphp/library/think/cache/driver/Memcache.php

@@ -0,0 +1,177 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\cache\driver;
+
+use think\cache\Driver;
+
+class Memcache extends Driver
+{
+    protected $options = [
+        'host'       => '127.0.0.1',
+        'port'       => 11211,
+        'expire'     => 0,
+        'timeout'    => 0, // 超时时间(单位:毫秒)
+        'persistent' => true,
+        'prefix'     => '',
+    ];
+
+    /**
+     * 构造函数
+     * @param array $options 缓存参数
+     * @access public
+     * @throws \BadFunctionCallException
+     */
+    public function __construct($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 = explode(',', $this->options['host']);
+        $ports = explode(',', $this->options['port']);
+        if (empty($ports[0])) {
+            $ports[0] = 11211;
+        }
+        // 建立连接
+        foreach ((array) $hosts as $i => $host) {
+            $port = isset($ports[$i]) ? $ports[$i] : $ports[0];
+            $this->options['timeout'] > 0 ?
+            $this->handler->addServer($host, $port, $this->options['persistent'], 1, $this->options['timeout']) :
+            $this->handler->addServer($host, $port, $this->options['persistent'], 1);
+        }
+    }
+
+    /**
+     * 判断缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @return bool
+     */
+    public function has($name)
+    {
+        $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 = false)
+    {
+        $result = $this->handler->get($this->getCacheKey($name));
+        return false !== $result ? $result : $default;
+    }
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param string            $name 缓存变量名
+     * @param mixed             $value  存储数据
+     * @param integer|\DateTime $expire  有效时间(秒)
+     * @return bool
+     */
+    public function set($name, $value, $expire = null)
+    {
+        if (is_null($expire)) {
+            $expire = $this->options['expire'];
+        }
+        if ($expire instanceof \DateTime) {
+            $expire = $expire->getTimestamp() - time();
+        }
+        if ($this->tag && !$this->has($name)) {
+            $first = true;
+        }
+        $key = $this->getCacheKey($name);
+        if ($this->handler->set($key, $value, 0, $expire)) {
+            isset($first) && $this->setTagItem($key);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 自增缓存(针对数值缓存)
+     * @access public
+     * @param string    $name 缓存变量名
+     * @param int       $step 步长
+     * @return false|int
+     */
+    public function inc($name, $step = 1)
+    {
+        $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($name, $step = 1)
+    {
+        $key   = $this->getCacheKey($name);
+        $value = $this->handler->get($key) - $step;
+        $res   = $this->handler->set($key, $value);
+        if (!$res) {
+            return false;
+        } else {
+            return $value;
+        }
+    }
+
+    /**
+     * 删除缓存
+     * @param    string  $name 缓存变量名
+     * @param bool|false $ttl
+     * @return bool
+     */
+    public function rm($name, $ttl = false)
+    {
+        $key = $this->getCacheKey($name);
+        return false === $ttl ?
+        $this->handler->delete($key) :
+        $this->handler->delete($key, $ttl);
+    }
+
+    /**
+     * 清除缓存
+     * @access public
+     * @param string $tag 标签名
+     * @return bool
+     */
+    public function clear($tag = null)
+    {
+        if ($tag) {
+            // 指定标签清除
+            $keys = $this->getTagItem($tag);
+            foreach ($keys as $key) {
+                $this->handler->delete($key);
+            }
+            $this->rm('tag_' . md5($tag));
+            return true;
+        }
+        return $this->handler->flush();
+    }
+}

+ 187 - 0
thinkphp/library/think/cache/driver/Memcached.php

@@ -0,0 +1,187 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\cache\driver;
+
+use think\cache\Driver;
+
+class Memcached extends Driver
+{
+    protected $options = [
+        'host'     => '127.0.0.1',
+        'port'     => 11211,
+        'expire'   => 0,
+        'timeout'  => 0, // 超时时间(单位:毫秒)
+        'prefix'   => '',
+        'username' => '', //账号
+        'password' => '', //密码
+        'option'   => [],
+    ];
+
+    /**
+     * 构造函数
+     * @param array $options 缓存参数
+     * @access public
+     */
+    public function __construct($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 = explode(',', $this->options['host']);
+        $ports = explode(',', $this->options['port']);
+        if (empty($ports[0])) {
+            $ports[0] = 11211;
+        }
+        // 建立连接
+        $servers = [];
+        foreach ((array) $hosts as $i => $host) {
+            $servers[] = [$host, (isset($ports[$i]) ? $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)
+    {
+        $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 = false)
+    {
+        $result = $this->handler->get($this->getCacheKey($name));
+        return false !== $result ? $result : $default;
+    }
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param string            $name 缓存变量名
+     * @param mixed             $value  存储数据
+     * @param integer|\DateTime $expire  有效时间(秒)
+     * @return bool
+     */
+    public function set($name, $value, $expire = null)
+    {
+        if (is_null($expire)) {
+            $expire = $this->options['expire'];
+        }
+        if ($expire instanceof \DateTime) {
+            $expire = $expire->getTimestamp() - time();
+        }
+        if ($this->tag && !$this->has($name)) {
+            $first = true;
+        }
+        $key    = $this->getCacheKey($name);
+        $expire = 0 == $expire ? 0 : $_SERVER['REQUEST_TIME'] + $expire;
+        if ($this->handler->set($key, $value, $expire)) {
+            isset($first) && $this->setTagItem($key);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 自增缓存(针对数值缓存)
+     * @access public
+     * @param string    $name 缓存变量名
+     * @param int       $step 步长
+     * @return false|int
+     */
+    public function inc($name, $step = 1)
+    {
+        $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($name, $step = 1)
+    {
+        $key   = $this->getCacheKey($name);
+        $value = $this->handler->get($key) - $step;
+        $res   = $this->handler->set($key, $value);
+        if (!$res) {
+            return false;
+        } else {
+            return $value;
+        }
+    }
+
+    /**
+     * 删除缓存
+     * @param    string  $name 缓存变量名
+     * @param bool|false $ttl
+     * @return bool
+     */
+    public function rm($name, $ttl = false)
+    {
+        $key = $this->getCacheKey($name);
+        return false === $ttl ?
+        $this->handler->delete($key) :
+        $this->handler->delete($key, $ttl);
+    }
+
+    /**
+     * 清除缓存
+     * @access public
+     * @param string $tag 标签名
+     * @return bool
+     */
+    public function clear($tag = null)
+    {
+        if ($tag) {
+            // 指定标签清除
+            $keys = $this->getTagItem($tag);
+            $this->handler->deleteMulti($keys);
+            $this->rm('tag_' . md5($tag));
+            return true;
+        }
+        return $this->handler->flush();
+    }
+}

+ 188 - 0
thinkphp/library/think/cache/driver/Redis.php

@@ -0,0 +1,188 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+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
+{
+    protected $options = [
+        'host'       => '127.0.0.1',
+        'port'       => 6379,
+        'password'   => '',
+        'select'     => 0,
+        'timeout'    => 0,
+        'expire'     => 0,
+        'persistent' => false,
+        'prefix'     => '',
+    ];
+
+    /**
+     * 构造函数
+     * @param array $options 缓存参数
+     * @access public
+     */
+    public function __construct($options = [])
+    {
+        if (!extension_loaded('redis')) {
+            throw new \BadFunctionCallException('not support: redis');
+        }
+        if (!empty($options)) {
+            $this->options = array_merge($this->options, $options);
+        }
+        $this->handler = new \Redis;
+        if ($this->options['persistent']) {
+            $this->handler->pconnect($this->options['host'], $this->options['port'], $this->options['timeout'], 'persistent_id_' . $this->options['select']);
+        } else {
+            $this->handler->connect($this->options['host'], $this->options['port'], $this->options['timeout']);
+        }
+
+        if ('' != $this->options['password']) {
+            $this->handler->auth($this->options['password']);
+        }
+
+        if (0 != $this->options['select']) {
+            $this->handler->select($this->options['select']);
+        }
+    }
+
+    /**
+     * 判断缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @return bool
+     */
+    public function has($name)
+    {
+        return $this->handler->exists($this->getCacheKey($name));
+    }
+
+    /**
+     * 读取缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @param mixed  $default 默认值
+     * @return mixed
+     */
+    public function get($name, $default = false)
+    {
+        $value = $this->handler->get($this->getCacheKey($name));
+        if (is_null($value) || false === $value) {
+            return $default;
+        }
+
+        try {
+            $result = 0 === strpos($value, 'think_serialize:') ? unserialize(substr($value, 16)) : $value;
+        } catch (\Exception $e) {
+            $result = $default;
+        }
+
+        return $result;
+    }
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param string            $name 缓存变量名
+     * @param mixed             $value  存储数据
+     * @param integer|\DateTime $expire  有效时间(秒)
+     * @return boolean
+     */
+    public function set($name, $value, $expire = null)
+    {
+        if (is_null($expire)) {
+            $expire = $this->options['expire'];
+        }
+        if ($expire instanceof \DateTime) {
+            $expire = $expire->getTimestamp() - time();
+        }
+        if ($this->tag && !$this->has($name)) {
+            $first = true;
+        }
+        $key   = $this->getCacheKey($name);
+        $value = is_scalar($value) ? $value : 'think_serialize:' . serialize($value);
+        if ($expire) {
+            $result = $this->handler->setex($key, $expire, $value);
+        } else {
+            $result = $this->handler->set($key, $value);
+        }
+        isset($first) && $this->setTagItem($key);
+        return $result;
+    }
+
+    /**
+     * 自增缓存(针对数值缓存)
+     * @access public
+     * @param  string    $name 缓存变量名
+     * @param  int       $step 步长
+     * @return false|int
+     */
+    public function inc($name, $step = 1)
+    {
+        $key = $this->getCacheKey($name);
+
+        return $this->handler->incrby($key, $step);
+    }
+
+    /**
+     * 自减缓存(针对数值缓存)
+     * @access public
+     * @param  string    $name 缓存变量名
+     * @param  int       $step 步长
+     * @return false|int
+     */
+    public function dec($name, $step = 1)
+    {
+        $key = $this->getCacheKey($name);
+
+        return $this->handler->decrby($key, $step);
+    }
+
+    /**
+     * 删除缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @return boolean
+     */
+    public function rm($name)
+    {
+        return $this->handler->delete($this->getCacheKey($name));
+    }
+
+    /**
+     * 清除缓存
+     * @access public
+     * @param string $tag 标签名
+     * @return boolean
+     */
+    public function clear($tag = null)
+    {
+        if ($tag) {
+            // 指定标签清除
+            $keys = $this->getTagItem($tag);
+            foreach ($keys as $key) {
+                $this->handler->delete($key);
+            }
+            $this->rm('tag_' . md5($tag));
+            return true;
+        }
+        return $this->handler->flushDB();
+    }
+
+}

+ 199 - 0
thinkphp/library/think/cache/driver/Sqlite.php

@@ -0,0 +1,199 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\cache\driver;
+
+use think\cache\Driver;
+
+/**
+ * Sqlite缓存驱动
+ * @author    liu21st <liu21st@gmail.com>
+ */
+class Sqlite extends Driver
+{
+    protected $options = [
+        'db'         => ':memory:',
+        'table'      => 'sharedmemory',
+        'prefix'     => '',
+        'expire'     => 0,
+        'persistent' => false,
+    ];
+
+    /**
+     * 构造函数
+     * @param array $options 缓存参数
+     * @throws \BadFunctionCallException
+     * @access public
+     */
+    public function __construct($options = [])
+    {
+        if (!extension_loaded('sqlite')) {
+            throw new \BadFunctionCallException('not support: sqlite');
+        }
+        if (!empty($options)) {
+            $this->options = array_merge($this->options, $options);
+        }
+        $func          = $this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open';
+        $this->handler = $func($this->options['db']);
+    }
+
+    /**
+     * 获取实际的缓存标识
+     * @access public
+     * @param string $name 缓存名
+     * @return string
+     */
+    protected function getCacheKey($name)
+    {
+        return $this->options['prefix'] . sqlite_escape_string($name);
+    }
+
+    /**
+     * 判断缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @return bool
+     */
+    public function has($name)
+    {
+        $name   = $this->getCacheKey($name);
+        $sql    = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1';
+        $result = sqlite_query($this->handler, $sql);
+        return sqlite_num_rows($result);
+    }
+
+    /**
+     * 读取缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @param mixed  $default 默认值
+     * @return mixed
+     */
+    public function get($name, $default = false)
+    {
+        $name   = $this->getCacheKey($name);
+        $sql    = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1';
+        $result = sqlite_query($this->handler, $sql);
+        if (sqlite_num_rows($result)) {
+            $content = sqlite_fetch_single($result);
+            if (function_exists('gzcompress')) {
+                //启用数据压缩
+                $content = gzuncompress($content);
+            }
+            return unserialize($content);
+        }
+        return $default;
+    }
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param string            $name 缓存变量名
+     * @param mixed             $value  存储数据
+     * @param integer|\DateTime $expire  有效时间(秒)
+     * @return boolean
+     */
+    public function set($name, $value, $expire = null)
+    {
+        $name  = $this->getCacheKey($name);
+        $value = sqlite_escape_string(serialize($value));
+        if (is_null($expire)) {
+            $expire = $this->options['expire'];
+        }
+        if ($expire instanceof \DateTime) {
+            $expire = $expire->getTimestamp();
+        } else {
+            $expire = (0 == $expire) ? 0 : (time() + $expire); //缓存有效期为0表示永久缓存
+        }
+        if (function_exists('gzcompress')) {
+            //数据压缩
+            $value = gzcompress($value, 3);
+        }
+        if ($this->tag) {
+            $tag       = $this->tag;
+            $this->tag = null;
+        } else {
+            $tag = '';
+        }
+        $sql = 'REPLACE INTO ' . $this->options['table'] . ' (var, value, expire, tag) VALUES (\'' . $name . '\', \'' . $value . '\', \'' . $expire . '\', \'' . $tag . '\')';
+        if (sqlite_query($this->handler, $sql)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 自增缓存(针对数值缓存)
+     * @access public
+     * @param string    $name 缓存变量名
+     * @param int       $step 步长
+     * @return false|int
+     */
+    public function inc($name, $step = 1)
+    {
+        if ($this->has($name)) {
+            $value = $this->get($name) + $step;
+        } else {
+            $value = $step;
+        }
+        return $this->set($name, $value, 0) ? $value : false;
+    }
+
+    /**
+     * 自减缓存(针对数值缓存)
+     * @access public
+     * @param string    $name 缓存变量名
+     * @param int       $step 步长
+     * @return false|int
+     */
+    public function dec($name, $step = 1)
+    {
+        if ($this->has($name)) {
+            $value = $this->get($name) - $step;
+        } else {
+            $value = -$step;
+        }
+        return $this->set($name, $value, 0) ? $value : false;
+    }
+
+    /**
+     * 删除缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @return boolean
+     */
+    public function rm($name)
+    {
+        $name = $this->getCacheKey($name);
+        $sql  = 'DELETE FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\'';
+        sqlite_query($this->handler, $sql);
+        return true;
+    }
+
+    /**
+     * 清除缓存
+     * @access public
+     * @param string $tag 标签名
+     * @return boolean
+     */
+    public function clear($tag = null)
+    {
+        if ($tag) {
+            $name = sqlite_escape_string($tag);
+            $sql  = 'DELETE FROM ' . $this->options['table'] . ' WHERE tag=\'' . $name . '\'';
+            sqlite_query($this->handler, $sql);
+            return true;
+        }
+        $sql = 'DELETE FROM ' . $this->options['table'];
+        sqlite_query($this->handler, $sql);
+        return true;
+    }
+}

+ 152 - 0
thinkphp/library/think/cache/driver/Wincache.php

@@ -0,0 +1,152 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\cache\driver;
+
+use think\cache\Driver;
+
+/**
+ * Wincache缓存驱动
+ * @author    liu21st <liu21st@gmail.com>
+ */
+class Wincache extends Driver
+{
+    protected $options = [
+        'prefix' => '',
+        'expire' => 0,
+    ];
+
+    /**
+     * 构造函数
+     * @param array $options 缓存参数
+     * @throws \BadFunctionCallException
+     * @access public
+     */
+    public function __construct($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)
+    {
+        $key = $this->getCacheKey($name);
+        return wincache_ucache_exists($key);
+    }
+
+    /**
+     * 读取缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @param mixed  $default 默认值
+     * @return mixed
+     */
+    public function get($name, $default = false)
+    {
+        $key = $this->getCacheKey($name);
+        return wincache_ucache_exists($key) ? wincache_ucache_get($key) : $default;
+    }
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param string            $name 缓存变量名
+     * @param mixed             $value  存储数据
+     * @param integer|\DateTime $expire  有效时间(秒)
+     * @return boolean
+     */
+    public function set($name, $value, $expire = null)
+    {
+        if (is_null($expire)) {
+            $expire = $this->options['expire'];
+        }
+        if ($expire instanceof \DateTime) {
+            $expire = $expire->getTimestamp() - time();
+        }
+        $key = $this->getCacheKey($name);
+        if ($this->tag && !$this->has($name)) {
+            $first = true;
+        }
+        if (wincache_ucache_set($key, $value, $expire)) {
+            isset($first) && $this->setTagItem($key);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 自增缓存(针对数值缓存)
+     * @access public
+     * @param string    $name 缓存变量名
+     * @param int       $step 步长
+     * @return false|int
+     */
+    public function inc($name, $step = 1)
+    {
+        $key = $this->getCacheKey($name);
+        return wincache_ucache_inc($key, $step);
+    }
+
+    /**
+     * 自减缓存(针对数值缓存)
+     * @access public
+     * @param string    $name 缓存变量名
+     * @param int       $step 步长
+     * @return false|int
+     */
+    public function dec($name, $step = 1)
+    {
+        $key = $this->getCacheKey($name);
+        return wincache_ucache_dec($key, $step);
+    }
+
+    /**
+     * 删除缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @return boolean
+     */
+    public function rm($name)
+    {
+        return wincache_ucache_delete($this->getCacheKey($name));
+    }
+
+    /**
+     * 清除缓存
+     * @access public
+     * @param string $tag 标签名
+     * @return boolean
+     */
+    public function clear($tag = null)
+    {
+        if ($tag) {
+            $keys = $this->getTagItem($tag);
+            foreach ($keys as $key) {
+                wincache_ucache_delete($key);
+            }
+            $this->rm('tag_' . md5($tag));
+            return true;
+        } else {
+            return wincache_ucache_clear();
+        }
+    }
+
+}

+ 155 - 0
thinkphp/library/think/cache/driver/Xcache.php

@@ -0,0 +1,155 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\cache\driver;
+
+use think\cache\Driver;
+
+/**
+ * Xcache缓存驱动
+ * @author    liu21st <liu21st@gmail.com>
+ */
+class Xcache extends Driver
+{
+    protected $options = [
+        'prefix' => '',
+        'expire' => 0,
+    ];
+
+    /**
+     * 构造函数
+     * @param array $options 缓存参数
+     * @access public
+     * @throws \BadFunctionCallException
+     */
+    public function __construct($options = [])
+    {
+        if (!function_exists('xcache_info')) {
+            throw new \BadFunctionCallException('not support: Xcache');
+        }
+        if (!empty($options)) {
+            $this->options = array_merge($this->options, $options);
+        }
+    }
+
+    /**
+     * 判断缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @return bool
+     */
+    public function has($name)
+    {
+        $key = $this->getCacheKey($name);
+        return xcache_isset($key);
+    }
+
+    /**
+     * 读取缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @param mixed  $default 默认值
+     * @return mixed
+     */
+    public function get($name, $default = false)
+    {
+        $key = $this->getCacheKey($name);
+        return xcache_isset($key) ? xcache_get($key) : $default;
+    }
+
+    /**
+     * 写入缓存
+     * @access public
+     * @param string            $name 缓存变量名
+     * @param mixed             $value  存储数据
+     * @param integer|\DateTime $expire  有效时间(秒)
+     * @return boolean
+     */
+    public function set($name, $value, $expire = null)
+    {
+        if (is_null($expire)) {
+            $expire = $this->options['expire'];
+        }
+        if ($expire instanceof \DateTime) {
+            $expire = $expire->getTimestamp() - time();
+        }
+        if ($this->tag && !$this->has($name)) {
+            $first = true;
+        }
+        $key = $this->getCacheKey($name);
+        if (xcache_set($key, $value, $expire)) {
+            isset($first) && $this->setTagItem($key);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 自增缓存(针对数值缓存)
+     * @access public
+     * @param string    $name 缓存变量名
+     * @param int       $step 步长
+     * @return false|int
+     */
+    public function inc($name, $step = 1)
+    {
+        $key = $this->getCacheKey($name);
+        return xcache_inc($key, $step);
+    }
+
+    /**
+     * 自减缓存(针对数值缓存)
+     * @access public
+     * @param string    $name 缓存变量名
+     * @param int       $step 步长
+     * @return false|int
+     */
+    public function dec($name, $step = 1)
+    {
+        $key = $this->getCacheKey($name);
+        return xcache_dec($key, $step);
+    }
+
+    /**
+     * 删除缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @return boolean
+     */
+    public function rm($name)
+    {
+        return xcache_unset($this->getCacheKey($name));
+    }
+
+    /**
+     * 清除缓存
+     * @access public
+     * @param string $tag 标签名
+     * @return boolean
+     */
+    public function clear($tag = null)
+    {
+        if ($tag) {
+            // 指定标签清除
+            $keys = $this->getTagItem($tag);
+            foreach ($keys as $key) {
+                xcache_unset($key);
+            }
+            $this->rm('tag_' . md5($tag));
+            return true;
+        }
+        if (function_exists('xcache_unset_by_prefix')) {
+            return xcache_unset_by_prefix($this->options['prefix']);
+        } else {
+            return false;
+        }
+    }
+}

+ 24 - 0
thinkphp/library/think/config/driver/Ini.php

@@ -0,0 +1,24 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\config\driver;
+
+class Ini
+{
+    public function parse($config)
+    {
+        if (is_file($config)) {
+            return parse_ini_file($config, true);
+        } else {
+            return parse_ini_string($config, true);
+        }
+    }
+}

+ 24 - 0
thinkphp/library/think/config/driver/Json.php

@@ -0,0 +1,24 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\config\driver;
+
+class Json
+{
+    public function parse($config)
+    {
+        if (is_file($config)) {
+            $config = file_get_contents($config);
+        }
+        $result = json_decode($config, true);
+        return $result;
+    }
+}

+ 31 - 0
thinkphp/library/think/config/driver/Xml.php

@@ -0,0 +1,31 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+namespace think\config\driver;
+
+class Xml
+{
+    public function parse($config)
+    {
+        if (is_file($config)) {
+            $content = simplexml_load_file($config);
+        } else {
+            $content = simplexml_load_string($config);
+        }
+        $result = (array) $content;
+        foreach ($result as $key => $val) {
+            if (is_object($val)) {
+                $result[$key] = (array) $val;
+            }
+        }
+        return $result;
+    }
+}

+ 470 - 0
thinkphp/library/think/console/Command.php

@@ -0,0 +1,470 @@
+<?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;
+
+use think\Console;
+use think\console\input\Argument;
+use think\console\input\Definition;
+use think\console\input\Option;
+
+class Command
+{
+
+    /** @var  Console */
+    private $console;
+    private $name;
+    private $aliases = [];
+    private $definition;
+    private $help;
+    private $description;
+    private $ignoreValidationErrors          = false;
+    private $consoleDefinitionMerged         = false;
+    private $consoleDefinitionMergedWithArgs = false;
+    private $code;
+    private $synopsis = [];
+    private $usages   = [];
+
+    /** @var  Input */
+    protected $input;
+
+    /** @var  Output */
+    protected $output;
+
+    /**
+     * 构造方法
+     * @param string|null $name 命令名称,如果没有设置则比如在 configure() 里设置
+     * @throws \LogicException
+     * @api
+     */
+    public function __construct($name = null)
+    {
+        $this->definition = new Definition();
+
+        if (null !== $name) {
+            $this->setName($name);
+        }
+
+        $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()
+    {
+        $this->ignoreValidationErrors = true;
+    }
+
+    /**
+     * 设置控制台
+     * @param Console $console
+     */
+    public function setConsole(Console $console = null)
+    {
+        $this->console = $console;
+    }
+
+    /**
+     * 获取控制台
+     * @return Console
+     * @api
+     */
+    public function getConsole()
+    {
+        return $this->console;
+    }
+
+    /**
+     * 是否有效
+     * @return bool
+     */
+    public function isEnabled()
+    {
+        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)
+    {
+        throw new \LogicException('You must override the execute() method in the concrete command class.');
+    }
+
+    /**
+     * 用户验证
+     * @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)
+    {
+        $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 ($input->isInteractive()) {
+            $this->interact($input, $output);
+        }
+
+        $input->validate();
+
+        if ($this->code) {
+            $statusCode = call_user_func($this->code, $input, $output);
+        } else {
+            $statusCode = $this->execute($input, $output);
+        }
+
+        return is_numeric($statusCode) ? (int) $statusCode : 0;
+    }
+
+    /**
+     * 设置执行代码
+     * @param callable $code callable(InputInterface $input, OutputInterface $output)
+     * @return Command
+     * @throws \InvalidArgumentException
+     * @see execute()
+     */
+    public function setCode(callable $code)
+    {
+        if (!is_callable($code)) {
+            throw new \InvalidArgumentException('Invalid callable provided to Command::setCode.');
+        }
+
+        if (PHP_VERSION_ID >= 50400 && $code instanceof \Closure) {
+            $r = new \ReflectionFunction($code);
+            if (null === $r->getClosureThis()) {
+                $code = \Closure::bind($code, $this);
+            }
+        }
+
+        $this->code = $code;
+
+        return $this;
+    }
+
+    /**
+     * 合并参数定义
+     * @param bool $mergeArgs
+     */
+    public function mergeConsoleDefinition($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()
+    {
+        return $this->definition;
+    }
+
+    /**
+     * 获取当前指令的参数定义
+     * @return Definition
+     */
+    public function getNativeDefinition()
+    {
+        return $this->getDefinition();
+    }
+
+    /**
+     * 添加参数
+     * @param string $name        名称
+     * @param int    $mode        类型
+     * @param string $description 描述
+     * @param mixed  $default     默认值
+     * @return Command
+     */
+    public function addArgument($name, $mode = null, $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($name, $shortcut = null, $mode = null, $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($name)
+    {
+        $this->validateName($name);
+
+        $this->name = $name;
+
+        return $this;
+    }
+
+    /**
+     * 获取指令名称
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * 设置描述
+     * @param string $description
+     * @return Command
+     */
+    public function setDescription($description)
+    {
+        $this->description = $description;
+
+        return $this;
+    }
+
+    /**
+     *  获取描述
+     * @return string
+     */
+    public function getDescription()
+    {
+        return $this->description;
+    }
+
+    /**
+     * 设置帮助信息
+     * @param string $help
+     * @return Command
+     */
+    public function setHelp($help)
+    {
+        $this->help = $help;
+
+        return $this;
+    }
+
+    /**
+     * 获取帮助信息
+     * @return string
+     */
+    public function getHelp()
+    {
+        return $this->help;
+    }
+
+    /**
+     * 描述信息
+     * @return string
+     */
+    public function getProcessedHelp()
+    {
+        $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($aliases)
+    {
+        if (!is_array($aliases) && !$aliases instanceof \Traversable) {
+            throw new \InvalidArgumentException('$aliases must be an array or an instance of \Traversable');
+        }
+
+        foreach ($aliases as $alias) {
+            $this->validateName($alias);
+        }
+
+        $this->aliases = $aliases;
+
+        return $this;
+    }
+
+    /**
+     * 获取别名
+     * @return array
+     */
+    public function getAliases()
+    {
+        return $this->aliases;
+    }
+
+    /**
+     * 获取简介
+     * @param bool $short 是否简单的
+     * @return string
+     */
+    public function getSynopsis($short = false)
+    {
+        $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($usage)
+    {
+        if (0 !== strpos($usage, $this->name)) {
+            $usage = sprintf('%s %s', $this->name, $usage);
+        }
+
+        $this->usages[] = $usage;
+
+        return $this;
+    }
+
+    /**
+     * 获取用法介绍
+     * @return array
+     */
+    public function getUsages()
+    {
+        return $this->usages;
+    }
+
+    /**
+     * 验证指令名称
+     * @param string $name
+     * @throws \InvalidArgumentException
+     */
+    private function validateName($name)
+    {
+        if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
+            throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
+        }
+    }
+}

+ 464 - 0
thinkphp/library/think/console/Input.php

@@ -0,0 +1,464 @@
+<?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;
+
+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)
+    {
+        $this->arguments  = [];
+        $this->options    = [];
+        $this->definition = $definition;
+
+        $this->parse();
+    }
+
+    /**
+     * 解析参数
+     */
+    protected function parse()
+    {
+        $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($token)
+    {
+        $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($name)
+    {
+        $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($token)
+    {
+        $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($token)
+    {
+        $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($shortcut, $value)
+    {
+        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($name, $value)
+    {
+        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)
+    {
+        $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()
+    {
+        return $this->interactive;
+    }
+
+    /**
+     * 设置输入的交互
+     * @param bool
+     */
+    public function setInteractive($interactive)
+    {
+        $this->interactive = (bool) $interactive;
+    }
+
+    /**
+     * 获取所有的参数
+     * @return Argument[]
+     */
+    public function getArguments()
+    {
+        return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
+    }
+
+    /**
+     * 根据名称获取参数
+     * @param string $name 参数名
+     * @return mixed
+     * @throws \InvalidArgumentException
+     */
+    public function getArgument($name)
+    {
+        if (!$this->definition->hasArgument($name)) {
+            throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
+        }
+
+        return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)
+            ->getDefault();
+    }
+
+    /**
+     * 设置参数的值
+     * @param string $name  参数名
+     * @param string $value 值
+     * @throws \InvalidArgumentException
+     */
+    public function setArgument($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)
+    {
+        return $this->definition->hasArgument($name);
+    }
+
+    /**
+     * 获取所有的选项
+     * @return Option[]
+     */
+    public function getOptions()
+    {
+        return array_merge($this->definition->getOptionDefaults(), $this->options);
+    }
+
+    /**
+     * 获取选项值
+     * @param string $name 选项名称
+     * @return mixed
+     * @throws \InvalidArgumentException
+     */
+    public function getOption($name)
+    {
+        if (!$this->definition->hasOption($name)) {
+            throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
+        }
+
+        return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
+    }
+
+    /**
+     * 设置选项值
+     * @param string      $name  选项名
+     * @param string|bool $value 值
+     * @throws \InvalidArgumentException
+     */
+    public function setOption($name, $value)
+    {
+        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($name)
+    {
+        return $this->definition->hasOption($name) && isset($this->options[$name]);
+    }
+
+    /**
+     * 转义指令
+     * @param string $token
+     * @return string
+     */
+    public function escapeToken($token)
+    {
+        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
thinkphp/library/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.

+ 222 - 0
thinkphp/library/think/console/Output.php

@@ -0,0 +1,222 @@
+<?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;
+
+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;
+
+/**
+ * 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($style, $message)
+    {
+        $this->writeln("<{$style}>{$message}</$style>");
+    }
+
+    /**
+     * 输出空行
+     * @param int $count
+     */
+    public function newLine($count = 1)
+    {
+        $this->write(str_repeat(PHP_EOL, $count));
+    }
+
+    /**
+     * 输出信息并换行
+     * @param string $messages
+     * @param int    $type
+     */
+    public function writeln($messages, $type = self::OUTPUT_NORMAL)
+    {
+        $this->write($messages, true, $type);
+    }
+
+    /**
+     * 输出信息
+     * @param string $messages
+     * @param bool   $newline
+     * @param int    $type
+     */
+    public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
+    {
+        $this->handle->write($messages, $newline, $type);
+    }
+
+    public function renderException(\Exception $e)
+    {
+        $this->handle->renderException($e);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function setVerbosity($level)
+    {
+        $this->verbosity = (int) $level;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getVerbosity()
+    {
+        return $this->verbosity;
+    }
+
+    public function isQuiet()
+    {
+        return self::VERBOSITY_QUIET === $this->verbosity;
+    }
+
+    public function isVerbose()
+    {
+        return self::VERBOSITY_VERBOSE <= $this->verbosity;
+    }
+
+    public function isVeryVerbose()
+    {
+        return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
+    }
+
+    public function isDebug()
+    {
+        return self::VERBOSITY_DEBUG <= $this->verbosity;
+    }
+
+    public function describe($object, array $options = [])
+    {
+        $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);
+        }
+    }
+
+}

+ 1 - 0
thinkphp/library/think/console/bin/README.md

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

BIN
thinkphp/library/think/console/bin/hiddeninput.exe


+ 56 - 0
thinkphp/library/think/console/command/Build.php

@@ -0,0 +1,56 @@
+<?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\Option;
+use think\console\Output;
+
+class Build extends Command
+{
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configure()
+    {
+        $this->setName('build')
+            ->setDefinition([
+                new Option('config', null, Option::VALUE_OPTIONAL, "build.php path"),
+                new Option('module', null, Option::VALUE_OPTIONAL, "module name"),
+            ])
+            ->setDescription('Build Application Dirs');
+    }
+
+    protected function execute(Input $input, Output $output)
+    {
+        if ($input->hasOption('module')) {
+            \think\Build::module($input->getOption('module'));
+            $output->writeln("Successed");
+            return;
+        }
+
+        if ($input->hasOption('config')) {
+            $build = include $input->getOption('config');
+        } else {
+            $build = include APP_PATH . 'build.php';
+        }
+        if (empty($build)) {
+            $output->writeln("Build Config Is Empty");
+            return;
+        }
+        \think\Build::run($build);
+        $output->writeln("Successed");
+
+    }
+}

+ 63 - 0
thinkphp/library/think/console/command/Clear.php

@@ -0,0 +1,63 @@
+<?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\Cache;
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\input\Option;
+use think\console\Output;
+
+class Clear extends Command
+{
+    protected function configure()
+    {
+        // 指令配置
+        $this
+            ->setName('clear')
+            ->addArgument('type', Argument::OPTIONAL, 'type to clear', null)
+            ->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null)
+            ->setDescription('Clear runtime file');
+    }
+
+    protected function execute(Input $input, Output $output)
+    {
+        $path = $input->getOption('path') ?: RUNTIME_PATH;
+
+        $type = $input->getArgument('type');
+
+        if ($type == 'route') {
+            Cache::clear('route_check');
+        } else {
+            if (is_dir($path)) {
+                $this->clearPath($path);
+            }
+        }
+
+        $output->writeln("<info>Clear Successed</info>");
+    }
+
+    protected function clearPath($path)
+    {
+        $path  = realpath($path) . DS;
+        $files = scandir($path);
+        if ($files) {
+            foreach ($files as $file) {
+                if ('.' != $file && '..' != $file && is_dir($path . $file)) {
+                    $this->clearPath($path . $file);
+                } elseif ('.gitignore' != $file && is_file($path . $file)) {
+                    unlink($path . $file);
+                }
+            }
+        }
+    }
+}

+ 69 - 0
thinkphp/library/think/console/command/Help.php

@@ -0,0 +1,69 @@
+<?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)
+    {
+        $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
thinkphp/library/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\Output;
+use think\console\input\Argument as InputArgument;
+use think\console\input\Option as InputOption;
+use think\console\input\Definition as InputDefinition;
+
+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()
+    {
+        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()
+    {
+        return new InputDefinition([
+            new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
+            new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list')
+        ]);
+    }
+}

+ 110 - 0
thinkphp/library/think/console/command/Make.php

@@ -0,0 +1,110 @@
+<?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\App;
+use think\Config;
+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 . ' already exists!</error>');
+            return false;
+        }
+
+        if (!is_dir(dirname($pathname))) {
+            mkdir(strtolower(dirname($pathname)), 0755, true);
+        }
+
+        file_put_contents($pathname, $this->buildClass($classname));
+
+        $output->writeln('<info>' . $this->type . ' created successfully.</info>');
+
+    }
+
+    protected function buildClass($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%}', '{%namespace%}', '{%app_namespace%}'], [
+            $class,
+            $namespace,
+            App::$namespace,
+        ], $stub);
+
+    }
+
+    protected function getPathName($name)
+    {
+        $name = str_replace(App::$namespace . '\\', '', $name);
+
+        return APP_PATH . str_replace('\\', '/', $name) . '.php';
+    }
+
+    protected function getClassName($name)
+    {
+        $appNamespace = App::$namespace;
+
+        if (strpos($name, $appNamespace . '\\') === 0) {
+            return $name;
+        }
+
+        if (Config::get('app_multi_module')) {
+            if (strpos($name, '/')) {
+                list($module, $name) = explode('/', $name, 2);
+            } else {
+                $module = 'common';
+            }
+        } else {
+            $module = null;
+        }
+
+        if (strpos($name, '/') !== false) {
+            $name = str_replace('/', '\\', $name);
+        }
+
+        return $this->getNamespace($appNamespace, $module) . '\\' . $name;
+    }
+
+    protected function getNamespace($appNamespace, $module)
+    {
+        return $module ? ($appNamespace . '\\' . $module) : $appNamespace;
+    }
+
+}

+ 50 - 0
thinkphp/library/think/console/command/make/Controller.php

@@ -0,0 +1,50 @@
+<?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\make;
+
+use think\Config;
+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('plain', null, Option::VALUE_NONE, 'Generate an empty controller class.')
+            ->setDescription('Create a new resource controller class');
+    }
+
+    protected function getStub()
+    {
+        if ($this->input->getOption('plain')) {
+            return __DIR__ . '/stubs/controller.plain.stub';
+        }
+
+        return __DIR__ . '/stubs/controller.stub';
+    }
+
+    protected function getClassName($name)
+    {
+        return parent::getClassName($name) . (Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : '');
+    }
+
+    protected function getNamespace($appNamespace, $module)
+    {
+        return parent::getNamespace($appNamespace, $module) . '\controller';
+    }
+
+}

+ 36 - 0
thinkphp/library/think/console/command/make/Model.php

@@ -0,0 +1,36 @@
+<?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\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()
+    {
+        return __DIR__ . '/stubs/model.stub';
+    }
+
+    protected function getNamespace($appNamespace, $module)
+    {
+        return parent::getNamespace($appNamespace, $module) . '\model';
+    }
+}

+ 10 - 0
thinkphp/library/think/console/command/make/stubs/controller.plain.stub

@@ -0,0 +1,10 @@
+<?php
+
+namespace {%namespace%};
+
+use think\Controller;
+
+class {%className%} extends Controller
+{
+    //
+}

+ 85 - 0
thinkphp/library/think/console/command/make/stubs/controller.stub

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

+ 10 - 0
thinkphp/library/think/console/command/make/stubs/model.stub

@@ -0,0 +1,10 @@
+<?php
+
+namespace {%namespace%};
+
+use think\Model;
+
+class {%className%} extends Model
+{
+    //
+}

+ 294 - 0
thinkphp/library/think/console/command/optimize/Autoload.php

@@ -0,0 +1,294 @@
+<?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\App;
+use think\Config;
+use think\console\Command;
+use think\console\Input;
+use think\console\Output;
+
+class Autoload extends Command
+{
+
+    protected function configure()
+    {
+        $this->setName('optimize:autoload')
+            ->setDescription('Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.');
+    }
+
+    protected function execute(Input $input, Output $output)
+    {
+
+        $classmapFile = <<<EOF
+<?php
+/**
+ * 类库映射
+ */
+
+return [
+
+EOF;
+
+        $namespacesToScan = [
+            App::$namespace . '\\' => realpath(rtrim(APP_PATH)),
+            'think\\'              => LIB_PATH . 'think',
+            'behavior\\'           => LIB_PATH . 'behavior',
+            'traits\\'             => LIB_PATH . 'traits',
+            ''                     => realpath(rtrim(EXTEND_PATH)),
+        ];
+
+        $root_namespace = Config::get('root_namespace');
+        foreach ($root_namespace as $namespace => $dir) {
+            $namespacesToScan[$namespace . '\\'] = realpath($dir);
+        }
+
+        krsort($namespacesToScan);
+        $classMap = [];
+        foreach ($namespacesToScan as $namespace => $dir) {
+
+            if (!is_dir($dir)) {
+                continue;
+            }
+
+            $namespaceFilter = $namespace === '' ? null : $namespace;
+            $classMap        = $this->addClassMapCode($dir, $namespaceFilter, $classMap);
+        }
+
+        ksort($classMap);
+        foreach ($classMap as $class => $code) {
+            $classmapFile .= '    ' . var_export($class, true) . ' => ' . $code;
+        }
+        $classmapFile .= "];\n";
+
+        if (!is_dir(RUNTIME_PATH)) {
+            @mkdir(RUNTIME_PATH, 0755, true);
+        }
+
+        file_put_contents(RUNTIME_PATH . 'classmap' . EXT, $classmapFile);
+
+        $output->writeln('<info>Succeed!</info>');
+    }
+
+    protected function addClassMapCode($dir, $namespace, $classMap)
+    {
+        foreach ($this->createMap($dir, $namespace) as $class => $path) {
+
+            $pathCode = $this->getPathCode($path) . ",\n";
+
+            if (!isset($classMap[$class])) {
+                $classMap[$class] = $pathCode;
+            } elseif ($classMap[$class] !== $pathCode && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class] . ' ' . $path, '\\', '/'))) {
+                $this->output->writeln(
+                    '<warning>Warning: Ambiguous class resolution, "' . $class . '"' .
+                    ' was found in both "' . str_replace(["',\n"], [
+                        '',
+                    ], $classMap[$class]) . '" and "' . $path . '", the first will be used.</warning>'
+                );
+            }
+        }
+        return $classMap;
+    }
+
+    protected function getPathCode($path)
+    {
+
+        $baseDir    = '';
+        $libPath    = $this->normalizePath(realpath(LIB_PATH));
+        $appPath    = $this->normalizePath(realpath(APP_PATH));
+        $extendPath = $this->normalizePath(realpath(EXTEND_PATH));
+        $rootPath   = $this->normalizePath(realpath(ROOT_PATH));
+        $path       = $this->normalizePath($path);
+
+        if ($libPath !== null && strpos($path, $libPath . '/') === 0) {
+            $path    = substr($path, strlen(LIB_PATH));
+            $baseDir = 'LIB_PATH';
+        } elseif ($appPath !== null && strpos($path, $appPath . '/') === 0) {
+            $path    = substr($path, strlen($appPath) + 1);
+            $baseDir = 'APP_PATH';
+        } elseif ($extendPath !== null && strpos($path, $extendPath . '/') === 0) {
+            $path    = substr($path, strlen($extendPath) + 1);
+            $baseDir = 'EXTEND_PATH';
+        } elseif ($rootPath !== null && strpos($path, $rootPath . '/') === 0) {
+            $path    = substr($path, strlen($rootPath) + 1);
+            $baseDir = 'ROOT_PATH';
+        }
+
+        if ($path !== false) {
+            $baseDir .= " . ";
+        }
+
+        return $baseDir . (($path !== false) ? var_export($path, true) : "");
+    }
+
+    protected function normalizePath($path)
+    {
+        if ($path === false) {
+            return;
+        }
+        $parts    = [];
+        $path     = strtr($path, '\\', '/');
+        $prefix   = '';
+        $absolute = false;
+
+        if (preg_match('{^([0-9a-z]+:(?://(?:[a-z]:)?)?)}i', $path, $match)) {
+            $prefix = $match[1];
+            $path   = substr($path, strlen($prefix));
+        }
+
+        if (substr($path, 0, 1) === '/') {
+            $absolute = true;
+            $path     = substr($path, 1);
+        }
+
+        $up = false;
+        foreach (explode('/', $path) as $chunk) {
+            if ('..' === $chunk && ($absolute || $up)) {
+                array_pop($parts);
+                $up = !(empty($parts) || '..' === end($parts));
+            } elseif ('.' !== $chunk && '' !== $chunk) {
+                $parts[] = $chunk;
+                $up      = '..' !== $chunk;
+            }
+        }
+
+        return $prefix . ($absolute ? '/' : '') . implode('/', $parts);
+    }
+
+    protected function createMap($path, $namespace = null)
+    {
+        if (is_string($path)) {
+            if (is_file($path)) {
+                $path = [new \SplFileInfo($path)];
+            } elseif (is_dir($path)) {
+
+                $objects = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::SELF_FIRST);
+
+                $path = [];
+
+                /** @var \SplFileInfo $object */
+                foreach ($objects as $object) {
+                    if ($object->isFile() && $object->getExtension() == 'php') {
+                        $path[] = $object;
+                    }
+                }
+            } else {
+                throw new \RuntimeException(
+                    'Could not scan for classes inside "' . $path .
+                    '" which does not appear to be a file nor a folder'
+                );
+            }
+        }
+
+        $map = [];
+
+        /** @var \SplFileInfo $file */
+        foreach ($path as $file) {
+            $filePath = $file->getRealPath();
+
+            if (pathinfo($filePath, PATHINFO_EXTENSION) != 'php') {
+                continue;
+            }
+
+            $classes = $this->findClasses($filePath);
+
+            foreach ($classes as $class) {
+                if (null !== $namespace && 0 !== strpos($class, $namespace)) {
+                    continue;
+                }
+
+                if (!isset($map[$class])) {
+                    $map[$class] = $filePath;
+                } elseif ($map[$class] !== $filePath && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($map[$class] . ' ' . $filePath, '\\', '/'))) {
+                    $this->output->writeln(
+                        '<warning>Warning: Ambiguous class resolution, "' . $class . '"' .
+                        ' was found in both "' . $map[$class] . '" and "' . $filePath . '", the first will be used.</warning>'
+                    );
+                }
+            }
+        }
+
+        return $map;
+    }
+
+    protected function findClasses($path)
+    {
+        $extraTypes = '|trait';
+
+        $contents = @php_strip_whitespace($path);
+        if (!$contents) {
+            if (!file_exists($path)) {
+                $message = 'File at "%s" does not exist, check your classmap definitions';
+            } elseif (!is_readable($path)) {
+                $message = 'File at "%s" is not readable, check its permissions';
+            } elseif ('' === trim(file_get_contents($path))) {
+                return [];
+            } else {
+                $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted';
+            }
+            $error = error_get_last();
+            if (isset($error['message'])) {
+                $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message'];
+            }
+            throw new \RuntimeException(sprintf($message, $path));
+        }
+
+        if (!preg_match('{\b(?:class|interface' . $extraTypes . ')\s}i', $contents)) {
+            return [];
+        }
+
+        // strip heredocs/nowdocs
+        $contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents);
+        // strip strings
+        $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents);
+        // strip leading non-php code if needed
+        if (substr($contents, 0, 2) !== '<?') {
+            $contents = preg_replace('{^.+?<\?}s', '<?', $contents, 1, $replacements);
+            if ($replacements === 0) {
+                return [];
+            }
+        }
+        // strip non-php blocks in the file
+        $contents = preg_replace('{\?>.+<\?}s', '?><?', $contents);
+        // strip trailing non-php code if needed
+        $pos = strrpos($contents, '?>');
+        if (false !== $pos && false === strpos(substr($contents, $pos), '<?')) {
+            $contents = substr($contents, 0, $pos);
+        }
+
+        preg_match_all('{
+            (?:
+                 \b(?<![\$:>])(?P<type>class|interface' . $extraTypes . ') \s++ (?P<name>[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+)
+               | \b(?<![\$:>])(?P<ns>namespace) (?P<nsname>\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;]
+            )
+        }ix', $contents, $matches);
+
+        $classes   = [];
+        $namespace = '';
+
+        for ($i = 0, $len = count($matches['type']); $i < $len; $i++) {
+            if (!empty($matches['ns'][$i])) {
+                $namespace = str_replace([' ', "\t", "\r", "\n"], '', $matches['nsname'][$i]) . '\\';
+            } else {
+                $name = $matches['name'][$i];
+                if ($name[0] === ':') {
+                    $name = 'xhp' . substr(str_replace(['-', ':'], ['_', '__'], $name), 1);
+                } elseif ($matches['type'][$i] === 'enum') {
+                    $name = rtrim($name, ':');
+                }
+                $classes[] = ltrim($namespace . $name, '\\');
+            }
+        }
+
+        return $classes;
+    }
+
+}

+ 93 - 0
thinkphp/library/think/console/command/optimize/Config.php

@@ -0,0 +1,93 @@
+<?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\Config as ThinkConfig;
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\Output;
+
+class Config extends Command
+{
+    /** @var  Output */
+    protected $output;
+
+    protected function configure()
+    {
+        $this->setName('optimize:config')
+            ->addArgument('module', Argument::OPTIONAL, 'Build module config cache .')
+            ->setDescription('Build config and common file cache.');
+    }
+
+    protected function execute(Input $input, Output $output)
+    {
+        if ($input->getArgument('module')) {
+            $module = $input->getArgument('module') . DS;
+        } else {
+            $module = '';
+        }
+
+        $content = '<?php ' . PHP_EOL . $this->buildCacheContent($module);
+
+        if (!is_dir(RUNTIME_PATH . $module)) {
+            @mkdir(RUNTIME_PATH . $module, 0755, true);
+        }
+
+        file_put_contents(RUNTIME_PATH . $module . 'init' . EXT, $content);
+
+        $output->writeln('<info>Succeed!</info>');
+    }
+
+    protected function buildCacheContent($module)
+    {
+        $content = '';
+        $path    = realpath(APP_PATH . $module) . DS;
+
+        if ($module) {
+            // 加载模块配置
+            $config = ThinkConfig::load(CONF_PATH . $module . 'config' . CONF_EXT);
+
+            // 读取数据库配置文件
+            $filename = CONF_PATH . $module . 'database' . CONF_EXT;
+            ThinkConfig::load($filename, 'database');
+
+            // 加载应用状态配置
+            if ($config['app_status']) {
+                $config = ThinkConfig::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT);
+            }
+            // 读取扩展配置文件
+            if (is_dir(CONF_PATH . $module . 'extra')) {
+                $dir   = CONF_PATH . $module . 'extra';
+                $files = scandir($dir);
+                foreach ($files as $file) {
+                    if (strpos($file, CONF_EXT)) {
+                        $filename = $dir . DS . $file;
+                        ThinkConfig::load($filename, pathinfo($file, PATHINFO_FILENAME));
+                    }
+                }
+            }
+        }
+
+        // 加载行为扩展文件
+        if (is_file(CONF_PATH . $module . 'tags' . EXT)) {
+            $content .= '\think\Hook::import(' . (var_export(include CONF_PATH . $module . 'tags' . EXT, true)) . ');' . PHP_EOL;
+        }
+
+        // 加载公共文件
+        if (is_file($path . 'common' . EXT)) {
+            $content .= substr(php_strip_whitespace($path . 'common' . EXT), 5) . PHP_EOL;
+        }
+
+        $content .= '\think\Config::set(' . var_export(ThinkConfig::get(), true) . ');';
+        return $content;
+    }
+}

+ 75 - 0
thinkphp/library/think/console/command/optimize/Route.php

@@ -0,0 +1,75 @@
+<?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\optimize;
+
+use think\console\Command;
+use think\console\Input;
+use think\console\Output;
+
+class Route extends Command
+{
+    /** @var  Output */
+    protected $output;
+
+    protected function configure()
+    {
+        $this->setName('optimize:route')
+            ->setDescription('Build route cache.');
+    }
+
+    protected function execute(Input $input, Output $output)
+    {
+
+        if (!is_dir(RUNTIME_PATH)) {
+            @mkdir(RUNTIME_PATH, 0755, true);
+        }
+
+        file_put_contents(RUNTIME_PATH . 'route.php', $this->buildRouteCache());
+        $output->writeln('<info>Succeed!</info>');
+    }
+
+    protected function buildRouteCache()
+    {
+        $files = \think\Config::get('route_config_file');
+        foreach ($files as $file) {
+            if (is_file(CONF_PATH . $file . CONF_EXT)) {
+                $config = include CONF_PATH . $file . CONF_EXT;
+                if (is_array($config)) {
+                    \think\Route::import($config);
+                }
+            }
+        }
+        $rules = \think\Route::rules(true);
+        array_walk_recursive($rules, [$this, 'buildClosure']);
+        $content = '<?php ' . PHP_EOL . 'return ';
+        $content .= var_export($rules, true) . ';';
+        $content = str_replace(['\'[__start__', '__end__]\''], '', stripcslashes($content));
+        return $content;
+    }
+
+    protected function buildClosure(&$value)
+    {
+        if ($value instanceof \Closure) {
+            $reflection = new \ReflectionFunction($value);
+            $startLine  = $reflection->getStartLine();
+            $endLine    = $reflection->getEndLine();
+            $file       = $reflection->getFileName();
+            $item       = file($file);
+            $content    = '';
+            for ($i = $startLine - 1; $i <= $endLine - 1; $i++) {
+                $content .= $item[$i];
+            }
+            $start = strpos($content, 'function');
+            $end   = strrpos($content, '}');
+            $value = '[__start__' . substr($content, $start, $end - $start + 1) . '__end__]';
+        }
+    }
+}

+ 118 - 0
thinkphp/library/think/console/command/optimize/Schema.php

@@ -0,0 +1,118 @@
+<?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\App;
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Option;
+use think\console\Output;
+use think\Db;
+
+class Schema extends Command
+{
+    /** @var  Output */
+    protected $output;
+
+    protected function configure()
+    {
+        $this->setName('optimize:schema')
+            ->addOption('config', null, Option::VALUE_REQUIRED, 'db config .')
+            ->addOption('db', null, Option::VALUE_REQUIRED, 'db name .')
+            ->addOption('table', null, Option::VALUE_REQUIRED, 'table name .')
+            ->addOption('module', null, Option::VALUE_REQUIRED, 'module name .')
+            ->setDescription('Build database schema cache.');
+    }
+
+    protected function execute(Input $input, Output $output)
+    {
+        if (!is_dir(RUNTIME_PATH . 'schema')) {
+            @mkdir(RUNTIME_PATH . 'schema', 0755, true);
+        }
+        $config = [];
+        if ($input->hasOption('config')) {
+            $config = $input->getOption('config');
+        }
+        if ($input->hasOption('module')) {
+            $module = $input->getOption('module');
+            // 读取模型
+            $path = APP_PATH . $module . DS . 'model';
+            $list = is_dir($path) ? scandir($path) : [];
+            $app  = App::$namespace;
+            foreach ($list as $file) {
+                if (0 === strpos($file, '.')) {
+                    continue;
+                }
+                $class = '\\' . $app . '\\' . $module . '\\model\\' . pathinfo($file, PATHINFO_FILENAME);
+                $this->buildModelSchema($class);
+            }
+            $output->writeln('<info>Succeed!</info>');
+            return;
+        } elseif ($input->hasOption('table')) {
+            $table = $input->getOption('table');
+            if (!strpos($table, '.')) {
+                $dbName = Db::connect($config)->getConfig('database');
+            }
+            $tables[] = $table;
+        } elseif ($input->hasOption('db')) {
+            $dbName = $input->getOption('db');
+            $tables = Db::connect($config)->getTables($dbName);
+        } elseif (!\think\Config::get('app_multi_module')) {
+            $app  = App::$namespace;
+            $path = APP_PATH . 'model';
+            $list = is_dir($path) ? scandir($path) : [];
+            foreach ($list as $file) {
+                if (0 === strpos($file, '.')) {
+                    continue;
+                }
+                $class = '\\' . $app . '\\model\\' . pathinfo($file, PATHINFO_FILENAME);
+                $this->buildModelSchema($class);
+            }
+            $output->writeln('<info>Succeed!</info>');
+            return;
+        } else {
+            $tables = Db::connect($config)->getTables();
+        }
+
+        $db = isset($dbName) ? $dbName . '.' : '';
+        $this->buildDataBaseSchema($tables, $db, $config);
+
+        $output->writeln('<info>Succeed!</info>');
+    }
+
+    protected function buildModelSchema($class)
+    {
+        $reflect = new \ReflectionClass($class);
+        if (!$reflect->isAbstract() && $reflect->isSubclassOf('\think\Model')) {
+            $table   = $class::getTable();
+            $dbName  = $class::getConfig('database');
+            $content = '<?php ' . PHP_EOL . 'return ';
+            $info    = $class::getConnection()->getFields($table);
+            $content .= var_export($info, true) . ';';
+            file_put_contents(RUNTIME_PATH . 'schema' . DS . $dbName . '.' . $table . EXT, $content);
+        }
+    }
+
+    protected function buildDataBaseSchema($tables, $db, $config)
+    {
+        if ('' == $db) {
+            $dbName = Db::connect($config)->getConfig('database') . '.';
+        } else {
+            $dbName = $db;
+        }
+        foreach ($tables as $table) {
+            $content = '<?php ' . PHP_EOL . 'return ';
+            $info    = Db::connect($config)->getFields($db . $table);
+            $content .= var_export($info, true) . ';';
+            file_put_contents(RUNTIME_PATH . 'schema' . DS . $dbName . $table . EXT, $content);
+        }
+    }
+}

+ 115 - 0
thinkphp/library/think/console/input/Argument.php

@@ -0,0 +1,115 @@
+<?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;
+
+    private $name;
+    private $mode;
+    private $default;
+    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($name, $mode = null, $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()
+    {
+        return $this->name;
+    }
+
+    /**
+     * 是否必须
+     * @return bool
+     */
+    public function isRequired()
+    {
+        return self::REQUIRED === (self::REQUIRED & $this->mode);
+    }
+
+    /**
+     * 该参数是否接受数组
+     * @return bool
+     */
+    public function isArray()
+    {
+        return self::IS_ARRAY === (self::IS_ARRAY & $this->mode);
+    }
+
+    /**
+     * 设置默认值
+     * @param mixed $default 默认值
+     * @throws \LogicException
+     */
+    public function setDefault($default = null)
+    {
+        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()
+    {
+        return $this->description;
+    }
+}

+ 375 - 0
thinkphp/library/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)
+    {
+        $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($arguments = [])
+    {
+        $this->arguments          = [];
+        $this->requiredCount      = 0;
+        $this->hasOptional        = false;
+        $this->hasAnArrayArgument = false;
+        $this->addArguments($arguments);
+    }
+
+    /**
+     * 添加参数
+     * @param Argument[] $arguments 参数数组
+     * @api
+     */
+    public function addArguments($arguments = [])
+    {
+        if (null !== $arguments) {
+            foreach ($arguments as $argument) {
+                $this->addArgument($argument);
+            }
+        }
+    }
+
+    /**
+     * 添加一个参数
+     * @param Argument $argument 参数
+     * @throws \LogicException
+     */
+    public function addArgument(Argument $argument)
+    {
+        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)
+    {
+        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)
+    {
+        $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments;
+
+        return isset($arguments[$name]);
+    }
+
+    /**
+     * 获取所有的参数
+     * @return Argument[] 参数数组
+     */
+    public function getArguments()
+    {
+        return $this->arguments;
+    }
+
+    /**
+     * 获取参数数量
+     * @return int
+     */
+    public function getArgumentCount()
+    {
+        return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments);
+    }
+
+    /**
+     * 获取必填的参数的数量
+     * @return int
+     */
+    public function getArgumentRequiredCount()
+    {
+        return $this->requiredCount;
+    }
+
+    /**
+     * 获取参数默认值
+     * @return array
+     */
+    public function getArgumentDefaults()
+    {
+        $values = [];
+        foreach ($this->arguments as $argument) {
+            $values[$argument->getName()] = $argument->getDefault();
+        }
+
+        return $values;
+    }
+
+    /**
+     * 设置选项
+     * @param Option[] $options 选项数组
+     */
+    public function setOptions($options = [])
+    {
+        $this->options   = [];
+        $this->shortcuts = [];
+        $this->addOptions($options);
+    }
+
+    /**
+     * 添加选项
+     * @param Option[] $options 选项数组
+     * @api
+     */
+    public function addOptions($options = [])
+    {
+        foreach ($options as $option) {
+            $this->addOption($option);
+        }
+    }
+
+    /**
+     * 添加一个选项
+     * @param Option $option 选项
+     * @throws \LogicException
+     * @api
+     */
+    public function addOption(Option $option)
+    {
+        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($name)
+    {
+        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($name)
+    {
+        return isset($this->options[$name]);
+    }
+
+    /**
+     * 获取所有选项
+     * @return Option[]
+     * @api
+     */
+    public function getOptions()
+    {
+        return $this->options;
+    }
+
+    /**
+     * 根据名称检查某个选项是否有短名称
+     * @param string $name 短名称
+     * @return bool
+     */
+    public function hasShortcut($name)
+    {
+        return isset($this->shortcuts[$name]);
+    }
+
+    /**
+     * 根据短名称获取选项
+     * @param string $shortcut 短名称
+     * @return Option
+     */
+    public function getOptionForShortcut($shortcut)
+    {
+        return $this->getOption($this->shortcutToName($shortcut));
+    }
+
+    /**
+     * 获取所有选项的默认值
+     * @return array
+     */
+    public function getOptionDefaults()
+    {
+        $values = [];
+        foreach ($this->options as $option) {
+            $values[$option->getName()] = $option->getDefault();
+        }
+
+        return $values;
+    }
+
+    /**
+     * 根据短名称获取选项名
+     * @param string $shortcut 短名称
+     * @return string
+     * @throws \InvalidArgumentException
+     */
+    private function shortcutToName($shortcut)
+    {
+        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($short = false)
+    {
+        $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);
+    }
+}

+ 190 - 0
thinkphp/library/think/console/input/Option.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\input;
+
+class Option
+{
+
+    const VALUE_NONE     = 1;
+    const VALUE_REQUIRED = 2;
+    const VALUE_OPTIONAL = 4;
+    const VALUE_IS_ARRAY = 8;
+
+    private $name;
+    private $shortcut;
+    private $mode;
+    private $default;
+    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();
+    }
+}

+ 340 - 0
thinkphp/library/think/console/output/Ask.php

@@ -0,0 +1,340 @@
+<?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('');
+
+            if (isset($tmpExe)) {
+                unlink($tmpExe);
+            }
+
+            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;
+    }
+}

+ 319 - 0
thinkphp/library/think/console/output/Descriptor.php

@@ -0,0 +1,319 @@
+<?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 void
+     */
+    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   = isset($options['total_width']) ? $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 void
+     */
+    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 = isset($options['total_width']) ? $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 void
+     */
+    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 void
+     */
+    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 void
+     */
+    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->getCommands());
+
+            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->getCommands());
+
+            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 Command[] $commands
+     * @return int
+     */
+    private function getColumnWidth(array $commands)
+    {
+        $width = 0;
+        foreach ($commands as $command) {
+            $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width;
+        }
+
+        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
thinkphp/library/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 = isset($matches[3][$i][0]) ? $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
thinkphp/library/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'));
+    }
+}

+ 149 - 0
thinkphp/library/think/console/output/descriptor/Console.php

@@ -0,0 +1,149 @@
+<?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()
+    {
+        if (null === $this->namespaces) {
+            $this->inspectConsole();
+        }
+
+        return $this->namespaces;
+    }
+
+    /**
+     * @return Command[]
+     */
+    public function getCommands()
+    {
+        if (null === $this->commands) {
+            $this->inspectConsole();
+        }
+
+        return $this->commands;
+    }
+
+    /**
+     * @param string $name
+     * @return Command
+     * @throws \InvalidArgumentException
+     */
+    public function getCommand($name)
+    {
+        if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
+            throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name));
+        }
+
+        return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name];
+    }
+
+    private function inspectConsole()
+    {
+        $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 (!$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)
+    {
+        $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
thinkphp/library/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, $newline = false, $options = Output::OUTPUT_NORMAL)
+    {
+        $messages = (array) $messages;
+
+        foreach ($messages as $message) {
+            $this->buffer .= $message;
+        }
+        if ($newline) {
+            $this->buffer .= "\n";
+        }
+    }
+
+    public function renderException(\Exception $e)
+    {
+        // do nothing
+    }
+
+}

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