Ver Fonte

完全复制youeryuan_server

lizhen_gitee há 3 anos atrás
commit
ecc44d3c05
100 ficheiros alterados com 24401 adições e 0 exclusões
  1. 2 0
      .gitignore
  2. 42 0
      .travis.yml
  3. 1236 0
      CHANGELOG.md
  4. 32 0
      LICENSE.txt
  5. 133 0
      README.md
  6. 1 0
      application/.htaccess
  7. 12 0
      application/command.php
  8. 12 0
      application/common.php
  9. 243 0
      application/config.php
  10. 55 0
      application/database.php
  11. 14 0
      application/extra/queue.php
  12. 32 0
      application/index/controller/Index.php
  13. 90 0
      application/index/view/index/index.html
  14. 21 0
      application/route.php
  15. 28 0
      application/tags.php
  16. 10 0
      application/task/controller/Index.php
  17. 25 0
      build.php
  18. 36 0
      composer.json
  19. 273 0
      composer.lock
  20. 2 0
      extend/.gitignore
  21. 1 0
      new 1.txt
  22. 42 0
      nfcserver.php
  23. 8 0
      public/.htaccess
  24. BIN
      public/favicon.ico
  25. 17 0
      public/index.php
  26. 2 0
      public/robots.txt
  27. 20 0
      public/router.php
  28. 2 0
      public/static/.gitignore
  29. 2 0
      runtime/.gitignore
  30. 42 0
      service.php
  31. 54 0
      service/Nfcserver.php
  32. 199 0
      service/Payment.php
  33. 60 0
      start.php
  34. 17 0
      think
  35. 1 0
      thinkphp/.gitignore
  36. 1 0
      thinkphp/.htaccess
  37. 47 0
      thinkphp/.travis.yml
  38. 119 0
      thinkphp/CONTRIBUTING.md
  39. 32 0
      thinkphp/LICENSE.txt
  40. 114 0
      thinkphp/README.md
  41. 65 0
      thinkphp/base.php
  42. 12 0
      thinkphp/codecov.yml
  43. 35 0
      thinkphp/composer.json
  44. 20 0
      thinkphp/console.php
  45. 298 0
      thinkphp/convention.php
  46. 589 0
      thinkphp/helper.php
  47. 136 0
      thinkphp/lang/zh-cn.php
  48. 677 0
      thinkphp/library/think/App.php
  49. 235 0
      thinkphp/library/think/Build.php
  50. 247 0
      thinkphp/library/think/Cache.php
  51. 467 0
      thinkphp/library/think/Collection.php
  52. 214 0
      thinkphp/library/think/Config.php
  53. 863 0
      thinkphp/library/think/Console.php
  54. 229 0
      thinkphp/library/think/Controller.php
  55. 268 0
      thinkphp/library/think/Cookie.php
  56. 180 0
      thinkphp/library/think/Db.php
  57. 252 0
      thinkphp/library/think/Debug.php
  58. 39 0
      thinkphp/library/think/Env.php
  59. 136 0
      thinkphp/library/think/Error.php
  60. 55 0
      thinkphp/library/think/Exception.php
  61. 478 0
      thinkphp/library/think/File.php
  62. 148 0
      thinkphp/library/think/Hook.php
  63. 265 0
      thinkphp/library/think/Lang.php
  64. 677 0
      thinkphp/library/think/Loader.php
  65. 237 0
      thinkphp/library/think/Log.php
  66. 2350 0
      thinkphp/library/think/Model.php
  67. 409 0
      thinkphp/library/think/Paginator.php
  68. 1205 0
      thinkphp/library/think/Process.php
  69. 1690 0
      thinkphp/library/think/Request.php
  70. 332 0
      thinkphp/library/think/Response.php
  71. 1645 0
      thinkphp/library/think/Route.php
  72. 366 0
      thinkphp/library/think/Session.php
  73. 1139 0
      thinkphp/library/think/Template.php
  74. 333 0
      thinkphp/library/think/Url.php
  75. 1371 0
      thinkphp/library/think/Validate.php
  76. 239 0
      thinkphp/library/think/View.php
  77. 231 0
      thinkphp/library/think/cache/Driver.php
  78. 268 0
      thinkphp/library/think/cache/driver/File.php
  79. 187 0
      thinkphp/library/think/cache/driver/Lite.php
  80. 177 0
      thinkphp/library/think/cache/driver/Memcache.php
  81. 187 0
      thinkphp/library/think/cache/driver/Memcached.php
  82. 188 0
      thinkphp/library/think/cache/driver/Redis.php
  83. 199 0
      thinkphp/library/think/cache/driver/Sqlite.php
  84. 152 0
      thinkphp/library/think/cache/driver/Wincache.php
  85. 155 0
      thinkphp/library/think/cache/driver/Xcache.php
  86. 24 0
      thinkphp/library/think/config/driver/Ini.php
  87. 24 0
      thinkphp/library/think/config/driver/Json.php
  88. 31 0
      thinkphp/library/think/config/driver/Xml.php
  89. 470 0
      thinkphp/library/think/console/Command.php
  90. 464 0
      thinkphp/library/think/console/Input.php
  91. 19 0
      thinkphp/library/think/console/LICENSE
  92. 222 0
      thinkphp/library/think/console/Output.php
  93. 1 0
      thinkphp/library/think/console/bin/README.md
  94. BIN
      thinkphp/library/think/console/bin/hiddeninput.exe
  95. 56 0
      thinkphp/library/think/console/command/Build.php
  96. 63 0
      thinkphp/library/think/console/command/Clear.php
  97. 69 0
      thinkphp/library/think/console/command/Help.php
  98. 74 0
      thinkphp/library/think/console/command/Lists.php
  99. 110 0
      thinkphp/library/think/console/command/Make.php
  100. 50 0
      thinkphp/library/think/console/command/make/Controller.php

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+.idea
+*.log

+ 42 - 0
.travis.yml

@@ -0,0 +1,42 @@
+sudo: false
+
+language: php
+
+branches:
+  only:
+    - stable
+
+cache:
+  directories:
+    - $HOME/.composer/cache
+
+before_install:
+  - composer self-update
+
+install:
+  - composer install --no-dev --no-interaction --ignore-platform-reqs
+  - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip .
+  - composer require --update-no-dev --no-interaction "topthink/think-image:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0"
+  - composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0"
+  - composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0"
+  - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip .
+
+script:
+  - php think unit
+
+deploy:
+  provider: releases
+  api_key:
+    secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw=
+  file:
+    - ThinkPHP_Core.zip
+    - ThinkPHP_Full.zip
+  skip_cleanup: true
+  on:
+    tags: true

+ 1236 - 0
CHANGELOG.md

@@ -0,0 +1,1236 @@
+## 2019-1-11 V5.0.24
+
+本次更新包含了一个安全更新,建议更新
+
+- 改进关联的save方法
+- 改进模型数据验证
+- Collection增加values方法
+- 改进unique验证方法
+- 改进Request类的method方法
+
+## 2018-12-9 V5.0.23
+
+本次版本更新主要涉及一个安全更新,推荐尽快更新到最新版本。
+
+* Query支持调用模型的查询范围
+* 聚合查询字段支持`DISTINCT`
+* 改进闭包验证的参数
+* 多对多关联支持指定中间表数据名称
+* after/before验证支持指定字段验证
+* 改进多对多关联
+* 改进验证类
+* 增加`afterWith`和`beforeWith`验证规则 用于比较日期字段
+* 完善规则提示
+* 改进断线重连
+* 修正软删除的`destroy`方法
+* 修复模型的`save`方法当`data`变量为空 数据不验证
+* 模型增加`replace`方法
+* MorphOne 增加 make 方法创建关联对象实例
+* 改进`count`方法返回值类型
+* 改进聚合查询方法的正则判断
+* 改进`sqlsrv`驱动
+* 完善关联的`save`方法
+* 修正控制器名获取
+
+
+## 2018-10-22 V5.0.22
+
+该版本主要增加了JSON日志格式的支持,并且包含了一个安全更新。
+
+* 调试模式下关闭路由解析缓存
+* 改进Log类支持`json`日志格式
+* 改进聚合查询的安全性
+* 改进`count`查询的返回值类型
+
+## 2018-9-7 V5.0.21
+
+该版本主要做了一些已知问题的修正,改进了对Swoole的支持,以及增加路由解析缓存功能。
+
+* 增加路由解析缓存功能
+* 改进url生成的端口问题
+* 改进缓存驱动
+* 改进value方法的缓存处理
+* 修正Builder类的insertAll方法
+* 改进对Swoole的支持(使用参考:[xavier-swoole](https://github.com/xavieryang007/xavier-swoole))
+
+## 2018-5-11 V5.0.20
+
+该版本为修正版本,修正了一些已知的问题。
+
+* `join`方法的条件支持传入`Expression`对象
+* 改进驱动的`parseKey`方法
+* 改进Request类的`host`方法
+* 使用`exp`表达式更新数据的异常提示
+* 修正查询
+* 改进多对多关联的中间表模型更新
+
+## 2018-4-25 V5.0.19
+
+该版本属于改进版本,主要改进了composer自动加载及内置模板引擎的一处可能的安全隐患。
+
+* 改进composer自动加载
+* 改进模板引擎一处安全隐患
+* 改进`comment`方法解析
+* 改进分布式写入数据后及时读取的问题
+* 改进url操作方法的自动转换
+* 改进分页类魔术方法的返回值
+* SQL日志增加主从标记
+
+## 2018-4-14 V5.0.18
+
+该版本主要修正上一个发布的一些BUG,并且改进了`exp`表达式查询/写入的严谨性。
+
+* 修正`field`方法`*`兼容问题;
+* 修正`inc/dec`方法;
+* 修正`setInc/setDec`方法;
+* 改进`insertAll`方法;
+* 改进`parseTime`方法;
+* 改进`exp`表达式查询/写入的严谨性;
+
+## 2018-4-12 V5.0.17
+
+该版本主要是一些修正和改进,并且包含了一个安全更新。
+
+* 改进Response类`create`方法
+* 改进`inc/dec`查询
+* 默认模板渲染规则支持直接使用操作方法名
+* 改进视图驱动
+* 改进Request类ip方法 支持代理设置
+* 修正request类的`create`方法
+* 闭包查询使用`cache(true)`抛出异常
+* 改进composer自动加载文件
+* 增加`Expression`类及相关方法
+
+## 2018-3-26 V5.0.16
+
+该版本主要做了一些修正和改进,由于包含了一个安全更新,是一个推荐更新的版本。
+
+* 改进Url生成
+* 改进composer自动加载性能
+* 改进一对一查询
+* 改进查询缓存
+* 改进field方法
+* 优化Template类
+* 修正分页参数
+* 改进默认模板的自动识别
+* 改进Query类查询
+* Collection类改进
+* 改进模型类`readTransform`方法对序列化类型的处理
+* 改进trace显示
+* 文件日志支持自动清理
+* 改进断线重连的判断
+* 改进验证方法
+* 修正Query类view方法的数组表名定义
+* 改进参数绑定
+* 改进文件缓存的并发删除
+* 改进`inc/dec/exp`更新的安全性
+* 增加控制台配置
+
+## 2018-1-31 V5.0.15
+
+该版本主要进行了一些修正和完善
+
+* 改进View类
+* 改进chunk方法
+* 改进模板引擎的表达式语法
+* 改进自关联查询多级调用问题
+* 关联定义增加`selfRelation`方法用于设置是否自关联
+* 改进file类型的缓存`inc`和`dec`方法不改变缓存有效期
+* 改进软删除 支持设置`deleteTime`属性关闭
+* 改进`union`查询
+* 改进查询缓存
+* 优化File缓存自动生成空目录的问题
+* 改进日志写入并发问题
+* 修正`MorphTo`关联
+* 改进`join`自关联查询
+* 改进`case`标签解析
+* 改进Url类对`url_convert`配置的支持
+
+
+## 2018-1-1 V5.0.14
+
+V5.0.14版本主对复合主键进行了更多支持,改进了PHP7的兼容性,并且对数据库的一些问题做了改进。
+
+主要更新如下:
+
+* 改进Validate类的unique验证
+* Validate类增加checkRule方法用于静态验证多个规则
+* 改进多对多关联的save方法
+* 改进多对多的pivot对象
+* 修正setDec方法的延迟写入
+* max和min方法增加第二个参数用于设置是否强制转换数字
+* 改进View类
+* 改进join关联自身的问题
+* 改进union查询
+* 改进Url类
+* 改进同名路由不同请求的注册
+* 改进Builder类parseData对空数组的判断
+* 改进模板替换
+* 调整BelongsTo的hasWhere方法
+* 改进模板的编译缓存命名规则 增加布局模板的标识
+* 改进insertall方法
+* 改进chunk方法支持复合主键
+* 改进Error类的一个兼容问题
+* 改进model类的save方法的复合主键包含自增的情况
+* save方法改进复合主键的支持
+* 改进mysql的insertAll方法
+* 改进redis长连接多编号库的情况
+
+## 2017-12-12 V5.0.13
+
+`V5.0.13`主要是对模型和日志方面做了一些改进
+
+### [数据库和模型]
+
+* 改进Model类`save`方法对`oracle`的支持
+* 改进中间表模型的实例化
+* 改进`Pivot`类
+* 模型`saveall`方法支持配合`isUpdate`方法
+* 模型类增加`force`方法设置是否强制更新所有数据
+* 关联自动删除增加一对多关联删除支持
+* 改进`hasWhere`查询的数据重复问题
+* 改进一对多`with`关联查询的`field`支持
+* 模型`saveall`方法支持返回数据集 读取`resultSetType`属性
+* 改进废弃字段判断
+* 模型的`hasWhere`方法增加`fields`参数
+* 改进断线重连异常捕获机制
+* 修正Query类的`inc`和`dec`方法的Mysql关键词问题
+* 修正数据集对象的BUG
+
+### [其它]
+
+* 增加`app_dispatch`钩子位置
+* cookie类`httponly`参数默认改为false
+* File日志驱动增加`single`参数配置是否记录单个文件日志
+* 单个日志文件支持大小设置
+* 改进日志记录的ip地址
+* Redis缓存驱动改用`serialize`序列化替代json序列化
+* 改进异常捕获
+* 改进上传文件验证
+* 修正redis驱动
+* 改进File缓存的`clear`方法
+* 代码格式化规范
+* 改进一处PHP7.2的兼容问题
+* 调试模式下不读取字段缓存文件
+* `default_filter`支持在模块中配置生效
+
+## 2017-11-06 V5.0.12
+
+5.0.12是一个修正版本,包含了上个版本发布以来的一些修正和完善,主要包括:
+
+* 上传类和验证类的多语言支持;
+* 模型增加排除和废弃字段支持;
+* 改进insertAll方法的分批处理;
+* 改进对枚举类型的参数绑定支持;
+* 修正社区反馈的问题;
+
+
+### [数据库和模型]
+
+* 改进Connection类的getRealSql方法
+* 改进append方法支持一对一关联的bind设置
+* 改进whereTime查询
+* 改进model类的`destroy`方法
+* 修正softdelete
+* 修正`chunk`方法对时间字段的支持
+* Collection类增加`push`方法
+* 改进alias方法 
+* 修正模型类的`append`处理
+* 改进`appendRelationAttr`方法
+* 改进HasManyThrough关联
+* 改进MorphTo关联
+* 模型增加废除字段`disuse`定义
+* 增加排除字段方法`except`
+* 修正`has`方法 
+* 改进参数绑定类型对枚举类型的支持
+* 改进`insertAll`方法的分批处理
+
+
+### [其它]
+
+* 改进Loader类`controller`和`validate`方法支持多层
+* 验证提示信息支持多语言
+* File类错误信息支持多语言 
+* 模板渲染异常处理
+* 修正rest控制器
+* 改进trace驱动
+* 改进Cache类的`remember`方法 
+* 改进`url_common_param`的情况下urlencode的问题
+* 改进Url类
+* 改进`exception_handle`配置参数对闭包的支持 
+* 执行路由缓存命令前检测RUNTIME_PATH是否存在 
+* 调整部分`CacheDriver::dec`在为空的时候递减的行为
+* 优化移动端的显示
+* 改进对JSON-Handle插件的支持
+* 改进redis的`get`方法
+* 改进Request类的`host`方法
+
+
+## 2017-09-08 V5.0.11
+
+5.0.11是一个安全及修正版本,包含了上个版本发布以来的一些修正和完善,更新了几处可能的安全问题,主要包括:
+
+* 完善缓存驱动;
+* 改进数据库查询;
+* 改进URL生成类;
+* 缓存有效期支持指定过期时间;
+
+### [数据库和模型]
+
+* 改进数据库驱动类
+* 改进`group`方法的字段关键字冲突
+* 修正聚合查询返回null的问题
+* 改进Db类的强制重连
+* 改进关联的属性绑定
+* 修正事务的断线重连
+* 修正对象的条件查询
+* Db类增加`clear`方法
+* 改进数组查询条件中的`null`查询
+* 改进Query类的`chunk`方法支持排序设置
+* 改进HasOne和HasMany关联的`has`方法
+* 改进软删除的关联删除
+* 改进一个字段多次查询条件
+
+### [其它]
+
+* 缓存有效期支持指定过期时间(`DateTime`);
+* 改进Url生成对端口号的支持
+* 改进`RouteNotFound`异常提示
+* 改进路由分组的全局完整路由匹配
+* 修正部分验证规则的错误提示问题
+* 支持数据集和模型的XML响应输出
+* 改进模板的三元运算标签
+* 改进控制器不存在的错误提示
+* input助手函数支持`route`变量获取
+* 支持在配置文件中读取额外配置参数
+* 完善分页类
+* 修复Trait命名空间重复问题
+* 修正Request类的env方法
+* 优先使用Cookie中的多语言设置
+* 获取缓存标签的时候过滤无效的缓存标识
+* 修正路由批量注册的一个BUG
+* `exception_handle`配置参数支持使用闭包定义`render`处理
+* 请求缓存支持缓存标签设置
+* 缓存类`remember`方法增加并发锁定机制
+* 改进上传类对`swf`的支持
+* 改进Session类的`prefix`方法
+
+## 2017-07-04 V5.0.10
+
+5.0.10是一个修正版本,并包含了一个安全更新,推荐更新,主要包含:
+
+* 数据库和模型的多处改进
+* 添加新的行为监听
+* 路由支持Response设置
+* 改进调试模式下数据库敏感信息暴露
+
+### [数据库和模型]
+
+* 修正join其他表时生成的delete语句错误
+* 修正远程一对多
+* insertall支持replace
+* 修正多对多默认的中间表获取
+* 改进更新后的模型`update_time`数据更新
+* model类增加`removeRelation`方法
+* 模型类增加`setInc`和`setDec`方法
+* 模型类增加`autoWriteTimestamp`方法动态设置时间字段写入
+* 改进驱动类方法的断线重连判断
+* 改进多对多的数据更新
+* 改进BelongsToMany关联查询
+* 修正Query类的value和column方法
+* 改进in查询的去重问题
+* 修正模型类的scope方法传值问题
+* 调整模型的save方法`before_update`检查位置
+* 修改器和获取器方法支持第三个关联数据参数
+
+### [其它]
+
+* 默认关闭调试模式
+* 修复配置extra目录临时文件的错误加载
+* 添加log存储完成行为监听 `log_write_done`
+* 改进Build类生成公共文件的目录判断
+* 增加`response_send`行为监听 
+* 路由增加response参数用于绑定response处理行为
+* 改进redirect的参数传入
+* 改进环境变量的布尔值读取
+* 改进Url类的域名传入
+* 修正命令行文件生成
+* 改进命令行下面的URL生成
+* 添加`app_host`参数设置默认的URL根地址
+* 改进`Request`类`isSsl`方法判断支持CDN
+* 增加`record_trace`配置参数用于日志记录trace信息
+
+## 2017-05-20 V5.0.9
+
+5.0.9是一个修正版本,推荐更新,主要更新包含:
+
+### [数据库和模型]
+
+* 修正关联自动写入
+* 修正模型数据变化判断对为空数据的支持
+* 修正Query类的useSoftDelete方法返回值
+* 修正一对一嵌套关联数组定义的问题
+* 修正使用了手动参数绑定的时候的缓存BUG
+* 改进数据库类的一处不能嵌套查询的缺陷
+* 改进数据库断线重连判断
+* 改进模型的appendRelationAttr方法
+* 改进模型类destroy方法传入空数组的时候不进行任何删除操作
+* 改进一对多关联数据的输出
+* 改进模型的save方法对allowField方法的支持
+* 改进分页类的toarray方法 增加总页数
+* 比较运算增加闭包子查询支持
+* db助手函数默认不再强制重新连接
+* 改进belongsToMany的查询字段指定
+* 分页类增加each方法
+
+### [其它]
+
+* 修正路由分组的路由规则大小写识别问题
+* 修正命令行的日志切割生成
+* 修复URL生成时路由规则中的参数没有进行 urlencode
+* 改进Request类的filter过滤机制 支持正则
+* 改进Response类支持手动设置contentType
+* 修正异常模板中助手函数未定义错误
+
+## 2017-04-28 V5.0.8
+
+### 主要调整
+
+* 改进关联模型
+* 改进日志记录
+* 增加多态一对一关联
+* 修正社区反馈的一些BUG
+
+### [ 请求和路由 ]
+
+* 修正Request类`cookie`方法对前缀的支持
+* 改进全局请求缓存的缓存标识
+* 改进Request类`param`方法
+* 修正别名路由
+
+### [ 模型和数据库 ]
+
+* 改进模型数据的更新检查
+* 改进Query类的`column`方法
+* 改进软删除条件在使用闭包查询情况下多次生成的问题
+* belongsToMany增加数据同步方法
+* 查询范围支持静态调用
+* 增加多态一对一(MorphOne)关联
+* 改进BelongsTo关联
+* 改进多态关联支持关联数据添加和注销
+* 改进多对多关联,支持中间表模型自定义 并且定义的时候不需要使用完整表名
+* 改进浮点数类型转换避免出现逗号
+* 调整关联模型的save方法返回值
+* 模型类的get方法第一个参数必须 如果传入null则返回null
+* model的save方法改进如果数据没有更新不执行
+* Query增加`useSoftDelete`方法可以单独设置软删除条件
+* 重载BelongsToMany的`selectOrFail`和`findOrFail`方法
+* 重载BelongsToMany的`select` 、`find`和 `paginate`方法
+* 增加模型和`Pivot`对象的`parent`属性
+* 多对多关联支持设置中间表模型
+* 改进Query类的`view`方法中字段的关键字问题
+* 主从数据库的时候开启事务始终操作主库
+
+### [ 其它 ]
+
+* 改进Cookie类的`get`方法支持获取全部
+* `schema`指令增加`config`参数,支持传入数据库连接配置
+* 改进cache类的`store`方法为当次有效
+* 修正cache助手函数对`option`传参的支持
+* 修复`optimize:autoload`命令在`EXTEND_PATH`目录不存在的情况下,类库映射生成错误问题
+* 支持自定义的根命名空间也可以生成类库映射缓存
+* 验证字段比较支持对比其他字段
+* 修复`Session::prefix('xxx');`设置当前作用域BUG
+* 改进`optimize::schema`指令
+* 修复`clear`指令无法删除多级目录下文件的问题
+* 改进默认语言读取和自动侦测
+* 改进日志记录格式 并且命令行下面日志改为实时写入
+* 修正模板标签默认值某些情况无效bug
+* 改进Url生成对完整域名的支持
+* 改进`Clear`指令不删除`.gitignore` 文件
+* 修复Memcache缓存驱动的`inc`方法
+
+### 调整
+
+* 如果自定义了应用的命名空间的话,原来的`app_namespace`配置参数改为`APP_NAMESPACE`常量在入口文件中定义
+* 多对多关联的中间表名称不需要添加表前缀
+* 模型的scope方法之后只能使用数据库查询方法而不能使用模型的方法
+
+## 2017-02-24 V5.0.7
+
+### 主要调整
+
+本次更新主要为BUG修正和改进,主要改进如下:
+
+* 改进全局请求缓存对子域名的支持;
+* 改进数据缓存自动更新机制;
+* 关联统计支持指定统计属性名;
+* 模型嵌套关联支持数组方式;
+* HasOne关联支持`has`和`hasWhere`方法;
+* 路由的`ext`和`deny_ext`参数允许设置为空(表示不允许任何后缀或者必须使用后缀访问);
+
+### 修正如下
+
+* 修正 IN / NOT IN 型查询条件为空导致的 sql 语法错误
+* 修正分页类的`toArray`方法对简洁模式的支持
+* 修正Model类`delete`方法对多主键的处理
+* 修正软删除对`Mongodb`的支持
+* 修正`Connection`类一处可能的错误
+* 改进Query类的find方法的缓存机制
+* 修正BelongsTo关联
+* 修正JOIN方式一对一关联预载入闭包查询
+* 修正Query类的`insert`方法一处可能存在的警告错误
+* 修正Model类一处Collection的`use`冲突
+* 修正Model类`hasWhere`方法
+* 修正URl生成对`ext`参数的支持
+* 文件缓存`clear`方法会删除空目录
+* 修正Route类的`parseUrlPath`方法一处问题
+
+### 调整如下
+
+* 默认关闭session的安全参数`secure`,此选项仅能在HTTPS下设置开启
+
+## 2017-02-07 V5.0.6
+
+### 主要调整:
+
+本次更新主要为BUG修正及优化(可无缝升级):
+
+* 数据库支持断线重连机制;
+* 改进查询事件的回调参数;
+* 改进数据自动缓存机制;
+* 增加时间字段自动格式转换设置;
+* `MongoDb`和`Oracle`扩展更新至最新核心框架;
+
+### [数据库和模型]
+
+* 修正hasMany关联的`has`方法
+* 去除一些数据库惯例配置 避免使用数据库扩展的时候影响
+* 改进多对多的`attach`方法的返回值
+* 增加Mysql的断线重连机制和开关
+* 改进Query类的`find`方法数据缓存机制
+* 改进Query类查询事件的回调参数
+* 改进Query类的自动缓存更新
+* Model类增加`readonly`方法
+* 改进Model类的`has`和`hasWhere`方法
+* 改进模型类的`get`和`all`方法 第二个参数为true或者数字表示缓存参数
+* 修复闭包查询条件为空导致的 sql 语法错误
+* 改进Query类的`setBuilder`方法 避免因自定义连接器类后找不到生成器类
+* 删除Connection类废弃属性`resultSetType`
+* 优化Connection类`close`方法
+* 修正Connection类的`bindParam`方法对存储过程的支持
+* 数据库配置参数`datetime_format` 设置为`false`表示关闭时间字段自动转换输出
+* 改进软删除的数据库兼容性问题 支持`Mongodb`
+
+### [其它]
+
+* 改进Url类生成 `root`为`/`的情况
+* redirect助手函数和controller类的redirect方法增加with参数
+* 全局请求缓存添加排除规则 添加request_cache_except配置参数
+* Cache类store方法参数允许为空 表示获取当前缓存驱动句柄
+* 改进Validate类的ip验证规则
+
+## 2017-01-23 V5.0.5
+### 主要调整:
+
+本次更新主要改进了数据访问层和模型关联:
+
+* 增加快捷查询及设置方法;
+* 增加关联统计功能;
+* 增加关联查询延迟预载入功能;
+* 增加关联一对一自动写入和删除;
+* 改进存储过程查询;
+* 改进关联数据输出;
+* 优化查询性能;
+* 模型时间字段自动格式化输出;
+
+### [请求和路由]
+
+* 改进路由定义的后缀检测
+* Route类的`rest`方法支持覆盖定义
+* 改进Request类的`put`和`post`方法对`json`格式参数的接收
+* Request类增加`contentType`方法
+* 改进Route类`setRule`方法 
+* 改进Request类的`create`方法
+* 改进路由到控制器类的方法对默认渲染模板的影响
+* 修正Url类`build`方法定义路由别名后的BUG
+
+### [数据库和模型]
+
+* 增加关联统计功能
+* 增加一对一关联自动写入功能
+* 修正聚合模型的`delete`方法
+* 改进Model类的`useGlobalScope`方法
+* Model类的日期类型支持设置为类名
+* Query类增加`data`/`inc`/`dec`/`exp`方法用于快捷设置数据 `insert`和`update`方法参数可以为空 读取`data`设置数据
+* 优化Connection的查询性能
+* 修正Builder类的`parseOrder`方法
+* 修正BelongsToMany类的`attach`方法
+* BelongsToMany类的`attach`方法改进 支持批量写入
+* 改进BelongsToMany类的`saveall`方法 增加第三个参数 用于指定额外参数是否一致
+* Query类的`order`方法支持多次调用合并
+* 改进`count`方法对`group`查询的支持
+* 增加时间戳自动写入的判断
+* 改进Model类`writeTransform`方法
+* 改进Model的时间戳字段写入和读取
+* 写入数据为对象的时候检测是否有`__toString`方法
+* 改进Mysql驱动的`getFields`方法
+* 改进自动时间字段的输出
+* `like`查询条件支持数组
+* 自动时间字段的获取自动使用时间格式化
+* 改进单个字段多次Or查询情况的查询
+* 修正`null`查询的条件合并
+* 改进Query类`paginate`方法第一个参数可以使用数组参数
+* 改进数据集对象的返回,由Query类的select方法进行数据集转换,原生查询不再支持返回数据集对象
+* 增加`whereNull`、`whereIn`等一系列快捷查询方法
+* `fetchPdo`方法调整
+* 改进对存储过程调用的支持 改进`getRealSql`的调用机制 改进数据表字段使用中划线的参数绑定支持
+* 数据库配置参数增加`result_type` 用于设置数据返回类型 方法参数名称调整
+* 改进Query类的`whereTime`方法支持更多的时间日期表达式(默认查询条件为大于指定时间表达式)
+* 取消`min`/`max`/`sum`/`avg`方法的参数默认值
+* Query类增加`getPdo`方法用于返回`PDOStatement`对象
+* 改进`today`的日期表达式查询
+* 改进关联属性的获取
+* 改进关联定义中包含查询条件后重复执行的问题
+* 改进参数绑定支持中文字段自动绑定
+* 改进Builder类的`insertall`方法 增加对null和对象数据的处理
+* 改进参数绑定类型 支持`bit`类型自动绑定
+* Connection类`model`方法更改为`getQuery`
+* 优化Connection类`__call`方法
+* 修正聚合模型
+* 一对一关联预载入默认改为IN查询方式
+* 增加`collection`助手函数用于数据集转换
+* 增加`load_relation`助手函数用于数组的延迟预载入
+* 改进Model类的`has`方法第二个参数支持使用数组和闭包,无需再使用`hasWhere`
+* `relation`方法支持嵌套关联查询
+* 增加`think\model\Collection`作为模型的数据集查询集合对象
+* 取消关联定义的`alias`参数(仅`morphTo`保留)
+* Model类的`delete`方法,支持没有主键的情况
+* Model类的`allowField`方法支持逗号分割的字符串
+* 改进写入数据的自动参数绑定的参数名混淆问题
+* 关联预载入查询的属性名默认使用小写+下划线命名
+* Query类的`with`和`relation`方法支持多次调用
+* Collection类增加`hidden`、`visible`和`append`方法
+* 修正软删除的强制删除方法
+
+### [其它]
+
+* `unique`验证规则支持指定完整模型类 并且默认会优先检测模型类是否存在 不存在则检测数据表
+* 改进`Loader`类的`model`、`controller` 和 `validate`方法 支持直接传入类名实例化
+* `Session`类增加安全选项`httponly`和`secure`
+* 可以允许自定义`Output`的driver,以适应命令行模式下调用其它命令行指令 
+* 改进`loader`类`action`的参数污染问题
+* Validate类的`confirm`验证改为恒等判断
+* 改进`Validate`类的错误信息处理
+* 修正`Validate`类的布尔值规则验证
+* 改进`cookie`助手函数对前缀的支持
+* 文件缓存默认开启子目录缓存避免文件过多导致性能问题
+
+### [调整]
+* Connection类`model`方法更改为`getQuery`
+* 原生查询不再支持返回数据集对象
+* 分页查询返回类型变成`think\Paginator`(用法不变)
+* 模型的时间日期字段会自动进行格式化输出,不需要进行额外处理。
+* Session类添加了`secure`和`httponly`参数,并且默认是true
+
+## 2016-12-20 V5.0.4
+### 主要调整:
+
+* 关联模型重构并增加多态一对多关联;
+* 数据库支持一个字段多次调用不同查询条件;
+* 增加数据库CURD事件支持;
+* 路由到类和控制器的方法支持传入额外参数;
+* 支持全局模板变量赋值;
+* 模型支持独立设置查询数据集对象;
+* 日志针对命令行及调试做出改进;
+* 改进Hook类的行为方法调用
+
+### [请求和路由]
+* 请求缓存支持模块单独开启
+* Request类`post`方法支持获取`json`方式的请求数据
+* 路由到类的方法和控制器方法 支持传入额外参数,用于方法的参数
+* 改进控制器自动搜索的目录规范
+* 改进请求缓存
+* 改进自动参数绑定
+* 修正路由的请求缓存设置
+* 改进Route类name方法
+
+### [数据库和模型]
+* 增加数据库查询(CURD)事件
+* 改进多表更新的字段不存在问题
+* 改进Model类的`useGlobalScope`方法
+* 修正子查询作为表名查询的问题
+* Model类增加`resultSetType`属性 用于指定模型查询的数据集对象(默认为空返回数组) 
+* Model类增加`toCollection`方法(自动调用)
+* 关联模型架构调整
+* 改进预载入`with`方法的参数支持小写和下划线定义
+* 修正关联多对多一处错误
+* 改进关联多对多的查询
+* 关联模型支持多态一对多关联
+* 预载入关联查询支持关联对象属性绑定到当前模型
+* 支持追加关联对象的属性到当前模型数据
+* 一对一关联预载入支持JOIN和IN两种方式(默认为JOIN)
+* 改进多对多查询
+* 改进模型更新的数据变化比较规则
+* 查询支持一个字段多次查询条件
+* 改进sql日志的sql语句
+* 修正`join`自身表的别名覆盖问题
+* 模型类的`connection`属性和数据库默认配置合并
+* 改进`in`和`between`查询条件的自动参数绑定
+* 改进Query类对数据集对象以及关联字段排序的支持
+* 增加模型的快捷事件方法
+* 改进Query类的`getTableInfo`方法缓存读取
+* model类的`saveAll`方法支持调用`allowField`方法进行字段过滤
+* 修正关联查询的时候 `whereTime`方法的bug
+* 改进Query类的聚合查询
+* table方法支持字符串方式的子查询
+* 修正`count` `avg`方法使用`fetchsql`无法正确返回sql的问题
+
+### [其它]
+* 改进命令行下的日志记录
+* 部署模式下简化日志记录
+* 增加debug日志类型 仅限调试模式记录
+* 改进Template类`parseTemplateFile`方法
+* 改进Validate类的`getRuleMsg`方法
+* 控制器的`error`方法在AJAX请求默认返回url为空
+* Validate类架构方法增加`field`参数 用于设置验证字段的描述
+* 改进App类`invokeMethod`方法对架构函数依赖注入的支持
+* 增加RedirectResponse的`restore`方法返回值
+* View类增加`share`静态方法 用于静态赋值模板变量
+* 验证类增加`hasScene`方法判断是否存在某个场景的验证配置
+* 修正redis和session驱动的`destroy`方法返回值
+* 空操作方法的参数传入去掉操作方法后缀
+* 在控制器中调用request和view增加类型提示
+* 改进`input`助手函数支持多维数据获取
+* Cache类增加`pull`和`remember`方法
+* 改进验证类的`confirm`验证规则 支持自动规则识别
+* 改进验证类的错误信息定义
+* 增加Validate类自定义验证错误信息的替换规则
+* Cookie类增加`forever`方法用于永久保存
+* 模板渲染支持从视图根目录读取模板
+* 改进Hook类的exec方法
+
+### [调整]
+* Db类查询不再支持设置自定义数据集对象
+* 废除Query类的`fetchClass`方法
+* 控制器的`error`方法在AJAX请求默认返回的url为空
+* 关联方法定义不支持使用小写下划线,必须使用驼峰法
+* 行为类的方法必须使用驼峰法命名
+
+## 2016-11-11 V5.0.3
+### 主要调整:
+* 请求缓存增强;
+* 路由增强;
+* 数据库和模型完善;
+* 支持反射的异常捕获;
+* File类改进;
+* 修正社区反馈的一些BUG;
+
+### [ 请求和路由 ]
+
+* 资源路由自动注册的路由规则的时候会记录当前使用的资源标识;
+* 增强请求缓存功能和规则定义,支持全局自动缓存
+* 修正控制器自动搜索的大小写问题
+* 修正路由绑定到命名空间后 类的自动定位
+* 改进Route类的parseRule方法 路由地址中的变量替换不自动去除路由变量
+* 改进控制器自动搜索
+* Route类增加setOption和getOption方法 用于记录当前路由执行过程中的参数信息
+* 优化路由分组方法
+* 改进分组路由的url生成
+
+### [ 数据库和模型 ]
+
+* 一对一关联查询方法支持定义`field`方法
+* 聚合模型支持设置`field`属性
+* 改进Query类的`alias`方法
+* 改进Query类`join`和`view`方法的table参数
+* 改进Query类`where`方法
+* 改进Query类的`paginate`方法,支持`order`方法
+* 改进Query类的`min`和`max`方法支持日期类型
+* 修正软删除`withTrashed`方法
+* 优化Connection类的`getRealSql`方法生成的sql
+
+### [ 其它 ]
+* 增加request_cache和request_cache_expire配置参数用于配置全局请求缓存;
+* 修正input助手函数的数组过滤
+* cache助手函数支持清空操作
+* 改进Config类load方法 一级配置名称强制转为小写
+* 修正Url多次生成的问题
+* File类修正某些环境下面无法识别上传文件的问题
+* 改进App类的空操作方法调用
+* 域名部署URL生成不依赖 url_domain_deploy 配置参数
+* 修正Url类域名部署的问题
+* 视图文件目录支持集中式存放 不放入模块目录
+* cache助手函数支持 remember方法
+* Request类的input方法或者input助手函数的`filter`参数支持传入null 表示不过滤
+
+## 2016-10-24 V5.0.2
+### 主要调整:
+
+* 数据库和模型完善;
+* 路由功能完善;
+* 增加`yaml`配置格式支持;
+* 依赖注入完善;
+* Session类完善;
+* Cookie类完善;
+* Validate类完善;
+* 支持反射类的异常捕获;
+* 修正社区反馈BUG;
+
+### [ 请求和路由 ]
+* 依赖注入的类如果定义了`invoke`方法则自动调用
+* Request类的`header`方法增加自定义header支持
+* Request类禁止直接实例化调用
+* 改进Request类ip方法
+* 路由变量规则支持闭包定义
+* 路由参数增加`ajax`和`pjax`判断
+* 别名路由增加允许和排除操作
+* 改进路由域名绑定后的url生成
+* 路由生成改进对路由到类的支持
+* 路由生成支持`url_param_type`配置参数
+* 路由生成支持别名路由
+* Route重定向规则支持更多` schema`
+* 别名路由支持定义单独方法的请求类型
+* 改进路由分组的url生成
+* 路由规则的组合变量支持可选分隔符定义
+* 改进路由合并参数的获取
+* 路由规则支持单独设置url分隔符,路由参数为 `param_depr`
+* 自动搜索控制器支持自定义访问控制器层的情况
+* 改进路由标识不区分大小写
+* 改进路由地址是否定义过路由规则的检测
+
+### [ 数据库和模型 ]
+* 改进Query类的join方法
+* 改进Query类分页方法的参数绑定
+* 修正软删除方法
+* 修正Query类parseOrder方法一处错误
+* 修正sqlsrv驱动parseOrder方法
+* 修正Query类setInc和setDec方法
+* 改进Model类的save方法支持非自增主键的处理
+* 整型字段的参数绑定如果为空写入默认值0
+* 改进Model类has和hasWhere方法
+* 改进Query类的value方法缓存判断
+* 改进Query类join方法对子查询支持
+* 改进Query类的table方法和alias方法用法
+* 关联预载入支持`hasOne`自关联
+* 改进Builder类的parseKey方法
+* 改进Builder类的join/alias/table方法的解析
+* 改进全局查询范围
+* 改进Query类的聚合查询方法的返回值
+* 改进关联属性的读取
+* 改进聚合模型主键和关联键相同的情况
+* 改进模型在开启`class_suffix`参数情况下的name属性的识别
+
+### [ 其它 ]
+* Cache类增加`remember`方法 用于当获取的缓存不存在的时候自动写入
+* Session类增加`flash`方法用于设置下一次请求有效的值 
+* Session类增加`flush`方法用于清空当前请求有效的值 
+* Session类增加`push`方法用于更新数组数据
+* 增加yaml配置格式支持
+* 改进App类的反射异常无法捕获问题
+* 修正session助手函数的清空操作
+* 改进验证类的`image`方法
+* 改进验证类的`activeUrl`方法 
+* 改进自定义验证规则的使用
+* 改进控制器自动搜索后的控制器名获取
+* 修正import方法加载extend目录类库
+* 修正json_encode时 "Failed calling XXX::jsonSerialize()" 的异常
+* 改进Loader类model和validate方法的单例问题
+* 改进方法执行的日志记录
+* 改进模板引擎的Think变量解析
+* 改进Lang类`load`方法
+* 验证错误信息支持多语言读取
+* 改进ROOT_PATH常量
+* 改进语言包加载
+* 改进模板session和cookie变量获取,自动判断前缀
+* 缓存驱动统一增加handler方法用于获取操作对象的句柄(某些缓存类型可能为null)
+* File类增加`__call`方法用于兼容5.0版本的`md5`和 `sha1`方法
+* 改进文件缓存驱动的`clear`方法
+* Lang类增加`setLangCookieExpire`方法设置多语言cookie过期时间
+* 增加`route_complete_match`配置参数
+
+### [ 调整 ]
+下列模型属性和方法由原来的静态(static)定义改为动态定义:
+* 聚合模型的`relationModel`属性
+* Model类的`useGlobalScope `属性
+* 全局查询范围方法`base`改为动态方法
+* 软删除属性 `deleteTime`属性
+
+
+## 2016-9-28 V5.0.1
+### 主要调整:
+* [依赖注入](215849)完善;
+* [扩展配置](118027)文件位置调整;
+* 新增数据表[字段缓存命令](211524);
+* 支持设置当前的查询对象;
+* 支持[请求和路由缓存](215850);
+
+### [ 请求和路由 ]
+* 改进Controller类的`success`和`error`方法的跳转地址识别 支持更多Scheme
+* 操作方法和架构方法支持任何对象自动注入
+* Requesst类增加`getInput`方法 用于获取` php://input`值
+* 路由到方法的时候 支持架构方法注入请求对象
+* 改进Route类路由到类的判断
+* Request增加`cache`方法,支持请求缓存
+* 绑定到模块后 路由依然优先检查
+* 路由增加请求缓存参数
+* 修正路由组合变量的可选变量的BUG
+
+### [ 数据库 ]
+* 修正`pgsql`数据库驱动的数据表字段信息读取
+* 改进Query类的`view`方法 第二个参数默认值更改为true 获取全部的字段
+* 数据库配置信息增加`query`参数用于配置查询对象名称
+* 型类增加`query`属性用于配置模型需要的查询对象名称
+* 改进数据表字段缓存读取
+* 改进数据表字段缓存生成 模型为抽象类或者 没有继承Model类 不生成字段缓存
+* 改进模型的字段缓存 虚拟模型不生成字段缓存
+* 改进数据表字段缓存生成 支持读取模块的模型生成
+* 改进聚合模型的`save`方法 主键写入
+* 模型类的field属性定义简化 取消`Query`类的`allowField`和`setFieldType`方法及相关属性
+* 改进数据表字段缓存生成 支持生成多个数据库的
+* 更新数据库驱动类 改进`getTables`方法
+* 增加` optimize:schema` 命令 用于生成数据表字段信息缓存
+* 修正一个查询条件多个条件的时候的参数绑定BUG
+* 分页查询方法`paginate`第二个参数传入数字表示总记录数
+* 修正mysql的`JSON`字段查询
+* 改进Query类的getOptions方法 当name参数不存在的时候返回null
+
+### [ 模型和关联 ]
+* 模型类的field属性不需要添加字段类型定义
+* 改进Model类 添加`getDb`静态方法获取db查询对象
+* 改进聚合模型`save`方法返回值
+* 改进Relation类`save`方法
+* 修正关联模型 多对多`save`方法一处问题
+* 改进Model类的save方法 修正不按主键查询的更新问题
+* 时间字段获取器获取的时候为NULL则不做转换
+
+### [ 其它 ]
+
+* 改进配置缓存生成 支持扩展配置
+* 取消`extra_config_list`配置参数 扩展配置文件直接放到 `extra`目录下面即可自动加载(数据库配置文件位置不变)
+* cache助手函数支持判断缓存是否有效
+* 修正 模板引擎驱动类的`config`方法
+* 修复在配置Model属性field=true情况下,通过`__call`调用db()引发的BUG
+* 改进模板引擎驱动的config方法 支持获取配置参数值
+* 改进redirct的url地址解析
+* 删除`File`类的`md5`和`sha1`方法 改为`hash`方法 支持更多的散列值类型生成
+* 增加`response_end`行为标签
+* 改进默认语言的加载
+
+## 2016-9-15 V5.0
+
+### [ 请求和路由 ]
+
+* Request对象支持动态绑定属性
+* 定义了路由规则的URL原地址禁止访问
+* 改进路由规则存储结构
+* 路由分组功能增强,支持嵌套和虚拟分组
+* 路由URL高效反解
+* 改进Request对象param方法获取优先级
+* 路由增加name方法设置和获取路由标识
+* 增加MISS和AUTO路由规则
+* Route类增加auto方法 支持注册一个自动解析URL的路由
+* 路由规则支持模型绑定
+* 路由变量统一使用param方法获取
+* 路由规则标识功能和自动标识
+* 增加生成路由缓存指令 optimize:route
+* Request对象增加route方法单独获取路由变量
+* Request对象的param get post put request delete server cookie env方法的第一个参数传入false 则表示获取原始数据 不进行过滤
+* 改进自动路由标识生成 支持不同的路由规则 指向同一个路由标识,改进Url自动生成对路由标识的支持
+* 改进Request类 filter属性的初始化
+* 改进Request类的isAjax和isPjax方法
+* Request类增加token方法
+* 路由配置文件支持多个 使用 route_config_file 配置参数配置
+* 域名绑定支持https检测
+* 改进域名绑定 支持同时绑定模块和其他 支持绑定到数组定义的路由规则,取消域名绑定到分组
+* 路由规则增加PATCH请求类型支持
+* 增加route_complete_match配置参数设置全局路由规则定义是否采用完整匹配 可以由路由规则的参数complete_match 进行覆盖
+* 改进路由的 后缀参数识别 优先于系统的伪静态后缀参数
+* Url类增加root方法用于指定当前root地址(不含域名)
+* 改进Url生成对可选参数的支持
+
+### [ 数据库 ]
+
+* 查询条件自动参数绑定
+* 改进分页方法支持参数绑定
+* Query类的cache方法增加缓存标签参数
+* Query类的update和delete方法支持调用cache方法 会自动清除指定key的缓存 配合查询方法的cache方法一起使用 
+* 改进Query类的延迟写入方法
+* Query类的column和value方法支持fetchsql
+* 改进日期查询方法
+* 改进存储过程方法exec的支持
+* 改进Connection类的getLastInsID方法获取
+* 记录数据库的连接日志(连接时间和DSN)
+* 改进Query类的select方法的返回结果集判断  
+* Connection类增加getNumRows方法
+* 数据库事务方法取消返回值
+* 改进Query类的chunk方法对主键的获取
+* 改进当数据库驱动类型使用完整命名空间的时候 Query类的builder方法的问题
+
+### [ 模型 ]
+
+* 增加软删除功能
+* 关联模型和预载入改进
+* 关联预载入查询闭包支持更多的连贯操作
+* 完善savell方法支持更新和验证
+* 关联定义统一返回Relation类
+* Model类的has和hasWhere方法对join类型的支持
+* Model类的data方法 批量赋值数据的时候 清空原始数据
+* Model类的get方法第三个参数传入true的时候会自动更新缓存
+* Model类增加只读字段支持
+* Model类增加useGlobalScope方法设置是否启用全局查询范围
+* Model类的base方法改为静态定义 全局多次调用有效
+* Model类支持设定主键、字段信息和字段类型,不依赖自动获取,提高性能
+* Model类的data方法 支持修改器
+* 改进Relation类对非数字类型主键的支持
+* 改进Relation类的一对多删除
+* 修正Relation类的一对多关联预载入查询
+
+### [ 日志和缓存 ]
+
+* 支持日志类型分离存储
+* 日志允许设置记录级别
+* 增加缓存标签功能
+* 缓存类增加pull方法用于获取并删除
+* cache助手函数增加tag参数
+* 简化日志信息,隐藏数据库密码
+* 增加cache/session redis驱动的库选择逻辑;
+* memcached驱动的配置参数支持option参数
+* 调试模式下面 日志记录增加页面的header和param参数记录
+* memcached缓存驱动增加连接账号密码参数
+* 缓存支持设置complex类型 支持配置多种缓存并用store切换
+* 缓存类增加tag方法 用于缓存标签设置 clear方法支持清除某个缓存标签的数据
+* File类型日志驱动支持设置单独文件记录不同的日志级别
+* 改进文件缓存和日志的存储文件名命名规范
+* 缓存类增加inc和dec方法 针对数值型数据提供自增和自减操作
+* Cache类增加has方法 get方法支持默认值
+
+### [ 其它 ]
+
+* 视图类支持设置模板引擎参数
+* 增加表单令牌生成和验证
+* 增加中文验证规则
+* 增加image和文件相关验证规则
+* 重定向Response对象支持with方法隐含传参
+* 改进Session类自动初始化
+* session类增加pull方法用于获取并删除
+* 增加Env类用于获取环境变量
+* Request类get/post/put等更改赋值后param方法依然有效
+* 改进Jump跳转地址支持Url::build 解析
+* 优化Hook类
+* 应用调试模式和页面trace支持环境变量设置
+* config助手函数支持 config('?name') 用法
+* 支持使用BIND_MODULE常量的方式绑定模块
+* 入口文件自动绑定模块功能
+* 改进验证异常类的错误信息和模板输出,支持批量验证的错误信息抛出
+* 完善console 增加output一些常用的方法
+* 增加token助手函数 用于在页面快速显示令牌
+* 增加halt方法用于变量调试并中断输出
+* 改进Validate类的number验证规则 和 integer区分开
+* optimize:autoload增加对extend扩展目录的扫描
+* 改进Validate类的boolean验证规则 支持表单数据
+* 改进cookie助手函数支持 判断是否存在某个cookie值
+* 改进abort助手函数 支持抛出HttpResponseException异常
+* 改进File类增加对上传错误的处理
+* 改进File类move方法的返回对象增加上传表单信息,增加获取文件散列值的方法
+* 改进File类的move方法的返回对象改为返回File对象实例
+* 增加clear和optimize:config 指令
+* 改进File类和Validate类的图像文件类型验证
+* 控制器的操作方法支持注入Request之外的对象实例
+* Request类 param(true) 支持获取带文件的数据
+* input助手函数第一个参数增加默认值
+* Validate类增加image验证规则 并改进max min length支持多种数据类型
+* json输出时数据编码失败后抛出异常
+
+### [ 调整 ]
+* 废除路由映射(静态路由)定义
+* 取消url_deny_suffix配置 改由路由的deny_ext参数设置
+* 模型save方法返回值改为影响的记录数,取消getId参数
+* Request对象controller方法返回驼峰控制器名
+* 控制器前置操作方法不存在则抛出异常
+* Loader类db方法增加name标识参数
+* db助手函数增加第三个参数用于指定连接标识
+* Sqlsrv驱动默认不对数据表字段进行小写转换
+* 移除sae驱动 改为扩展包
+* Oracle驱动移出核心包
+* Firebird驱动移出核心包
+* 取消别名定义文件alias.php
+* 配置参数读取的时候取消环境变量判断 需要读取环境变量的时候使用Env类
+* 环境变量定义文件更改为 .env 由原来的PHP数组改为ini格式定义(支持数组方式)
+* 状态配置和扩展配置的加载顺序调整 便于状态配置文件中可以更改扩展配置的参数
+* 取消域名绑定到路由分组功能
+* 控制器类的success和error方法url参数支持传入空字符串,则不做任何处理
+* 控制器的error success result redirect方法均不需要使用return
+* 创建目录的权限修改为0644
+
+
+## 2016-7-1 RC4版本
+### [ 底层架构 ]
+* 增加Request类 并支持自动注入
+* 统一Composer的自动加载机制
+* 增加Response类的子类扩展
+* 增加File类用于上传和文件操作
+* 取消模式扩展 SAE支持降权
+* 优化框架入口文件
+* 改进异常机制
+* App类输入/输出调整
+* 单元测试的完美支持
+* 增加新的控制台指令
+* 取消系统路径之外的大部分常量定义
+* 类库映射文件由命令行动态生成 包含应用类库
+
+### [ 数据库 ]
+
+* 增加分表规则方法
+* 增加日期和时间表达式查询方法
+* 增加分页查询方法
+* 增加视图查询方法
+* 默认保持数据表字段大小写
+* 数据缓存自动更新机制
+* 完善事务嵌套支持
+* 改进存储过程数据读取
+* 支持设置数据库查询数据集返回类型
+
+### [ 模型 ]
+* 增加Merge扩展模型
+* 模型支持动态查询
+* 增加更多的类型自动转换支持
+* 增加全局查询范围
+* toJson/toArray支持隐藏和增加属性输出
+* 增加远程一对多关联
+
+### [ 其它 ]
+* 日志存储结构调整
+* Trace调试功能从日志类独立并增强
+* 原Input类功能并入Request类
+* 类库映射文件采用命令行生成 包含应用类库
+* 验证类的check方法data数据取消引用传参
+* 路由增加MISS路由规则
+* 路由增加路由别名功能
+
+## 2016-4-23 RC3版本
+### [ 底层架构 ]
+* 框架核心仓库和应用仓库分离 便于composer独立更新
+* 数据库类重构,拆分为Connection(连接器)/Query(查询器)/Builder(SQL生成器)
+* 模型类重构,更加对象化
+
+### [ 数据库 ]
+
+* 新的查询语法
+* 闭包查询和闭包事务
+* Query对象查询
+* 数据分批处理
+* 数据库SQL执行监听
+
+### [ 模型 ]
+* 对象化操作
+* 支持静态调用(查询)
+* 支持读取器/修改器
+* 时间戳字段
+* 对象/数组访问
+* JSON序列化
+* 事件触发
+* 命名范围
+* 类型自动转换
+* 数据验证和完成
+* 关联查询/写入
+* 关联预载入
+
+### [ 其它更新 ]
+* 路由类增加快速路由支持
+* 验证Validate类重构
+* Build类增加快速创建模块的方法
+* Url生成类改进
+* Validate类改进
+* View类及模板引擎驱动设计改进
+* 取消模板引擎的模板主题设计
+* 修正社区反馈的一些问题
+* 助手函数重新命名
+* `router.php`文件位置移动
+
+## 2016-3-11 RC2版本
+
+* 重新设计的自动验证和自动完成机制(原有自动验证和完成支持采用traits\model\Auto兼容);
+* 验证类Validate独立设计;
+* 自动生成功能交给Console完成;
+* 对数据表字段大小写的处理;
+* 改进Controller类(取消traits\contorller\View);
+* 改进Input类;
+* 改进Url类;
+* 改进Cookie类;
+* 优化Loader类;
+* 优化Route类;
+* 优化Template类;
+* Session类自动初始化;
+* 增加traits\model\Bulk模型扩展用于大批量数据写入和更新;
+* 缓存类和日志类增加Test驱动;
+* 对异常机制和错误处理的改进;
+* 增加URL控制器和操作是否自动转换开关;
+* 支持类名后缀设置;
+* 取消操作绑定到类的功能;
+* 取消use_db_switch参数设计;
+
+## 2016-1-30 RC1版本
+### [ 底层架构 ]
+
+*   真正的惰性加载
+*   核心类库组件化
+*   框架引导文件
+*   完善的类库自动加载(支持Composer)
+*   采用Traits扩展
+*   API友好(输出、异常和调试)
+*   文件命名规范调整
+
+### [ 调试和异常 ]
+
+*   专为API开发而设计的输出、调试和异常处理
+*   日志类支持本地文件/SAE/页面Trace/SocketLog输出,可以实现远程浏览器插件调试
+*   内置trace方法直接远程调试
+*   异常预警通知驱动设计
+*   数据库SQL性能分析支持
+
+### [ 路由 ]
+
+*   动态注册路由
+*   自定义路由检测方法
+*   路由分组功能
+*   规则路由中的变量支持采用正则规则定义(包括全局和局部)
+*   闭包路由
+*   支持路由到多层控制器
+
+### [ 控制器 ]
+
+*   控制器类无需继承controller类
+*   灵活的多层控制器支持
+*   可以Traits引入高级控制器功能
+*   rest/yar/rpc/hprose/jsonrpc控制器扩展
+*   前置操作方法支持排除和指定操作
+
+
+### [ 模型 ]
+
+*   简化的核心模型
+*   Traits引入高级模型/视图模型/关联模型
+*   主从分布时候主数据库读操作支持
+*   改进的join方法和order方法
+
+### [ 视图 ]
+
+*   视图解析驱动设计(模板引擎)
+*   所有方法不再直接输出而是返回交由系统统一输出处理
+*   动态切换模板主题设计
+*   动态切换模板引擎设计
+
+### [ 数据库 ]
+
+*   完全基于PDO实现
+*   简化的数据库驱动设计
+*   SQL性能监控(需要开启数据库调试模式)
+*   PDO参数绑定改进
+
+### [ 其他方面 ]
+
+*   目录和MVC文件自动生成支持
+*   I函数默认添加变量修饰符为/s
+*   一个行为类里面支持为多个标签位定义不同的方法
+*   更多的社交扩展类库

+ 32 - 0
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.

+ 133 - 0
README.md

@@ -0,0 +1,133 @@
+ThinkPHP 5.0
+===============
+
+[![Total Downloads](https://poser.pugx.org/topthink/think/downloads)](https://packagist.org/packages/topthink/think)
+[![Latest Stable Version](https://poser.pugx.org/topthink/think/v/stable)](https://packagist.org/packages/topthink/think)
+[![Latest Unstable Version](https://poser.pugx.org/topthink/think/v/unstable)](https://packagist.org/packages/topthink/think)
+[![License](https://poser.pugx.org/topthink/think/license)](https://packagist.org/packages/topthink/think)
+
+ThinkPHP5在保持快速开发和大道至简的核心理念不变的同时,PHP版本要求提升到5.4,对已有的CBD模式做了更深的强化,优化核心,减少依赖,基于全新的架构思想和命名空间实现,是ThinkPHP突破原有框架思路的颠覆之作,其主要特性包括:
+
+ + 基于命名空间和众多PHP新特性
+ + 核心功能组件化
+ + 强化路由功能
+ + 更灵活的控制器
+ + 重构的模型和数据库类
+ + 配置文件可分离
+ + 重写的自动验证和完成
+ + 简化扩展机制
+ + API支持完善
+ + 改进的Log类
+ + 命令行访问支持
+ + REST支持
+ + 引导文件支持
+ + 方便的自动生成定义
+ + 真正惰性加载
+ + 分布式环境支持
+ + 更多的社交类库
+
+> ThinkPHP5的运行环境要求PHP5.4以上。
+
+详细开发文档参考 [ThinkPHP5完全开发手册](http://www.kancloud.cn/manual/thinkphp5)
+
+## 目录结构
+
+初始的目录结构如下:
+
+~~~
+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自动加载规范,并且注意如下规范:
+
+### 目录和文件
+
+*   目录不强制规范,驼峰和小写+下划线模式均支持;
+*   类库、函数文件统一以`.php`为后缀;
+*   类的文件名均以命名空间定义,并且命名空间的路径和类库文件所在路径一致;
+*   类名和类文件名保持一致,统一采用驼峰法命名(首字母大写);
+
+### 函数和类、属性命名
+
+*   类的命名采用驼峰法,并且首字母大写,例如 `User`、`UserType`,默认不需要添加后缀,例如`UserController`应该直接命名为`User`;
+*   函数的命名使用小写字母和下划线(小写字母开头)的方式,例如 `get_client_ip`;
+*   方法的命名使用驼峰法,并且首字母小写,例如 `getUserName`;
+*   属性的命名使用驼峰法,并且首字母小写,例如 `tableName`、`instance`;
+*   以双下划线“__”打头的函数或方法作为魔法方法,例如 `__call` 和 `__autoload`;
+
+### 常量和配置
+
+*   常量以大写字母和下划线命名,例如 `APP_PATH`和 `THINK_PATH`;
+*   配置参数以小写字母和下划线命名,例如 `url_route_on` 和`url_convert`;
+
+### 数据表和字段
+
+*   数据表和字段采用小写加下划线方式命名,并注意字段名不要以下划线开头,例如 `think_user` 表和 `user_name`字段,不建议使用驼峰和中文作为数据表字段命名。
+
+## 参与开发
+
+请参阅 [ThinkPHP5 核心框架包](https://github.com/top-think/framework)。
+
+## 版权信息
+
+ThinkPHP遵循Apache2开源协议发布,并提供免费使用。
+
+本项目包含的第三方源码和二进制文件之版权信息另行标注。
+
+版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn)
+
+All rights reserved。
+
+ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
+
+更多细节参阅 [LICENSE.txt](LICENSE.txt)

+ 1 - 0
application/.htaccess

@@ -0,0 +1 @@
+deny from all

+ 12 - 0
application/command.php

@@ -0,0 +1,12 @@
+<?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>
+// +----------------------------------------------------------------------
+
+return [];

+ 12 - 0
application/common.php

@@ -0,0 +1,12 @@
+<?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@gmail.com>
+// +----------------------------------------------------------------------
+
+// 应用公共文件

+ 243 - 0
application/config.php

@@ -0,0 +1,243 @@
+<?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 [
+    // +----------------------------------------------------------------------
+    // | 应用设置
+    // +----------------------------------------------------------------------
+
+    // 应用调试模式
+    'app_debug'              => true,
+    // 应用Trace
+    'app_trace'              => true,
+    // 应用模式状态
+    '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',
+    // 操作方法后缀
+    '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'          => '/',
+    // URL伪静态后缀
+    'url_html_suffix'        => 'html',
+    // URL普通方式参数 用于自动生成
+    'url_common_param'       => false,
+    // URL参数方式 0 按名称成对解析 1 按顺序解析
+    'url_param_type'         => 0,
+    // 是否开启路由
+    'url_route_on'           => true,
+    // 路由使用完整匹配
+    'route_complete_match'   => false,
+    // 路由配置文件(支持配置多个)
+    'route_config_file'      => ['route'],
+    // 是否开启路由解析缓存
+    'route_check_cache'      => 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'               => [
+        // 模板引擎类型 支持 php think 支持扩展
+        'type'         => 'Think',
+        // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写
+        'auto_rule'    => 1,
+        // 模板路径
+        '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'       => '',
+
+    // +----------------------------------------------------------------------
+    // | 日志设置
+    // +----------------------------------------------------------------------
+
+    '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,
+    ],
+
+    // +----------------------------------------------------------------------
+    // | Cookie设置
+    // +----------------------------------------------------------------------
+    'cookie'                 => [
+        // cookie 名称前缀
+        'prefix'    => '',
+        // cookie 保存时间
+        'expire'    => 0,
+        // cookie 保存路径
+        'path'      => '/',
+        // cookie 有效域名
+        'domain'    => '',
+        //  cookie 启用安全传输
+        'secure'    => false,
+        // httponly设置
+        'httponly'  => '',
+        // 是否使用 setcookie
+        'setcookie' => true,
+    ],
+
+    //分页配置
+    'paginate'               => [
+        'type'      => 'bootstrap',
+        'var_page'  => 'page',
+        'list_rows' => 15,
+    ],
+];

+ 55 - 0
application/database.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: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+
+return [
+    // 数据库类型
+    'type'            => 'mysql',
+    // 服务器地址
+    'hostname'        => '1.14.197.70',
+    // 数据库名
+    'database'        => 'youeryuan',
+    // 用户名
+    'username'        => 'youeryuan',
+    // 密码
+    'password'        => 'XwXXAFbp8kaYsLKF',
+    // 端口
+    'hostport'        => '',
+    // 连接dsn
+    'dsn'             => '',
+    // 数据库连接参数
+    'params'          => [],
+    // 数据库编码默认采用utf8
+    'charset'         => 'utf8',
+    // 数据库表前缀
+    'prefix'          => '',
+    // 数据库调试模式
+    'debug'           => true,
+    // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
+    'deploy'          => 0,
+    // 数据库读写是否分离 主从式有效
+    'rw_separate'     => false,
+    // 读写分离后 主服务器数量
+    'master_num'      => 1,
+    // 指定从服务器序号
+    'slave_no'        => '',
+    // 自动读取主库数据
+    'read_master'     => false,
+    // 是否严格检查字段是否存在
+    'fields_strict'   => true,
+    // 数据集返回类型
+    'resultset_type'  => 'array',
+    // 自动写入时间戳字段
+    'auto_timestamp'  => false,
+    // 时间字段取出后的默认时间格式
+    'datetime_format' => 'Y-m-d H:i:s',
+    // 是否需要进行SQL性能分析
+    'sql_explain'     => false,
+];

+ 14 - 0
application/extra/queue.php

@@ -0,0 +1,14 @@
+<?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>
+// +----------------------------------------------------------------------
+
+return [
+    'connector' => 'Sync'
+];

+ 32 - 0
application/index/controller/Index.php

@@ -0,0 +1,32 @@
+<?php
+namespace app\index\controller;
+
+use think\Controller;
+
+class Index extends Controller
+{
+    public function index()
+    {
+        $st="test/test";
+        $length = strlen($st);
+//创建tcp套接字
+        $socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
+//连接tcp
+        socket_connect($socket, '119.23.47.137',8083);
+//向打开的套集字写入数据(发送数据)
+        $s = socket_write($socket, $st, $length);
+//从套接字中获取服务器发送来的数据
+        /*while(true){*/
+            $msg = socket_read($socket,8190);
+            echo $msg;
+            /*sleep(1);
+        }*/
+
+
+
+//关闭连接
+        socket_close($socket);
+    }
+}
+
+

+ 90 - 0
application/index/view/index/index.html

@@ -0,0 +1,90 @@
+
+<html>
+<title>测试</title>
+<style>
+*{
+ 	margin:0;
+ 	padding:0;
+}
+.meta{
+	width:500px;
+	height:800px;
+	border-radius: 5%;
+	border:3px solid #000;
+	margin:10px auto;
+	position:relative;
+}
+.login_info{
+	width:100%;
+	height:40px;
+	border-bottom:3px solid #000;
+	position:absolute;
+	left:0;
+	top:0;
+	line-height:40px;
+	text-align:center;
+	display:none;
+}
+</style>
+<body>
+<div class="meta">
+	<div class="login_info">
+
+	</div>
+</div>
+<script type="text/javascript">
+    if (typeof console == "undefined") {
+        this.console = {
+            log: function (msg) {}
+        };
+    }
+    var ws, name, client_list={};
+
+    function connect(){
+        // 创建websocket
+        ws = new WebSocket("ws://127.0.0.1:2346");
+        // 当socket连接打开时,输入用户名
+        ws.onopen = onopen;
+        // 当有消息时根据消息类型显示不同信息
+        ws.onmessage = onmessage;
+        ws.onclose = function() {
+            console.log("连接关闭,定时重连");
+            connect();
+        };
+        ws.onerror = function() {
+            console.log("出现错误");
+        };
+    }
+    // 连接建立时发送登录信息
+    function onopen()
+    {
+        if(!name)
+        {
+            name = prompt('输入你的名字:', '');
+            if(!name || name=='null'){
+                name = '游客';
+            }
+        }
+        var login_data = '{"type":"login","client_name":"'+name.replace(/"/g, '\\"')+'","room_id":"房间1"}';
+        console.log(login_data);
+        ws.send(login_data);
+    }
+
+    // 服务端发来消息时
+    function onmessage(e)
+    {
+        var data = eval("("+e.data+")");
+        switch(data['type']){
+            // 服务端ping客户端
+            case 'ping':
+                ws.send('{"type":"pong"}');
+                break;;
+            // 登录 更新用户列表
+            case 'login':
+                alert(data['data']);
+        }
+    }
+    connect();
+</script>
+</body>
+</html>

+ 21 - 0
application/route.php

@@ -0,0 +1,21 @@
+<?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 [
+    '__pattern__' => [
+        'name' => '\w+',
+    ],
+    '[hello]'     => [
+        ':id'   => ['index/hello', ['method' => 'get'], ['id' => '\d+']],
+        ':name' => ['index/hello', ['method' => 'post']],
+    ],
+
+];

+ 28 - 0
application/tags.php

@@ -0,0 +1,28 @@
+<?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 [
+    // 应用初始化
+    'app_init'     => [],
+    // 应用开始
+    'app_begin'    => [],
+    // 模块初始化
+    'module_init'  => [],
+    // 操作开始执行
+    'action_begin' => [],
+    // 视图内容过滤
+    'view_filter'  => [],
+    // 日志写入
+    'log_write'    => [],
+    // 应用结束
+    'app_end'      => [],
+];

+ 10 - 0
application/task/controller/Index.php

@@ -0,0 +1,10 @@
+<?php
+namespace app\index\controller;
+
+class Index
+{
+    public function index()
+    {
+        return '<style type="text/css">*{ padding: 0; margin: 0; } .think_default_text{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:)</h1><p> ThinkPHP V5<br/><span style="font-size:30px">十年磨一剑 - 为API开发设计的高性能框架</span></p><span style="font-size:22px;">[ V5.0 版本由 <a href="http://www.qiniu.com" target="qiniu">七牛云</a> 独家赞助发布 ]</span></div><script type="text/javascript" src="https://tajs.qq.com/stats?sId=9347272" charset="UTF-8"></script><script type="text/javascript" src="https://e.topthink.com/Public/static/client.js"></script><think id="ad_bd568ce7058a1091"></think>';
+    }
+}

+ 25 - 0
build.php

@@ -0,0 +1,25 @@
+<?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 [
+    // 生成应用公共文件
+    '__file__' => ['common.php', 'config.php', 'database.php'],
+
+    // 定义demo模块的自动生成 (按照实际定义的文件名生成)
+    'demo'     => [
+        '__file__'   => ['common.php'],
+        '__dir__'    => ['behavior', 'controller', 'model', 'view'],
+        'controller' => ['Index', 'Test', 'UserType'],
+        'model'      => ['User', 'UserType'],
+        'view'       => ['index/index'],
+    ],
+    // 其他更多的模块定义
+];

+ 36 - 0
composer.json

@@ -0,0 +1,36 @@
+{
+    "name": "topthink/think",
+    "description": "the new thinkphp framework",
+    "type": "project",
+    "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/framework": "5.0.*",
+        "workerman/gatewayclient": "^3.0",
+        "workerman/workerman": "^4.0",
+        "workerman/gateway-worker": "^3.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "app\\": "application"
+        }
+    },
+    "extra": {
+        "think-path": "thinkphp"
+    },
+    "config": {
+        "preferred-install": "dist"
+    }
+}

+ 273 - 0
composer.lock

@@ -0,0 +1,273 @@
+{
+    "_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": "0a7b81b2bb0ae16d2cfbb5b189873ff5",
+    "packages": [
+        {
+            "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-installer",
+            "version": "v1.0.14",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-installer.git",
+                "reference": "eae1740ac264a55c06134b6685dfb9f837d004d1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-installer/zipball/eae1740ac264a55c06134b6685dfb9f837d004d1",
+                "reference": "eae1740ac264a55c06134b6685dfb9f837d004d1",
+                "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.14"
+            },
+            "time": "2021-03-25T08:34:02+00:00"
+        },
+        {
+            "name": "workerman/gateway-worker",
+            "version": "v3.0.20",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/walkor/GatewayWorker.git",
+                "reference": "75b21653b3eaa99c4d5ae84ce8c5b27cda96d095"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/walkor/GatewayWorker/zipball/75b21653b3eaa99c4d5ae84ce8c5b27cda96d095",
+                "reference": "75b21653b3eaa99c4d5ae84ce8c5b27cda96d095",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "workerman/workerman": ">=3.5.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "GatewayWorker\\": "./src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "homepage": "http://www.workerman.net",
+            "keywords": [
+                "communication",
+                "distributed"
+            ],
+            "support": {
+                "issues": "https://github.com/walkor/GatewayWorker/issues",
+                "source": "https://github.com/walkor/GatewayWorker/tree/v3.0.20"
+            },
+            "time": "2021-04-13T13:19:21+00:00"
+        },
+        {
+            "name": "workerman/gatewayclient",
+            "version": "v3.0.13",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/walkor/GatewayClient.git",
+                "reference": "6f4e76f38947be5cabca2c6fee367151f248d949"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/walkor/GatewayClient/zipball/6f4e76f38947be5cabca2c6fee367151f248d949",
+                "reference": "6f4e76f38947be5cabca2c6fee367151f248d949",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "GatewayClient\\": "./"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "homepage": "http://www.workerman.net",
+            "support": {
+                "issues": "https://github.com/walkor/GatewayClient/issues",
+                "source": "https://github.com/walkor/GatewayClient/tree/v3.0.13"
+            },
+            "time": "2018-09-15T03:03:50+00:00"
+        },
+        {
+            "name": "workerman/workerman",
+            "version": "v4.0.19",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/walkor/Workerman.git",
+                "reference": "af6025976fba817eeb4d5fbf8d0c1059a5819da3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/walkor/Workerman/zipball/af6025976fba817eeb4d5fbf8d0c1059a5819da3",
+                "reference": "af6025976fba817eeb4d5fbf8d0c1059a5819da3",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3"
+            },
+            "suggest": {
+                "ext-event": "For better performance. "
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Workerman\\": "./"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "walkor",
+                    "email": "walkor@workerman.net",
+                    "homepage": "http://www.workerman.net",
+                    "role": "Developer"
+                }
+            ],
+            "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.",
+            "homepage": "http://www.workerman.net",
+            "keywords": [
+                "asynchronous",
+                "event-loop"
+            ],
+            "support": {
+                "email": "walkor@workerman.net",
+                "forum": "http://wenda.workerman.net/",
+                "issues": "https://github.com/walkor/workerman/issues",
+                "source": "https://github.com/walkor/workerman",
+                "wiki": "http://doc.workerman.net/"
+            },
+            "time": "2021-03-05T06:44:28+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=5.4.0"
+    },
+    "platform-dev": [],
+    "plugin-api-version": "2.1.0"
+}

+ 2 - 0
extend/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

+ 1 - 0
new 1.txt

@@ -0,0 +1 @@
+git clone http://lizhen:lizhen123@git.huxiukeji.com/lizhen/youeryuan_server.git

+ 42 - 0
nfcserver.php

@@ -0,0 +1,42 @@
+<?php
+use Workerman\Worker;
+
+//重定向目录
+$cur_dir = dirname(__FILE__);
+chdir($cur_dir);
+//php版本判断
+$ver = '7.0.0';
+if(version_compare(PHP_VERSION, $ver) < 0) {
+    die('PHP版本最低为:'.$ver.'(当前为:'.PHP_VERSION.')');
+}
+/*
+由于TP5自带composer库的加载器.
+如果直接使用vendor的autoload会导致函数重定义错误
+所以直接从入口处引入tp文件,即可加载workerman
+*/
+define('GLOBAL_START', 1);
+ini_set('memory_limit', '10000M');
+
+define('APP_PATH', __DIR__.'/application/');
+require __DIR__.'/thinkphp/base.php';
+
+$file_name = basename(__FILE__);
+//日志
+Worker::$stdoutFile = RUNTIME_PATH."workerman.{$file_name}.stdout.log";
+Worker::$logFile = RUNTIME_PATH."workerman.{$file_name}.log";
+Worker::$pidFile = RUNTIME_PATH."workerman.{$file_name}.pid";
+
+$files = [
+    //__DIR__."/service/Payment.php",
+    __DIR__."/service/Nfcserver.php",
+   // __DIR__."/service/Nfcclient.php",
+];
+
+foreach($files as $file) {
+    require_once $file;
+}
+
+//foreach(glob('service/*Service.php') as $file) {
+//    require_once $file;
+//}
+Worker::runAll();

+ 8 - 0
public/.htaccess

@@ -0,0 +1,8 @@
+<IfModule mod_rewrite.c>
+  Options +FollowSymlinks -Multiviews
+  RewriteEngine On
+
+  RewriteCond %{REQUEST_FILENAME} !-d
+  RewriteCond %{REQUEST_FILENAME} !-f
+  RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L]
+</IfModule>

BIN
public/favicon.ico


+ 17 - 0
public/index.php

@@ -0,0 +1,17 @@
+<?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>
+// +----------------------------------------------------------------------
+
+// [ 应用入口文件 ]
+
+// 定义应用目录
+define('APP_PATH', __DIR__ . '/../application/');
+// 加载框架引导文件
+require __DIR__ . '/../thinkphp/start.php';

+ 2 - 0
public/robots.txt

@@ -0,0 +1,2 @@
+User-agent: *
+Disallow:

+ 20 - 0
public/router.php

@@ -0,0 +1,20 @@
+<?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>
+// +----------------------------------------------------------------------
+// $Id$
+
+if (is_file($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SCRIPT_NAME"])) {
+    return false;
+} else {
+    if (!isset($_SERVER['PATH_INFO'])) {
+        $_SERVER['PATH_INFO'] = $_SERVER['REQUEST_URI'];
+    }
+    require __DIR__ . "/index.php";
+}

+ 2 - 0
public/static/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

+ 2 - 0
runtime/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

+ 42 - 0
service.php

@@ -0,0 +1,42 @@
+<?php
+use Workerman\Worker;
+
+//重定向目录
+$cur_dir = dirname(__FILE__);
+chdir($cur_dir);
+//php版本判断
+$ver = '7.0.0';
+if(version_compare(PHP_VERSION, $ver) < 0) {
+    die('PHP版本最低为:'.$ver.'(当前为:'.PHP_VERSION.')');
+}
+/*
+由于TP5自带composer库的加载器.
+如果直接使用vendor的autoload会导致函数重定义错误
+所以直接从入口处引入tp文件,即可加载workerman
+*/
+define('GLOBAL_START', 1);
+ini_set('memory_limit', '10000M');
+
+define('APP_PATH', __DIR__.'/application/');
+require __DIR__.'/thinkphp/base.php';
+
+$file_name = basename(__FILE__);
+//日志
+Worker::$stdoutFile = RUNTIME_PATH."workerman.{$file_name}.stdout.log";
+Worker::$logFile = RUNTIME_PATH."workerman.{$file_name}.log";
+Worker::$pidFile = RUNTIME_PATH."workerman.{$file_name}.pid";
+
+$files = [
+    __DIR__."/service/Payment.php",
+    //__DIR__."/service/Nfcserver.php",
+   // __DIR__."/service/Nfcclient.php",
+];
+
+foreach($files as $file) {
+    require_once $file;
+}
+
+//foreach(glob('service/*Service.php') as $file) {
+//    require_once $file;
+//}
+Worker::runAll();

+ 54 - 0
service/Nfcserver.php

@@ -0,0 +1,54 @@
+<?php
+use Workerman\Worker;
+use think\App;
+use think\Db;
+// ######## 消息队列消费者 ########
+$consumer = new Worker();
+// 慢任务,消费者的进程数可以开多一些
+$consumer->count = 1;
+//进程启动
+$consumer->onWorkerStart = function($consumer)
+{
+    App::initCommon();
+
+    //创建socket套接字
+    $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+//设置阻塞模式
+    socket_set_block($socket);
+//为套接字绑定ip和端口
+    socket_connect($socket,'127.0.0.1',2346);
+//监听socket
+    socket_listen($socket,4);
+
+    while(true)
+    {
+        //接收客户端请求
+        if(($msgsocket = socket_accept($socket)) !== false)
+        {
+            //读取请求内容
+            $buf = socket_read($msgsocket, 8192);
+            echo "Received msg: $buf \n";
+            $str = "this is a service message";
+            //向连接的客户端发送数据
+            socket_write($msgsocket, $str,strlen($str));
+            //操作完之后需要关闭该连接否则 feof() 函数无法正确识别打开的句柄是否读取完成
+            socket_close($msgsocket);
+        }else{
+            echo 37;
+
+        }
+    }
+
+
+
+};
+$consumer->onMessage = function($consumer){
+    echo date('Y-m-d H:i:s');
+    sleep(1);
+};
+//进程关闭
+$consumer->onWorkerStop = function($consumer)
+{
+    restore_error_handler();
+};
+

+ 199 - 0
service/Payment.php

@@ -0,0 +1,199 @@
+<?php
+use Workerman\Worker;
+use think\App;
+use think\Db;
+// ######## 消息队列消费者 ########
+$consumer = new Worker();
+// 慢任务,消费者的进程数可以开多一些
+$consumer->count = 1;
+//进程启动
+$consumer->onWorkerStart = function($consumer)
+{
+    App::initCommon();
+
+    $st="test/test";
+    $length = strlen($st);
+//创建tcp套接字
+    global $socket;
+    $socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
+//连接tcp
+    socket_connect($socket, '119.23.47.137',8083);
+//向打开的套集字写入数据(发送数据)
+    $s = socket_write($socket, $st, $length);
+//从套接字中获取服务器发送来的数据
+    /*while(true){
+        $msg = socket_read($socket,8190);
+        echo $msg;
+        sleep(1);
+    }*/
+
+
+    $time_interval = 1;
+    \Workerman\Lib\Timer::add($time_interval, function () {
+        try
+        {
+            global $socket;
+            $msg = socket_read($socket,8190);
+            //echo $msg;
+            //[{"type":"LOGIN","status":"success"}]
+            //[{"type":"DATA","wbMac":"C9DBBBFCEE26","rssi":-59,"gtMac":"D1127A899D6A","hr":93,"tp":"36.0","step":0,"cal":0}]
+            $msg = json_decode($msg,true);
+            
+            if(!empty($msg)){
+            foreach($msg as $key => $val){
+                if($val['type'] == 'DATA'){
+                    $data = [
+                        'createtime' => time(),
+                        'type' => 'DATA',
+                        'wbmac'=> isset($val['wbMac']) ? $val['wbMac'] : '',
+                        'rssi' => isset($val['rssi']) ? $val['rssi'] : '',
+                        'gtmac'=> isset($val['gtMac']) ? $val['gtMac'] : '',
+                        'hr'   => isset($val['hr']) ? $val['hr'] : '',
+                        'tp'   => isset($val['tp']) ? $val['tp'] : '',
+                        'step' => isset($val['step']) ? $val['step'] : '',
+                        'cal'  => isset($val['cal']) ? $val['cal'] : '',
+                    ];
+
+                    $rs = Db::name('socket_watch')->insertGetId($data);
+
+                    //每个学生每一个小时只保留一条记录
+                    $student = Db::name('student')->where('watchmac',strtolower($data['wbmac']))->find();
+                    if($student){
+                        $map = [
+                            'student_id' => $student['id'],
+                            'typedata'   => ['IN','1,4,5'],
+                            'createtime' => ['gt',time()-3600],
+                        ];
+                        $check = Db::name('student_life')->where($map)->find();
+                        if(empty($check)){
+                            $life = [
+                                [
+                                    'typedata' => 1,
+                                    'student_id' => $student['id'],
+                                    'info' => $data['tp'],
+                                    'createtime' => time(),
+                                    'classes_id' => $student['classes_id'],
+                                    'school_id' => $student['school_id'],
+                                ],
+                                [
+                                    'typedata' => 4,
+                                    'student_id' => $student['id'],
+                                    'info' => $data['hr'],
+                                    'createtime' => time(),
+                                    'classes_id' => $student['classes_id'],
+                                    'school_id' => $student['school_id'],
+                                ],
+                                [
+                                    'typedata' => 5,
+                                    'student_id' => $student['id'],
+                                    'info' => $data['step'],
+                                    'createtime' => time(),
+                                    'classes_id' => $student['classes_id'],
+                                    'school_id' => $student['school_id'],
+                                ],
+                            ];
+                            Db::name('student_life')->insertAll($life);
+                        }
+                    }
+                    //每个学生每一个小时只保留一条记录
+
+                }
+            }
+            }
+        }
+        catch (\Exception $e)
+        {
+            echo $e->getMessage();
+        }
+    });
+
+};
+//进程关闭
+$consumer->onWorkerStop = function($consumer)
+{
+    restore_error_handler();
+};
+
+
+
+//日志
+class LogService
+{
+    // 日志级别 从上到下,由低到高
+    const EMERG = 'EMERG';  // 严重错误: 导致系统崩溃无法使用
+    const ALERT = 'ALERT';  // 警戒性错误: 必须被立即修改的错误
+    const CRIT = 'CRIT';  // 临界值错误: 超过临界值的错误,例如一天24小时,而输入的是25小时这样
+    const ERR = 'ERR';  // 一般错误: 一般性错误
+    const WARN = 'WARN';  // 警告性错误: 需要发出警告的错误
+    const NOTICE = 'NOTIC';  // 通知: 程序可以运行但是还不够完美的错误
+    const INFO = 'INFO';  // 信息: 程序输出信息
+    const DEBUG = 'DEBUG';  // 调试: 调试信息
+    const SQL = 'SQL';  // SQL:SQL语句 注意只在调试模式开启时有效
+
+    public static function record($message, $level = self::INFO)
+    {
+        $mem = intval(memory_get_usage() / 1024 / 1024);
+        $msg = date('Ymd H:i:s', time()).' '.$level.' ['.str_pad($mem, 4, " ", STR_PAD_LEFT).'M] '.$message;
+        echo $msg."\n";
+    }
+
+    static public function appError($errno, $errstr, $errfile, $errline)
+    {
+        if($errno == E_ERROR) {
+
+            $errorStr = "$errstr ".$errfile." 第 $errline 行.";
+            self::record("[$errno] $errstr ".$errfile." 第 $errline 行.", self::ERR);
+        }
+
+    }
+
+    function heat()
+    {
+
+        //调试模式下输出错误信息
+        $trace = debug_backtrace();
+        //$e['message'] = $error;
+        $e['file'] = $trace[0]['file'];
+        $e['class'] = $trace[0]['class'];
+        $e['function'] = $trace[0]['function'];
+        $e['line'] = $trace[0]['line'];
+        $traceInfo = '';
+        $time = date('y-m-d H:i:m');
+        foreach($trace as $t) {
+            $traceInfo .= '['.$time.'] ';
+
+
+            if(isset($t['file'])) {
+                $traceInfo .= $t['file'];
+            }
+            if(isset($t['line'])) {
+                $traceInfo .= ' ('.$t['line'].') ';
+            }
+            if(isset($t['class'])) {
+                $traceInfo .= $t['class'];
+            }
+
+
+            $traceInfo .= $t['function'].'(';
+            //$traceInfo .= implode(', ', $t['args']);
+            $traceInfo .= ')'."\n";
+        }
+        echo $traceInfo;
+        //$e['trace'] = $traceInfo;
+
+    }
+
+    function clear()
+    {
+        $cmd = "echo -ne \"\033[2J\n\"";
+        $a = exec($cmd);
+        print "$a"."\n";
+    }
+
+    function color_b($string, $line = 0)
+    {
+        $cmd = "printf \"\033[".$line.";0H \033[01;40;32m".$string."\033[0m\n\"";
+        $a = exec($cmd);
+        print "$a"."\n";
+    }
+}

+ 60 - 0
start.php

@@ -0,0 +1,60 @@
+<?php
+use Workerman\Worker;
+//require_once __DIR__ . '/Workerman/Autoloader.php';
+//use Workerman\Autoloader;
+require_once __DIR__ . '/vendor/workerman/workerman/Autoloader.php';
+
+$worker = new Worker('ws://127.0.0.1:2346');
+$worker->count = 4;
+$worker->onConnect = function($connection)
+{
+    echo $connection->id;
+};
+$worker->onMessage = function($connection, $data)
+{
+    // 向浏览器发送hello world
+    $connection->send('hello world');
+
+    // 客户端传递的是json数据
+    $message_data = json_decode($data, true);
+    if(!$message_data)
+    {
+        return ;
+    }
+    // 根据类型执行不同的业务
+    switch($message_data['type'])
+    {
+        // 客户端回应服务端的心跳
+        case 'pong':
+
+            return ;
+
+        // 客户端登录 message格式: {type:login, name:xx, room_id:1} ,添加到客户端,广播给所有客户端xx进入聊天室
+        case 'login':
+            if(!isset($message_data['room_id']))
+            {
+                throw new \Exception("\$message_data['room_id'] not set. client_ip:{$_SERVER['REMOTE_ADDR']} \$message:$message");
+            }
+            // 判断当前客户端是否已经验证,即是否设置了uid
+            if(!isset($connection->uid))
+            {
+                // 没验证的话把第一个包当做uid(这里为了方便演示,没做真正的验证)
+                $connection->uid = $message_data['client_name'];
+                /* 保存uid到connection的映射,这样可以方便的通过uid查找connection,
+                 * 实现针对特定uid推送数据
+                 */
+                $worker = $this->worker;
+                foreach($worker->connections as $conn)
+                {
+                    $conn->send("{'type':'login','data':'".$message_data['client_name']."登陆成功'}");
+                }
+            }
+
+        case 'say':
+
+            return;
+    }
+};
+
+// 运行worker
+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 __DIR__.'/thinkphp/console.php';

+ 1 - 0
thinkphp/.gitignore

@@ -0,0 +1 @@
+

+ 1 - 0
thinkphp/.htaccess

@@ -0,0 +1 @@
+deny from all

+ 47 - 0
thinkphp/.travis.yml

@@ -0,0 +1,47 @@
+sudo: false
+
+language: php
+
+services:
+  - memcached
+  - mongodb
+  - mysql
+  - postgresql
+  - redis-server
+
+matrix:
+  fast_finish: true
+  include:
+    - php: 5.4
+    - php: 5.5
+    - php: 5.6
+    - php: 7.0
+    - php: hhvm
+  allow_failures:
+    - php: hhvm
+
+cache:
+  directories:
+    - $HOME/.composer/cache
+
+before_install:
+  - composer self-update
+  - mysql -e "create database IF NOT EXISTS test;" -uroot
+  - psql -c 'DROP DATABASE IF EXISTS test;' -U postgres
+  - psql -c 'create database test;' -U postgres
+
+install:
+  - ./tests/script/install.sh
+
+script:
+  ## LINT
+  - find . -path ./vendor -prune -o -type f -name \*.php -exec php -l {} \;
+  ## PHP Copy/Paste Detector
+  - vendor/bin/phpcpd --verbose --exclude vendor ./ || true
+  ## PHPLOC
+  - vendor/bin/phploc --exclude vendor ./
+  ## PHPUNIT
+  - vendor/bin/phpunit --coverage-clover=coverage.xml --configuration=phpunit.xml
+
+after_success:
+  - bash <(curl -s https://codecov.io/bash)

+ 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'                                  => '规则错误',
+];

+ 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 = 'create_time';
+    // 更新时间字段
+    protected $updateTime = 'update_time';
+    // 时间字段取出后的默认时间格式
+    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';
+    }
+
+}

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff