Anyon преди 7 години
родител
ревизия
d57b1989c0
променени са 73 файла, в които са добавени 2818 реда и са изтрити 1871 реда
  1. 5 6
      thinkphp/README.md
  2. 20 18
      thinkphp/base.php
  3. 9 1
      thinkphp/convention.php
  4. 7 2
      thinkphp/helper.php
  5. 6 1
      thinkphp/lang/zh-cn.php
  6. 72 40
      thinkphp/library/think/App.php
  7. 48 15
      thinkphp/library/think/Build.php
  8. 6 7
      thinkphp/library/think/Config.php
  9. 35 3
      thinkphp/library/think/Console.php
  10. 137 67
      thinkphp/library/think/Container.php
  11. 11 10
      thinkphp/library/think/Controller.php
  12. 3 1
      thinkphp/library/think/Db.php
  13. 1 2
      thinkphp/library/think/Debug.php
  14. 12 12
      thinkphp/library/think/Env.php
  15. 2 2
      thinkphp/library/think/Error.php
  16. 4 0
      thinkphp/library/think/Facade.php
  17. 52 52
      thinkphp/library/think/File.php
  18. 3 6
      thinkphp/library/think/Hook.php
  19. 2 2
      thinkphp/library/think/Lang.php
  20. 33 18
      thinkphp/library/think/Loader.php
  21. 51 28
      thinkphp/library/think/Log.php
  22. 48 21
      thinkphp/library/think/Middleware.php
  23. 13 13
      thinkphp/library/think/Model.php
  24. 2 2
      thinkphp/library/think/Paginator.php
  25. 172 104
      thinkphp/library/think/Request.php
  26. 4 4
      thinkphp/library/think/Response.php
  27. 130 314
      thinkphp/library/think/Route.php
  28. 29 65
      thinkphp/library/think/Template.php
  29. 9 10
      thinkphp/library/think/Url.php
  30. 28 27
      thinkphp/library/think/Validate.php
  31. 6 3
      thinkphp/library/think/cache/driver/File.php
  32. 36 1
      thinkphp/library/think/cache/driver/Redis.php
  33. 1 1
      thinkphp/library/think/console/command/Clear.php
  34. 1 1
      thinkphp/library/think/console/command/RunServer.php
  35. 9 2
      thinkphp/library/think/console/command/make/Controller.php
  36. 36 0
      thinkphp/library/think/console/command/make/Middleware.php
  37. 1 1
      thinkphp/library/think/console/command/make/Model.php
  38. 64 0
      thinkphp/library/think/console/command/make/stubs/controller.api.stub
  39. 10 0
      thinkphp/library/think/console/command/make/stubs/middleware.stub
  40. 15 2
      thinkphp/library/think/console/command/optimize/Config.php
  41. 4 2
      thinkphp/library/think/console/command/optimize/Route.php
  42. 1 1
      thinkphp/library/think/console/command/optimize/Schema.php
  43. 28 10
      thinkphp/library/think/db/Builder.php
  44. 270 250
      thinkphp/library/think/db/Connection.php
  45. 190 108
      thinkphp/library/think/db/Query.php
  46. 2 2
      thinkphp/library/think/db/builder/Mysql.php
  47. 1 1
      thinkphp/library/think/debug/Html.php
  48. 16 4
      thinkphp/library/think/facade/Middleware.php
  49. 0 45
      thinkphp/library/think/http/middleware/DispatcherInterface.php
  50. 0 69
      thinkphp/library/think/http/tests/middleware/DispatcherTest.php
  51. 24 7
      thinkphp/library/think/log/driver/File.php
  52. 0 12
      thinkphp/library/think/model/Collection.php
  53. 67 49
      thinkphp/library/think/model/concern/Attribute.php
  54. 4 4
      thinkphp/library/think/model/concern/RelationShip.php
  55. 3 4
      thinkphp/library/think/model/concern/SoftDelete.php
  56. 24 1
      thinkphp/library/think/model/relation/BelongsTo.php
  57. 28 2
      thinkphp/library/think/model/relation/HasOne.php
  58. 1 1
      thinkphp/library/think/model/relation/MorphMany.php
  59. 1 1
      thinkphp/library/think/process/pipes/Windows.php
  60. 0 1
      thinkphp/library/think/response/View.php
  61. 9 0
      thinkphp/library/think/response/Xml.php
  62. 126 0
      thinkphp/library/think/route/AliasRule.php
  63. 55 91
      thinkphp/library/think/route/Domain.php
  64. 22 24
      thinkphp/library/think/route/Resource.php
  65. 248 78
      thinkphp/library/think/route/Rule.php
  66. 309 79
      thinkphp/library/think/route/RuleGroup.php
  67. 154 136
      thinkphp/library/think/route/RuleItem.php
  68. 63 0
      thinkphp/library/think/route/RuleName.php
  69. 17 7
      thinkphp/library/think/route/dispatch/Module.php
  70. 1 1
      vendor/autoload.php
  71. 7 7
      vendor/composer/autoload_real.php
  72. 4 4
      vendor/composer/autoload_static.php
  73. 6 6
      vendor/composer/installed.json

+ 5 - 6
thinkphp/README.md

@@ -1,12 +1,11 @@
-ThinkPHP 5.1
+![](http://www.thinkphp.cn/Uploads/editor/2016-06-23/576b4732a6e04.png) 
+
+ThinkPHP 5.1 —— 12载初心,你值得信赖的PHP框架
 ===============
 
-[![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.1对底层架构做了进一步的改进,减少依赖,其主要特性包括:
@@ -30,12 +29,12 @@ ThinkPHP5.1对底层架构做了进一步的改进,减少依赖,其主要特
  + 内置控制器扩展类
  + 模型自动验证
 
-> ThinkPHP5的运行环境要求PHP5.6以上
+> ThinkPHP5.1的运行环境要求PHP5.6+
 
 
 ## 在线手册
 
-+ [完全开发手册](https://www.kancloud.cn/manual/thinkphp5_1)
++ [完全开发手册](https://www.kancloud.cn/manual/thinkphp5_1/content)
 + [升级指导](https://www.kancloud.cn/manual/thinkphp5_1/354155) 
 
 ## 命名规范

+ 20 - 18
thinkphp/base.php

@@ -40,6 +40,7 @@ Container::getInstance()->bind([
     'hook'                  => Hook::class,
     'lang'                  => Lang::class,
     'log'                   => Log::class,
+    'middleware'            => Middleware::class,
     'request'               => Request::class,
     'response'              => Response::class,
     'route'                 => Route::class,
@@ -47,30 +48,31 @@ Container::getInstance()->bind([
     'url'                   => Url::class,
     'validate'              => Validate::class,
     'view'                  => View::class,
-    'middlewareDispatcher'  => http\middleware\Dispatcher::class,
+    'rule_name'             => route\RuleName::class,
     // 接口依赖注入
     'think\LoggerInterface' => Log::class,
 ]);
 
 // 注册核心类的静态代理
 Facade::bind([
-    facade\App::class      => App::class,
-    facade\Build::class    => Build::class,
-    facade\Cache::class    => Cache::class,
-    facade\Config::class   => Config::class,
-    facade\Cookie::class   => Cookie::class,
-    facade\Debug::class    => Debug::class,
-    facade\Env::class      => Env::class,
-    facade\Hook::class     => Hook::class,
-    facade\Lang::class     => Lang::class,
-    facade\Log::class      => Log::class,
-    facade\Request::class  => Request::class,
-    facade\Response::class => Response::class,
-    facade\Route::class    => Route::class,
-    facade\Session::class  => Session::class,
-    facade\Url::class      => Url::class,
-    facade\Validate::class => Validate::class,
-    facade\View::class     => View::class,
+    facade\App::class        => App::class,
+    facade\Build::class      => Build::class,
+    facade\Cache::class      => Cache::class,
+    facade\Config::class     => Config::class,
+    facade\Cookie::class     => Cookie::class,
+    facade\Debug::class      => Debug::class,
+    facade\Env::class        => Env::class,
+    facade\Hook::class       => Hook::class,
+    facade\Lang::class       => Lang::class,
+    facade\Log::class        => Log::class,
+    facade\Middleware::class => Middleware::class,
+    facade\Request::class    => Request::class,
+    facade\Response::class   => Response::class,
+    facade\Route::class      => Route::class,
+    facade\Session::class    => Session::class,
+    facade\Url::class        => Url::class,
+    facade\Validate::class   => Validate::class,
+    facade\View::class       => View::class,
 ]);
 
 // 注册类库别名

+ 9 - 1
thinkphp/convention.php

@@ -30,7 +30,7 @@ return [
         // 默认JSONP处理方法
         'var_jsonp_handler'      => 'callback',
         // 默认时区
-        'default_timezone'       => 'PRC',
+        'default_timezone'       => 'Asia/Shanghai',
         // 是否开启多语言
         'lang_switch_on'         => false,
         // 默认全局过滤方法 用逗号分隔多个
@@ -89,6 +89,8 @@ return [
         'url_lazy_route'         => false,
         // 是否强制使用路由
         'url_route_must'         => false,
+        // 合并路由规则
+        'route_rule_merge'       => false,
         // 路由是否完全匹配
         'route_complete_match'   => false,
         // 使用注解路由
@@ -289,4 +291,10 @@ return [
         'list_rows' => 15,
     ],
 
+    //控制台配置
+    'console'  => [
+        'name'    => 'Think Console',
+        'version' => '0.1',
+        'user'    => null,
+    ],
 ];

+ 7 - 2
thinkphp/helper.php

@@ -29,6 +29,7 @@ use think\facade\Request;
 use think\facade\Route;
 use think\facade\Session;
 use think\facade\Url;
+use think\Loader;
 use think\Response;
 use think\route\RuleItem;
 
@@ -370,7 +371,7 @@ if (!function_exists('input')) {
      * @param string    $filter 过滤方法
      * @return mixed
      */
-    function input($key = '', $default = null, $filter = null)
+    function input($key = '', $default = null, $filter = '')
     {
         if (0 === strpos($key, '?')) {
             $key = substr($key, 1);
@@ -653,11 +654,15 @@ if (!function_exists('view')) {
      * @param string    $template 模板文件
      * @param array     $vars 模板变量
      * @param integer   $code 状态码
-     * @param callable  $filer 内容过滤
+     * @param callable  $filter 内容过滤
      * @return \think\response\View
      */
     function view($template = '', $vars = [], $code = 200, $filter = null)
     {
+        if ('' === $template) {
+            $template = Loader::parseName(request()->action(true));
+        }
+
         return Response::create($template, 'view', $code)->assign($vars)->filter($filter);
     }
 }

+ 6 - 1
thinkphp/lang/zh-cn.php

@@ -24,6 +24,7 @@ return [
     'dispatch type not support'                                 => '不支持的调度类型',
     'method param miss'                                         => '方法参数错误',
     'method not exists'                                         => '方法不存在',
+    'function not exists'                                       => '函数不存在',
     'module not exists'                                         => '模块不存在',
     'controller not exists'                                     => '控制器不存在',
     'class not exists'                                          => '类不存在',
@@ -32,7 +33,7 @@ return [
     'illegal controller name'                                   => '非法的控制器名称',
     'illegal action name'                                       => '非法的操作名称',
     'url suffix deny'                                           => '禁止的URL后缀访问',
-    'Route Not Found'                                           => '当前访问路由未定义',
+    'Route Not Found'                                           => '当前访问路由未定义或不匹配',
     'Undefined db type'                                         => '未定义数据库类型',
     'variable type error'                                       => '变量类型错误',
     'PSR-4 error'                                               => 'PSR-4 规范错误',
@@ -66,6 +67,8 @@ return [
     'relation data not exists'                                  => '关联数据不存在',
     'relation not support'                                      => '关联不支持',
     'chunk not support order'                                   => 'Chunk不支持调用order方法',
+    'route pattern error'                                       => '路由变量规则定义错误',
+    'route behavior will not support'                           => '路由行为废弃(使用中间件替代)',
 
     // 上传错误信息
     'unknown upload error'                                      => '未知上传错误!',
@@ -83,6 +86,8 @@ return [
     'filesize not match'                                        => '上传文件大小不符!',
     'directory {:path} creation failed'                         => '目录 {:path} 创建失败!',
 
+    'The middleware must return Response instance'              => '中间件方法必须返回Response对象实例',
+    'The queue was exhausted, with no response returned'        => '中间件队列为空',
     // Validate Error Message
     ':attribute require'                                        => ':attribute不能为空',
     ':attribute must'                                           => ':attribute必须',

+ 72 - 40
thinkphp/library/think/App.php

@@ -20,7 +20,7 @@ use think\route\Dispatch;
  */
 class App implements \ArrayAccess
 {
-    const VERSION = '5.1.5';
+    const VERSION = '5.1.6';
 
     /**
      * 当前模块路径
@@ -126,7 +126,7 @@ class App implements \ArrayAccess
 
     public function __construct($appPath = '')
     {
-        $this->appPath   = $appPath ?: realpath(dirname($_SERVER['SCRIPT_FILENAME']) . '/../application') . '/';
+        $this->appPath   = $appPath ?: realpath(dirname(dirname($_SERVER['SCRIPT_FILENAME'])) . DIRECTORY_SEPARATOR . 'application') . DIRECTORY_SEPARATOR;
         $this->container = Container::getInstance();
     }
 
@@ -163,11 +163,11 @@ class App implements \ArrayAccess
     {
         $this->beginTime   = microtime(true);
         $this->beginMem    = memory_get_usage();
-        $this->thinkPath   = dirname(dirname(__DIR__)) . '/';
-        $this->rootPath    = dirname(realpath($this->appPath)) . '/';
-        $this->runtimePath = $this->rootPath . 'runtime/';
-        $this->routePath   = $this->rootPath . 'route/';
-        $this->configPath  = $this->rootPath . 'config/';
+        $this->thinkPath   = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR;
+        $this->rootPath    = dirname(realpath($this->appPath)) . DIRECTORY_SEPARATOR;
+        $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;
+        $this->routePath   = $this->rootPath . 'route' . DIRECTORY_SEPARATOR;
+        $this->configPath  = $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
 
         // 设置路径环境变量
         $this->env->set([
@@ -177,8 +177,8 @@ class App implements \ArrayAccess
             'config_path'  => $this->configPath,
             'route_path'   => $this->routePath,
             'runtime_path' => $this->runtimePath,
-            'extend_path'  => $this->rootPath . 'extend/',
-            'vendor_path'  => $this->rootPath . 'vendor/',
+            'extend_path'  => $this->rootPath . 'extend' . DIRECTORY_SEPARATOR,
+            'vendor_path'  => $this->rootPath . 'vendor' . DIRECTORY_SEPARATOR,
         ]);
 
         // 加载环境变量配置文件
@@ -228,6 +228,9 @@ class App implements \ArrayAccess
         // 设置系统时区
         date_default_timezone_set($this->config('app.default_timezone'));
 
+        // 读取语言包
+        $this->loadLangPack();
+
         // 监听app_init
         $this->hook->listen('app_init');
     }
@@ -252,7 +255,10 @@ class App implements \ArrayAccess
         } else {
             // 加载行为扩展文件
             if (is_file($path . 'tags.php')) {
-                $this->hook->import(include $path . 'tags.php');
+                $tags = include $path . 'tags.php';
+                if (is_array($tags)) {
+                    $this->hook->import($tags);
+                }
             }
 
             // 加载公共文件
@@ -263,11 +269,21 @@ class App implements \ArrayAccess
             if ('' == $module) {
                 // 加载系统助手函数
                 include $this->thinkPath . 'helper.php';
+                // 加载全局中间件
+                if (is_file($path . 'middleware.php')) {
+                    $middleware = include $path . 'middleware.php';
+                    if (is_array($middleware)) {
+                        $this->middleware->import($middleware);
+                    }
+                }
             }
 
             // 注册服务的容器对象实例
             if (is_file($path . 'provider.php')) {
-                $this->container->bind(include $path . 'provider.php');
+                $provider = include $path . 'provider.php';
+                if (is_array($provider)) {
+                    $this->container->bind($provider);
+                }
             }
 
             // 自动读取配置文件
@@ -298,10 +314,10 @@ class App implements \ArrayAccess
      */
     public function run()
     {
-        // 初始化应用
-        $this->initialize();
-
         try {
+            // 初始化应用
+            $this->initialize();
+
             if ($this->bind) {
                 // 模块/控制器绑定
                 $this->route->bind($this->bind);
@@ -313,28 +329,18 @@ class App implements \ArrayAccess
                 }
             }
 
-            // 读取默认语言
-            $this->lang->range($this->config('app.default_lang'));
-            if ($this->config('app.lang_switch_on')) {
-                // 开启多语言机制 检测当前语言
-                $this->lang->detect();
-            }
-
-            $this->request->langset($this->lang->range());
-
-            // 加载系统语言包
-            $this->lang->load([
-                $this->thinkPath . 'lang/' . $this->request->langset() . '.php',
-                $this->appPath . 'lang/' . $this->request->langset() . '.php',
-            ]);
-
             // 监听app_dispatch
             $this->hook->listen('app_dispatch');
 
             // 获取应用调度信息
             $dispatch = $this->dispatch;
             if (empty($dispatch)) {
-                // 进行URL路由检测
+                // 路由检测
+                $this->route
+                    ->lazy($this->config('app.url_lazy_route'))
+                    ->autoSearchController($this->config('app.controller_auto_search'))
+                    ->mergeRuleRegex($this->config('app.route_rule_merge'));
+
                 $dispatch = $this->routeCheck();
             }
 
@@ -358,14 +364,22 @@ class App implements \ArrayAccess
                 $this->config('app.request_cache_except')
             );
 
-            // 执行调度
-            $data = $dispatch->run();
-
+            $data = null;
         } catch (HttpResponseException $exception) {
-            $data = $exception->getResponse();
+            $dispatch = null;
+            $data     = $exception->getResponse();
         }
 
-        $this->middlewareDispatcher->add(function (Request $request, $next) use ($data) {
+        $this->middleware->add(function (Request $request, $next) use ($dispatch, $data) {
+            if (is_null($data)) {
+                try {
+                    // 执行调度
+                    $data = $dispatch->run();
+                } catch (HttpResponseException $exception) {
+                    $data = $exception->getResponse();
+                }
+            }
+
             // 输出数据到客户端
             if ($data instanceof Response) {
                 $response = $data;
@@ -381,7 +395,7 @@ class App implements \ArrayAccess
             return $response;
         });
 
-        $response = $this->middlewareDispatcher->dispatch($this->request);
+        $response = $this->middleware->dispatch($this->request);
 
         // 监听app_end
         $this->hook->listen('app_end', $response);
@@ -389,6 +403,24 @@ class App implements \ArrayAccess
         return $response;
     }
 
+    protected function loadLangPack()
+    {
+        // 读取默认语言
+        $this->lang->range($this->config('app.default_lang'));
+        if ($this->config('app.lang_switch_on')) {
+            // 开启多语言机制 检测当前语言
+            $this->lang->detect();
+        }
+
+        $this->request->langset($this->lang->range());
+
+        // 加载系统语言包
+        $this->lang->load([
+            $this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php',
+            $this->appPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php',
+        ]);
+    }
+
     /**
      * 设置当前请求的调度信息
      * @access public
@@ -408,9 +440,9 @@ class App implements \ArrayAccess
      * @param  string $type 信息类型
      * @return void
      */
-    public function log($log, $type = 'info')
+    public function log($msg, $type = 'info')
     {
-        $this->debug && $this->log->record($log, $type);
+        $this->debug && $this->log->record($msg, $type);
     }
 
     /**
@@ -574,9 +606,9 @@ class App implements \ArrayAccess
             return $this->__get($class);
         } elseif ($empty && class_exists($emptyClass = $this->parseClass($module, $layer, $empty, $appendSuffix))) {
             return $this->__get($emptyClass);
-        } else {
-            throw new ClassNotFoundException('class not exists:' . $class, $class);
         }
+
+        throw new ClassNotFoundException('class not exists:' . $class, $class);
     }
 
     /**

+ 48 - 15
thinkphp/library/think/Build.php

@@ -135,7 +135,7 @@ class Build
 
         // 创建子目录和文件
         foreach ($list as $path => $file) {
-            $modulePath = $this->basePath . $module . '/';
+            $modulePath = $this->basePath . $module . DIRECTORY_SEPARATOR;
             if ('__dir__' == $path) {
                 // 生成子目录
                 foreach ($file as $dir) {
@@ -187,7 +187,7 @@ class Build
      * @param  string $layer  控制器层目录名
      * @return string
      */
-    public function buildRoute($alias = false, $layer = '')
+    public function buildRoute($suffix = false, $layer = '')
     {
         $namespace = $this->app->getNameSpace();
         $modules   = glob($this->basePath . '*', GLOB_ONLYDIR);
@@ -204,11 +204,8 @@ class Build
                 continue;
             }
 
-            $controllers = glob($this->basePath . $module . '/' . $layer . '/*.php');
-
-            foreach ($controllers as $controller) {
-                $content .= $this->getControllerRoute($namespace, $module, basename($controller, '.php'), $alias, $layer);
-            }
+            $path = $this->basePath . $module . DIRECTORY_SEPARATOR . $layer . DIRECTORY_SEPARATOR;
+            $content .= $this->buildDirRoute($path, $namespace, $module, $suffix, $layer);
         }
 
         $filename = $this->app->getRuntimePath() . 'build_route.php';
@@ -218,25 +215,61 @@ class Build
     }
 
     /**
-     * 生成控制器类的路由规则
+     * 生成子目录控制器类的路由规则
      * @access protected
+     * @param  string $path  控制器目录
      * @param  string $namespace 应用命名空间
      * @param  string $module 模块
-     * @param  string $controller 控制器名
      * @param  bool   $suffix 类库后缀
      * @param  string $layer 控制器层目录名
      * @return string
      */
-    protected function getControllerRoute($namespace, $module, $controller, $alias = false, $layer = '')
+    protected function buildDirRoute($path, $namespace, $module, $suffix, $layer)
     {
-        $class   = new \ReflectionClass($namespace . '\\' . $module . '\\' . $layer . '\\' . $controller);
-        $content = '';
-        $comment = $class->getDocComment();
+        $content     = '';
+        $controllers = glob($path . '*.php');
+
+        foreach ($controllers as $controller) {
+            $controller = basename($controller, '.php');
+
+            if ($suffix) {
+                // 控制器后缀
+                $controller = substr($controller, 0, -10);
+            }
+
+            $class = new \ReflectionClass($namespace . '\\' . $module . '\\' . $layer . '\\' . $controller);
+
+            if (strpos($layer, DIRECTORY_SEPARATOR)) {
+                // 多级控制器
+                $level      = str_replace(DIRECTORY_SEPARATOR, '.', substr($layer, 11));
+                $controller = $level . '.' . $controller;
+            }
+
+            $content .= $this->getControllerRoute($class, $module, $controller);
+        }
 
-        if ($alias) {
-            $controller = substr($controller, 0, -10);
+        $subDir = glob($path . '*', GLOB_ONLYDIR);
+
+        foreach ($subDir as $dir) {
+            $content .= $this->buildDirRoute($dir . DIRECTORY_SEPARATOR, $namespace, $module, $suffix, $layer . '\\' . basename($dir));
         }
 
+        return $content;
+    }
+
+    /**
+     * 生成控制器类的路由规则
+     * @access protected
+     * @param  string $class        控制器完整类名
+     * @param  string $module       模块名
+     * @param  string $controller   控制器名
+     * @return string
+     */
+    protected function getControllerRoute($class, $module, $controller)
+    {
+        $content = '';
+        $comment = $class->getDocComment();
+
         if (false !== strpos($comment, '@route(')) {
             $comment = $this->parseRouteComment($comment);
             $route   = $module . '/' . $controller;

+ 6 - 7
thinkphp/library/think/Config.php

@@ -72,12 +72,11 @@ class Config implements \ArrayAccess
                 return $this->set(include $file, $name);
             } elseif ('yaml' == $type && function_exists('yaml_parse_file')) {
                 return $this->set(yaml_parse_file($file), $name);
-            } else {
-                return $this->parse($file, $type, $name);
             }
-        } else {
-            return $this->config;
+            return $this->parse($file, $type, $name);
         }
+
+        return $this->config;
     }
 
     /**
@@ -90,12 +89,12 @@ class Config implements \ArrayAccess
     {
         // 如果尚未载入 则动态加载配置文件
         $module = Container::get('request')->module();
-        $module = $module ? $module . '/' : '';
+        $module = $module ? $module . DIRECTORY_SEPARATOR : '';
         $app    = Container::get('app');
         $path   = $app->getAppPath() . $module;
 
         if (is_dir($path . 'config')) {
-            $file = $path . 'config/' . $name . $app->getConfigExt();
+            $file = $path . 'config' . DIRECTORY_SEPARATOR . $name . $app->getConfigExt();
         } elseif (is_dir($app->getConfigPath() . $module)) {
             $file = $app->getConfigPath() . $module . $name . $app->getConfigExt();
         }
@@ -117,7 +116,7 @@ class Config implements \ArrayAccess
             $name = $this->prefix . '.' . $name;
         }
 
-        return $this->get($name) ? true : false;
+        return !is_null($this->get($name)) ? true : false;
     }
 
     /**

+ 35 - 3
thinkphp/library/think/Console.php

@@ -41,6 +41,7 @@ class Console
         "think\\console\\command\\Clear",
         "think\\console\\command\\make\\Controller",
         "think\\console\\command\\make\\Model",
+        "think\\console\\command\\make\\Middleware",
         "think\\console\\command\\optimize\\Autoload",
         "think\\console\\command\\optimize\\Config",
         "think\\console\\command\\optimize\\Schema",
@@ -48,11 +49,22 @@ class Console
         "think\\console\\command\\RunServer",
     ];
 
-    public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
+    /**
+     * 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();
 
@@ -61,13 +73,33 @@ class Console
         }
     }
 
+    /**
+     * 设置执行用户
+     * @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) {
-            // 实例化console
-            $console = new self('Think Console', '0.1');
+            $config = Container::get('config')->pull('console');
+            // 实例化 console
+            $console = new self($config['name'], $config['version'], $config['user']);
 
             // 读取指令集
             $file = Container::get('env')->get('app_path') . 'command.php';

+ 137 - 67
thinkphp/library/think/Container.php

@@ -14,8 +14,10 @@ namespace think;
 use Closure;
 use InvalidArgumentException;
 use ReflectionClass;
+use ReflectionException;
 use ReflectionFunction;
 use ReflectionMethod;
+use think\exception\ClassNotFoundException;
 
 class Container
 {
@@ -77,6 +79,27 @@ class Container
     }
 
     /**
+     * 移除容器中的对象实例
+     * @access public
+     * @param  string  $abstract    类标识、接口
+     * @return void
+     */
+    public static function remove($abstract)
+    {
+        return static::getInstance()->delete($abstract);
+    }
+
+    /**
+     * 清除容器中的对象实例
+     * @access public
+     * @return void
+     */
+    public static function clear()
+    {
+        return static::getInstance()->flush();
+    }
+
+    /**
      * 绑定一个类、闭包、实例、接口实现到容器
      * @access public
      * @param  string|array  $abstract    类标识、接口
@@ -155,137 +178,184 @@ class Container
         }
 
         if (isset($this->instances[$abstract]) && !$newInstance) {
-            $object = $this->instances[$abstract];
-        } else {
-            if (isset($this->bind[$abstract])) {
-                $concrete = $this->bind[$abstract];
-
-                if ($concrete instanceof Closure) {
-                    $object = $this->invokeFunction($concrete, $vars);
-                } else {
-                    $object = $this->make($concrete, $vars, $newInstance);
-                }
+            return $this->instances[$abstract];
+        }
+
+        if (isset($this->bind[$abstract])) {
+            $concrete = $this->bind[$abstract];
+
+            if ($concrete instanceof Closure) {
+                $object = $this->invokeFunction($concrete, $vars);
             } else {
-                $object = $this->invokeClass($abstract, $vars);
+                $object = $this->make($concrete, $vars, $newInstance);
             }
+        } else {
+            $object = $this->invokeClass($abstract, $vars);
+        }
 
-            if (!$newInstance) {
-                $this->instances[$abstract] = $object;
-            }
+        if (!$newInstance) {
+            $this->instances[$abstract] = $object;
         }
 
         return $object;
     }
 
     /**
+     * 删除容器中的对象实例
+     * @access public
+     * @param  string    $abstract    类名或者标识
+     * @return void
+     */
+    public function delete($abstract)
+    {
+        if (isset($this->instances[$abstract])) {
+            unset($this->instances[$abstract]);
+        }
+    }
+
+    /**
+     * 清除容器中的对象实例
+     * @access public
+     * @return void
+     */
+    public function flush()
+    {
+        $this->instances = [];
+        $this->bind      = [];
+    }
+
+    /**
      * 执行函数或者闭包方法 支持参数调用
      * @access public
-     * @param  string|array|\Closure $function 函数或者闭包
-     * @param  array                 $vars     变量
+     * @param  mixed  $function 函数或者闭包
+     * @param  array  $vars     参数
      * @return mixed
      */
     public function invokeFunction($function, $vars = [])
     {
-        $reflect = new ReflectionFunction($function);
-        $args    = $this->bindParams($reflect, $vars);
+        try {
+            $reflect = new ReflectionFunction($function);
+
+            $args = $this->bindParams($reflect, $vars);
 
-        return $reflect->invokeArgs($args);
+            return $reflect->invokeArgs($args);
+        } catch (ReflectionException $e) {
+            throw new Exception('function not exists: ' . $function . '()');
+        }
     }
 
     /**
      * 调用反射执行类的方法 支持参数绑定
      * @access public
-     * @param  string|array $method 方法
-     * @param  array        $vars   变量
+     * @param  mixed   $method 方法
+     * @param  array   $vars   参数
      * @return mixed
      */
     public function invokeMethod($method, $vars = [])
     {
-        if (is_array($method)) {
-            $class   = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]);
-            $reflect = new ReflectionMethod($class, $method[1]);
-        } else {
-            // 静态方法
-            $reflect = new ReflectionMethod($method);
+        try {
+            if (is_array($method)) {
+                $class   = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]);
+                $reflect = new ReflectionMethod($class, $method[1]);
+            } else {
+                // 静态方法
+                $reflect = new ReflectionMethod($method);
+            }
+
+            $args = $this->bindParams($reflect, $vars);
+
+            return $reflect->invokeArgs(isset($class) ? $class : null, $args);
+        } catch (ReflectionException $e) {
+            throw new Exception('method not exists: ' . (is_array($method) ? $method[0] . '::' . $method[1] : $method) . '()');
         }
+    }
 
+    /**
+     * 调用反射执行类的方法 支持参数绑定
+     * @access public
+     * @param  object  $instance 对象实例
+     * @param  mixed   $reflect 反射类
+     * @param  array   $vars   参数
+     * @return mixed
+     */
+    public function invokeReflectMethod($instance, $reflect, $vars = [])
+    {
         $args = $this->bindParams($reflect, $vars);
 
-        return $reflect->invokeArgs(isset($class) ? $class : null, $args);
+        return $reflect->invokeArgs($instance, $args);
     }
 
     /**
      * 调用反射执行callable 支持参数绑定
      * @access public
      * @param  mixed $callable
-     * @param  array $vars   变量
+     * @param  array $vars   参数
      * @return mixed
      */
     public function invoke($callable, $vars = [])
     {
         if ($callable instanceof Closure) {
-            $result = $this->invokeFunction($callable, $vars);
-        } else {
-            $result = $this->invokeMethod($callable, $vars);
+            return $this->invokeFunction($callable, $vars);
         }
 
-        return $result;
+        return $this->invokeMethod($callable, $vars);
     }
 
     /**
      * 调用反射执行类的实例化 支持依赖注入
      * @access public
      * @param  string    $class 类名
-     * @param  array     $vars  变量
+     * @param  array     $vars  参数
      * @return mixed
      */
     public function invokeClass($class, $vars = [])
     {
-        $reflect     = new ReflectionClass($class);
-        $constructor = $reflect->getConstructor();
+        try {
+            $reflect = new ReflectionClass($class);
 
-        if ($constructor) {
-            $args = $this->bindParams($constructor, $vars);
-        } else {
-            $args = [];
-        }
+            $constructor = $reflect->getConstructor();
 
-        return $reflect->newInstanceArgs($args);
+            $args = $constructor ? $this->bindParams($constructor, $vars) : [];
+
+            return $reflect->newInstanceArgs($args);
+        } catch (ReflectionException $e) {
+            throw new ClassNotFoundException('class not exists: ' . $class, $class);
+        }
     }
 
     /**
      * 绑定参数
      * @access protected
      * @param  \ReflectionMethod|\ReflectionFunction $reflect 反射类
-     * @param  array                                 $vars    变量
+     * @param  array                                 $vars    参数
      * @return array
      */
     protected function bindParams($reflect, $vars = [])
     {
-        $args = [];
-
-        if ($reflect->getNumberOfParameters() > 0) {
-            // 判断数组类型 数字数组时按顺序绑定参数
-            reset($vars);
-            $type   = key($vars) === 0 ? 1 : 0;
-            $params = $reflect->getParameters();
-
-            foreach ($params as $param) {
-                $name  = $param->getName();
-                $class = $param->getClass();
-
-                if ($class) {
-                    $className = $class->getName();
-                    $args[]    = $this->make($className);
-                } elseif (1 == $type && !empty($vars)) {
-                    $args[] = array_shift($vars);
-                } elseif (0 == $type && isset($vars[$name])) {
-                    $args[] = $vars[$name];
-                } elseif ($param->isDefaultValueAvailable()) {
-                    $args[] = $param->getDefaultValue();
-                } else {
-                    throw new InvalidArgumentException('method param miss:' . $name);
-                }
+        if ($reflect->getNumberOfParameters() == 0) {
+            return [];
+        }
+
+        // 判断数组类型 数字数组时按顺序绑定参数
+        reset($vars);
+        $type   = key($vars) === 0 ? 1 : 0;
+        $params = $reflect->getParameters();
+
+        foreach ($params as $param) {
+            $name  = $param->getName();
+            $class = $param->getClass();
+
+            if ($class) {
+                $className = $class->getName();
+                $args[]    = $this->make($className);
+            } elseif (1 == $type && !empty($vars)) {
+                $args[] = array_shift($vars);
+            } elseif (0 == $type && isset($vars[$name])) {
+                $args[] = $vars[$name];
+            } elseif ($param->isDefaultValueAvailable()) {
+                $args[] = $param->getDefaultValue();
+            } else {
+                throw new InvalidArgumentException('method param miss:' . $name);
             }
         }
 

+ 11 - 10
thinkphp/library/think/Controller.php

@@ -70,12 +70,10 @@ class Controller
         $this->initialize();
 
         // 前置操作方法
-        if ($this->beforeActionList) {
-            foreach ($this->beforeActionList as $method => $options) {
-                is_numeric($method) ?
-                $this->beforeAction($options) :
-                $this->beforeAction($method, $options);
-            }
+        foreach ((array) $this->beforeActionList as $method => $options) {
+            is_numeric($method) ?
+            $this->beforeAction($options) :
+            $this->beforeAction($method, $options);
         }
     }
 
@@ -120,6 +118,10 @@ class Controller
      */
     protected function fetch($template = '', $vars = [], $config = [])
     {
+        if ('' === $template) {
+            $template = Loader::parseName($this->request->action(true));
+        }
+
         return $this->view->fetch($template, $vars, $config);
     }
 
@@ -232,11 +234,10 @@ class Controller
         if (!$v->check($data)) {
             if ($this->failException) {
                 throw new ValidateException($v->getError());
-            } else {
-                return $v->getError();
             }
-        } else {
-            return true;
+            return $v->getError();
         }
+
+        return true;
     }
 }

+ 3 - 1
thinkphp/library/think/Db.php

@@ -14,6 +14,7 @@ namespace think;
 /**
  * Class Db
  * @package think
+ * @method \think\db\Query connect(array $config =[], mixed $name = false) static 连接/切换数据库连接
  * @method \think\db\Query table(string $table) static 指定数据表(含前缀)
  * @method \think\db\Query name(string $name) static 指定数据表(不含前缀)
  * @method \think\db\Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件
@@ -42,7 +43,8 @@ namespace think;
  * @method void commit() static 用于非自动提交状态下面的查询提交
  * @method void rollback() static 事务回滚
  * @method boolean batchQuery(array $sqlArray) static 批处理执行SQL语句
- * @method string getLastInsID($sequence = null) static 获取最近插入的ID
+ * @method string getLastInsID(string $sequence = null) static 获取最近插入的ID
+ * @method mixed getConfig(string $name = '') static 获取数据库的配置参数
  */
 class Db
 {

+ 1 - 2
thinkphp/library/think/Debug.php

@@ -223,9 +223,8 @@ class Debug
         if ($echo) {
             echo($output);
             return;
-        } else {
-            return $output;
         }
+        return $output;
     }
 
     public function inject(Response $response, &$content)

+ 12 - 12
thinkphp/library/think/Env.php

@@ -62,21 +62,21 @@ class Env
     {
         $result = getenv('PHP_' . $name);
 
-        if (false !== $result) {
-            if ('false' === $result) {
-                $result = false;
-            } elseif ('true' === $result) {
-                $result = true;
-            }
+        if (false === $result) {
+            return $default;
+        }
 
-            if (!isset($this->data[$name])) {
-                $this->data[$name] = $result;
-            }
+        if ('false' === $result) {
+            $result = false;
+        } elseif ('true' === $result) {
+            $result = true;
+        }
 
-            return $result;
-        } else {
-            return $default;
+        if (!isset($this->data[$name])) {
+            $this->data[$name] = $result;
         }
+
+        return $result;
     }
 
     /**

+ 2 - 2
thinkphp/library/think/Error.php

@@ -66,9 +66,9 @@ class Error
         if (error_reporting() & $errno) {
             // 将错误信息托管至 think\exception\ErrorException
             throw $exception;
-        } else {
-            self::getExceptionHandler()->report($exception);
         }
+
+        self::getExceptionHandler()->report($exception);
     }
 
     /**

+ 4 - 0
thinkphp/library/think/Facade.php

@@ -88,6 +88,10 @@ class Facade
      */
     public static function instance(...$args)
     {
+        if (__CLASS__ != static::class) {
+            return self::__callStatic('instance', $args);
+        }
+
         return self::createFacade('', $args);
     }
 

+ 52 - 52
thinkphp/library/think/File.php

@@ -147,7 +147,7 @@ class File extends SplFileObject
 
     /**
      * 检查目录是否可写
-     * @access public
+     * @access protected
      * @param  string   $path    目录
      * @return boolean
      */
@@ -159,10 +159,10 @@ class File extends SplFileObject
 
         if (mkdir($path, 0755, true)) {
             return true;
-        } else {
-            $this->error = ['directory {:path} creation failed', ['path' => $path]];
-            return false;
         }
+
+        $this->error = ['directory {:path} creation failed', ['path' => $path]];
+        return false;
     }
 
     /**
@@ -227,27 +227,10 @@ class File extends SplFileObject
     {
         $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';
+        if ((isset($rule['size']) && !$this->checkSize($rule['size']))
+            || (isset($rule['type']) && !$this->checkMime($rule['type']))
+            || (isset($rule['ext']) && !$this->checkExt($rule['ext']))
+            || !$this->checkImg()) {
             return false;
         }
 
@@ -269,6 +252,7 @@ class File extends SplFileObject
         $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION));
 
         if (!in_array($extension, $ext)) {
+            $this->error = 'extensions to upload is not allowed';
             return false;
         }
 
@@ -286,6 +270,7 @@ class File extends SplFileObject
 
         /* 对图像文件进行严格检测 */
         if (in_array($extension, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']) && !in_array($this->getImageType($this->filename), [1, 2, 3, 4, 6, 13])) {
+            $this->error = 'illegal image files';
             return false;
         }
 
@@ -297,13 +282,13 @@ class File extends SplFileObject
     {
         if (function_exists('exif_imagetype')) {
             return exif_imagetype($image);
-        } else {
-            try {
-                $info = getimagesize($image);
-                return $info ? $info[2] : false;
-            } catch (\Exception $e) {
-                return false;
-            }
+        }
+
+        try {
+            $info = getimagesize($image);
+            return $info ? $info[2] : false;
+        } catch (\Exception $e) {
+            return false;
         }
     }
 
@@ -316,6 +301,7 @@ class File extends SplFileObject
     public function checkSize($size)
     {
         if ($this->getSize() > $size) {
+            $this->error = 'filesize not match';
             return false;
         }
 
@@ -335,6 +321,7 @@ class File extends SplFileObject
         }
 
         if (!in_array(strtolower($this->getMime()), $mime)) {
+            $this->error = 'mimetype to upload is not allowed';
             return false;
         }
 
@@ -402,7 +389,7 @@ class File extends SplFileObject
 
     /**
      * 获取保存文件名
-     * @access public
+     * @access protected
      * @param  string|bool   $savename    保存的文件名 默认自动生成
      * @return string
      */
@@ -410,25 +397,9 @@ class File extends SplFileObject
     {
         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') . '/' . md5(microtime(true));
-                        break;
-                    default:
-                        if (in_array($this->rule, hash_algos())) {
-                            $hash     = $this->hash($this->rule);
-                            $savename = substr($hash, 0, 2) . '/' . substr($hash, 2);
-                        } elseif (is_callable($this->rule)) {
-                            $savename = call_user_func($this->rule);
-                        } else {
-                            $savename = date('Ymd') . '/' . md5(microtime(true));
-                        }
-                }
-            }
+            $savename = $this->autoBuildName();
         } elseif ('' === $savename || false === $savename) {
+            // 保留原文件名
             $savename = $this->getInfo('name');
         }
 
@@ -440,8 +411,37 @@ class File extends SplFileObject
     }
 
     /**
+     * 自动生成文件名
+     * @access protected
+     * @return string
+     */
+    protected function autoBuildName()
+    {
+        if ($this->rule instanceof \Closure) {
+            $savename = call_user_func_array($this->rule, [$this]);
+        } else {
+            switch ($this->rule) {
+                case 'date':
+                    $savename = date('Ymd') . DIRECTORY_SEPARATOR . md5(microtime(true));
+                    break;
+                default:
+                    if (in_array($this->rule, hash_algos())) {
+                        $hash     = $this->hash($this->rule);
+                        $savename = substr($hash, 0, 2) . DIRECTORY_SEPARATOR . substr($hash, 2);
+                    } elseif (is_callable($this->rule)) {
+                        $savename = call_user_func($this->rule);
+                    } else {
+                        $savename = date('Ymd') . DIRECTORY_SEPARATOR . md5(microtime(true));
+                    }
+            }
+        }
+
+        return $savename;
+    }
+
+    /**
      * 获取错误代码信息
-     * @access public
+     * @access private
      * @param  int $errorNo  错误号
      */
     private function error($errorNo)

+ 3 - 6
thinkphp/library/think/Hook.php

@@ -116,9 +116,9 @@ class Hook
         if (empty($tag)) {
             //获取全部的插件信息
             return $this->tags;
-        } else {
-            return array_key_exists($tag, $this->tags) ? $this->tags[$tag] : [];
         }
+
+        return array_key_exists($tag, $this->tags) ? $this->tags[$tag] : [];
     }
 
     /**
@@ -137,10 +137,7 @@ class Hook
         foreach ($tags as $key => $name) {
             $results[$key] = $this->execTag($name, $tag, $params);
 
-            if (false === $results[$key]) {
-                // 如果返回false 则中断行为执行
-                break;
-            } elseif (!is_null($results[$key]) && $once) {
+            if (false === $results[$key] || (!is_null($results[$key]) && $once)) {
                 break;
             }
         }

+ 2 - 2
thinkphp/library/think/Lang.php

@@ -79,9 +79,9 @@ class Lang
 
         if (is_array($name)) {
             return $this->lang[$range] = array_change_key_case($name) + $this->lang[$range];
-        } else {
-            return $this->lang[$range][strtolower($name)] = $value;
         }
+
+        return $this->lang[$range][strtolower($name)] = $value;
     }
 
     /**

+ 33 - 18
thinkphp/library/think/Loader.php

@@ -58,29 +58,44 @@ class Loader
         // 注册系统自动加载
         spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true);
 
-        // 注册命名空间定义
-        self::addNamespace([
-            'think'  => __DIR__ . '/',
-            'traits' => __DIR__ . '/../traits/',
-        ]);
+        $path = realpath(dirname($_SERVER['SCRIPT_FILENAME']));
 
-        $path = dirname($_SERVER['SCRIPT_FILENAME']);
-        if (is_file('./think')) {
-            $rootPath = realpath($path) . '/';
+        if ('cli-server' == PHP_SAPI || !is_file('./think')) {
+            $rootPath = dirname($path) . DIRECTORY_SEPARATOR;
         } else {
-            $rootPath = realpath($path . '/../') . '/';
-        }
-
-        // 加载类库映射文件
-        if (is_file($rootPath . 'runtime/classmap.php')) {
-            self::addClassMap(__include_file($rootPath . 'runtime/classmap.php'));
+            $rootPath = $path . DIRECTORY_SEPARATOR;
         }
 
-        self::$composerPath = $rootPath . 'vendor/composer/';
+        self::$composerPath = $rootPath . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR;
 
         // Composer自动加载支持
         if (is_dir(self::$composerPath)) {
-            self::registerComposerLoader(self::$composerPath);
+            if (is_file(self::$composerPath . 'autoload_static.php')) {
+                require self::$composerPath . 'autoload_static.php';
+
+                $declaredClass = get_declared_classes();
+                $composerClass = array_pop($declaredClass);
+
+                self::$prefixLengthsPsr4 = $composerClass::$prefixLengthsPsr4;
+
+                self::$prefixDirsPsr4 = property_exists($composerClass, 'prefixDirsPsr4') ? $composerClass::$prefixDirsPsr4 : [];
+
+                self::$prefixesPsr0 = property_exists($composerClass, 'prefixesPsr0') ? $composerClass::$prefixesPsr0 : [];
+                self::$map          = property_exists($composerClass, 'classMap') ? $composerClass::$classMap : [];
+            } else {
+                self::registerComposerLoader(self::$composerPath);
+            }
+        }
+
+        // 注册命名空间定义
+        self::addNamespace([
+            'think'  => __DIR__,
+            'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits',
+        ]);
+
+        // 加载类库映射文件
+        if (is_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')) {
+            self::addClassMap(__include_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php'));
         }
 
         // 自动加载extend目录
@@ -346,9 +361,9 @@ class Loader
                 return strtoupper($match[1]);
             }, $name);
             return $ucfirst ? ucfirst($name) : lcfirst($name);
-        } else {
-            return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
         }
+
+        return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_"));
     }
 }
 

+ 51 - 28
thinkphp/library/think/Log.php

@@ -50,6 +50,12 @@ class Log implements LoggerInterface
     protected $key;
 
     /**
+     * 是否允许日志写入
+     * @var bool
+     */
+    protected $allowWrite = true;
+
+    /**
      * 应用对象
      * @var App
      */
@@ -108,6 +114,10 @@ class Log implements LoggerInterface
      */
     public function record($msg, $type = 'info', array $context = [])
     {
+        if (!$this->allowWrite) {
+            return;
+        }
+
         if (is_string($msg)) {
             $replace = [];
             foreach ($context as $key => $val) {
@@ -168,47 +178,60 @@ class Log implements LoggerInterface
     }
 
     /**
+     * 关闭本次请求日志写入
+     * @access public
+     * @return $this
+     */
+    public function close()
+    {
+        $this->allowWrite = false;
+        $this->log        = [];
+
+        return $this;
+    }
+
+    /**
      * 保存调试信息
      * @access public
      * @return bool
      */
     public function save()
     {
-        if (!empty($this->log)) {
-            if (is_null($this->driver)) {
-                $this->init($this->app['config']->pull('log'));
-            }
+        if (empty($this->log) || !$this->allowWrite) {
+            return true;
+        }
 
-            if (!$this->check($this->config)) {
-                // 检测日志写入权限
-                return false;
-            }
+        if (is_null($this->driver)) {
+            $this->init($this->app['config']->pull('log'));
+        }
 
-            if (empty($this->config['level'])) {
-                // 获取全部日志
-                $log = $this->log;
-                if (!$this->app->isDebug() && isset($log['debug'])) {
-                    unset($log['debug']);
-                }
-            } else {
-                // 记录允许级别
-                $log = [];
-                foreach ($this->config['level'] as $level) {
-                    if (isset($this->log[$level])) {
-                        $log[$level] = $this->log[$level];
-                    }
-                }
-            }
+        if (!$this->check($this->config)) {
+            // 检测日志写入权限
+            return false;
+        }
 
-            $result = $this->driver->save($log);
-            if ($result) {
-                $this->log = [];
+        if (empty($this->config['level'])) {
+            // 获取全部日志
+            $log = $this->log;
+            if (!$this->app->isDebug() && isset($log['debug'])) {
+                unset($log['debug']);
             }
+        } else {
+            // 记录允许级别
+            $log = [];
+            foreach ($this->config['level'] as $level) {
+                if (isset($this->log[$level])) {
+                    $log[$level] = $this->log[$level];
+                }
+            }
+        }
 
-            return $result;
+        $result = $this->driver->save($log);
+        if ($result) {
+            $this->log = [];
         }
 
-        return true;
+        return $result;
     }
 
     /**

+ 48 - 21
thinkphp/library/think/http/middleware/Dispatcher.php → thinkphp/library/think/Middleware.php

@@ -9,18 +9,17 @@
 // | Author: Slince <taosikai@yeah.net>
 // +----------------------------------------------------------------------
 
-namespace think\http\middleware;
+namespace think;
 
-use think\Request;
-use think\Response;
-
-class Dispatcher implements DispatcherInterface
+class Middleware
 {
-    protected $queue;
+    protected $queue = [];
 
-    public function __construct($middlewares = [])
+    public function import(array $middlewares = [])
     {
-        $this->queue = (array) $middlewares;
+        foreach ($middlewares as $middleware) {
+            $this->add($middleware);
+        }
     }
 
     /**
@@ -28,16 +27,26 @@ class Dispatcher implements DispatcherInterface
      */
     public function add($middleware)
     {
-        $this->assertValid($middleware);
+        if (is_null($middleware)) {
+            return;
+        }
+
+        $middleware = $this->buildMiddleware($middleware);
+
         $this->queue[] = $middleware;
     }
 
     /**
      * {@inheritdoc}
      */
-    public function insert($middleware)
+    public function unshift($middleware)
     {
-        $this->assertValid($middleware);
+        if (is_null($middleware)) {
+            return;
+        }
+
+        $middleware = $this->buildMiddleware($middleware);
+
         array_unshift($this->queue, $middleware);
     }
 
@@ -54,8 +63,30 @@ class Dispatcher implements DispatcherInterface
      */
     public function dispatch(Request $request)
     {
-        $requestHandler = $this->resolve();
-        return call_user_func($requestHandler, $request);
+        return call_user_func($this->resolve(), $request);
+    }
+
+    protected function buildMiddleware($middleware)
+    {
+        if (is_array($middleware)) {
+            list($middleware, $param) = $middleware;
+        }
+
+        if ($middleware instanceof \Closure) {
+            return [$middleware, null];
+        }
+
+        if (!is_string($middleware)) {
+            throw new \InvalidArgumentException('The middleware is invalid');
+        }
+
+        $class = false === strpos($middleware, '\\') ? Container::get('app')->getNamespace() . '\\http\\middleware\\' . $middleware : $middleware;
+
+        if (strpos($class, ':')) {
+            list($class, $param) = explode(':', $class, 2);
+        }
+
+        return [[Container::get($class), 'handle'], isset($param) ? $param : null];
     }
 
     protected function resolve()
@@ -64,7 +95,9 @@ class Dispatcher implements DispatcherInterface
             $middleware = array_shift($this->queue);
 
             if (null !== $middleware) {
-                $response = call_user_func($middleware, $request, $this->resolve());
+                list($call, $param) = $middleware;
+
+                $response = call_user_func_array($call, [$request, $this->resolve(), $param]);
 
                 if (!$response instanceof Response) {
                     throw new \LogicException('The middleware must return Response instance');
@@ -72,15 +105,9 @@ class Dispatcher implements DispatcherInterface
 
                 return $response;
             } else {
-                throw new MissingResponseException('The queue was exhausted, with no response returned');
+                throw new \InvalidArgumentException('The queue was exhausted, with no response returned');
             }
         };
     }
 
-    protected function assertValid($middleware)
-    {
-        if (!is_callable($middleware)) {
-            throw new \InvalidArgumentException('The middleware is invalid');
-        }
-    }
 }

+ 13 - 13
thinkphp/library/think/Model.php

@@ -389,7 +389,7 @@ abstract class Model implements \JsonSerializable, \ArrayAccess
     /**
      * 检查数据是否允许写入
      * @access protected
-     * @param  array   $autoFields 自动完成的字段列表
+     * @param  array   $append 自动完成的字段列表
      * @return array
      */
     protected function checkAllowFields(array $append = [])
@@ -478,13 +478,11 @@ abstract class Model implements \JsonSerializable, \ArrayAccess
             $where = $array;
         }
 
-        if (!empty($this->relationWrite)) {
-            foreach ($this->relationWrite as $name => $val) {
-                if (is_array($val)) {
-                    foreach ($val as $key) {
-                        if (isset($data[$key])) {
-                            unset($data[$key]);
-                        }
+        foreach ((array) $this->relationWrite as $name => $val) {
+            if (is_array($val)) {
+                foreach ($val as $key) {
+                    if (isset($data[$key])) {
+                        unset($data[$key]);
                     }
                 }
             }
@@ -861,13 +859,15 @@ abstract class Model implements \JsonSerializable, \ArrayAccess
      */
     public static function destroy($data)
     {
+        if (empty($data) && 0 !== $data) {
+            return 0;
+        }
+
         $model = new static();
 
         $query = $model->db();
 
-        if (empty($data) && 0 !== $data) {
-            return 0;
-        } elseif (is_array($data) && key($data) !== 0) {
+        if (is_array($data) && key($data) !== 0) {
             $query->where($data);
             $data = null;
         } elseif ($data instanceof \Closure) {
@@ -947,9 +947,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess
     {
         if (array_key_exists($name, $this->data) || array_key_exists($name, $this->relation)) {
             return true;
-        } else {
-            return false;
         }
+
+        return false;
     }
 
     /**

+ 2 - 2
thinkphp/library/think/Paginator.php

@@ -104,8 +104,8 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
      * @param       $items
      * @param       $listRows
      * @param null  $currentPage
-     * @param bool  $simple
      * @param null  $total
+     * @param bool  $simple
      * @param array $options
      * @return Paginator
      */
@@ -150,7 +150,7 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
 
         $url = $path;
         if (!empty($parameters)) {
-            $url .= '?' . urldecode(http_build_query($parameters, null, '&'));
+            $url .= '?' . http_build_query($parameters, null, '&');
         }
 
         return $url . $this->buildFragment();

+ 172 - 104
thinkphp/library/think/Request.php

@@ -252,6 +252,12 @@ class Request
     protected $isCheckCache;
 
     /**
+     * 请求安全Key
+     * @var string
+     */
+    protected $secureKey;
+
+    /**
      * 架构函数
      * @access public
      * @param  array  $options 参数
@@ -279,9 +285,9 @@ class Request
         if (array_key_exists($method, $this->hook)) {
             array_unshift($args, $this);
             return call_user_func_array($this->hook[$method], $args);
-        } else {
-            throw new Exception('method not exists:' . static::class . '->' . $method);
         }
+
+        throw new Exception('method not exists:' . static::class . '->' . $method);
     }
 
     /**
@@ -404,9 +410,27 @@ class Request
     }
 
     /**
+     * 获取当前根域名
+     * @access public
+     * @return string
+     */
+    public function rootDomain()
+    {
+        $root = $this->config->get('app.url_domain_root');
+
+        if (!$root) {
+            $item  = explode('.', $this->host());
+            $count = count($item);
+            $root  = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0];
+        }
+
+        return $root;
+    }
+
+    /**
      * 获取当前子域名
      * @access public
-     * @return string|$this
+     * @return string
      */
     public function subDomain()
     {
@@ -437,10 +461,10 @@ class Request
     {
         if (is_null($domain)) {
             return $this->panDomain;
-        } else {
-            $this->panDomain = $domain;
-            return $this;
         }
+
+        $this->panDomain = $domain;
+        return $this;
     }
 
     /**
@@ -592,7 +616,7 @@ class Request
                 }
             }
 
-            $this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/');
+            $this->pathinfo = empty($_SERVER['PATH_INFO']) || '/' == $_SERVER['PATH_INFO'] ? '' : ltrim($_SERVER['PATH_INFO'], '/');
         }
 
         return $this->pathinfo;
@@ -1081,37 +1105,12 @@ class Request
         $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);
-                    }
-                }
-            }
+            $array = $this->dealUploadFile($files);
+
             if (strpos($name, '.')) {
                 list($name, $sub) = explode('.', $name);
             }
+
             if ('' === $name) {
                 // 获取全部文件
                 return $array;
@@ -1125,6 +1124,46 @@ class Request
         return;
     }
 
+    protected function dealUploadFile($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);
+                }
+            }
+        }
+
+        return $array;
+    }
+
     /**
      * 获取环境变量
      * @access public
@@ -1214,6 +1253,7 @@ class Request
             } else {
                 $type = 's';
             }
+
             // 按.拆分成多维数组进行判断
             foreach (explode('.', $name) as $val) {
                 if (isset($data[$val])) {
@@ -1223,6 +1263,7 @@ class Request
                     return $default;
                 }
             }
+
             if (is_object($data)) {
                 return $data;
             }
@@ -1256,9 +1297,9 @@ class Request
     {
         if (is_null($filter)) {
             return $this->filter;
-        } else {
-            $this->filter = $filter;
         }
+
+        $this->filter = $filter;
     }
 
     protected function getFilter($filter, $default)
@@ -1478,9 +1519,9 @@ class Request
 
         if (true === $ajax) {
             return $result;
-        } else {
-            return $this->param($this->config->get('var_ajax')) ? true : $result;
         }
+
+        return $this->param($this->config->get('var_ajax')) ? true : $result;
     }
 
     /**
@@ -1495,9 +1536,9 @@ class Request
 
         if (true === $pjax) {
             return $result;
-        } else {
-            return $this->param($this->config->get('var_pjax')) ? true : $result;
         }
+
+        return $this->param($this->config->get('var_pjax')) ? true : $result;
     }
 
     /**
@@ -1555,9 +1596,9 @@ class Request
             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;
         }
+
+        return false;
     }
 
     /**
@@ -1655,9 +1696,9 @@ class Request
     {
         if (!empty($route)) {
             $this->routeInfo = $route;
-        } else {
-            return $this->routeInfo;
         }
+
+        return $this->routeInfo;
     }
 
     /**
@@ -1676,6 +1717,20 @@ class Request
     }
 
     /**
+     * 获取当前请求的安全Key
+     * @access public
+     * @return string
+     */
+    public function secureKey()
+    {
+        if (is_null($this->secureKey)) {
+            $this->secureKey = uniqid('', true);
+        }
+
+        return $this->secureKey;
+    }
+
+    /**
      * 设置或者获取当前的模块名
      * @access public
      * @param  string $module 模块名
@@ -1686,9 +1741,9 @@ class Request
         if (!is_null($module)) {
             $this->module = $module;
             return $this;
-        } else {
-            return $this->module ?: '';
         }
+
+        return $this->module ?: '';
     }
 
     /**
@@ -1702,9 +1757,9 @@ class Request
         if (!is_null($controller)) {
             $this->controller = $controller;
             return $this;
-        } else {
-            return $this->controller ?: '';
         }
+
+        return $this->controller ?: '';
     }
 
     /**
@@ -1715,12 +1770,13 @@ class Request
      */
     public function action($action = null)
     {
-        if (!is_null($action)) {
+        if (!is_null($action) && !is_bool($action)) {
             $this->action = $action;
             return $this;
-        } else {
-            return $this->action ?: '';
         }
+
+        $name = $this->action ?: '';
+        return true === $action ? $name : strtolower($name);
     }
 
     /**
@@ -1734,9 +1790,9 @@ class Request
         if (!is_null($lang)) {
             $this->langset = $lang;
             return $this;
-        } else {
-            return $this->langset ?: '';
         }
+
+        return $this->langset ?: '';
     }
 
     /**
@@ -1800,66 +1856,67 @@ class Request
             $except = [];
         }
 
-        if (false !== $key && $this->isGet() && !$this->isCheckCache) {
-            // 标记请求缓存检查
-            $this->isCheckCache = true;
-            if (false === $expire) {
-                // 关闭当前缓存
-                return;
-            }
+        if (false === $key || !$this->isGet() || $this->isCheckCache || false === $expire) {
+            // 关闭当前缓存
+            return;
+        }
 
-            foreach ($except as $rule) {
-                if (0 === stripos($this->url(), $rule)) {
-                    return;
-                }
-            }
+        // 标记请求缓存检查
+        $this->isCheckCache = true;
 
-            if ($key instanceof \Closure) {
-                $key = call_user_func_array($key, [$this]);
-            } elseif (true === $key) {
-                // 自动缓存功能
-                $key = '__URL__';
-            } elseif (strpos($key, '|')) {
-                list($key, $fun) = explode('|', $key);
+        foreach ($except as $rule) {
+            if (0 === stripos($this->url(), $rule)) {
+                return;
             }
+        }
 
-            // 特殊规则替换
-            if (false !== strpos($key, '__')) {
-                $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__'], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key);
-            }
+        if ($key instanceof \Closure) {
+            $key = call_user_func_array($key, [$this]);
+        } elseif (true === $key) {
+            // 自动缓存功能
+            $key = '__URL__';
+        } elseif (strpos($key, '|')) {
+            list($key, $fun) = explode('|', $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 (false !== strpos($key, '__')) {
+            $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__'], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key);
+        }
 
-            if (isset($fun)) {
-                $key = $fun($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);
+                }
             }
-            $cache = Container::get('cache');
-            if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) {
-                // 读取缓存
-                $response = Response::create()->code(304);
-                throw new HttpResponseException($response);
-            } elseif ($cache->has($key)) {
-                list($content, $header) = $cache->get($key);
-                $response               = Response::create($content)->header($header);
-                throw new HttpResponseException($response);
+        } elseif (strpos($key, ']')) {
+            if ('[' . $this->ext() . ']' == $key) {
+                // 缓存某个后缀的请求
+                $key = md5($this->url());
             } else {
-                $this->cache = [$key, $expire, $tag];
+                return;
             }
         }
+
+        if (isset($fun)) {
+            $key = $fun($key);
+        }
+        $cache = Container::get('cache');
+
+        if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) {
+            // 读取缓存
+            $response = Response::create()->code(304);
+            throw new HttpResponseException($response);
+        } elseif ($cache->has($key)) {
+            list($content, $header) = $cache->get($key);
+
+            $response = Response::create($content)->header($header);
+            throw new HttpResponseException($response);
+        }
+
+        $this->cache = [$key, $expire, $tag];
     }
 
     /**
@@ -1872,4 +1929,15 @@ class Request
         return $this->cache;
     }
 
+    /**
+     * 获取请求数据的值
+     * @access public
+     * @param  string $name 名称
+     * @return mixed
+     */
+    public function __get($name)
+    {
+        return $this->param($name);
+    }
+
 }

+ 4 - 4
thinkphp/library/think/Response.php

@@ -103,9 +103,9 @@ class Response
 
         if (class_exists($class)) {
             return new $class($data, $code, $header, $options);
-        } else {
-            return new static($data, $code, $header, $options);
         }
+
+        return new static($data, $code, $header, $options);
     }
 
     /**
@@ -352,9 +352,9 @@ class Response
     {
         if (!empty($name)) {
             return isset($this->header[$name]) ? $this->header[$name] : null;
-        } else {
-            return $this->header;
         }
+
+        return $this->header;
     }
 
     /**

+ 130 - 314
thinkphp/library/think/Route.php

@@ -12,6 +12,7 @@
 namespace think;
 
 use think\exception\RouteNotFoundException;
+use think\route\AliasRule;
 use think\route\dispatch\Url as UrlDispatch;
 use think\route\Domain;
 use think\route\Resource;
@@ -71,18 +72,12 @@ class Route
     protected $domain;
 
     /**
-     * 当前分组
-     * @var string
+     * 当前分组对象
+     * @var RuleGroup
      */
     protected $group;
 
     /**
-     * 路由标识
-     * @var array
-     */
-    protected $name = [];
-
-    /**
      * 路由绑定
      * @var array
      */
@@ -101,20 +96,31 @@ class Route
     protected $cross;
 
     /**
-     * 当前路由标识
-     * @var string
-     */
-    protected $ruleName;
-
-    /**
      * 路由别名
      * @var array
      */
     protected $alias = [];
 
-    public function __construct(Request $request, Config $config)
+    /**
+     * 路由是否延迟解析
+     * @var bool
+     */
+    protected $lazy = true;
+
+    /**
+     * (分组)路由规则是否合并解析
+     * @var bool
+     */
+    protected $mergeRuleRegex = true;
+
+    /**
+     * 路由解析自动搜索多级控制器
+     * @var bool
+     */
+    protected $autoSearchController = true;
+
+    public function __construct(Request $request)
     {
-        $this->config  = $config;
         $this->request = $request;
         $this->host    = $this->request->host();
 
@@ -122,6 +128,44 @@ class Route
     }
 
     /**
+     * 设置路由域名及分组(包括资源路由)是否延迟解析
+     * @access public
+     * @param  bool     $lazy   路由是否延迟解析
+     * @return $this
+     */
+    public function lazy($lazy = true)
+    {
+        $this->lazy = $lazy;
+        return $this;
+    }
+
+    /**
+     * 设置路由域名及分组(包括资源路由)是否合并解析
+     * @access public
+     * @param  bool     $merge   路由是否合并解析
+     * @return $this
+     */
+    public function mergeRuleRegex($merge = true)
+    {
+        $this->mergeRuleRegex = $merge;
+        $this->group->mergeRuleRegex($merge);
+
+        return $this;
+    }
+
+    /**
+     * 设置路由自动解析是否搜索多级控制器
+     * @access public
+     * @param  bool     $auto   是否自动搜索多级控制器
+     * @return $this
+     */
+    public function autoSearchController($auto = true)
+    {
+        $this->autoSearchController = $auto;
+        return $this;
+    }
+
+    /**
      * 初始化默认域名
      * @access protected
      * @return void
@@ -137,22 +181,7 @@ class Route
         $this->domains[$this->host] = $domain;
 
         // 默认分组
-        $this->group = $this->createTopGroup($domain);
-    }
-
-    /**
-     * 创建一个域名下的顶级路由分组
-     * @access protected
-     * @param  Domain    $domain 域名
-     * @return RuleGroup
-     */
-    protected function createTopGroup(Domain $domain)
-    {
-        $group = new RuleGroup($this);
-        // 注册分组到当前域名
-        $domain->addRule($group);
-
-        return $group;
+        $this->group = $domain;
     }
 
     /**
@@ -205,22 +234,6 @@ class Route
     }
 
     /**
-     * 获取当前根域名
-     * @access protected
-     * @return string
-     */
-    protected function getRootDomain()
-    {
-        $root = $this->config->get('app.url_domain_root');
-        if (!$root) {
-            $item  = explode('.', $this->host);
-            $count = count($item);
-            $root  = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0];
-        }
-        return $root;
-    }
-
-    /**
      * 注册域名路由
      * @access public
      * @param  string|array  $name 子域名
@@ -235,33 +248,22 @@ class Route
         $domainName = is_array($name) ? array_shift($name) : $name;
 
         if ('*' != $domainName && !strpos($domainName, '.')) {
-            $domainName .= '.' . $this->getRootDomain();
+            $domainName .= '.' . $this->request->rootDomain();
         }
 
-        $route = $this->config->get('url_lazy_route') ? $rule : null;
+        if (!isset($this->domains[$domainName])) {
+            $domain = (new Domain($this, $domainName, $rule, $option, $pattern))
+                ->lazy($this->lazy)
+                ->mergeRuleRegex($this->mergeRuleRegex);
 
-        $domain = new Domain($this, $domainName, $route, $option, $pattern);
-
-        if (is_null($route)) {
-            // 获取原始分组
-            $originGroup = $this->group;
-            // 设置当前域名
-            $this->domain = $domainName;
-            $this->group  = $this->createTopGroup($domain);
-
-            // 解析域名路由规则
-            $this->parseGroupRule($domain, $rule);
-
-            // 还原默认域名
-            $this->domain = $this->host;
-            // 还原默认分组
-            $this->group = $originGroup;
+            $this->domains[$domainName] = $domain;
+        } else {
+            $domain = $this->domains[$domainName];
+            $domain->parseGroupRule($rule);
         }
 
-        $this->domains[$domainName] = $domain;
-
         if (is_array($name) && !empty($name)) {
-            $root = $this->getRootDomain();
+            $root = $this->request->rootDomain();
             foreach ($name as $item) {
                 if (!strpos($item, '.')) {
                     $item .= '.' . $root;
@@ -276,32 +278,6 @@ class Route
     }
 
     /**
-     * 解析分组和域名的路由规则及绑定
-     * @access public
-     * @param  RuleGroup    $group 分组路由对象
-     * @param  mixed        $rule 路由规则
-     * @return void
-     */
-    public function parseGroupRule($group, $rule)
-    {
-        if ($rule instanceof \Closure) {
-            Container::getInstance()->invokeFunction($rule);
-        } elseif ($rule instanceof Response) {
-            $group->setRule($rule);
-        } elseif (is_array($rule)) {
-            $this->rules($rule);
-        } elseif ($rule) {
-            if (false !== strpos($rule, '?')) {
-                list($rule, $query) = explode('?', $rule);
-                parse_str($query, $vars);
-                $group->append($vars);
-            }
-
-            $this->bind($rule);
-        }
-    }
-
-    /**
      * 获取域名
      * @access public
      * @return array
@@ -315,11 +291,14 @@ class Route
      * 设置路由绑定
      * @access public
      * @param  string     $bind 绑定信息
+     * @param  string     $domain 域名
      * @return $this
      */
-    public function bind($bind)
+    public function bind($bind, $domain = null)
     {
-        $this->bind[$this->domain] = $bind;
+        $domain = is_null($domain) ? $this->domain : $domain;
+
+        $this->bind[$domain] = $bind;
 
         return $this;
     }
@@ -356,19 +335,6 @@ class Route
     }
 
     /**
-     * 设置当前路由标识
-     * @access public
-     * @param  string     $name 路由命名标识
-     * @return $this
-     */
-    public function name($name)
-    {
-        $this->ruleName = $name;
-
-        return $this;
-    }
-
-    /**
      * 读取路由标识
      * @access public
      * @param  string    $name 路由标识
@@ -376,13 +342,7 @@ class Route
      */
     public function getName($name = null)
     {
-        if (is_null($name)) {
-            return $this->name;
-        }
-
-        $name = strtolower($name);
-
-        return isset($this->name[$name]) ? $this->name[$name] : null;
+        return Container::get('rule_name')->get($name);
     }
 
     /**
@@ -393,7 +353,7 @@ class Route
      */
     public function setName($name)
     {
-        $this->name = $name;
+        Container::get('rule_name')->import($name);
         return $this;
     }
 
@@ -465,68 +425,9 @@ class Route
      * @param  array     $pattern    变量规则
      * @return RuleItem
      */
-    public function rule($rule, $route, $method = '*', $option = [], $pattern = [])
-    {
-        // 读取路由标识
-        if (is_array($rule)) {
-            $name = $rule[0];
-            $rule = $rule[1];
-        } elseif ($this->ruleName) {
-            $name = $this->ruleName;
-
-            $this->ruleName = null;
-        } elseif (is_string($route)) {
-            $name = $route;
-        }
-
-        $method = strtolower($method);
-
-        // 创建路由规则实例
-        $ruleItem = new RuleItem($this, $this->group, $rule, $route, $method, $option, $pattern);
-
-        if (isset($name)) {
-            // 上级完整分组名
-            $group = $this->group->getFullName();
-
-            if ($group) {
-                $rule = $group . '/' . $rule;
-            }
-
-            // 设置路由标识 用于URL快速生成
-            $this->setRuleName($rule, $name, $option);
-        }
-
-        // 添加到当前分组
-        $this->group->addRule($ruleItem, $method);
-
-        if (!empty($option['cross_domain'])) {
-            $this->setCrossDomainRule($ruleItem, $method);
-        }
-
-        return $ruleItem;
-    }
-
-    /**
-     * 设置路由标识 用于URL反解生成
-     * @access public
-     * @param  string    $rule      路由规则
-     * @param  string    $name      路由标识
-     * @param  array     $option    路由参数
-     * @return void
-     */
-    public function setRuleName($rule, $name, $option = [])
+    public function rule($rule, $route, $method = '*', array $option = [], array $pattern = [])
     {
-        $vars = $this->parseVar($rule);
-
-        if (isset($option['ext'])) {
-            $suffix = $option['ext'];
-        } elseif ($this->group->getOption('ext')) {
-            $suffix = $this->group->getOption('ext');
-        } else {
-            $suffix = null;
-        }
-
-        $this->name[strtolower($name)][] = [$rule, $vars, $this->domain, $suffix];
+        return $this->group->addRule($rule, $route, $method, $option, $pattern);
     }
 
     /**
@@ -539,10 +440,10 @@ class Route
     public function setCrossDomainRule($rule, $method = '*')
     {
         if (!isset($this->cross)) {
-            $this->cross = new RuleGroup($this);
+            $this->cross = (new RuleGroup($this))->mergeRuleRegex($this->mergeRuleRegex);
         }
 
-        $this->cross->addRule($rule, $method);
+        $this->cross->addRuleItem($rule, $method);
 
         return $this;
     }
@@ -556,23 +457,9 @@ class Route
      * @param  array     $pattern    变量规则
      * @return void
      */
-    public function rules($rules, $method = '*', $option = [], $pattern = [])
+    public function rules($rules, $method = '*', array $option = [], array $pattern = [])
     {
-        foreach ($rules as $key => $val) {
-            if (is_numeric($key)) {
-                $key = array_shift($val);
-            }
-
-            if (is_array($val)) {
-                $route   = array_shift($val);
-                $option  = $val ? array_shift($val) : [];
-                $pattern = $val ? array_shift($val) : [];
-            } else {
-                $route = $val;
-            }
-
-            $this->rule($key, $route, $method, $option, $pattern);
-        }
+        $this->group->addRules($rules, $method, $option, $pattern);
     }
 
     /**
@@ -584,49 +471,28 @@ class Route
      * @param  array             $pattern    变量规则
      * @return RuleGroup
      */
-    public function group($name, $route, $option = [], $pattern = [])
+    public function group($name, $route, array $option = [], array $pattern = [])
     {
         if (is_array($name)) {
             $option = $name;
             $name   = isset($option['name']) ? $option['name'] : '';
         }
 
-        // 创建分组实例
-        $rule  = $this->config->get('url_lazy_route') ? $route : null;
-        $group = new RuleGroup($this, $this->group, $name, $rule, $option, $pattern);
-
-        if (is_null($rule)) {
-            // 解析分组路由
-            $parent = $this->getGroup();
-
-            $this->group = $group;
-
-            // 解析分组路由规则
-            $this->parseGroupRule($group, $route);
-
-            $this->group = $parent;
-        }
-
-        // 注册子分组
-        $this->group->addRule($group);
-
-        if (!empty($option['cross_domain'])) {
-            $this->setCrossDomainRule($group);
-        }
-
-        return $group;
+        return (new RuleGroup($this, $this->group, $name, $route, $option, $pattern))
+            ->lazy($this->lazy)
+            ->mergeRuleRegex($this->mergeRuleRegex);
     }
 
     /**
      * 注册路由
      * @access public
      * @param  string    $rule 路由规则
-     * @param  string    $route 路由地址
+     * @param  mixed     $route 路由地址
      * @param  array     $option 路由参数
      * @param  array     $pattern 变量规则
      * @return RuleItem
      */
-    public function any($rule, $route = '', $option = [], $pattern = [])
+    public function any($rule, $route = '', array $option = [], array $pattern = [])
     {
         return $this->rule($rule, $route, '*', $option, $pattern);
     }
@@ -635,12 +501,12 @@ class Route
      * 注册GET路由
      * @access public
      * @param  string    $rule 路由规则
-     * @param  string    $route 路由地址
+     * @param  mixed     $route 路由地址
      * @param  array     $option 路由参数
      * @param  array     $pattern 变量规则
      * @return RuleItem
      */
-    public function get($rule, $route = '', $option = [], $pattern = [])
+    public function get($rule, $route = '', array $option = [], array $pattern = [])
     {
         return $this->rule($rule, $route, 'GET', $option, $pattern);
     }
@@ -649,12 +515,12 @@ class Route
      * 注册POST路由
      * @access public
      * @param  string    $rule 路由规则
-     * @param  string    $route 路由地址
+     * @param  mixed     $route 路由地址
      * @param  array     $option 路由参数
      * @param  array     $pattern 变量规则
      * @return RuleItem
      */
-    public function post($rule, $route = '', $option = [], $pattern = [])
+    public function post($rule, $route = '', array $option = [], array $pattern = [])
     {
         return $this->rule($rule, $route, 'POST', $option, $pattern);
     }
@@ -663,12 +529,12 @@ class Route
      * 注册PUT路由
      * @access public
      * @param  string    $rule 路由规则
-     * @param  string    $route 路由地址
+     * @param  mixed     $route 路由地址
      * @param  array     $option 路由参数
      * @param  array     $pattern 变量规则
      * @return RuleItem
      */
-    public function put($rule, $route = '', $option = [], $pattern = [])
+    public function put($rule, $route = '', array $option = [], array $pattern = [])
     {
         return $this->rule($rule, $route, 'PUT', $option, $pattern);
     }
@@ -677,12 +543,12 @@ class Route
      * 注册DELETE路由
      * @access public
      * @param  string    $rule 路由规则
-     * @param  string    $route 路由地址
+     * @param  mixed     $route 路由地址
      * @param  array     $option 路由参数
      * @param  array     $pattern 变量规则
      * @return RuleItem
      */
-    public function delete($rule, $route = '', $option = [], $pattern = [])
+    public function delete($rule, $route = '', array $option = [], array $pattern = [])
     {
         return $this->rule($rule, $route, 'DELETE', $option, $pattern);
     }
@@ -691,12 +557,12 @@ class Route
      * 注册PATCH路由
      * @access public
      * @param  string    $rule 路由规则
-     * @param  string    $route 路由地址
+     * @param  mixed     $route 路由地址
      * @param  array     $option 路由参数
      * @param  array     $pattern 变量规则
      * @return RuleItem
      */
-    public function patch($rule, $route = '', $option = [], $pattern = [])
+    public function patch($rule, $route = '', array $option = [], array $pattern = [])
     {
         return $this->rule($rule, $route, 'PATCH', $option, $pattern);
     }
@@ -710,32 +576,31 @@ class Route
      * @param  array     $pattern 变量规则
      * @return Resource
      */
-    public function resource($rule, $route = '', $option = [], $pattern = [])
+    public function resource($rule, $route = '', array $option = [], array $pattern = [])
     {
-        $resource = new Resource($this, $this->group, $rule, $route, $option, $pattern, $this->rest);
-
-        // 添加到当前分组
-        $this->group->addRule($resource);
-
-        return $resource;
+        return (new Resource($this, $this->group, $rule, $route, $option, $pattern, $this->rest))
+            ->lazy($this->lazy);
     }
 
     /**
-     * 注册控制器路由 操作方法对应不同的请求
+     * 注册控制器路由 操作方法对应不同的请求前缀
      * @access public
      * @param  string    $rule 路由规则
      * @param  string    $route 路由地址
      * @param  array     $option 路由参数
      * @param  array     $pattern 变量规则
-     * @return $this
+     * @return RuleGroup
      */
-    public function controller($rule, $route = '', $option = [], $pattern = [])
+    public function controller($rule, $route = '', array $option = [], array $pattern = [])
     {
+        $group = new RuleGroup($this, $this->group, $rule, null, $option, $pattern);
+
         foreach ($this->methodPrefix as $type => $val) {
-            $this->$type($rule . '/:action', $route . '/' . $val . ':action', $option, $pattern);
+            $item = $this->$type(':action', $val . ':action');
+            $group->addRuleItem($item, $type);
         }
 
-        return $this;
+        return $group->prefix($route . '/');
     }
 
     /**
@@ -748,7 +613,7 @@ class Route
      * @param  array        $pattern 变量规则
      * @return RuleItem
      */
-    public function view($rule, $template = '', $vars = [], $option = [], $pattern = [])
+    public function view($rule, $template = '', array $vars = [], array $option = [], array $pattern = [])
     {
         return $this->rule($rule, $template, 'GET', $option, $pattern)->view($vars);
     }
@@ -757,13 +622,13 @@ class Route
      * 注册重定向路由
      * @access public
      * @param  string|array $rule 路由规则
-     * @param  string       $template 路由模板地址
+     * @param  string       $route 路由地址
      * @param  array        $status 状态码
      * @param  array        $option 路由参数
      * @param  array        $pattern 变量规则
      * @return RuleItem
      */
-    public function redirect($rule, $route = '', $status = 301, $option = [], $pattern = [])
+    public function redirect($rule, $route = '', $status = 301, array $option = [], array $pattern = [])
     {
         return $this->rule($rule, $route, '*', $option, $pattern)->redirect()->status($status);
     }
@@ -771,20 +636,18 @@ class Route
     /**
      * 注册别名路由
      * @access public
-     * @param  string|array  $rule 路由别名
-     * @param  string        $route 路由地址
-     * @param  array         $option 路由参数
-     * @return $this
+     * @param  string  $rule 路由别名
+     * @param  string  $route 路由地址
+     * @param  array   $option 路由参数
+     * @return AliasRule
      */
-    public function alias($rule = null, $route = '', $option = [])
+    public function alias($rule, $route, array $option = [])
     {
-        if (is_array($rule)) {
-            $this->alias = array_merge($this->alias, $rule);
-        } else {
-            $this->alias[$rule] = $option ? [$route, $option] : $route;
-        }
+        $aliasRule = new AliasRule($this, $this->group, $rule, $route, $option);
 
-        return $this;
+        $this->alias[$rule] = $aliasRule;
+
+        return $aliasRule;
     }
 
     /**
@@ -875,9 +738,9 @@ class Route
      * @param  array     $option 路由参数
      * @return RuleItem
      */
-    public function miss($route, $method = '*', $option = [])
+    public function miss($route, $method = '*', array $option = [])
     {
-        return $this->rule('', $route, $method, $option)->isMiss();
+        return $this->group->addMissRule($route, $method, $option);
     }
 
     /**
@@ -888,7 +751,7 @@ class Route
      */
     public function auto($route)
     {
-        return $this->rule('', $route)->isAuto();
+        return $this->group->addAutoRule($route);
     }
 
     /**
@@ -910,7 +773,7 @@ class Route
         $result = $domain->check($this->request, $url, $depr, $completeMatch);
 
         if (false === $result && !empty($this->cross)) {
-            // 检测跨路由
+            // 检测跨路由
             $result = $this->cross->check($this->request, $url, $depr, $completeMatch);
         }
 
@@ -920,16 +783,15 @@ class Route
         } elseif ($must) {
             // 强制路由不匹配则抛出异常
             throw new RouteNotFoundException();
-        } else {
-            // 默认路由解析
-            return new UrlDispatch($url, ['depr' => $depr, 'auto_search' => $this->config->get('app.controller_auto_search')]);
         }
+
+        // 默认路由解析
+        return new UrlDispatch($url, ['depr' => $depr, 'auto_search' => $this->autoSearchController]);
     }
 
     /**
      * 检测域名的路由规则
      * @access protected
-     * @param  string    $host 当前主机地址
      * @return Domain
      */
     protected function checkDomain()
@@ -982,52 +844,6 @@ class Route
     }
 
     /**
-     * 分析路由规则中的变量
-     * @access public
-     * @param  string    $rule 路由规则
-     * @return array
-     */
-    public 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);
-                if ('$' == substr($name, -1)) {
-                    $name = substr($name, 0, -1);
-                }
-
-                $var[$name] = $optional ? 2 : 1;
-            }
-        }
-
-        return $var;
-    }
-
-    /**
      * 设置全局的路由分组参数
      * @access public
      * @param  string    $method     方法名

+ 29 - 65
thinkphp/library/think/Template.php

@@ -85,33 +85,22 @@ class Template
      */
     public function __construct(array $config = [])
     {
-        $this->config['cache_path']          = Container::get('app')->getRuntimePath() . 'temp/';
-        $this->config                        = array_merge($this->config, $config);
+        $this->config['cache_path'] = Container::get('app')->getRuntimePath() . 'temp/';
+        $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']        = $this->stripPreg($this->config['taglib_begin']);
-        $this->config['taglib_end']          = $this->stripPreg($this->config['taglib_end']);
-        $this->config['tpl_begin']           = $this->stripPreg($this->config['tpl_begin']);
-        $this->config['tpl_end']             = $this->stripPreg($this->config['tpl_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();
-    }
+        $type  = $this->config['compile_type'] ? $this->config['compile_type'] : 'File';
+        $class = false !== strpos($type, '\\') ? $type : '\\think\\template\\driver\\' . ucwords($type);
 
-    /**
-     * 字符串替换 避免正则混淆
-     * @access private
-     * @param  string $str
-     * @return string
-     */
-    private function stripPreg($str)
-    {
-        return str_replace(
-            ['{', '}', '(', ')', '|', '[', ']', '-', '+', '*', '.', '^', '?'],
-            ['\{', '\}', '\(', '\)', '\|', '\[', '\]', '\-', '\+', '\*', '\.', '\^', '\?'],
-            $str);
+        $this->storage = new $class();
     }
 
     /**
@@ -153,8 +142,6 @@ class Template
             $this->config = array_merge($this->config, $config);
         } elseif (isset($this->config[$config])) {
             return $this->config[$config];
-        } else {
-            return;
         }
     }
 
@@ -168,20 +155,20 @@ class Template
     {
         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;
-                }
-            }
+        $data = $this->data;
 
-            return $data;
+        foreach (explode('.', $name) as $key => $val) {
+            if (isset($data[$val])) {
+                $data = $data[$val];
+            } else {
+                $data = null;
+                break;
+            }
         }
+
+        return $data;
     }
 
     /**
@@ -217,7 +204,7 @@ class Template
         $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'], '.');
+            $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_on'] . $this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.');
 
             if (!$this->checkCache($cacheFile)) {
                 // 缓存无效 重新模板编译
@@ -311,18 +298,7 @@ class Template
      */
     private function checkCache($cacheFile)
     {
-        // 未开启缓存功能
-        if (!$this->config['tpl_cache']) {
-            return false;
-        }
-
-        // 缓存文件不存在
-        if (!is_file($cacheFile)) {
-            return false;
-        }
-
-        // 读取缓存文件失败
-        if (!$handle = @fopen($cacheFile, "r")) {
+        if (!$this->config['tpl_cache'] || !is_file($cacheFile) || !$handle = @fopen($cacheFile, "r")) {
             return false;
         }
 
@@ -417,8 +393,6 @@ class Template
         $this->storage->write($cacheFile, $content);
 
         $this->includeFile = [];
-
-        return;
     }
 
     /**
@@ -490,8 +464,6 @@ class Template
 
         // 还原被替换的Literal标签
         $this->parseLiteral($content, true);
-
-        return;
     }
 
     /**
@@ -508,10 +480,8 @@ class Template
 
         // PHP语法检查
         if ($this->config['tpl_deny_php'] && false !== strpos($content, '<?php')) {
-            throw new Exception('not allow php tag', 11600);
+            throw new Exception('not allow php tag');
         }
-
-        return;
     }
 
     /**
@@ -542,8 +512,6 @@ class Template
         } else {
             $content = str_replace('{__NOLAYOUT__}', '', $content);
         }
-
-        return;
     }
 
     /**
@@ -777,8 +745,6 @@ class Template
 
             return explode(',', $matches['name']);
         }
-
-        return;
     }
 
     /**
@@ -802,8 +768,6 @@ class Template
         $tLib = new $className($this);
 
         $tLib->parseTag($content, $hide ? '' : $tagLib);
-
-        return;
     }
 
     /**
@@ -827,9 +791,9 @@ class Template
 
         if (!empty($name) && isset($array[$name])) {
             return $array[$name];
-        } else {
-            return $array;
         }
+
+        return $array;
     }
 
     /**
@@ -1268,9 +1232,9 @@ class Template
             $this->includeFile[$template] = filemtime($template);
 
             return $template;
-        } else {
-            throw new TemplateNotFoundException('template not exists:' . $template, $template);
         }
+
+        throw new TemplateNotFoundException('template not exists:' . $template, $template);
     }
 
     /**

+ 9 - 10
thinkphp/library/think/Url.php

@@ -123,10 +123,8 @@ class Url
 
             if ($alias) {
                 // 别名路由解析
-                foreach ($alias as $key => $val) {
-                    if (is_array($val)) {
-                        $val = $val[0];
-                    }
+                foreach ($alias as $key => $item) {
+                    $val = $item->gerRoute();
 
                     if (0 === strpos($url, $val)) {
                         $url        = $key . substr($url, strlen($val));
@@ -320,20 +318,21 @@ class Url
         foreach ($rule as $item) {
             list($url, $pattern, $domain, $suffix) = $item;
             if (empty($pattern)) {
-                return [rtrim($url, '$'), $domain, $suffix];
+                return [rtrim($url, '?/-'), $domain, $suffix];
             }
 
             $type = $this->app['config']->get('url_common_param');
 
             foreach ($pattern as $key => $val) {
                 if (isset($vars[$key])) {
-                    $url = str_replace(['[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>$', '<' . $key . '?>', ':' . $key . '$', ':' . $key . '', '<' . $key . '>$', '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url);
+                    $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key, '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url);
                     unset($vars[$key]);
-
-                    $result = [$url, $domain, $suffix];
+                    $url    = str_replace(['/?', '-?'], ['/', '-'], $url);
+                    $result = [rtrim($url, '?/-'), $domain, $suffix];
                 } elseif (2 == $val) {
-                    $url    = str_replace(['/[:' . $key . ']', '/[:' . $key . '$]', '[:' . $key . ']', '[:' . $key . '$]', '<' . $key . '?>$', '<' . $key . '?>'], '', $url);
-                    $result = [$url, $domain, $suffix];
+                    $url    = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url);
+                    $url    = str_replace(['/?', '-?'], ['/', '-'], $url);
+                    $result = [rtrim($url, '?/-'), $domain, $suffix];
                 } else {
                     break;
                 }

+ 28 - 27
thinkphp/library/think/Validate.php

@@ -187,7 +187,7 @@ class Validate
      */
     public function __construct(array $rules = [], array $message = [], array $field = [])
     {
-        $this->rule    = array_merge($this->rule, $rules);
+        $this->rule    = $rules + $this->rule;
         $this->message = array_merge($this->message, $message);
         $this->field   = array_merge($this->field, $field);
     }
@@ -214,7 +214,7 @@ class Validate
     public function rule($name, $rule = '')
     {
         if (is_array($name)) {
-            $this->rule = array_merge($this->rule, $name);
+            $this->rule = $name + $this->rule;
             if (is_array($rule)) {
                 $this->field = array_merge($this->field, $rule);
             }
@@ -784,13 +784,13 @@ class Validate
     {
         if (function_exists('exif_imagetype')) {
             return exif_imagetype($image);
-        } else {
-            try {
-                $info = getimagesize($image);
-                return $info ? $info[2] : false;
-            } catch (\Exception $e) {
-                return false;
-            }
+        }
+
+        try {
+            $info = getimagesize($image);
+            return $info ? $info[2] : false;
+        } catch (\Exception $e) {
+            return false;
         }
     }
 
@@ -844,9 +844,9 @@ class Validate
             return true;
         } elseif ($file instanceof File) {
             return $file->checkExt($rule);
-        } else {
-            return false;
         }
+
+        return false;
     }
 
     /**
@@ -867,9 +867,9 @@ class Validate
             return true;
         } elseif ($file instanceof File) {
             return $file->checkMime($rule);
-        } else {
-            return false;
         }
+
+        return false;
     }
 
     /**
@@ -890,9 +890,9 @@ class Validate
             return true;
         } elseif ($file instanceof File) {
             return $file->checkSize($rule);
-        } else {
-            return false;
         }
+
+        return false;
     }
 
     /**
@@ -928,9 +928,9 @@ class Validate
             list($w, $h) = $rule;
 
             return $w == $width && $h == $height;
-        } else {
-            return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]);
         }
+
+        return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]);
     }
 
     /**
@@ -1010,6 +1010,7 @@ class Validate
         if ($db->where($map)->field($pk)->find()) {
             return false;
         }
+
         return true;
     }
 
@@ -1061,9 +1062,9 @@ class Validate
 
         if ($this->getDataValue($data, $field) == $val) {
             return !empty($value) || '0' == $value;
-        } else {
-            return true;
         }
+
+        return true;
     }
 
     /**
@@ -1080,9 +1081,9 @@ class Validate
 
         if ($result) {
             return !empty($value) || '0' == $value;
-        } else {
-            return true;
         }
+
+        return true;
     }
 
     /**
@@ -1099,9 +1100,9 @@ class Validate
 
         if (!empty($val)) {
             return !empty($value) || '0' == $value;
-        } else {
-            return true;
         }
+
+        return true;
     }
 
     /**
@@ -1183,10 +1184,10 @@ class Validate
             // 长度区间
             list($min, $max) = explode(',', $rule);
             return $length >= $min && $length <= $max;
-        } else {
-            // 指定长度
-            return $length == $rule;
         }
+
+        // 指定长度
+        return $length == $rule;
     }
 
     /**
@@ -1321,7 +1322,7 @@ class Validate
             $rule = '/^' . $rule . '$/';
         }
 
-        return 1 === preg_match($rule, (string) $value);
+        return is_scalar($value) && 1 === preg_match($rule, (string) $value);
     }
 
     /**

+ 6 - 3
thinkphp/library/think/cache/driver/File.php

@@ -43,7 +43,7 @@ class File extends Driver
         }
 
         if (empty($this->options['path'])) {
-            $this->options['path'] = Container::get('app')->getRuntimePath() . 'cache/';
+            $this->options['path'] = Container::get('app')->getRuntimePath() . 'cache' . DIRECTORY_SEPARATOR;
         } elseif (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) {
             $this->options['path'] .= DIRECTORY_SEPARATOR;
         }
@@ -242,7 +242,10 @@ class File extends Driver
     {
         $this->writeTimes++;
 
-        return $this->unlink($this->getCacheKey($name));
+        try {
+            return $this->unlink($this->getCacheKey($name));
+        } catch (\Exception $e) {
+        }
     }
 
     /**
@@ -269,7 +272,7 @@ class File extends Driver
 
         foreach ($files as $path) {
             if (is_dir($path)) {
-                $matches = glob($path . '/*.php');
+                $matches = glob($path . DIRECTORY_SEPARATOR . '*.php');
                 if (is_array($matches)) {
                     array_map('unlink', $matches);
                 }

+ 36 - 1
thinkphp/library/think/cache/driver/Redis.php

@@ -74,7 +74,7 @@ class Redis extends Driver
      */
     public function has($name)
     {
-        return $this->handler->get($this->getCacheKey($name)) ? true : false;
+        return $this->handler->exists($this->getCacheKey($name));
     }
 
     /**
@@ -203,4 +203,39 @@ class Redis extends Driver
         return $this->handler->flushDB();
     }
 
+    /**
+     * 如果不存在则写入缓存
+     * @access public
+     * @param string $name 缓存变量名
+     * @param mixed $value 存储数据
+     * @param int $expire  有效时间 0为永久
+     * @return mixed
+     */
+    public function remember($name, $value, $expire = null)
+    {
+        if (is_null($expire)) {
+            $expire = $this->options['expire'];
+        }
+
+        // 没有过期参数时,使用setnx
+        if (!$expire) {
+            $key = $this->getCacheKey($name);
+            $val = $this->serialize($value);
+            $res = $this->handler->setnx($key, $val);
+            if ($res) {
+                $this->writeTimes++;
+                return $value;
+            } else {
+                return $this->get($name);
+            }
+        }
+
+        if ($this->has($name)) {
+            return $this->get($name);
+        } else {
+            $this->set($name, $value, $expire);
+        }
+
+        return $value;
+    }
 }

+ 1 - 1
thinkphp/library/think/console/command/Clear.php

@@ -34,7 +34,7 @@ class Clear extends Command
         if ($files) {
             foreach ($files as $file) {
                 if ('.' != $file && '..' != $file && is_dir($path . $file)) {
-                    array_map('unlink', glob($path . $file . '/*.*'));
+                    array_map('unlink', glob($path . $file . DIRECTORY_SEPARATOR . '*.*'));
                 } elseif ('.gitignore' != $file && is_file($path . $file)) {
                     unlink($path . $file);
                 }

+ 1 - 1
thinkphp/library/think/console/command/RunServer.php

@@ -42,7 +42,7 @@ class RunServer extends Command
             $host,
             $port,
             escapeshellarg($root),
-            escapeshellarg($root . '/router.php')
+            escapeshellarg($root . DIRECTORY_SEPARATOR . 'router.php')
         );
 
         $output->writeln(sprintf('ThinkPHP Development server is started On <http://%s:%s/>', $host, $port));

+ 9 - 2
thinkphp/library/think/console/command/make/Controller.php

@@ -24,17 +24,24 @@ class Controller extends Make
     {
         parent::configure();
         $this->setName('make:controller')
+            ->addOption('api', null, Option::VALUE_NONE, 'Generate an api controller class.')
             ->addOption('plain', null, Option::VALUE_NONE, 'Generate an empty controller class.')
             ->setDescription('Create a new resource controller class');
     }
 
     protected function getStub()
     {
+        $stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR;
+
+        if ($this->input->getOption('api')) {
+            return $stubPath . 'controller.api.stub';
+        }
+
         if ($this->input->getOption('plain')) {
-            return __DIR__ . '/stubs/controller.plain.stub';
+            return $stubPath . 'controller.plain.stub';
         }
 
-        return __DIR__ . '/stubs/controller.stub';
+        return $stubPath . 'controller.stub';
     }
 
     protected function getClassName($name)

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

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

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

@@ -26,7 +26,7 @@ class Model extends Make
 
     protected function getStub()
     {
-        return __DIR__ . '/stubs/model.stub';
+        return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'model.stub';
     }
 
     protected function getNamespace($appNamespace, $module)

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

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

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

@@ -0,0 +1,10 @@
+<?php
+
+namespace {%namespace%};
+
+class {%className%}
+{
+    public function handle($request, \Closure $next)
+    {
+    }
+}

+ 15 - 2
thinkphp/library/think/console/command/optimize/Config.php

@@ -71,7 +71,10 @@ class Config extends Command
 
         // 加载行为扩展文件
         if (is_file($path . 'tags.php')) {
-            $content .= PHP_EOL . '\think\facade\Hook::import(' . (var_export(include $path . 'tags.php' ?: [], true)) . ');' . PHP_EOL;
+            $tags = include $path . 'tags.php';
+            if (is_array($tags)) {
+                $content .= PHP_EOL . '\think\facade\Hook::import(' . (var_export($tags, true)) . ');' . PHP_EOL;
+            }
         }
 
         // 加载公共文件
@@ -84,10 +87,20 @@ class Config extends Command
 
         if ('' == $module) {
             $content .= PHP_EOL . substr(php_strip_whitespace(App::getThinkPath() . 'helper.php'), 6) . PHP_EOL;
+
+            if (is_file($path . 'middleware.php')) {
+                $middleware = include $path . 'middleware.php';
+                if (is_array($middleware)) {
+                    $content .= PHP_EOL . '\think\Container::get("middleware")->import(' . var_export($middleware, true) . ');' . PHP_EOL;
+                }
+            }
         }
 
         if (is_file($path . 'provider.php')) {
-            $content .= PHP_EOL . '\think\Container::getInstance()->bind(' . var_export(include $path . 'provider.php' ?: [], true) . ');' . PHP_EOL;
+            $provider = include $path . 'provider.php';
+            if (is_array($provider)) {
+                $content .= PHP_EOL . '\think\Container::getInstance()->bind(' . var_export($provider, true) . ');' . PHP_EOL;
+            }
         }
 
         $content .= PHP_EOL . '\think\facade\Config::set(' . var_export($config->get(), true) . ');' . PHP_EOL;

+ 4 - 2
thinkphp/library/think/console/command/optimize/Route.php

@@ -28,14 +28,16 @@ class Route extends Command
 
     protected function execute(Input $input, Output $output)
     {
-        file_put_contents(Container::get('app')->getRuntimePath() . 'route.php', $this->buildRouteCache());
+        $filename = Container::get('app')->getRuntimePath() . 'route.php';
+        unlink($filename);
+        file_put_contents($filename, $this->buildRouteCache());
         $output->writeln('<info>Succeed!</info>');
     }
 
     protected function buildRouteCache()
     {
         Container::get('route')->setName([]);
-        Container::get('config')->set('url_lazy_route', false);
+        Container::get('route')->lazy(false);
         // 路由检测
         $path = Container::get('app')->getRoutePath();
 

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

@@ -99,7 +99,7 @@ class Schema extends Command
             $info    = $class::getConnection()->getFields($table);
             $content .= var_export($info, true) . ';';
 
-            file_put_contents(App::getRuntimePath() . 'schema/' . $dbName . '.' . $table . '.php', $content);
+            file_put_contents(App::getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR . $dbName . '.' . $table . '.php', $content);
         }
     }
 

+ 28 - 10
thinkphp/library/think/db/Builder.php

@@ -135,13 +135,19 @@ abstract class Builder
             } elseif (is_array($val) && !empty($val)) {
                 switch ($val[0]) {
                     case 'exp':
-                        $result[$item] = $val[1];
+                        if (isset($val[2]) && $query->getSecureKey() == $val[2]) {
+                            $result[$item] = $val[1];
+                        }
                         break;
                     case 'inc':
-                        $result[$item] = $this->parseKey($query, $val[1]) . ' + ' . floatval($val[2]);
+                        if ($key == $val[1]) {
+                            $result[$item] = $this->parseKey($query, $val[1]) . ' + ' . floatval($val[2]);
+                        }
                         break;
                     case 'dec':
-                        $result[$item] = $this->parseKey($query, $val[1]) . ' - ' . floatval($val[2]);
+                        if ($key == $val[1]) {
+                            $result[$item] = $this->parseKey($query, $val[1]) . ' - ' . floatval($val[2]);
+                        }
                         break;
                 }
             } elseif (is_scalar($val)) {
@@ -168,12 +174,11 @@ abstract class Builder
         // 过滤非标量数据
         if (0 === strpos($data, ':') && $query->isBind(substr($data, 1))) {
             return $data;
-        } else {
-            $key  = str_replace(['.', '->'], '_', $key);
-            $name = 'data__' . $key . $suffix;
-            $query->bind($name, $data, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR);
-            return ':' . $name;
         }
+        $key  = str_replace(['.', '->'], '_', $key);
+        $name = 'data__' . $key . $suffix;
+        $query->bind($name, $data, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR);
+        return ':' . $name;
     }
 
     /**
@@ -228,6 +233,7 @@ abstract class Builder
     {
         $item    = [];
         $options = $query->getOptions();
+
         foreach ((array) $tables as $key => $table) {
             if (!is_numeric($key)) {
                 $key    = $this->connection->parseSqlTable($key);
@@ -387,7 +393,7 @@ abstract class Builder
             $exp = $this->exp[$exp];
         }
 
-        $bindName = $bindName ?: 'where_' . str_replace(['.', '-'], '_', $field);
+        $bindName = $bindName ?: 'where_' . $rule . '_' . str_replace(['.', '-'], '_', $field);
 
         if (preg_match('/\W/', $bindName)) {
             // 处理带非单词字符的字段名
@@ -845,7 +851,19 @@ abstract class Builder
      */
     protected function parseGroup(Query $query, $group)
     {
-        return !empty($group) ? ' GROUP BY ' . $this->parseKey($query, $group) : '';
+        if (empty($group)) {
+            return '';
+        }
+
+        if (is_string($group)) {
+            $group = explode(',', $group);
+        }
+
+        foreach ($group as $key) {
+            $val[] = $this->parseKey($query, $key);
+        }
+
+        return ' GROUP BY ' . implode(',', $val);
     }
 
     /**

+ 270 - 250
thinkphp/library/think/db/Connection.php

@@ -106,6 +106,8 @@ abstract class Connection
         'query'           => '\\think\\db\\Query',
         // 是否需要断线重连
         'break_reconnect' => false,
+        // 断线标识字符串
+        'break_match_str' => [],
     ];
 
     // PDO连接参数
@@ -117,6 +119,21 @@ abstract class Connection
         PDO::ATTR_EMULATE_PREPARES  => false,
     ];
 
+    // 服务器断线标识字符
+    protected $breakMatchStr = [
+        'server has gone away',
+        'no connection to the server',
+        'Lost connection',
+        'is dead or not enabled',
+        'Error while sending',
+        'decryption failed or bad record mac',
+        'server closed the connection unexpectedly',
+        'SSL connection has been closed unexpectedly',
+        'Error writing data to the connection',
+        'Resource deadlock avoided',
+        'failed with errno',
+    ];
+
     // 绑定参数
     protected $bind = [];
 
@@ -193,9 +210,9 @@ abstract class Connection
     {
         if (!empty($this->builderClassName)) {
             return $this->builderClassName;
-        } else {
-            return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type'));
         }
+
+        return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type'));
     }
 
     /**
@@ -351,7 +368,8 @@ abstract class Connection
 
         if (!isset(self::$info[$schema])) {
             // 读取缓存
-            $cacheFile = Container::get('app')->getRuntimePath() . 'schema/' . $schema . '.php';
+            $cacheFile = Container::get('app')->getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR . $schema . '.php';
+
             if (!$this->config['debug'] && is_file($cacheFile)) {
                 $info = include $cacheFile;
             } else {
@@ -365,6 +383,7 @@ abstract class Connection
                 // 记录字段类型
                 $type[$key] = $val['type'];
                 $bind[$key] = $this->getFieldBindType($val['type']);
+
                 if (!empty($val['primary'])) {
                     $pk[] = $key;
                 }
@@ -472,49 +491,55 @@ abstract class Connection
      */
     public function connect(array $config = [], $linkNum = 0, $autoConnection = false)
     {
-        if (!isset($this->links[$linkNum])) {
-            if (!$config) {
-                $config = $this->config;
-            } else {
-                $config = array_merge($this->config, $config);
-            }
+        if (isset($this->links[$linkNum])) {
+            return $this->links[$linkNum];
+        }
 
-            // 连接参数
-            if (isset($config['params']) && is_array($config['params'])) {
-                $params = $config['params'] + $this->params;
-            } else {
-                $params = $this->params;
-            }
+        if (!$config) {
+            $config = $this->config;
+        } else {
+            $config = array_merge($this->config, $config);
+        }
 
-            // 记录当前字段属性大小写设置
-            $this->attrCase = $params[PDO::ATTR_CASE];
+        // 连接参数
+        if (isset($config['params']) && is_array($config['params'])) {
+            $params = $config['params'] + $this->params;
+        } else {
+            $params = $this->params;
+        }
 
-            try {
-                if (empty($config['dsn'])) {
-                    $config['dsn'] = $this->parseDsn($config);
-                }
+        // 记录当前字段属性大小写设置
+        $this->attrCase = $params[PDO::ATTR_CASE];
 
-                if ($config['debug']) {
-                    $startTime = microtime(true);
-                }
+        if (!empty($config['break_match_str'])) {
+            $this->breakMatchStr = array_merge($this->breakMatchStr, (array) $config['break_match_str']);
+        }
 
-                $this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params);
+        try {
+            if (empty($config['dsn'])) {
+                $config['dsn'] = $this->parseDsn($config);
+            }
 
-                if ($config['debug']) {
-                    // 记录数据库连接信息
-                    $this->log('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']);
-                }
-            } catch (\PDOException $e) {
-                if ($autoConnection) {
-                    $this->log($e->getMessage(), 'error');
-                    return $this->connect($autoConnection, $linkNum);
-                } else {
-                    throw $e;
-                }
+            if ($config['debug']) {
+                $startTime = microtime(true);
             }
-        }
 
-        return $this->links[$linkNum];
+            $this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params);
+
+            if ($config['debug']) {
+                // 记录数据库连接信息
+                $this->log('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']);
+            }
+
+            return $this->links[$linkNum];
+        } catch (\PDOException $e) {
+            if ($autoConnection) {
+                $this->log($e->getMessage(), 'error');
+                return $this->connect($autoConnection, $linkNum);
+            } else {
+                throw $e;
+            }
+        }
     }
 
     /**
@@ -535,9 +560,9 @@ abstract class Connection
     {
         if (!$this->linkID) {
             return false;
-        } else {
-            return $this->linkID;
         }
+
+        return $this->linkID;
     }
 
     /**
@@ -783,11 +808,10 @@ abstract class Connection
         $pk      = $query->getPk($options);
 
         if (!empty($options['cache']) && true === $options['cache']['key'] && is_string($pk) && isset($options['where']['AND'][$pk])) {
-            $key = $this->getCacheKey($options['where']['AND'][$pk], $options);
+            $key = $this->getCacheKey($query, $options['where']['AND'][$pk]);
         }
 
-        $data   = $options['data'];
-        $result = false;
+        $data = $options['data'];
 
         if (empty($options['fetch_sql']) && !empty($options['cache'])) {
             // 判断查询缓存
@@ -796,55 +820,57 @@ abstract class Connection
             if (is_string($cache['key'])) {
                 $key = $cache['key'];
             } elseif (!isset($key)) {
-                $key = $this->getCacheKey($data, $options, $query->getBind(false));
+                $key = $this->getCacheKey($query, $data);
             }
 
             $result = Container::get('cache')->get($key);
+
+            if (false !== $result) {
+                return $result;
+            }
         }
 
-        if (false === $result) {
-            if (is_string($pk)) {
-                if (!is_array($data)) {
-                    if (isset($key) && strpos($key, '|')) {
-                        list($a, $val) = explode('|', $key);
-                        $item[$pk]     = $val;
-                    } else {
-                        $item[$pk] = $data;
-                    }
-                    $data = $item;
-                }
+        if (is_string($pk) && !is_array($data)) {
+            if (isset($key) && strpos($key, '|')) {
+                list($a, $val) = explode('|', $key);
+                $item[$pk]     = $val;
+            } else {
+                $item[$pk] = $data;
             }
-            $query->setOption('data', $data);
-            $query->setOption('limit', 1);
+            $data = $item;
+        }
 
-            // 生成查询SQL
-            $sql = $this->builder->select($query);
+        $query->setOption('data', $data);
+        $query->setOption('limit', 1);
 
-            $bind = $query->getBind();
+        // 生成查询SQL
+        $sql = $this->builder->select($query);
 
-            if (!empty($options['fetch_sql'])) {
-                // 获取实际执行的SQL语句
-                return $this->getRealSql($sql, $bind);
-            }
+        $bind = $query->getBind();
 
-            // 事件回调
-            if ($result = $query->trigger('before_find')) {
-            } else {
-                // 执行查询
-                $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']);
+        if (!empty($options['fetch_sql'])) {
+            // 获取实际执行的SQL语句
+            return $this->getRealSql($sql, $bind);
+        }
 
-                if ($resultSet instanceof \PDOStatement) {
-                    // 返回PDOStatement对象
-                    return $resultSet;
-                }
+        // 事件回调
+        $result = $query->trigger('before_find');
 
-                $result = isset($resultSet[0]) ? $resultSet[0] : null;
-            }
+        if (!$result) {
+            // 执行查询
+            $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']);
 
-            if (isset($cache) && $result) {
-                // 缓存数据
-                $this->cacheData($key, $result, $cache);
+            if ($resultSet instanceof \PDOStatement) {
+                // 返回PDOStatement对象
+                return $resultSet;
             }
+
+            $result = isset($resultSet[0]) ? $resultSet[0] : null;
+        }
+
+        if (isset($cache) && $result) {
+            // 缓存数据
+            $this->cacheData($key, $result, $cache);
         }
 
         return $result;
@@ -885,42 +911,41 @@ abstract class Connection
     public function select(Query $query)
     {
         // 分析查询表达式
-        $options   = $query->getOptions();
-        $resultSet = false;
+        $options = $query->getOptions();
 
         if (empty($options['fetch_sql']) && !empty($options['cache'])) {
-            // 判断查询缓存
-            $cache     = $options['cache'];
-            $key       = is_string($cache['key']) ? $cache['key'] : md5(serialize($options) . serialize($query->getBind(false)));
-            $resultSet = Container::get('cache')->get($key);
+            $resultSet = $this->getCacheData($query, $options['cache'], null, $key);
+
+            if (false !== $resultSet) {
+                return $resultSet;
+            }
         }
 
-        if (false === $resultSet) {
-            // 生成查询SQL
-            $sql = $this->builder->select($query);
+        // 生成查询SQL
+        $sql = $this->builder->select($query);
+
+        $bind = $query->getBind();
 
-            $bind = $query->getBind();
+        if (!empty($options['fetch_sql'])) {
+            // 获取实际执行的SQL语句
+            return $this->getRealSql($sql, $bind);
+        }
 
-            if (!empty($options['fetch_sql'])) {
-                // 获取实际执行的SQL语句
-                return $this->getRealSql($sql, $bind);
-            }
+        $resultSet = $query->trigger('before_select');
 
-            if ($resultSet = $query->trigger('before_select')) {
-            } else {
-                // 执行查询操作
-                $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']);
+        if (!$resultSet) {
+            // 执行查询操作
+            $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']);
 
-                if ($resultSet instanceof \PDOStatement) {
-                    // 返回PDOStatement对象
-                    return $resultSet;
-                }
+            if ($resultSet instanceof \PDOStatement) {
+                // 返回PDOStatement对象
+                return $resultSet;
             }
+        }
 
-            if (isset($cache) && false !== $resultSet) {
-                // 缓存数据集
-                $this->cacheData($key, $resultSet, $cache);
-            }
+        if (!empty($options['cache']) && false !== $resultSet) {
+            // 缓存数据集
+            $this->cacheData($key, $resultSet, $options['cache']);
         }
 
         return $resultSet;
@@ -1008,6 +1033,7 @@ abstract class Connection
                 foreach ($array as $item) {
                     $sql  = $this->builder->insertAll($query, $item, $replace);
                     $bind = $query->getBind();
+
                     if (!empty($options['fetch_sql'])) {
                         $fetchSql[] = $this->getRealSql($sql, $bind);
                     } else {
@@ -1032,12 +1058,10 @@ abstract class Connection
         $bind = $query->getBind();
 
         if (!empty($options['fetch_sql'])) {
-            // 获取实际执行的SQL语句
             return $this->getRealSql($sql, $bind);
-        } else {
-            // 执行操作
-            return $this->execute($sql, $bind);
         }
+
+        return $this->execute($sql, $bind);
     }
 
     /**
@@ -1054,7 +1078,6 @@ abstract class Connection
         // 分析查询表达式
         $options = $query->getOptions();
 
-        // 生成SQL语句
         $table = $this->parseSqlTable($table);
 
         $sql = $this->builder->selectInsert($query, $fields, $table);
@@ -1062,12 +1085,10 @@ abstract class Connection
         $bind = $query->getBind();
 
         if (!empty($options['fetch_sql'])) {
-            // 获取实际执行的SQL语句
             return $this->getRealSql($sql, $bind);
-        } else {
-            // 执行操作
-            return $this->execute($sql, $bind);
         }
+
+        return $this->execute($sql, $bind);
     }
 
     /**
@@ -1094,7 +1115,7 @@ abstract class Connection
             if (is_string($pk) && isset($data[$pk])) {
                 $where[$pk] = [$pk, '=', $data[$pk]];
                 if (!isset($key)) {
-                    $key = $this->getCacheKey($data[$pk], $options);
+                    $key = $this->getCacheKey($query, $data[$pk]);
                 }
                 unset($data[$pk]);
             } elseif (is_array($pk)) {
@@ -1118,7 +1139,7 @@ abstract class Connection
                 $query->setOption('where', ['AND' => $where]);
             }
         } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) {
-            $key = $this->getCacheKey($options['where']['AND'][$pk], $options);
+            $key = $this->getCacheKey($query, $options['where']['AND'][$pk]);
         }
 
         // 更新数据
@@ -1131,34 +1152,34 @@ abstract class Connection
         if (!empty($options['fetch_sql'])) {
             // 获取实际执行的SQL语句
             return $this->getRealSql($sql, $bind);
-        } else {
-            // 检测缓存
-            $cache = Container::get('cache');
-
-            if (isset($key) && $cache->get($key)) {
-                // 删除缓存
-                $cache->rm($key);
-            } elseif (!empty($options['cache']['tag'])) {
-                $cache->clear($options['cache']['tag']);
-            }
+        }
 
-            // 执行操作
-            $result = '' == $sql ? 0 : $this->execute($sql, $bind);
+        // 检测缓存
+        $cache = Container::get('cache');
 
-            if ($result) {
-                if (is_string($pk) && isset($where[$pk])) {
-                    $data[$pk] = $where[$pk];
-                } elseif (is_string($pk) && isset($key) && strpos($key, '|')) {
-                    list($a, $val) = explode('|', $key);
-                    $data[$pk]     = $val;
-                }
+        if (isset($key) && $cache->get($key)) {
+            // 删除缓存
+            $cache->rm($key);
+        } elseif (!empty($options['cache']['tag'])) {
+            $cache->clear($options['cache']['tag']);
+        }
 
-                $query->setOption('data', $data);
-                $query->trigger('after_update');
+        // 执行操作
+        $result = '' == $sql ? 0 : $this->execute($sql, $bind);
+
+        if ($result) {
+            if (is_string($pk) && isset($where[$pk])) {
+                $data[$pk] = $where[$pk];
+            } elseif (is_string($pk) && isset($key) && strpos($key, '|')) {
+                list($a, $val) = explode('|', $key);
+                $data[$pk]     = $val;
             }
 
-            return $result;
+            $query->setOption('data', $data);
+            $query->trigger('after_update');
         }
+
+        return $result;
     }
 
     /**
@@ -1179,9 +1200,9 @@ abstract class Connection
         if (isset($options['cache']) && is_string($options['cache']['key'])) {
             $key = $options['cache']['key'];
         } elseif (!is_null($data) && true !== $data && !is_array($data)) {
-            $key = $this->getCacheKey($data, $options);
+            $key = $this->getCacheKey($query, $data);
         } elseif (is_string($pk) && isset($options['where']['AND'][$pk])) {
-            $key = $this->getCacheKey($options['where']['AND'][$pk], $options);
+            $key = $this->getCacheKey($query, $options['where']['AND'][$pk]);
         }
 
         if (true !== $data && empty($options['where'])) {
@@ -1239,50 +1260,44 @@ abstract class Connection
     {
         $options = $query->getOptions();
 
-        $result = false;
         if (empty($options['fetch_sql']) && !empty($options['cache'])) {
-            // 判断查询缓存
-            $cache = $options['cache'];
 
-            $key    = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($options) . serialize($query->getBind(false)));
-            $result = Container::get('cache')->get($key);
-        }
+            $result = $this->getCacheData($query, $options['cache'], $field, $key);
 
-        if (false === $result) {
-            if (isset($options['field'])) {
-                $query->removeOption('field');
+            if (false !== $result) {
+                return $result;
             }
+        }
 
-            if (is_string($field)) {
-                $field = array_map('trim', explode(',', $field));
-            }
+        if (isset($options['field'])) {
+            $query->removeOption('field');
+        }
 
-            $query->setOption('field', $field);
-            $query->setOption('limit', 1);
-            // 生成查询SQL
-            $sql = $this->builder->select($query);
+        if (is_string($field)) {
+            $field = array_map('trim', explode(',', $field));
+        }
 
-            $bind = $query->getBind();
+        $query->setOption('field', $field);
+        $query->setOption('limit', 1);
 
-            if (!empty($options['fetch_sql'])) {
-                // 获取实际执行的SQL语句
-                return $this->getRealSql($sql, $bind);
-            }
+        // 生成查询SQL
+        $sql = $this->builder->select($query);
 
-            // 执行查询操作
-            $pdo = $this->query($sql, $bind, $options['master'], true);
+        $bind = $query->getBind();
 
-            if (is_string($pdo)) {
-                // 返回SQL语句
-                return $pdo;
-            }
+        if (!empty($options['fetch_sql'])) {
+            // 获取实际执行的SQL语句
+            return $this->getRealSql($sql, $bind);
+        }
 
-            $result = $pdo->fetchColumn();
+        // 执行查询操作
+        $pdo = $this->query($sql, $bind, $options['master'], true);
 
-            if (isset($cache) && false !== $result) {
-                // 缓存数据
-                $this->cacheData($key, $result, $cache);
-            }
+        $result = $pdo->fetchColumn();
+
+        if (isset($cache) && false !== $result) {
+            // 缓存数据
+            $this->cacheData($key, $result, $cache);
         }
 
         return false !== $result ? $result : $default;
@@ -1315,84 +1330,85 @@ abstract class Connection
     {
         $options = $query->getOptions();
 
-        $result = false;
-
         if (empty($options['fetch_sql']) && !empty($options['cache'])) {
             // 判断查询缓存
             $cache = $options['cache'];
 
-            $guid   = is_string($cache['key']) ? $cache['key'] : md5($field . serialize($options) . serialize($query->getBind(false)));
+            $guid = is_string($cache['key']) ? $cache['key'] : $this->getCacheKey($query, $field);
+
             $result = Container::get('cache')->get($guid);
-        }
 
-        if (false === $result) {
-            if (isset($options['field'])) {
-                $query->removeOption('field');
+            if (false !== $result) {
+                return $result;
             }
+        }
 
-            if (is_null($field)) {
-                $field = '*';
-            } elseif ($key && '*' != $field) {
-                $field = $key . ',' . $field;
-            }
+        if (isset($options['field'])) {
+            $query->removeOption('field');
+        }
 
-            if (is_string($field)) {
-                $field = array_map('trim', explode(',', $field));
-            }
+        if (is_null($field)) {
+            $field = '*';
+        } elseif ($key && '*' != $field) {
+            $field = $key . ',' . $field;
+        }
 
-            $query->setOption('field', $field);
+        if (is_string($field)) {
+            $field = array_map('trim', explode(',', $field));
+        }
 
-            // 生成查询SQL
-            $sql = $this->builder->select($query);
+        $query->setOption('field', $field);
 
-            $bind = $query->getBind();
+        // 生成查询SQL
+        $sql = $this->builder->select($query);
 
-            if (!empty($options['fetch_sql'])) {
-                // 获取实际执行的SQL语句
-                return $this->getRealSql($sql, $bind);
-            }
+        $bind = $query->getBind();
 
-            // 执行查询操作
-            $pdo = $this->query($sql, $bind, $options['master'], true);
+        if (!empty($options['fetch_sql'])) {
+            // 获取实际执行的SQL语句
+            return $this->getRealSql($sql, $bind);
+        }
 
-            if (1 == $pdo->columnCount()) {
-                $result = $pdo->fetchAll(PDO::FETCH_COLUMN);
-            } else {
-                $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC);
-
-                if ('*' == $field && $key) {
-                    $result = array_column($resultSet, null, $key);
-                } elseif ($resultSet) {
-                    $fields = array_keys($resultSet[0]);
-                    $count  = count($fields);
-                    $key1   = array_shift($fields);
-                    $key2   = $fields ? array_shift($fields) : '';
-                    $key    = $key ?: $key1;
-
-                    if (strpos($key, '.')) {
-                        list($alias, $key) = explode('.', $key);
-                    }
+        // 执行查询操作
+        $pdo = $this->query($sql, $bind, $options['master'], true);
 
-                    if (2 == $count) {
-                        $column = $key2;
-                    } elseif (1 == $count) {
-                        $column = $key1;
-                    } else {
-                        $column = null;
-                    }
+        if (1 == $pdo->columnCount()) {
+            $result = $pdo->fetchAll(PDO::FETCH_COLUMN);
+        } else {
+            $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC);
+
+            if ('*' == $field && $key) {
+                $result = array_column($resultSet, null, $key);
+            } elseif ($resultSet) {
+                $fields = array_keys($resultSet[0]);
+                $count  = count($fields);
+                $key1   = array_shift($fields);
+                $key2   = $fields ? array_shift($fields) : '';
+                $key    = $key ?: $key1;
+
+                if (strpos($key, '.')) {
+                    list($alias, $key) = explode('.', $key);
+                }
 
-                    $result = array_column($resultSet, $column, $key);
+                if (2 == $count) {
+                    $column = $key2;
+                } elseif (1 == $count) {
+                    $column = $key1;
                 } else {
-                    $result = [];
+                    $column = null;
                 }
-            }
 
-            if (isset($cache) && isset($guid)) {
-                // 缓存数据
-                $this->cacheData($guid, $result, $cache);
+                $result = array_column($resultSet, $column, $key);
+            } else {
+                $result = [];
             }
         }
 
+        if (isset($cache) && isset($guid)) {
+            // 缓存数据
+            $this->cacheData($guid, $result, $cache);
+        }
+
         return $result;
     }
 
@@ -1784,22 +1800,9 @@ abstract class Connection
             return false;
         }
 
-        $info = [
-            'server has gone away',
-            'no connection to the server',
-            'Lost connection',
-            'is dead or not enabled',
-            'Error while sending',
-            'decryption failed or bad record mac',
-            'server closed the connection unexpectedly',
-            'SSL connection has been closed unexpectedly',
-            'Error writing data to the connection',
-            'Resource deadlock avoided',
-        ];
-
         $error = $e->getMessage();
 
-        foreach ($info as $msg) {
+        foreach ($this->breakMatchStr as $msg) {
             if (false !== stripos($error, $msg)) {
                 return true;
             }
@@ -2047,14 +2050,29 @@ abstract class Connection
     }
 
     /**
+     * 获取缓存数据
+     * @access protected
+     * @param  Query     $query   查询对象
+     * @param  mixed     $cache   缓存设置
+     * @param  array     $options 缓存
+     * @return mixed
+     */
+    protected function getCacheData(Query $query, $cache, $data, &$key = null)
+    {
+        // 判断查询缓存
+        $key = is_string($cache['key']) ? $cache['key'] : $this->getCacheKey($query, $data);
+
+        return Container::get('cache')->get($key);
+    }
+
+    /**
      * 生成缓存标识
      * @access protected
+     * @param  Query     $query   查询对象
      * @param  mixed     $value   缓存数据
-     * @param  array     $options 缓存参数
-     * @param  array     $bind    绑定参数
      * @return string
      */
-    protected function getCacheKey($value, $options, $bind = [])
+    protected function getCacheKey(Query $query, $value)
     {
         if (is_scalar($value)) {
             $data = $value;
@@ -2062,14 +2080,16 @@ abstract class Connection
             $data = $value[2];
         }
 
+        $prefix = 'think:' . $this->getConfig('database') . '.';
+
         if (isset($data)) {
-            return 'think:' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data;
-        } else {
-            try {
-                return md5(serialize($options) . serialize($bind));
-            } catch (\Exception $e) {
-                return;
-            }
+            return $prefix . $query->getTable() . '|' . $data;
+        }
+
+        try {
+            return md5($prefix . serialize($query->getOptions()) . serialize($query->getBind(false)));
+        } catch (\Exception $e) {
+            return;
         }
     }
 

+ 190 - 108
thinkphp/library/think/db/Query.php

@@ -59,6 +59,12 @@ class Query
     protected $pk;
 
     /**
+     * 查询安全Key
+     * @var string
+     */
+    protected $secureKey;
+
+    /**
      * 当前数据表前缀
      * @var string
      */
@@ -121,7 +127,8 @@ class Query
             $this->connection = $connection;
         }
 
-        $this->prefix = $this->connection->getConfig('prefix');
+        $this->prefix    = $this->connection->getConfig('prefix');
+        $this->secureKey = Container::get('request')->secureKey();
     }
 
     /**
@@ -265,6 +272,16 @@ class Query
     }
 
     /**
+     * 获取查询安全Key
+     * @access public
+     * @return string
+     */
+    public function getSecureKey()
+    {
+        return $this->secureKey;
+    }
+
+    /**
      * 得到当前或者指定名称的数据表
      * @access public
      * @param  string $name
@@ -292,7 +309,8 @@ class Query
     public function connect($config = [], $name = false)
     {
         $this->connection = Connection::instance($config, $name);
-        $query            = $this->connection->getConfig('query');
+
+        $query = $this->connection->getConfig('query');
 
         if (__CLASS__ != trim($query, '\\')) {
             return new $query($this->connection);
@@ -356,6 +374,16 @@ class Query
     }
 
     /**
+     * 获取返回或者影响的记录数
+     * @access public
+     * @return integer
+     */
+    public function getNumRows()
+    {
+        return $this->connection->getNumRows();
+    }
+
+    /**
      * 获取最近一次查询的sql语句
      * @access public
      * @return string
@@ -424,7 +452,7 @@ class Query
      * 获取数据库的配置参数
      * @access public
      * @param  string $name 参数名称
-     * @return boolean
+     * @return mixed
      */
     public function getConfig($name = '')
     {
@@ -507,18 +535,17 @@ class Query
                     }
             }
             return $this->getTable() . '_' . $seq;
-        } else {
-            // 当设置的分表字段不在查询条件或者数据中
-            // 进行联合查询,必须设定 partition['num']
-            $tableName = [];
-            for ($i = 0; $i < $rule['num']; $i++) {
-                $tableName[] = 'SELECT * FROM ' . $this->getTable() . '_' . ($i + 1);
-            }
+        }
+        // 当设置的分表字段不在查询条件或者数据中
+        // 进行联合查询,必须设定 partition['num']
+        $tableName = [];
+        for ($i = 0; $i < $rule['num']; $i++) {
+            $tableName[] = 'SELECT * FROM ' . $this->getTable() . '_' . ($i + 1);
+        }
 
-            $tableName = '( ' . implode(" UNION ", $tableName) . ') AS ' . $this->name;
+        $tableName = '( ' . implode(" UNION ", $tableName) . ') AS ' . $this->name;
 
-            return $tableName;
-        }
+        return $tableName;
     }
 
     /**
@@ -844,6 +871,7 @@ class Query
     {
         if (is_array($join)) {
             $table = $join;
+            $alias = array_shift($join);
         } else {
             $join = trim($join);
 
@@ -1030,7 +1058,7 @@ class Query
      */
     public function exp($field, $value)
     {
-        $this->data($field, ['exp', $value]);
+        $this->data($field, ['exp', $value, $this->secureKey]);
         return $this;
     }
 
@@ -1047,9 +1075,9 @@ class Query
     {
         $this->options['view'] = true;
 
-        if (is_array($join) && key($join) !== 0) {
+        if (is_array($join) && key($join) === 0) {
             foreach ($join as $key => $val) {
-                $this->view($key, $val[0], isset($val[1]) ? $val[1] : null, isset($val[2]) ? $val[2] : 'INNER');
+                $this->view($val[0], $val[1], isset($val[2]) ? $val[2] : null, isset($val[3]) ? $val[3] : 'INNER');
             }
         } else {
             $fields = [];
@@ -1162,7 +1190,7 @@ class Query
      */
     public function whereNull($field, $logic = 'AND')
     {
-        return $this->parseWhereExp($logic, $field, 'null', null);
+        return $this->parseWhereExp($logic, $field, 'null', null, [], true);
     }
 
     /**
@@ -1174,7 +1202,7 @@ class Query
      */
     public function whereNotNull($field, $logic = 'AND')
     {
-        return $this->parseWhereExp($logic, $field, 'notnull', null);
+        return $this->parseWhereExp($logic, $field, 'notnull', null, [], true);
     }
 
     /**
@@ -1213,7 +1241,7 @@ class Query
      */
     public function whereIn($field, $condition, $logic = 'AND')
     {
-        return $this->parseWhereExp($logic, $field, 'in', $condition);
+        return $this->parseWhereExp($logic, $field, 'in', $condition, [], true);
     }
 
     /**
@@ -1226,7 +1254,7 @@ class Query
      */
     public function whereNotIn($field, $condition, $logic = 'AND')
     {
-        return $this->parseWhereExp($logic, $field, 'not in', $condition);
+        return $this->parseWhereExp($logic, $field, 'not in', $condition, [], true);
     }
 
     /**
@@ -1239,7 +1267,7 @@ class Query
      */
     public function whereLike($field, $condition, $logic = 'AND')
     {
-        return $this->parseWhereExp($logic, $field, 'like', $condition);
+        return $this->parseWhereExp($logic, $field, 'like', $condition, [], true);
     }
 
     /**
@@ -1252,7 +1280,7 @@ class Query
      */
     public function whereNotLike($field, $condition, $logic = 'AND')
     {
-        return $this->parseWhereExp($logic, $field, 'not like', $condition);
+        return $this->parseWhereExp($logic, $field, 'not like', $condition, [], true);
     }
 
     /**
@@ -1265,7 +1293,7 @@ class Query
      */
     public function whereBetween($field, $condition, $logic = 'AND')
     {
-        return $this->parseWhereExp($logic, $field, 'between', $condition);
+        return $this->parseWhereExp($logic, $field, 'between', $condition, [], true);
     }
 
     /**
@@ -1278,7 +1306,7 @@ class Query
      */
     public function whereNotBetween($field, $condition, $logic = 'AND')
     {
-        return $this->parseWhereExp($logic, $field, 'not between', $condition);
+        return $this->parseWhereExp($logic, $field, 'not between', $condition, [], true);
     }
 
     /**
@@ -1321,25 +1349,27 @@ class Query
      * @access public
      * @param  mixed  $field     查询字段
      * @param  mixed  $condition 查询条件
+     * @param  array  $bind      参数绑定
      * @param  string $logic     查询逻辑 and or xor
      * @return $this
      */
-    public function whereExp($field, $condition, $logic = 'AND')
+    public function whereExp($field, $condition, $bind = [], $logic = 'AND')
     {
-        return $this->parseWhereExp($logic, $field, 'exp', $condition);
+        return $this->parseWhereExp($logic, $field, 'exp', $condition, $bind, true);
     }
 
     /**
      * 分析查询表达式
-     * @access public
+     * @access protected
      * @param  string   $logic     查询逻辑 and or xor
      * @param  mixed    $field     查询字段
      * @param  mixed    $op        查询表达式
      * @param  mixed    $condition 查询条件
      * @param  array    $param     查询参数
+     * @param  bool     $strict    严格模式
      * @return $this
      */
-    protected function parseWhereExp($logic, $field, $op, $condition, $param = [])
+    protected function parseWhereExp($logic, $field, $op, $condition, array $param = [], $strict = false)
     {
         if ($field instanceof $this) {
             $this->options['where'] = $field->getOptions('where');
@@ -1352,64 +1382,104 @@ class Query
             $field = $this->options['via'] . '.' . $field;
         }
 
-        if ($field instanceof \Closure) {
-            $where = is_string($op) ? [$op, $field] : $field;
+        if ($strict) {
+            // 使用严格模式查询
+            $where = [$field, $op, $condition];
+            if ('exp' == strtolower($op) && !empty($param)) {
+                // 参数绑定
+                $this->bind($param);
+            }
+        } elseif (is_array($field)) {
+            // 解析数组批量查询
+            return $this->parseArrayWhereItems($field, $logic);
+        } elseif ($field instanceof \Closure) {
+            $where = $field;
             $field = '';
-        } elseif (is_string($field) && preg_match('/[,=\<\'\"\(\s]/', $field)) {
+        } elseif (is_string($field)) {
+            // 解析条件单元
+            $where = $this->parseWhereItem($logic, $field, $op, $condition, $param);
+        }
+
+        if (!empty($where)) {
+            if (isset($this->options['where'][$logic][$field])) {
+                $this->options['where'][$logic][] = $where;
+            } else {
+                $this->options['where'][$logic][$field] = $where;
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * 分析查询表达式
+     * @access protected
+     * @param  string   $logic     查询逻辑 and or xor
+     * @param  mixed    $field     查询字段
+     * @param  mixed    $op        查询表达式
+     * @param  mixed    $condition 查询条件
+     * @param  array    $param     查询参数
+     * @return mixed
+     */
+    protected function parseWhereItem($logic, $field, $op, $condition, $param = [])
+    {
+        if (preg_match('/[,=\<\'\"\(\s]/', $field)) {
             $where = ['', 'exp', $field];
             if (is_array($op)) {
                 // 参数绑定
                 $this->bind($op);
             }
-        } elseif (is_null($op) && is_null($condition)) {
-            if (is_array($field)) {
-                if (key($field) !== 0) {
-                    $where = [];
-                    foreach ($field as $key => $val) {
-                        if (is_null($val)) {
-                            $where[$key] = [$key, 'null', ''];
-                        } else {
-                            $where[$key] = !is_scalar($val) ? $val : [$key, '=', $val];
-                        }
-                    }
-                } else {
-                    // 数组批量查询
-                    $where = $field;
-                }
-
-                if (!empty($where)) {
-                    $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? array_merge($this->options['where'][$logic], $where) : $where;
-                }
-
-                return $this;
-            } elseif ($field && is_string($field)) {
-                // 字符串查询
-                $where = [$field, 'null', ''];
-            }
         } elseif (is_array($op)) {
+            // 同一字段多条件查询
             array_unshift($param, $field);
             $where = $param;
-        } elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) {
-            // null查询
-            $where = [$field, $op, ''];
-        } elseif (is_null($condition)) {
-            // 字段相等查询
-            $where = [$field, '=', $op];
+        } elseif ($field && is_null($condition)) {
+            if (in_array(strtolower($op), ['null', 'notnull', 'not null'])) {
+                // null查询
+                $where = [$field, $op, ''];
+            } else {
+                // 字段相等查询
+                $where = is_null($op) ? [$field, 'null', ''] : [$field, '=', $op];
+            }
+        } elseif (strtolower($op) == 'exp') {
+            $bind  = isset($param[2]) && is_array($param[2]) ? $param[2] : null;
+            $where = [$field, 'exp', $condition, $bind];
+            if ($bind) {
+                // 参数绑定
+                $this->bind($bind);
+            }
         } else {
-            $where = [$field, $op, $condition, isset($param[2]) ? $param[2] : null];
+            $where = $field ? [$field, $op, $condition] : null;
+        }
 
-            if ('exp' == strtolower($op) && isset($param[2]) && is_array($param[2])) {
-                // 参数绑定
-                $this->bind($param[2]);
+        return $where;
+    }
+
+    /**
+     * 数组批量查询
+     * @access protected
+     * @param  array    $field     批量查询
+     * @param  string   $logic     查询逻辑 and or xor
+     * @return $this
+     */
+    protected function parseArrayWhereItems($field, $logic)
+    {
+        if (key($field) !== 0) {
+            $where = [];
+            foreach ($field as $key => $val) {
+                if (is_null($val)) {
+                    $where[$key] = [$key, 'null', ''];
+                } else {
+                    $where[$key] = !is_scalar($val) ? $val : [$key, '=', $val];
+                }
             }
+        } else {
+            // 数组批量查询
+            $where = $field;
         }
 
         if (!empty($where)) {
-            if (isset($this->options['where'][$logic][$field])) {
-                $this->options['where'][$logic][] = $where;
-            } else {
-                $this->options['where'][$logic][$field] = $where;
-            }
+            $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? array_merge($this->options['where'][$logic], $where) : $where;
         }
 
         return $this;
@@ -1622,6 +1692,7 @@ class Query
                 }
             }
         }
+
         $this->options['table'] = $table;
 
         return $this;
@@ -1648,33 +1719,35 @@ class Query
      */
     public function order($field, $order = null)
     {
-        if (!empty($field)) {
-            if (is_string($field)) {
-                if (!empty($this->options['via'])) {
-                    $field = $this->options['via'] . '.' . $field;
-                }
+        if (empty($field)) {
+            return $this;
+        }
 
-                $field = empty($order) ? $field : [$field => $order];
-            } elseif (!empty($this->options['via'])) {
-                foreach ($field as $key => $val) {
-                    if (is_numeric($key)) {
-                        $field[$key] = $this->options['via'] . '.' . $val;
-                    } else {
-                        $field[$this->options['via'] . '.' . $key] = $val;
-                        unset($field[$key]);
-                    }
-                }
+        if (is_string($field)) {
+            if (!empty($this->options['via'])) {
+                $field = $this->options['via'] . '.' . $field;
             }
 
-            if (!isset($this->options['order'])) {
-                $this->options['order'] = [];
+            $field = empty($order) ? $field : [$field => $order];
+        } elseif (!empty($this->options['via'])) {
+            foreach ($field as $key => $val) {
+                if (is_numeric($key)) {
+                    $field[$key] = $this->options['via'] . '.' . $val;
+                } else {
+                    $field[$this->options['via'] . '.' . $key] = $val;
+                    unset($field[$key]);
+                }
             }
+        }
 
-            if (is_array($field)) {
-                $this->options['order'] = array_merge($this->options['order'], $field);
-            } else {
-                $this->options['order'][] = $field;
-            }
+        if (!isset($this->options['order'])) {
+            $this->options['order'] = [];
+        }
+
+        if (is_array($field)) {
+            $this->options['order'] = array_merge($this->options['order'], $field);
+        } else {
+            $this->options['order'][] = $field;
         }
 
         return $this;
@@ -1733,7 +1806,7 @@ class Query
     /**
      * 指定group查询
      * @access public
-     * @param  string $group GROUP
+     * @param  string|array $group GROUP
      * @return $this
      */
     public function group($group)
@@ -1986,9 +2059,10 @@ class Query
      * @param  string       $field 日期字段名
      * @param  string|array $op    比较运算符或者表达式
      * @param  string|array $range 比较范围
+     * @param  string       $logic AND OR
      * @return $this
      */
-    public function whereTime($field, $op, $range = null)
+    public function whereTime($field, $op, $range = null, $logic = 'AND')
     {
         if (is_null($range)) {
             if (is_array($op)) {
@@ -2008,9 +2082,7 @@ class Query
             $op = is_array($range) ? 'between' : '>=';
         }
 
-        $this->where($field, strtolower($op) . ' time', $range);
-
-        return $this;
+        return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true);
     }
 
     /**
@@ -2019,18 +2091,17 @@ class Query
      * @param  string    $field 日期字段名
      * @param  string    $startTime    开始时间
      * @param  string    $endTime 结束时间
+     * @param  string    $logic AND OR
      * @return $this
      */
-    public function whereBetweenTime($field, $startTime, $endTime = null)
+    public function whereBetweenTime($field, $startTime, $endTime = null, $logic = 'AND')
     {
         if (is_null($endTime)) {
             $time    = is_string($startTime) ? strtotime($startTime) : $startTime;
             $endTime = strtotime('+1 day', $time);
         }
 
-        $this->where($field, 'between time', [$startTime, $endTime]);
-
-        return $this;
+        return $this->parseWhereExp($logic, $field, 'between time', [$startTime, $endTime], [], true);
     }
 
     /**
@@ -2044,7 +2115,7 @@ class Query
         if (!empty($this->pk)) {
             $pk = $this->pk;
         } else {
-            $pk = $this->connection->getPk(is_array($options) ? $options['table'] : $this->getTable());
+            $pk = $this->connection->getPk(is_array($options) && isset($options['table']) ? $options['table'] : $this->getTable());
         }
 
         return $pk;
@@ -2082,6 +2153,19 @@ class Query
 
     /**
      * 查询参数赋值
+     * @access public
+     * @param  string $name     参数名
+     * @param  mixed  $value    值
+     * @return $this
+     */
+    public function option($name, $value)
+    {
+        $this->options[$name] = $value;
+        return $this;
+    }
+
+    /**
+     * 查询参数赋值
      * @access protected
      * @param  array $options 表达式参数
      * @return $this
@@ -2102,9 +2186,8 @@ class Query
     {
         if ('' === $name) {
             return $this->options;
-        } else {
-            return isset($this->options[$name]) ? $this->options[$name] : null;
         }
+        return isset($this->options[$name]) ? $this->options[$name] : null;
     }
 
     /**
@@ -2679,10 +2762,9 @@ class Query
         if (!empty($this->model)) {
             $class = get_class($this->model);
             throw new ModelNotFoundException('model data Not Found:' . $class, $class, $options);
-        } else {
-            $table = is_array($options['table']) ? key($options['table']) : $options['table'];
-            throw new DataNotFoundException('table data not Found:' . $table, $table, $options);
         }
+        $table = is_array($options['table']) ? key($options['table']) : $options['table'];
+        throw new DataNotFoundException('table data not Found:' . $table, $table, $options);
     }
 
     /**

+ 2 - 2
thinkphp/library/think/db/builder/Mysql.php

@@ -116,9 +116,9 @@ class Mysql extends Builder
 
         if (strpos($key, '->') && false === strpos($key, '(')) {
             // JSON字段支持
-            list($field, $name) = explode('->', $key);
+            list($field, $name) = explode('->', $key, 2);
 
-            $key = 'json_extract(' . $this->parseKey($query, $field) . ', \'$.' . $name . '\')';
+            $key = 'json_extract(' . $this->parseKey($query, $field) . ', \'$.' . str_replace('->', '.', $name) . '\')';
         } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) {
             list($table, $key) = explode('.', $key, 2);
 

+ 1 - 1
thinkphp/library/think/debug/Html.php

@@ -49,7 +49,7 @@ class Html
             return false;
         }
         // 获取基本信息
-        $runtime = number_format(microtime(true) - Container::get('app')->getBeginTime(), 10);
+        $runtime = number_format(microtime(true) - Container::get('app')->getBeginTime(), 10, '.', '');
         $reqs    = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞';
         $mem     = number_format((memory_get_usage() - Container::get('app')->getBeginMem()) / 1024, 2);
 

+ 16 - 4
thinkphp/library/think/http/middleware/MissingResponseException.php → thinkphp/library/think/facade/Middleware.php

@@ -6,10 +6,22 @@
 // +----------------------------------------------------------------------
 // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
 // +----------------------------------------------------------------------
-// | Author: Slince <taosikai@yeah.net>
+// | Author: liu21st <liu21st@gmail.com>
 // +----------------------------------------------------------------------
 
-namespace think\http\middleware;
+namespace think\facade;
 
-class MissingResponseException extends \InvalidArgumentException
-{}
+use think\Facade;
+
+/**
+ * @see \think\Middleware
+ * @mixin \think\Middleware
+ * @method void import(array $middlewares = []) static 批量设置中间件
+ * @method void add(mixed $middleware) static 添加中间件到队列
+ * @method void unshift(mixed $middleware) static 添加中间件到队列开头
+ * @method array all() static 获取中间件队列
+ * @method \think\Response dispatch(\think\Request $request) static 执行中间件调度
+ */
+class Middleware extends Facade
+{
+}

+ 0 - 45
thinkphp/library/think/http/middleware/DispatcherInterface.php

@@ -1,45 +0,0 @@
-<?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: Slince <taosikai@yeah.net>
-// +----------------------------------------------------------------------
-
-namespace think\http\middleware;
-
-use think\Request;
-use think\Response;
-
-interface DispatcherInterface
-{
-    /**
-     * 在队尾添加 middleware
-     * @param callable $middleware
-     * @return DispatcherInterface
-     */
-    public function add($middleware);
-
-    /**
-     * 在队前插入 middleware
-     * @param callable $middleware
-     * @return DispatcherInterface
-     */
-    public function insert($middleware);
-
-    /**
-     * 获取所有的middleware
-     * @return array
-     */
-    public function all();
-
-    /**
-     * 处理 request 并返回 response
-     * @param Request $request
-     * @return Response
-     */
-    public function dispatch(Request $request);
-}

+ 0 - 69
thinkphp/library/think/http/tests/middleware/DispatcherTest.php

@@ -1,69 +0,0 @@
-<?php
-
-namespace think\http\tests\middleware;
-
-use PHPUnit\Framework\TestCase;
-use think\http\middleware\Dispatcher;
-use think\http\middleware\MissingResponseException;
-use think\Request;
-use think\Response;
-
-class DispatcherTest extends TestCase
-{
-    public function testValidMiddleware()
-    {
-        $dispatcher = new Dispatcher();
-        $dispatcher->add(function () {
-        });
-        $this->assertCount(1, $dispatcher->all());
-        $this->expectException(\InvalidArgumentException::class);
-        $dispatcher->add('foo middleware');
-    }
-
-    public function testAddAndInsert()
-    {
-        $middleware1 = function () {};
-        $middleware2 = function () {};
-        $dispatcher = new Dispatcher();
-        $dispatcher->add($middleware1);
-        $dispatcher->insert($middleware2);
-        $this->assertSame([$middleware2, $middleware1], $dispatcher->all());
-    }
-
-    public function testDispatch()
-    {
-        $middleware1 = function ($request, $next) {
-            return $next($request);
-        };
-        $middleware2 = function ($request) {
-            return Response::create('hello world');
-        };
-        $dispatcher = new Dispatcher([$middleware1, $middleware2]);
-        $response   = $dispatcher->dispatch(new Request());
-        $this->assertInstanceOf(Response::class, $response);
-        $this->assertEquals('hello world', $response->getContent());
-    }
-
-    public function testDispatchWithoutResponse()
-    {
-        $middleware1 = function ($request, $next) {
-            return $next($request);
-        };
-        $middleware2 = function ($request, $next) {
-            return $next($request);
-        };
-        $dispatcher = new Dispatcher([$middleware1, $middleware2]);
-        $this->expectException(MissingResponseException::class);
-        $dispatcher->dispatch(new Request());
-    }
-
-    public function testDispatchWithBadResponse()
-    {
-        $middleware = function ($request, $next) {
-            return 'invalid response';
-        };
-        $dispatcher = new Dispatcher($middleware);
-        $this->expectException(\LogicException::class);
-        $dispatcher->dispatch(new Request());
-    }
-}

+ 24 - 7
thinkphp/library/think/log/driver/File.php

@@ -24,6 +24,7 @@ class File
         'file_size'   => 2097152,
         'path'        => '',
         'apart_level' => [],
+        'max_files'   => 0,
     ];
 
     protected $writed = [];
@@ -36,7 +37,9 @@ class File
         }
 
         if (empty($this->config['path'])) {
-            $this->config['path'] = Container::get('app')->getRuntimePath() . 'log/';
+            $this->config['path'] = Container::get('app')->getRuntimePath() . 'log' . DIRECTORY_SEPARATOR;
+        } elseif (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) {
+            $this->config['path'] .= DIRECTORY_SEPARATOR;
         }
     }
 
@@ -49,11 +52,23 @@ class File
     public function save(array $log = [])
     {
         if ($this->config['single']) {
-            $name        = is_string($single) ? $single : 'single';
+            $name        = is_string($this->config['single']) ? $this->config['single'] : 'single';
             $destination = $this->config['path'] . $name . '.log';
         } else {
-            $cli         = PHP_SAPI == 'cli' ? '_cli' : '';
-            $destination = $this->config['path'] . date('Ym') . '/' . date('d') . $cli . '.log';
+            $cli = PHP_SAPI == 'cli' ? '_cli' : '';
+
+            if ($this->config['max_files']) {
+                $filename = date('Ymd') . $cli . '.log';
+                $files    = glob($this->config['path'] . '*.log');
+
+                if (count($files) > $this->config['max_files']) {
+                    unlink($files[0]);
+                }
+            } else {
+                $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . $cli . '.log';
+            }
+
+            $destination = $this->config['path'] . $filename;
         }
 
         $path = dirname($destination);
@@ -72,9 +87,11 @@ class File
             if (in_array($type, $this->config['apart_level'])) {
                 // 独立记录的日志级别
                 if ($this->config['single']) {
-                    $filename = $path . '/' . $name . '_' . $type . '.log';
+                    $filename = $path . DIRECTORY_SEPARATOR . $name . '_' . $type . '.log';
+                } elseif ($this->config['max_files']) {
+                    $filename = $path . DIRECTORY_SEPARATOR . date('Ymd') . '_' . $type . $cli . '.log';
                 } else {
-                    $filename = $path . '/' . date('d') . '_' . $type . $cli . '.log';
+                    $filename = $path . DIRECTORY_SEPARATOR . date('d') . '_' . $type . $cli . '.log';
                 }
 
                 $this->write($level, $filename, true);
@@ -103,7 +120,7 @@ class File
         // 检测日志文件大小,超过配置大小则备份日志文件重新生成
         if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) {
             try {
-                rename($destination, dirname($destination) . '/' . time() . '-' . basename($destination));
+                rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination));
             } catch (\Exception $e) {
             }
 

+ 0 - 12
thinkphp/library/think/model/Collection.php

@@ -17,18 +17,6 @@ use think\Model;
 class Collection extends BaseCollection
 {
     /**
-     * 返回数组中指定的一列
-     * @access public
-     * @param  string        $column_key
-     * @param  string|null   $index_key
-     * @return array
-     */
-    public function column($column_key, $index_key = null)
-    {
-        return array_column($this->toArray(), $column_key, $index_key);
-    }
-
-    /**
      * 延迟预载入关联查询
      * @access public
      * @param  mixed $relation 关联

+ 67 - 49
thinkphp/library/think/model/concern/Attribute.php

@@ -139,37 +139,38 @@ trait Attribute
     {
         if (is_string($data)) {
             $this->data[$data] = $value;
-        } else {
-            // 清空数据
-            $this->data = [];
+            return $this;
+        }
 
-            if (is_object($data)) {
-                $data = get_object_vars($data);
-            }
+        // 清空数据
+        $this->data = [];
 
-            if ($this->disuse) {
-                // 废弃字段
-                foreach ((array) $this->disuse as $key) {
-                    if (array_key_exists($key, $data)) {
-                        unset($data[$key]);
-                    }
+        if (is_object($data)) {
+            $data = get_object_vars($data);
+        }
+
+        if ($this->disuse) {
+            // 废弃字段
+            foreach ((array) $this->disuse as $key) {
+                if (array_key_exists($key, $data)) {
+                    unset($data[$key]);
                 }
             }
+        }
 
-            if (true === $value) {
-                // 数据对象赋值
-                foreach ($data as $key => $value) {
-                    $this->setAttr($key, $value, $data);
-                }
-            } elseif (is_array($value)) {
-                foreach ($value as $name) {
-                    if (isset($data[$name])) {
-                        $this->data[$name] = $data[$name];
-                    }
+        if (true === $value) {
+            // 数据对象赋值
+            foreach ($data as $key => $value) {
+                $this->setAttr($key, $value, $data);
+            }
+        } elseif (is_array($value)) {
+            foreach ($value as $name) {
+                if (isset($data[$name])) {
+                    $this->data[$name] = $data[$name];
                 }
-            } else {
-                $this->data = $data;
             }
+        } else {
+            $this->data = $data;
         }
 
         return $this;
@@ -210,9 +211,8 @@ trait Attribute
     {
         if (is_null($name)) {
             return $this->origin;
-        } else {
-            return array_key_exists($name, $this->origin) ? $this->origin[$name] : null;
         }
+        return array_key_exists($name, $this->origin) ? $this->origin[$name] : null;
     }
 
     /**
@@ -230,9 +230,8 @@ trait Attribute
             return $this->data[$name];
         } elseif (array_key_exists($name, $this->relation)) {
             return $this->relation[$name];
-        } else {
-            throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
         }
+        throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
     }
 
     /**
@@ -454,36 +453,51 @@ trait Attribute
                 $value = $this->formatDateTime($value, $this->dateFormat);
             }
         } elseif ($notFound) {
-            $relation = $this->isRelationAttr($name);
+            $value = $this->getRelationAttribute($name, $item);
+        }
 
-            if ($relation) {
-                $modelRelation = $this->$relation();
-                if ($modelRelation instanceof Relation) {
-                    $value = $this->getRelationData($modelRelation);
+        return $value;
+    }
 
-                    if ($item && method_exists($modelRelation, 'getBindAttr') && $bindAttr = $modelRelation->getBindAttr()) {
+    /**
+     * 获取关联属性值
+     * @access protected
+     * @param  string   $name  属性名
+     * @param  array    $item  数据
+     * @return mixed
+     */
+    protected function getRelationAttribute($name, &$item)
+    {
+        $relation = $this->isRelationAttr($name);
 
-                        foreach ($bindAttr as $key => $attr) {
-                            $key = is_numeric($key) ? $attr : $key;
+        if ($relation) {
+            $modelRelation = $this->$relation();
+            if ($modelRelation instanceof Relation) {
+                $value = $this->getRelationData($modelRelation);
 
-                            if (isset($item[$key])) {
-                                throw new Exception('bind attr has exists:' . $key);
-                            } else {
-                                $item[$key] = $value ? $value->getAttr($attr) : null;
-                            }
+                if ($item && method_exists($modelRelation, 'getBindAttr') && $bindAttr = $modelRelation->getBindAttr()) {
+
+                    foreach ($bindAttr as $key => $attr) {
+                        $key = is_numeric($key) ? $attr : $key;
+
+                        if (isset($item[$key])) {
+                            throw new Exception('bind attr has exists:' . $key);
+                        } else {
+                            $item[$key] = $value ? $value->getAttr($attr) : null;
                         }
-                        return false;
                     }
 
-                    // 保存关联对象值
-                    $this->relation[$name] = $value;
-
-                    return $value;
+                    return false;
                 }
+
+                // 保存关联对象值
+                $this->relation[$name] = $value;
+
+                return $value;
             }
-            throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
         }
-        return $value;
+
+        throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
     }
 
     /**
@@ -541,7 +555,11 @@ trait Attribute
                 $value = empty($value) ? new \stdClass() : json_decode($value);
                 break;
             case 'serialize':
-                $value = unserialize($value);
+                try {
+                    $value = unserialize($value);
+                } catch (\Exception $e) {
+                    $value = null;
+                }
                 break;
             default:
                 if (false !== strpos($type, '\\')) {

+ 4 - 4
thinkphp/library/think/model/concern/RelationShip.php

@@ -89,9 +89,8 @@ trait RelationShip
             return $this->relation;
         } elseif (array_key_exists($name, $this->relation)) {
             return $this->relation[$name];
-        } else {
-            return;
         }
+        return;
     }
 
     /**
@@ -142,9 +141,10 @@ trait RelationShip
      * @param  mixed   $operator 比较操作符
      * @param  integer $count    个数
      * @param  string  $id       关联表的统计字段
+     * @param  string  $joinType JOIN类型
      * @return Query
      */
-    public static function has($relation, $operator = '>=', $count = 1, $id = '*')
+    public static function has($relation, $operator = '>=', $count = 1, $id = '*', $joinType = 'INNER')
     {
         $relation = (new static())->$relation();
 
@@ -152,7 +152,7 @@ trait RelationShip
             return $relation->hasWhere($operator);
         }
 
-        return $relation->has($operator, $count, $id);
+        return $relation->has($operator, $count, $id, $joinType);
     }
 
     /**

+ 3 - 4
thinkphp/library/think/model/concern/SoftDelete.php

@@ -52,9 +52,8 @@ trait SoftDelete
             return $model
                 ->db(false)
                 ->useSoftDelete($field, ['not null', '']);
-        } else {
-            return $model->db(false);
         }
+        return $model->db(false);
     }
 
     /**
@@ -154,9 +153,9 @@ trait SoftDelete
                 ->where($where)
                 ->useSoftDelete($name, ['not null', ''])
                 ->update([$name => null]);
-        } else {
-            return 0;
         }
+
+        return 0;
     }
 
     /**

+ 24 - 1
thinkphp/library/think/model/relation/BelongsTo.php

@@ -52,6 +52,7 @@ class BelongsTo extends OneToOne
         $foreignKey = $this->foreignKey;
 
         $relationModel = $this->query
+            ->removeWhereField($this->localKey)
             ->where($this->localKey, $this->parent->$foreignKey)
             ->relation($subRelation)
             ->find();
@@ -69,9 +70,10 @@ class BelongsTo extends OneToOne
      * @param  string  $operator 比较操作符
      * @param  integer $count    个数
      * @param  string  $id       关联表的统计字段
+     * @param  string  $joinType JOIN类型
      * @return Query
      */
-    public function has($operator = '>=', $count = 1, $id = '*')
+    public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER')
     {
         return $this->parent;
     }
@@ -125,6 +127,8 @@ class BelongsTo extends OneToOne
         }
 
         if (!empty($range)) {
+            $this->query->removeWhereField($localKey);
+
             $data = $this->eagerlyWhere([
                 [$localKey, 'in', $range],
             ], $localKey, $relation, $subRelation, $closure);
@@ -168,6 +172,8 @@ class BelongsTo extends OneToOne
         $localKey   = $this->localKey;
         $foreignKey = $this->foreignKey;
 
+        $this->query->removeWhereField($localKey);
+
         $data = $this->eagerlyWhere([
             [$localKey, '=', $result->$foreignKey],
         ], $localKey, $relation, $subRelation, $closure);
@@ -221,4 +227,21 @@ class BelongsTo extends OneToOne
 
         return $this->parent->setRelation($this->relation, null);
     }
+
+    /**
+     * 执行基础查询(仅执行一次)
+     * @access protected
+     * @return void
+     */
+    protected function baseQuery()
+    {
+        if (empty($this->baseQuery)) {
+            if (isset($this->parent->{$this->foreignKey})) {
+                // 关联查询带入关联条件
+                $this->query->where($this->localKey, '=', $this->parent->{$this->foreignKey});
+            }
+
+            $this->baseQuery = true;
+        }
+    }
 }

+ 28 - 2
thinkphp/library/think/model/relation/HasOne.php

@@ -52,6 +52,7 @@ class HasOne extends OneToOne
 
         // 判断关联类型执行查询
         $relationModel = $this->query
+            ->removeWhereField($this->foreignKey)
             ->where($this->foreignKey, $this->parent->$localKey)
             ->relation($subRelation)
             ->find();
@@ -66,9 +67,13 @@ class HasOne extends OneToOne
     /**
      * 根据关联条件查询当前模型
      * @access public
+     * @param  string  $operator 比较操作符
+     * @param  integer $count    个数
+     * @param  string  $id       关联表的统计字段
+     * @param  string  $joinType JOIN类型
      * @return Query
      */
-    public function has()
+    public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER')
     {
         $table      = $this->query->getTable();
         $model      = basename(str_replace('\\', '/', get_class($this->parent)));
@@ -134,6 +139,8 @@ class HasOne extends OneToOne
         }
 
         if (!empty($range)) {
+            $this->query->removeWhereField($foreignKey);
+
             $data = $this->eagerlyWhere([
                 [$foreignKey, 'in', $range],
             ], $foreignKey, $relation, $subRelation, $closure);
@@ -176,7 +183,10 @@ class HasOne extends OneToOne
     {
         $localKey   = $this->localKey;
         $foreignKey = $this->foreignKey;
-        $data       = $this->eagerlyWhere([
+
+        $this->query->removeWhereField($foreignKey);
+
+        $data = $this->eagerlyWhere([
             [$foreignKey, '=', $result->$localKey],
         ], $foreignKey, $relation, $subRelation, $closure);
 
@@ -197,4 +207,20 @@ class HasOne extends OneToOne
         }
     }
 
+    /**
+     * 执行基础查询(仅执行一次)
+     * @access protected
+     * @return void
+     */
+    protected function baseQuery()
+    {
+        if (empty($this->baseQuery)) {
+            if (isset($this->parent->{$this->localKey})) {
+                // 关联查询带入关联条件
+                $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey});
+            }
+
+            $this->baseQuery = true;
+        }
+    }
 }

+ 1 - 1
thinkphp/library/think/model/relation/MorphMany.php

@@ -244,7 +244,7 @@ class MorphMany extends Relation
     protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false)
     {
         // 预载入关联查询 支持嵌套预载入
-        $this->query->removeOptions('where');
+        $this->query->removeOption('where');
 
         if ($closure) {
             $closure($this->query);

+ 1 - 1
thinkphp/library/think/process/pipes/Windows.php

@@ -196,7 +196,7 @@ class Windows extends Pipes
             return;
         }
 
-        if (null !== $w && 0 < count($r)) {
+        if (null !== $r && 0 < count($r)) {
             $data = '';
             while ($dataread = fread($r['input'], self::CHUNK_SIZE)) {
                 $data .= $dataread;

+ 0 - 1
thinkphp/library/think/response/View.php

@@ -64,7 +64,6 @@ class View extends Response
     {
         if (is_array($name)) {
             $this->vars = array_merge($this->vars, $name);
-            return $this;
         } else {
             $this->vars[$name] = $value;
         }

+ 9 - 0
thinkphp/library/think/response/Xml.php

@@ -41,6 +41,15 @@ class Xml extends Response
      */
     protected function output($data)
     {
+        if (is_string($data)) {
+            if (0 !== strpos($data, '<?xml')) {
+                $encoding = $this->options['encoding'];
+                $xml      = "<?xml version=\"1.0\" encoding=\"{$encoding}\"?>";
+                $data     = $xml . $data;
+            }
+            return $data;
+        }
+
         // XML数据转换
         return $this->xmlEncode($data, $this->options['root_node'], $this->options['item_node'], $this->options['root_attr'], $this->options['item_key'], $this->options['encoding']);
     }

+ 126 - 0
thinkphp/library/think/route/AliasRule.php

@@ -0,0 +1,126 @@
+<?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\route;
+
+use think\Route;
+
+class AliasRule extends Domain
+{
+    protected $route;
+
+    /**
+     * 架构函数
+     * @access public
+     * @param  Route             $router 路由实例
+     * @param  RuleGroup         $parent 上级对象
+     * @param  string            $name   路由别名
+     * @param  string            $route  路由绑定
+     * @param  array             $option 路由参数
+     */
+    public function __construct(Route $router, RuleGroup $parent, $name, $route, $option = [])
+    {
+        $this->router = $router;
+        $this->parent = $parent;
+        $this->name   = $name;
+        $this->route  = $route;
+        $this->option = $option;
+    }
+
+    /**
+     * 检测域名路由
+     * @access public
+     * @param  Request      $request  请求对象
+     * @param  string       $url      访问地址
+     * @param  string       $depr     路径分隔符
+     * @param  bool         $completeMatch   路由是否完全匹配
+     * @return Dispatch|false
+     */
+    public function check($request, $url, $depr = '/', $completeMatch = false)
+    {
+        if ($dispatch = $this->checkCrossDomain($request)) {
+            // 允许跨域
+            return $dispatch;
+        }
+
+        // 检查参数有效性
+        if (!$this->checkOption($this->option, $request)) {
+            return false;
+        }
+
+        list($action, $bind) = array_pad(explode('|', $url, 2), 2, '');
+
+        if (isset($this->option['allow']) && !in_array($action, $this->option['allow'])) {
+            // 允许操作
+            return false;
+        } elseif (isset($this->option['except']) && in_array($action, $this->option['except'])) {
+            // 排除操作
+            return false;
+        }
+
+        if (isset($this->option['method'][$action])) {
+            $this->option['method'] = $this->option['method'][$action];
+        }
+
+        // 匹配后执行的行为
+        $this->afterMatchGroup($request);
+
+        if ($this->parent) {
+            // 合并分组参数
+            $this->mergeGroupOptions();
+        }
+
+        $this->parseBindAppendParam($this->route);
+
+        if (0 === strpos($this->route, '\\')) {
+            // 路由到类
+            return $this->bindToClass($bind, substr($this->route, 1));
+        } elseif (0 === strpos($this->route, '@')) {
+            // 路由到控制器类
+            return $this->bindToController($bind, substr($this->route, 1));
+        } else {
+            // 路由到模块/控制器
+            return $this->bindToModule($bind, $this->route);
+        }
+    }
+
+    /**
+     * 设置允许的操作方法
+     * @access public
+     * @param  array      $action  操作方法
+     * @return $this
+     */
+    public function allow($action = [])
+    {
+        return $this->option('allow', $action);
+    }
+
+    /**
+     * 设置排除的操作方法
+     * @access public
+     * @param  array      $action  操作方法
+     * @return $this
+     */
+    public function except($action = [])
+    {
+        return $this->option('except', $action);
+    }
+
+    /**
+     * 获取当前的路由
+     * @access public
+     * @return string
+     */
+    public function getRoute()
+    {
+        return $this->route;
+    }
+}

+ 55 - 91
thinkphp/library/think/route/Domain.php

@@ -13,12 +13,10 @@ namespace think\route;
 
 use think\Container;
 use think\Loader;
-use think\Response;
 use think\Route;
 use think\route\dispatch\Callback as CallbackDispatch;
 use think\route\dispatch\Controller as ControllerDispatch;
 use think\route\dispatch\Module as ModuleDispatch;
-use think\route\dispatch\Response as ResponseDispatch;
 
 class Domain extends RuleGroup
 {
@@ -26,7 +24,7 @@ class Domain extends RuleGroup
      * 架构函数
      * @access public
      * @param  Route       $router   路由对象
-     * @param  string      $name     分组名称
+     * @param  string      $name     路由域名
      * @param  mixed       $rule     域名路由
      * @param  array       $option   路由参数
      * @param  array       $pattern  变量规则
@@ -34,7 +32,7 @@ class Domain extends RuleGroup
     public function __construct(Route $router, $name = '', $rule = null, $option = [], $pattern = [])
     {
         $this->router  = $router;
-        $this->name    = trim($name, '/');
+        $this->domain  = $name;
         $this->option  = $option;
         $this->rule    = $rule;
         $this->pattern = $pattern;
@@ -51,30 +49,11 @@ class Domain extends RuleGroup
      */
     public function check($request, $url, $depr = '/', $completeMatch = false)
     {
-        if ($this->rule) {
-            // 延迟解析域名路由
-            if ($this->rule instanceof Response) {
-                return new ResponseDispatch($this->rule);
-            }
-
-            $group = new RuleGroup($this->router);
-
-            $this->addRule($group);
-
-            $this->router->setGroup($group);
-
-            $this->router->parseGroupRule($this, $this->rule);
-
-            $this->rule = null;
-        }
-
         // 检测别名路由
-        if ($this->router->getAlias($url) || $this->router->getAlias(strstr($url, '|', true))) {
-            // 检测路由别名
-            $result = $this->checkRouteAlias($request, $url, $depr);
-            if (false !== $result) {
-                return $result;
-            }
+        $result = $this->checkRouteAlias($request, $url, $depr);
+
+        if (false !== $result) {
+            return $result;
         }
 
         // 检测URL绑定
@@ -88,6 +67,19 @@ class Domain extends RuleGroup
     }
 
     /**
+     * 设置路由绑定
+     * @access public
+     * @param  string     $bind 绑定信息
+     * @return $this
+     */
+    public function bind($bind)
+    {
+        $this->router->bind($bind, $this->domain);
+
+        return $this;
+    }
+
+    /**
      * 检测路由别名
      * @access private
      * @param  Request   $request
@@ -97,45 +89,11 @@ class Domain extends RuleGroup
      */
     private function checkRouteAlias($request, $url, $depr)
     {
-        $array = explode('|', $url);
-        $alias = array_shift($array);
-        $item  = $this->router->getAlias($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;
-            }
+        $alias = strpos($url, '|') ? strstr($url, '|', true) : $url;
 
-            if (isset($option['method'][$action])) {
-                $option['method'] = $option['method'][$action];
-            }
-        } else {
-            $rule = $item;
-        }
+        $item = $this->router->getAlias($alias);
 
-        $bind = implode('|', $array);
-
-        // 参数有效性检查
-        if (isset($option) && !$this->checkOption($option, $request)) {
-            // 路由不匹配
-            return false;
-        } elseif (0 === strpos($rule, '\\')) {
-            // 路由到类
-            return $this->bindToClass($bind, substr($rule, 1), $depr);
-        } elseif (0 === strpos($rule, '@')) {
-            // 路由到控制器类
-            return $this->bindToController($bind, substr($rule, 1), $depr);
-        } else {
-            // 路由到模块/控制器
-            return $this->bindToModule($bind, $rule, $depr);
-        }
+        return $item ? $item->check($request, $url, $depr) : false;
     }
 
     /**
@@ -145,41 +103,53 @@ class Domain extends RuleGroup
      * @param  string    $depr URL分隔符
      * @return Dispatch|false
      */
-    private function checkUrlBind(&$url, $depr = '/')
+    private function checkUrlBind($url, $depr = '/')
     {
-        $bind = $this->router->getBind($this->name);
+        $bind = $this->router->getBind($this->domain);
 
         if (!empty($bind)) {
+
+            $this->parseBindAppendParam($bind);
+
             // 记录绑定信息
             Container::get('app')->log('[ BIND ] ' . var_export($bind, true));
 
             // 如果有URL绑定 则进行绑定检测
-            if (0 === strpos($bind, '\\')) {
-                // 绑定到类
-                return $this->bindToClass($url, substr($bind, 1), $depr);
-            } elseif (0 === strpos($bind, '@')) {
-                // 绑定到控制器类
-                return $this->bindToController($url, substr($bind, 1), $depr);
-            } elseif (0 === strpos($bind, ':')) {
-                // 绑定到命名空间
-                return $this->bindToNamespace($url, substr($bind, 1), $depr);
+            $type = substr($bind, 0, 1);
+            $bind = substr($bind, 1);
+
+            $bindTo = [
+                '\\' => 'bindToClass',
+                '@'  => 'bindToController',
+                ':'  => 'bindToNamespace',
+            ];
+
+            if (isset($bindTo[$type])) {
+                return $this->{$bindTo[$type]}($url, $bind, $depr);
             }
         }
 
         return false;
     }
 
+    protected function parseBindAppendParam(&$bind)
+    {
+        if (false !== strpos($bind, '?')) {
+            list($bind, $query) = explode('?', $bind);
+            parse_str($query, $vars);
+            $this->append($vars);
+        }
+    }
+
     /**
      * 绑定到类
-     * @access public
+     * @access protected
      * @param  string    $url URL地址
      * @param  string    $class 类名(带命名空间)
-     * @param  string    $depr URL分隔符
      * @return CallbackDispatch
      */
-    public function bindToClass($url, $class, $depr = '/')
+    protected function bindToClass($url, $class)
     {
-        $url    = str_replace($depr, '|', $url);
         $array  = explode('|', $url, 2);
         $action = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_action');
 
@@ -192,15 +162,13 @@ class Domain extends RuleGroup
 
     /**
      * 绑定到命名空间
-     * @access public
+     * @access protected
      * @param  string    $url URL地址
      * @param  string    $namespace 命名空间
-     * @param  string    $depr URL分隔符
      * @return CallbackDispatch
      */
-    public function bindToNamespace($url, $namespace, $depr = '/')
+    protected function bindToNamespace($url, $namespace)
     {
-        $url    = str_replace($depr, '|', $url);
         $array  = explode('|', $url, 3);
         $class  = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_controller');
         $method = !empty($array[1]) ? $array[1] : Container::get('config')->get('default_action');
@@ -214,15 +182,13 @@ class Domain extends RuleGroup
 
     /**
      * 绑定到控制器类
-     * @access public
+     * @access protected
      * @param  string    $url URL地址
      * @param  string    $controller 控制器名 (支持带模块名 index/user )
-     * @param  string    $depr URL分隔符
      * @return ControllerDispatch
      */
-    public function bindToController($url, $controller, $depr = '/')
+    protected function bindToController($url, $controller)
     {
-        $url    = str_replace($depr, '|', $url);
         $array  = explode('|', $url, 2);
         $action = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_action');
 
@@ -235,15 +201,13 @@ class Domain extends RuleGroup
 
     /**
      * 绑定到模块/控制器
-     * @access public
+     * @access protected
      * @param  string    $url URL地址
      * @param  string    $controller 控制器类名(带命名空间)
-     * @param  string    $depr URL分隔符
      * @return ModuleDispatch
      */
-    public function bindToModule($url, $controller, $depr = '/')
+    protected function bindToModule($url, $controller)
     {
-        $url    = str_replace($depr, '|', $url);
         $array  = explode('|', $url, 2);
         $action = !empty($array[0]) ? $array[0] : Container::get('config')->get('default_action');
 

+ 22 - 24
thinkphp/library/think/route/Resource.php

@@ -26,17 +26,17 @@ class Resource extends RuleGroup
      * 架构函数
      * @access public
      * @param  Route         $router     路由对象
-     * @param  RuleGroup     $group      路由所属分组对象
+     * @param  RuleGroup     $parent     上级对象
      * @param  string        $name       资源名称
      * @param  string        $route      路由地址
      * @param  array         $option     路由参数
      * @param  array         $pattern    变量规则
      * @param  array         $rest       资源定义
      */
-    public function __construct(Route $router, RuleGroup $group = null, $name = '', $route = '', $option = [], $pattern = [], $rest = [])
+    public function __construct(Route $router, RuleGroup $parent = null, $name = '', $route = '', $option = [], $pattern = [], $rest = [])
     {
         $this->router   = $router;
-        $this->parent   = $group;
+        $this->parent   = $parent;
         $this->resource = $name;
         $this->route    = $route;
         $this->name     = strpos($name, '.') ? strstr($name, '.', true) : $name;
@@ -49,23 +49,28 @@ class Resource extends RuleGroup
         $this->pattern = $pattern;
         $this->option  = $option;
         $this->rest    = $rest;
+
+        if ($this->parent) {
+            $this->domain = $this->parent->getDomain();
+            $this->parent->addRuleItem($this);
+        }
     }
 
     /**
-     * 检测分组路由
+     * 解析资源路由规则
      * @access public
-     * @param  Request      $request  请求对象
-     * @param  string       $url      访问地址
-     * @param  string       $depr     路径分隔符
-     * @param  bool         $completeMatch   路由是否完全匹配
-     * @return Dispatch
+     * @param  mixed        $rule    路由规则
+     * @return void
      */
-    public function check($request, $url, $depr = '/', $completeMatch = false)
+    public function parseGroupRule($rule)
     {
+        $origin = $this->router->getGroup();
+        $this->router->setGroup($this);
+
         // 生成资源路由的路由规则
         $this->buildResourceRule($this->resource, $this->option);
 
-        return parent::check($request, $url, $depr, $completeMatch);
+        $this->router->setGroup($origin);
     }
 
     /**
@@ -84,17 +89,12 @@ class Resource extends RuleGroup
             $item  = [];
 
             foreach ($array as $val) {
-                $item[] = $val . '/:' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id');
+                $item[] = $val . '/<' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id') . '>';
             }
 
             $rule = implode('/', $item) . '/' . $last;
         }
 
-        // 注册分组
-        $group = $this->router->getGroup();
-
-        $this->router->setGroup($this);
-
         // 注册资源路由
         foreach ($this->rest as $key => $val) {
             if ((isset($option['only']) && !in_array($key, $option['only']))
@@ -102,18 +102,16 @@ class Resource extends RuleGroup
                 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]);
+            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]);
             }
 
             $option['rest'] = $key;
 
-            $this->router->rule(trim($val[1], '/'), $this->route . '/' . $val[2], $val[0], $option);
+            $this->addRule(trim($val[1], '/'), $this->route . '/' . $val[2], $val[0], $option);
         }
-
-        $this->router->setGroup($group);
     }
 
     /**

+ 248 - 78
thinkphp/library/think/route/Rule.php

@@ -34,7 +34,7 @@ abstract class Rule
     // 路由变量规则
     protected $pattern = [];
     // 需要合并的路由参数
-    protected $mergeOptions = ['after', 'before', 'model'];
+    protected $mergeOptions = ['after', 'before', 'model', 'header', 'response', 'append', 'middleware'];
 
     abstract public function check($request, $url, $depr = '/');
 
@@ -75,14 +75,14 @@ abstract class Rule
     }
 
     /**
-     * 设置Name
+     * 设置标识
      * @access public
-     * @param  string|array  $name 变量
+     * @param  string  $name 标识
      * @return $this
      */
     public function name($name)
     {
-        $this->name = '/' != $name ? ltrim($name, '/') : '/';
+        $this->name = $name;
 
         return $this;
     }
@@ -128,23 +128,6 @@ abstract class Rule
     }
 
     /**
-     * 附加路由隐式参数
-     * @access public
-     * @param  array     $append
-     * @return $this
-     */
-    public function append(array $append = [])
-    {
-        if (isset($this->option['append'])) {
-            $this->option['append'] = array_merge($this->option['append'], $append);
-        } else {
-            $this->option['append'] = $append;
-        }
-
-        return $this;
-    }
-
-    /**
      * 设置路由请求类型
      * @access public
      * @param  string     $method
@@ -220,7 +203,9 @@ abstract class Rule
      */
     public function model($var, $model = null, $exception = true)
     {
-        if (is_array($var)) {
+        if ($var instanceof \Closure) {
+            $this->option['model'][] = $var;
+        } elseif (is_array($var)) {
             $this->option['model'] = $var;
         } elseif (is_null($model)) {
             $this->option['model']['id'] = [$var, true];
@@ -232,6 +217,23 @@ abstract class Rule
     }
 
     /**
+     * 附加路由隐式参数
+     * @access public
+     * @param  array     $append
+     * @return $this
+     */
+    public function append(array $append = [])
+    {
+        if (isset($this->option['append'])) {
+            $this->option['append'] = array_merge($this->option['append'], $append);
+        } else {
+            $this->option['append'] = $append;
+        }
+
+        return $this;
+    }
+
+    /**
      * 绑定验证
      * @access public
      * @param  mixed    $validate 验证器类
@@ -255,7 +257,8 @@ abstract class Rule
      */
     public function response($response)
     {
-        return $this->option('response', $response);
+        $this->option['response'][] = $response;
+        return $this;
     }
 
     /**
@@ -267,12 +270,8 @@ abstract class Rule
      */
     public function header($header, $value = null)
     {
-        if (empty($this->option['header'])) {
-            $this->option['header'] = [];
-        }
-
         if (is_array($header)) {
-            $this->option['header'] = array_merge($this->option['header'], $header);
+            $this->option['header'] = $header;
         } else {
             $this->option['header'][$header] = $value;
         }
@@ -281,6 +280,26 @@ abstract class Rule
     }
 
     /**
+     * 指定路由中间件
+     * @access public
+     * @param  string|array|\Closure    $middleware
+     * @param  mixed                    $param
+     * @return $this
+     */
+    public function middleware($middleware, $param = null)
+    {
+        if (is_null($param) && is_array($middleware)) {
+            $this->option['middleware'] = $middleware;
+        } else {
+            foreach ((array) $middleware as $item) {
+                $this->option['middleware'][] = [$item, $param];
+            }
+        }
+
+        return $this;
+    }
+
+    /**
      * 设置路由缓存
      * @access public
      * @param  array|string     $cache
@@ -416,7 +435,7 @@ abstract class Rule
         }
 
         if ($allow && $this->parent) {
-            $this->parent->addRule($this, 'options');
+            $this->parent->addRuleItem($this, 'options');
         }
 
         return $this->option('cross_domain', $allow);
@@ -484,6 +503,8 @@ abstract class Rule
         }
 
         $this->option = array_merge($parentOption, $this->option);
+
+        return $this->option;
     }
 
     /**
@@ -574,34 +595,15 @@ abstract class Rule
         // 替换路由地址中的变量
         if (is_string($route) && !empty($matches)) {
             foreach ($matches as $key => $val) {
-                if (false !== strpos($route, ':' . $key)) {
+                if (false !== strpos($route, '<' . $key . '>')) {
+                    $route = str_replace('<' . $key . '>', $val, $route);
+                } elseif (false !== strpos($route, ':' . $key)) {
                     $route = str_replace(':' . $key, $val, $route);
                 }
             }
         }
 
-        // 绑定模型数据
-        if (isset($option['model'])) {
-            $this->createBindModel($option['model'], $matches);
-        }
-
-        // 指定Header数据
-        if (!empty($option['header'])) {
-            $header = $option['header'];
-            Container::get('hook')->add('response_send', function ($response) use ($header) {
-                $response->header($header);
-            });
-        }
-
-        // 指定Response响应数据
-        if (!empty($option['response'])) {
-            Container::get('hook')->add('response_send', $option['response']);
-        }
-
-        // 开启请求缓存
-        if (isset($option['cache']) && $request->isGet()) {
-            $this->parseRequestCache($request, $option['cache']);
-        }
+        $this->afterMatchRule($request, $option, $matches);
 
         // 解析额外参数
         $count = substr_count($rule, '/');
@@ -629,6 +631,43 @@ abstract class Rule
         return $this->dispatch($request, $route, $option);
     }
 
+    protected function afterMatchRule($request, $option = [], $matches = [])
+    {
+        // 添加中间件
+        if (!empty($option['middleware'])) {
+            foreach ($option['middleware'] as $middleware) {
+                Container::get('middleware')->add($middleware);
+            }
+        }
+
+        // 绑定模型数据
+        if (!empty($option['model'])) {
+            $this->createBindModel($option['model'], $matches);
+        }
+
+        // 指定Header数据
+        if (!empty($option['header'])) {
+            $header = $option['header'];
+            Container::get('hook')->add('response_send', function ($response) use ($header) {
+                $response->header($header);
+            });
+        }
+
+        // 指定Response响应数据
+        if (!empty($option['response'])) {
+            Container::get('hook')->add('response_send', $option['response']);
+        }
+
+        // 开启请求缓存
+        if (isset($option['cache']) && $request->isGet()) {
+            $this->parseRequestCache($request, $option['cache']);
+        }
+
+        if (!empty($option['append'])) {
+            $request->route($option['append']);
+        }
+    }
+
     /**
      * 验证数据
      * @access protected
@@ -694,6 +733,8 @@ abstract class Rule
      */
     protected function checkAfter($after)
     {
+        Container::get('log')->notice('路由后置行为建议使用中间件替代!');
+
         $hook = Container::get('hook');
 
         $result = null;
@@ -711,9 +752,9 @@ abstract class Rule
             return new ResponseDispatch($result);
         } elseif ($result instanceof Dispatch) {
             return $result;
-        } else {
-            return false;
         }
+
+        return false;
     }
 
     /**
@@ -738,24 +779,13 @@ abstract class Rule
             $result = new RedirectDispatch($route, [], isset($option['status']) ? $option['status'] : 301);
         } elseif (false !== strpos($route, '\\')) {
             // 路由到方法
-            list($path, $var) = $this->parseUrlPath($route);
-            $route            = str_replace('/', '@', implode('/', $path));
-            $method           = strpos($route, '@') ? explode('@', $route) : $route;
-            $result           = new CallbackDispatch($method, $var);
+            $result = $this->dispatchMethod($route);
         } elseif (0 === strpos($route, '@')) {
             // 路由到控制器
-            $route             = substr($route, 1);
-            list($route, $var) = $this->parseUrlPath($route);
-            $result            = new ControllerDispatch(implode('/', $route), $var);
-
-            $request->action(array_pop($route));
-            $app = Container::get('app');
-            $request->controller($route ? array_pop($route) : $app->config('default_controller'));
-            $request->module($route ? array_pop($route) : $app->config('default_module'));
-            $app->setModulePath($app->getAppPath() . ($app->config('app_multi_module') ? $request->module() . DIRECTORY_SEPARATOR : ''));
+            $result = $this->dispatchController($request, substr($route, 1));
         } else {
             // 路由到模块/控制器/操作
-            $result = $this->parseModule($route);
+            $result = $this->dispatchModule($request, $route);
         }
 
         return $result;
@@ -764,18 +794,57 @@ abstract class Rule
     /**
      * 解析URL地址为 模块/控制器/操作
      * @access protected
-     * @param  string    $url URL地址
-     * @return array
+     * @param  string    $route 路由地址
+     * @return CallbackDispatch
+     */
+    protected function dispatchMethod($route)
+    {
+        list($path, $var) = $this->parseUrlPath($route);
+
+        $route  = str_replace('/', '@', implode('/', $path));
+        $method = strpos($route, '@') ? explode('@', $route) : $route;
+
+        return new CallbackDispatch($method, $var);
+    }
+
+    /**
+     * 解析URL地址为 模块/控制器/操作
+     * @access protected
+     * @param  Request   $request Request对象
+     * @param  string    $route 路由地址
+     * @return ControllerDispatch
      */
-    protected function parseModule($url)
+    protected function dispatchController($request, $route)
     {
-        list($path, $var) = $this->parseUrlPath($url);
-        $config           = Container::get('config');
-        $request          = Container::get('request');
-        $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->method();
+        list($route, $var) = $this->parseUrlPath($route);
+
+        $result = new ControllerDispatch(implode('/', $route), $var);
+
+        $request->action(array_pop($route));
+        $app = Container::get('app');
+        $request->controller($route ? array_pop($route) : $app->config('default_controller'));
+        $request->module($route ? array_pop($route) : $app->config('default_module'));
+        $app->setModulePath($app->getAppPath() . ($app->config('app_multi_module') ? $request->module() . DIRECTORY_SEPARATOR : ''));
+
+        return $result;
+    }
+
+    /**
+     * 解析URL地址为 模块/控制器/操作
+     * @access protected
+     * @param  Request   $request Request对象
+     * @param  string    $route 路由地址
+     * @return ModuleDispatch
+     */
+    protected function dispatchModule($request, $route)
+    {
+        list($path, $var) = $this->parseUrlPath($route);
+
+        $config     = Container::get('config');
+        $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->method();
 
         if ($config->get('use_action_prefix') && $this->router->getMethodPrefix($method)) {
             $prefix = $this->router->getMethodPrefix($method);
@@ -891,6 +960,107 @@ abstract class Rule
     }
 
     /**
+     * 生成路由的正则规则
+     * @access protected
+     * @param  string    $rule 路由规则
+     * @param  array     $match 匹配的变量
+     * @param  array     $pattern   路由变量规则
+     * @param  array     $option    路由参数
+     * @param  bool      $completeMatch   路由是否完全匹配
+     * @param  string    $suffix   路由正则变量后缀
+     * @return string
+     */
+    protected function buildRuleRegex($rule, $match, $pattern = [], $option = [], $completeMatch = false, $suffix = '')
+    {
+        foreach ($match as $name) {
+            $replace[] = $this->buildNameRegex($name, $pattern, $suffix);
+        }
+
+        // 是否区分 / 地址访问
+        if (!empty($option['remove_slash']) && '/' != $rule) {
+            $rule = rtrim($rule, '/');
+        }
+
+        $regex = str_replace($match, $replace, $rule);
+        $regex = str_replace([')?/', ')/', ')?-', ')-', '\\\\/'], [')\/', ')\/', ')\-', ')\-', '\/'], $regex);
+
+        return $regex . ($completeMatch ? '$' : '');
+    }
+
+    /**
+     * 生成路由变量的正则规则
+     * @access protected
+     * @param  string    $name      路由变量
+     * @param  string    $pattern   变量规则
+     * @param  string    $suffix    路由正则变量后缀
+     * @return string
+     */
+    protected function buildNameRegex($name, $pattern, $suffix)
+    {
+        $optional = '';
+        $slash    = substr($name, 0, 1);
+
+        if (in_array($slash, ['/', '-'])) {
+            $prefix = '\\' . $slash;
+            $name   = substr($name, 1);
+            $slash  = substr($name, 0, 1);
+        } else {
+            $prefix = '';
+        }
+
+        if ('<' != $slash) {
+            return $prefix . preg_quote($name, '/');
+        }
+
+        if (strpos($name, '?')) {
+            $name     = substr($name, 1, -2);
+            $optional = '?';
+        } elseif (strpos($name, '>')) {
+            $name = substr($name, 1, -1);
+        }
+
+        if (isset($pattern[$name])) {
+            $nameRule = $pattern[$name];
+            if (0 === strpos($nameRule, '/') && '/' == substr($nameRule, -1)) {
+                $nameRule = substr($nameRule, 1, -1);
+            }
+        } else {
+            $nameRule = '\w+';
+        }
+
+        return '(' . $prefix . '(?<' . $name . $suffix . '>' . $nameRule . '))' . $optional;
+    }
+
+    /**
+     * 分析路由规则中的变量
+     * @access protected
+     * @param  string    $rule 路由规则
+     * @return array
+     */
+    protected function parseVar($rule)
+    {
+        // 提取路由规则中的变量
+        $var = [];
+
+        if (preg_match_all('/<\w+\??>/', $rule, $matches)) {
+            foreach ($matches[0] as $name) {
+                $optional = false;
+
+                if (strpos($name, '?')) {
+                    $name     = substr($name, 1, -2);
+                    $optional = true;
+                } else {
+                    $name = substr($name, 1, -1);
+                }
+
+                $var[$name] = $optional ? 2 : 1;
+            }
+        }
+
+        return $var;
+    }
+
+    /**
      * 设置路由参数
      * @access public
      * @param  string    $method     方法名

+ 309 - 79
thinkphp/library/think/route/RuleGroup.php

@@ -12,6 +12,7 @@
 namespace think\route;
 
 use think\Container;
+use think\Exception;
 use think\Request;
 use think\Response;
 use think\Route;
@@ -43,35 +44,51 @@ class RuleGroup extends Rule
     // 完整名称
     protected $fullName;
 
+    // 所在域名
+    protected $domain;
+
     /**
      * 架构函数
      * @access public
      * @param  Route       $router   路由对象
-     * @param  RuleGroup   $group    路由所属分组对象
+     * @param  RuleGroup   $parent   上级对象
      * @param  string      $name     分组名称
      * @param  mixed       $rule     分组路由
      * @param  array       $option   路由参数
      * @param  array       $pattern  变量规则
      */
-    public function __construct(Route $router, RuleGroup $group = null, $name = '', $rule = [], $option = [], $pattern = [])
+    public function __construct(Route $router, RuleGroup $parent = null, $name = '', $rule = [], $option = [], $pattern = [])
     {
         $this->router  = $router;
-        $this->parent  = $group;
+        $this->parent  = $parent;
         $this->rule    = $rule;
         $this->name    = trim($name, '/');
         $this->option  = $option;
         $this->pattern = $pattern;
 
         $this->setFullName();
+
+        if ($this->parent) {
+            $this->domain = $this->parent->getDomain();
+            $this->parent->addRuleItem($this);
+        }
+
+        if (!empty($option['cross_domain'])) {
+            $this->router->setCrossDomainRule($this);
+        }
     }
 
     /**
      * 设置分组的路由规则
      * @access public
-     * @return $this
+     * @return void
      */
     protected function setFullName()
     {
+        if (false !== strpos($this->name, ':')) {
+            $this->name = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $this->name);
+        }
+
         if ($this->parent && $this->parent->getFullName()) {
             $this->fullName = $this->parent->getFullName() . ($this->name ? '/' . $this->name : '');
         } else {
@@ -80,15 +97,13 @@ class RuleGroup extends Rule
     }
 
     /**
-     * 设置分组的路由规则
+     * 获取所属域名
      * @access public
-     * @param  mixed      $rule     路由规则
-     * @return $this
+     * @return string
      */
-    public function setRule($rule)
+    public function getDomain()
     {
-        $this->rule = $rule;
-        return $this;
+        return $this->domain;
     }
 
     /**
@@ -103,87 +118,53 @@ class RuleGroup extends Rule
     public function check($request, $url, $depr = '/', $completeMatch = false)
     {
         if ($dispatch = $this->checkCrossDomain($request)) {
-            // 允许跨域
+            // 跨域OPTIONS请求
             return $dispatch;
         }
 
-        // 检查参数有效性
-        if (!$this->checkOption($this->option, $request)) {
+        // 检查分组有效性
+        if (!$this->checkOption($this->option, $request) || !$this->checkUrl($url)) {
             return false;
         }
 
-        if ($this->fullName) {
-            // 分组URL匹配检查
-            $pos = strpos(str_replace('<', ':', $this->fullName), ':');
-
-            if (false !== $pos) {
-                $str = substr($this->fullName, 0, $pos);
-            } else {
-                $str = $this->fullName;
-            }
-
-            if (0 !== stripos(str_replace('|', '/', $url), $str)) {
-                return false;
-            }
-        }
-
+        // 解析分组路由
         if ($this->rule) {
-            // 延迟解析分组路由
             if ($this->rule instanceof Response) {
                 return new ResponseDispatch($this->rule);
             }
 
-            $group = $this->router->getGroup();
-
-            $this->router->setGroup($this);
-
-            $this->router->parseGroupRule($this, $this->rule);
-
-            $this->router->setGroup($group);
-
-            $this->rule = null;
-        }
-
-        // 分组匹配后执行的行为
-
-        // 指定Response响应数据
-        if (!empty($this->option['response'])) {
-            Container::get('hook')->add('response_send', $this->option['response']);
-        }
-
-        // 开启请求缓存
-        if (isset($this->option['cache']) && $request->isGet()) {
-            $this->parseRequestCache($request, $this->option['cache']);
+            $this->parseGroupRule($this->rule);
         }
 
         // 获取当前路由规则
         $method = strtolower($request->method());
-        $rules  = array_merge($this->rules['*'], $this->rules[$method]);
+        $rules  = $this->getMethodRules($method);
+
+        if (count($rules) == 0) {
+            return false;
+        }
 
         if ($this->parent) {
             // 合并分组参数
             $this->mergeGroupOptions();
+            // 合并分组变量规则
+            $this->pattern = array_merge($this->parent->getPattern(), $this->pattern);
         }
 
         if (isset($this->option['complete_match'])) {
             $completeMatch = $this->option['complete_match'];
         }
 
-        if (!empty($this->option['append'])) {
-            $request->route($this->option['append']);
-        }
-
-        if (isset($rules[$url])) {
-            // 快速定位
-            $item   = $rules[$url];
-            $result = $item->check($request, $url, $depr, $completeMatch);
+        if (!empty($this->option['merge_rule_regex'])) {
+            // 合并路由正则规则进行路由匹配检查
+            $result = $this->checkMergeRuleRegex($request, $rules, $url, $depr, $completeMatch);
 
             if (false !== $result) {
                 return $result;
             }
         }
 
-        // 遍历分组路由
+        // 检查分组路由
         foreach ($rules as $key => $item) {
             $result = $item->check($request, $url, $depr, $completeMatch);
 
@@ -192,10 +173,10 @@ class RuleGroup extends Rule
             }
         }
 
-        if (isset($this->auto)) {
+        if ($this->auto) {
             // 自动解析URL地址
-            $result = new UrlDispatch($this->auto->getRoute() . '/' . $url, ['depr' => $depr, 'auto_search' => false]);
-        } elseif (isset($this->miss)) {
+            $result = new UrlDispatch($this->auto . '/' . $url, ['depr' => $depr, 'auto_search' => false]);
+        } elseif ($this->miss && in_array($this->miss->getMethod(), ['*', $method])) {
             // 未匹配所有路由的路由规则处理
             $result = $this->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->getOption());
         } else {
@@ -206,37 +187,275 @@ class RuleGroup extends Rule
     }
 
     /**
-     * 设置自动路由
+     * 获取当前请求的路由规则(包括子分组、资源路由)
+     * @access protected
+     * @param  string      $method
+     * @return array
+     */
+    protected function getMethodRules($method)
+    {
+        return array_merge($this->rules['*'], $this->rules[$method]);
+    }
+
+    /**
+     * 分组URL匹配检查
+     * @access protected
+     * @param  string     $url
+     * @return bool
+     */
+    protected function checkUrl($url)
+    {
+        if ($this->fullName) {
+            $pos = strpos($this->fullName, '<');
+
+            if (false !== $pos) {
+                $str = substr($this->fullName, 0, $pos);
+            } else {
+                $str = $this->fullName;
+            }
+
+            if ($str && 0 !== stripos(str_replace('|', '/', $url), $str)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * 延迟解析分组的路由规则
      * @access public
-     * @param  RuleItem     $rule   路由规则
+     * @param  bool     $lazy   路由是否延迟解析
      * @return $this
      */
-    public function setAutoRule(RuleItem $rule)
+    public function lazy($lazy = true)
     {
-        $this->auto = $rule;
+        if (!$lazy && !is_object($this->rule)) {
+            $this->parseGroupRule($this->rule);
+            $this->rule = null;
+        }
+
         return $this;
     }
 
     /**
-     * 设置为MISS路由
+     * 解析分组和域名的路由规则及绑定
      * @access public
-     * @param  RuleItem     $rule   路由规则
-     * @return $this
+     * @param  mixed        $rule    路由规则
+     * @return void
      */
-    public function setMissRule(RuleItem $rule)
+    public function parseGroupRule($rule)
     {
-        $this->miss = $rule;
-        return $this;
+        $origin = $this->router->getGroup();
+        $this->router->setGroup($this);
+
+        if ($rule instanceof \Closure) {
+            Container::getInstance()->invokeFunction($rule);
+        } elseif (is_array($rule)) {
+            $this->addRules($rule);
+        } elseif (is_string($rule) && $rule) {
+            $this->router->bind($rule, $this->domain);
+        }
+
+        $this->router->setGroup($origin);
+    }
+
+    /**
+     * 检测分组路由
+     * @access public
+     * @param  Request      $request  请求对象
+     * @param  array        $rules    路由规则
+     * @param  string       $url      访问地址
+     * @param  string       $depr     路径分隔符
+     * @param  bool         $completeMatch   路由是否完全匹配
+     * @return Dispatch|false
+     */
+    protected function checkMergeRuleRegex($request, &$rules, $url, $depr, $completeMatch)
+    {
+        $url = $depr . str_replace('|', $depr, $url);
+
+        foreach ($rules as $key => $item) {
+            if ($item instanceof RuleItem) {
+                $rule = $depr . str_replace('/', $depr, $item->getRule());
+                if ($depr == $rule && $depr != $url) {
+                    unset($rules[$key]);
+                    continue;
+                }
+
+                $complete = null !== $item->getOption('complete_match') ? $item->getOption('complete_match') : $completeMatch;
+
+                if (false === strpos($rule, '<')) {
+                    if (0 === strcasecmp($rule, $url) || (!$complete && 0 === strncasecmp($rule, $url, strlen($rule)))) {
+                        return $item->checkRule($request, $url, []);
+                    }
+
+                    unset($rules[$key]);
+                    continue;
+                }
+
+                $slash = preg_quote('/-' . $depr, '/');
+
+                if ($matchRule = preg_split('/[' . $slash . ']<\w+\??>/', $rule, 2)) {
+                    if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) {
+                        unset($rules[$key]);
+                        continue;
+                    }
+                }
+
+                if (preg_match_all('/[' . $slash . ']?<?\w+\??>?/', $rule, $matches)) {
+                    unset($rules[$key]);
+                    $pattern = array_merge($this->getPattern(), $item->getPattern());
+                    $option  = array_merge($this->getOption(), $item->getOption());
+
+                    $regex[$key] = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $complete, '_THINK_' . $key);
+                    $items[$key] = $item;
+                }
+            }
+        }
+
+        try {
+            if (!empty($regex) && preg_match('/^(?:' . implode('|', $regex) . ')/', $url, $match)) {
+                $var = [];
+                foreach ($match as $key => $val) {
+                    if (is_string($key) && '' !== $val) {
+                        list($name, $pos) = explode('_THINK_', $key);
+
+                        $var[$name] = $val;
+                    }
+                }
+
+                if (!isset($pos)) {
+                    foreach ($regex as $key => $item) {
+                        if (0 === strpos(str_replace(['\/', '\-', '\\' . $depr], ['/', '-', $depr], $item), $match[0])) {
+                            $pos = $key;
+                            break;
+                        }
+                    }
+                }
+
+                return $items[$pos]->checkRule($request, $url, $var);
+            }
+
+            return false;
+        } catch (\Exception $e) {
+            throw new Exception('route pattern error');
+        }
+    }
+
+    /**
+     * 获取分组的MISS路由
+     * @access public
+     * @return RuleItem|null
+     */
+    public function getMissRule()
+    {
+        return $this->miss;
+    }
+
+    /**
+     * 获取分组的自动路由
+     * @access public
+     * @return string
+     */
+    public function getAutoRule()
+    {
+        return $this->auto;
+    }
+
+    /**
+     * 注册自动路由
+     * @access public
+     * @param  string     $route   路由规则
+     * @return void
+     */
+    public function addAutoRule($route)
+    {
+        $this->auto = $route;
+    }
+
+    /**
+     * 注册MISS路由
+     * @access public
+     * @param  string    $route      路由地址
+     * @param  string    $method     请求类型
+     * @param  array     $option     路由参数
+     * @return RuleItem
+     */
+    public function addMissRule($route, $method = '*', $option = [])
+    {
+        // 创建路由规则实例
+        $ruleItem = new RuleItem($this->router, $this, null, '', $route, strtolower($method), $option);
+
+        $this->miss = $ruleItem;
+
+        return $ruleItem;
     }
 
     /**
      * 添加分组下的路由规则或者子分组
      * @access public
-     * @param  Rule     $rule   路由规则
-     * @param  string   $method 请求类型
+     * @param  string    $rule       路由规则
+     * @param  string    $route      路由地址
+     * @param  string    $method     请求类型
+     * @param  array     $option     路由参数
+     * @param  array     $pattern    变量规则
      * @return $this
      */
-    public function addRule($rule, $method = '*')
+    public function addRule($rule, $route, $method = '*', $option = [], $pattern = [])
+    {
+        // 读取路由标识
+        if (is_array($rule)) {
+            $name = $rule[0];
+            $rule = $rule[1];
+        } elseif (is_string($route)) {
+            $name = $route;
+        } else {
+            $name = null;
+        }
+
+        $method = strtolower($method);
+
+        // 创建路由规则实例
+        $ruleItem = new RuleItem($this->router, $this, $name, $rule, $route, $method, $option, $pattern);
+
+        if (!empty($option['cross_domain'])) {
+            $this->router->setCrossDomainRule($ruleItem, $method);
+        }
+
+        $this->addRuleItem($ruleItem, $method);
+
+        return $ruleItem;
+    }
+
+    /**
+     * 批量注册路由规则
+     * @access public
+     * @param  array     $rules      路由规则
+     * @param  string    $method     请求类型
+     * @param  array     $option     路由参数
+     * @param  array     $pattern    变量规则
+     * @return void
+     */
+    public function addRules($rules, $method = '*', $option = [], $pattern = [])
+    {
+        foreach ($rules as $key => $val) {
+            if (is_numeric($key)) {
+                $key = array_shift($val);
+            }
+
+            if (is_array($val)) {
+                $route   = array_shift($val);
+                $option  = $val ? array_shift($val) : [];
+                $pattern = $val ? array_shift($val) : [];
+            } else {
+                $route = $val;
+            }
+
+            $this->addRule($key, $route, $method, $option, $pattern);
+        }
+    }
+
+    public function addRuleItem($rule, $method = '*')
     {
         if (strpos($method, '|')) {
             $rule->method($method);
@@ -256,7 +475,7 @@ class RuleGroup extends Rule
      */
     public function prefix($prefix)
     {
-        if ($this->parent->getOption('prefix')) {
+        if ($this->parent && $this->parent->getOption('prefix')) {
             $prefix = $this->parent->getOption('prefix') . $prefix;
         }
 
@@ -264,6 +483,17 @@ class RuleGroup extends Rule
     }
 
     /**
+     * 合并分组的路由规则正则
+     * @access public
+     * @param  bool     $merge
+     * @return $this
+     */
+    public function mergeRuleRegex($merge = true)
+    {
+        return $this->option('merge_rule_regex', $merge);
+    }
+
+    /**
      * 获取完整分组Name
      * @access public
      * @return string
@@ -283,9 +513,9 @@ class RuleGroup extends Rule
     {
         if ('' === $method) {
             return $this->rules;
-        } else {
-            return isset($this->rules[strtolower($method)]) ? $this->rules[strtolower($method)] : [];
         }
+
+        return isset($this->rules[strtolower($method)]) ? $this->rules[strtolower($method)] : [];
     }
 
 }

+ 154 - 136
thinkphp/library/think/route/RuleItem.php

@@ -11,6 +11,8 @@
 
 namespace think\route;
 
+use think\Container;
+use think\Exception;
 use think\Route;
 
 class RuleItem extends Rule
@@ -19,7 +21,7 @@ class RuleItem extends Rule
      * 路由规则
      * @var string
      */
-    protected $name;
+    protected $rule;
 
     /**
      * 路由地址
@@ -37,23 +39,29 @@ class RuleItem extends Rule
      * 架构函数
      * @access public
      * @param  Route             $router 路由实例
-     * @param  RuleGroup         $group 路由所属分组对象
-     * @param  string|array      $name 路由规则
+     * @param  RuleGroup         $parent 上级对象
+     * @param  string            $name 路由标识
+     * @param  string|array      $rule 路由规则
      * @param  string            $method 请求类型
      * @param  string|\Closure   $route 路由地址
      * @param  array             $option 路由参数
      * @param  array             $pattern 变量规则
      */
-    public function __construct(Route $router, RuleGroup $group, $name, $route, $method = '*', $option = [], $pattern = [])
+    public function __construct(Route $router, RuleGroup $parent, $name, $rule, $route, $method = '*', $option = [], $pattern = [])
     {
         $this->router  = $router;
-        $this->parent  = $group;
+        $this->parent  = $parent;
+        $this->name    = $name;
         $this->route   = $route;
         $this->method  = $method;
         $this->option  = $option;
         $this->pattern = $pattern;
 
-        $this->setRule($name);
+        $this->setRule($rule);
+
+        if (!empty($option['cross_domain'])) {
+            $this->router->setCrossDomainRule($this, $method);
+        }
     }
 
     /**
@@ -71,7 +79,30 @@ class RuleItem extends Rule
             $this->option['complete_match'] = true;
         }
 
-        $this->name($rule);
+        $rule = '/' != $rule ? ltrim($rule, '/') : '';
+
+        if ($this->parent && $prefix = $this->parent->getFullName()) {
+            $rule = $prefix . ($rule ? '/' . ltrim($rule, '/') : '');
+        }
+
+        if (false !== strpos($rule, ':')) {
+            $this->rule = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $rule);
+        } else {
+            $this->rule = $rule;
+        }
+
+        // 生成路由标识的快捷访问
+        $this->setRuleName();
+    }
+
+    /**
+     * 获取当前路由规则
+     * @access public
+     * @return string
+     */
+    public function getRule()
+    {
+        return $this->rule;
     }
 
     /**
@@ -85,42 +116,67 @@ class RuleItem extends Rule
     }
 
     /**
-     * 设置为自动路由
+     * 获取当前路由的请求类型
      * @access public
-     * @return $this
+     * @return string
      */
-    public function isAuto()
+    public function getMethod()
     {
-        $this->parent->setAutoRule($this);
-        return $this;
+        return strtolower($this->method);
     }
 
     /**
-     * 设置为MISS路由
+     * 检查后缀
      * @access public
+     * @param  string     $ext
      * @return $this
      */
-    public function isMiss()
+    public function ext($ext = '')
     {
-        $this->parent->setMissRule($this);
+        $this->option('ext', $ext);
+        $this->setRuleName(true);
+
         return $this;
     }
 
     /**
+     * 设置路由标识 用于URL反解生成
+     * @access protected
+     * @param  bool     $first   是否插入开头
+     * @return void
+     */
+    protected function setRuleName($first = false)
+    {
+        if ($this->name) {
+            $vars = $this->parseVar($this->rule);
+            $name = strtolower($this->name);
+
+            if (isset($this->option['ext'])) {
+                $suffix = $this->option['ext'];
+            } elseif ($this->parent->getOption('ext')) {
+                $suffix = $this->parent->getOption('ext');
+            } else {
+                $suffix = null;
+            }
+
+            $value = [$this->rule, $vars, $this->parent->getDomain(), $suffix];
+
+            Container::get('rule_name')->set($name, $value, $first);
+        }
+    }
+
+    /**
      * 检测路由
      * @access public
      * @param  Request      $request  请求对象
      * @param  string       $url      访问地址
+     * @param  array        $match    匹配路由变量
      * @param  string       $depr     路径分隔符
      * @param  bool         $completeMatch   路由是否完全匹配
-     * @return Dispatch
+     * @return Dispatch|false
      */
-    public function check($request, $url, $depr = '/', $completeMatch = false)
+    public function checkRule($request, $url, $match = null, $depr = '/', $completeMatch = false)
     {
-        if ($this->parent && $prefix = $this->parent->getFullName()) {
-            $this->name = $prefix . ($this->name ? '/' . ltrim($this->name, '/') : '');
-        }
-
         if ($dispatch = $this->checkCrossDomain($request)) {
             // 允许跨域
             return $dispatch;
@@ -132,22 +188,54 @@ class RuleItem extends Rule
         }
 
         // 合并分组参数
-        $this->mergeGroupOptions();
-        $option = $this->option;
+        $option = $this->mergeGroupOptions();
 
-        if (!empty($option['append'])) {
-            $request->route($option['append']);
+        // 检查前置行为
+        if (isset($option['before']) && false === $this->checkBefore($option['before'])) {
+            return false;
         }
 
-        // 是否区分 / 地址访问
-        if (!empty($option['remove_slash']) && '/' != $this->name) {
-            $this->name = rtrim($this->name, '/');
-            $url        = rtrim($url, '|');
+        $url = $this->urlSuffixCheck($request, $url, $option);
+
+        if (is_null($match)) {
+            $match = $this->match($url, $option, $depr, $completeMatch);
         }
 
-        // 检查前置行为
-        if (isset($option['before']) && false === $this->checkBefore($option['before'])) {
-            return false;
+        if (false !== $match) {
+            return $this->parseRule($request, $this->rule, $this->route, $url, $option, $match);
+        }
+
+        return false;
+    }
+
+    /**
+     * 检测路由(含路由匹配)
+     * @access public
+     * @param  Request      $request  请求对象
+     * @param  string       $url      访问地址
+     * @param  string       $depr     路径分隔符
+     * @param  bool         $completeMatch   路由是否完全匹配
+     * @return Dispatch|false
+     */
+    public function check($request, $url, $depr = '/', $completeMatch = false)
+    {
+        return $this->checkRule($request, $url, null, $depr, $completeMatch);
+    }
+
+    /**
+     * URL后缀及Slash检查
+     * @access protected
+     * @param  Request      $request  请求对象
+     * @param  string       $url      访问地址
+     * @param  array        $option   路由参数
+     * @return string
+     */
+    protected function urlSuffixCheck($request, $url, $option = [])
+    {
+        // 是否区分 / 地址访问
+        if (!empty($option['remove_slash']) && '/' != $this->rule) {
+            $this->rule = rtrim($this->rule, '/');
+            $url        = rtrim($url, '|');
         }
 
         if (isset($option['ext'])) {
@@ -155,139 +243,69 @@ class RuleItem extends Rule
             $url = preg_replace('/\.(' . $request->ext() . ')$/i', '', $url);
         }
 
-        return $this->checkRule($request, $url, $depr, $completeMatch, $option);
+        return $url;
     }
 
     /**
-     * 检测路由规则
+     * 检测URL和规则路由是否匹配
      * @access private
-     * @param  Request   $request 请求对象
      * @param  string    $url URL地址
+     * @param  array     $option    路由参数
      * @param  string    $depr URL分隔符(全局)
      * @param  bool      $completeMatch   路由是否完全匹配
-     * @param  array     $option   路由参数
      * @return array|false
      */
-    private function checkRule($request, $url, $depr, $completeMatch = false, $option = [])
+    private function match($url, $option, $depr, $completeMatch)
     {
-        // 检查完整规则定义
-        if (isset($this->pattern['__url__']) && !preg_match(0 === strpos($this->pattern['__url__'], '/') ? $this->pattern['__url__'] : '/^' . $this->pattern['__url__'] . '/', str_replace('|', $depr, $url))) {
-            return false;
-        }
-
-        // 检查路由的参数分隔符
-        if (isset($option['param_depr'])) {
-            $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url);
+        if (isset($option['complete_match'])) {
+            $completeMatch = $option['complete_match'];
         }
 
-        $len1 = substr_count($url, '|');
-        $len2 = substr_count($this->name, '/');
+        $pattern = array_merge($this->parent->getPattern(), $this->pattern);
 
-        // 多余参数是否合并
-        $merge = !empty($option['merge_extra_vars']) ? true : false;
-
-        if ($merge && $len1 > $len2) {
-            $url = str_replace('|', $depr, $url);
-            $url = implode('|', explode($depr, $url, $len2 + 1));
+        // 检查完整规则定义
+        if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) {
+            return false;
         }
 
-        if (isset($option['complete_match'])) {
-            $completeMatch = $option['complete_match'];
+        $var  = [];
+        $url  = $depr . str_replace('|', $depr, $url);
+        $rule = $depr . str_replace('/', $depr, $this->rule);
+
+        if ($depr == $rule && $depr != $url) {
+            return false;
         }
 
-        if ($len1 >= $len2 || strpos($this->name, '[')) {
-            // 完整匹配
-            if ($completeMatch && (!$merge && $len1 != $len2 && (false === strpos($this->name, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($this->name, '[')))) {
-                return false;
+        if (false === strpos($rule, '<')) {
+            if (0 === strcasecmp($rule, $url) || (!$completeMatch && 0 === strncasecmp($rule, $url, strlen($rule)))) {
+                return $var;
             }
+            return false;
+        }
 
-            $pattern = array_merge($this->parent->getPattern(), $this->pattern);
+        $slash = preg_quote('/-' . $depr, '/');
 
-            if (false !== $match = $this->match($url, $pattern)) {
-                // 匹配到路由规则
-                return $this->parseRule($request, $this->name, $this->route, $url, $option, $match);
+        if ($matchRule = preg_split('/[' . $slash . ']?<\w+\??>/', $rule, 2)) {
+            if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) {
+                return false;
             }
         }
 
-        return false;
-    }
+        if (preg_match_all('/[' . $slash . ']?<?\w+\??>?/', $rule, $matches)) {
+            $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $completeMatch);
 
-    /**
-     * 检测URL和规则路由是否匹配
-     * @access private
-     * @param  string    $url URL地址
-     * @param  array     $pattern 变量规则
-     * @return array|false
-     */
-    private function match($url, $pattern)
-    {
-        $m2 = explode('/', $this->name);
-        $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 {
+            try {
+                if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/', $url, $match)) {
                     return false;
                 }
+            } catch (\Exception $e) {
+                throw new Exception('route pattern error');
             }
 
-            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;
-                    }
+            foreach ($match as $key => $val) {
+                if (is_string($key)) {
+                    $var[$key] = $val;
                 }
-
-                $var[$name] = isset($m1[$key]) ? $m1[$key] : '';
-            } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) {
-                return false;
             }
         }
 

+ 63 - 0
thinkphp/library/think/route/RuleName.php

@@ -0,0 +1,63 @@
+<?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\route;
+
+class RuleName
+{
+    protected $item = [];
+
+    /**
+     * 注册路由标识
+     * @access public
+     * @param  string   $name      路由标识
+     * @param  array    $value     路由规则
+     * @param  bool     $first     是否置顶
+     * @return void
+     */
+    public function set($name, $value, $first = false)
+    {
+        if ($first) {
+            array_unshift($this->item[$name], $value);
+        } else {
+            $this->item[$name][] = $value;
+        }
+    }
+
+    /**
+     * 导入路由标识
+     * @access public
+     * @param  array   $name      路由标识
+     * @return void
+     */
+    public function import($item)
+    {
+        $this->item = $item;
+    }
+
+    /**
+     * 根据路由标识获取路由信息(用于URL生成)
+     * @access public
+     * @param  string   $name      路由标识
+     * @return array|null
+     */
+    public function get($name = null)
+    {
+        if (is_null($name)) {
+            return $this->item;
+        }
+
+        $name = strtolower($name);
+
+        return isset($this->item[$name]) ? $this->item[$name] : null;
+    }
+
+}

+ 17 - 7
thinkphp/library/think/route/dispatch/Module.php

@@ -11,6 +11,7 @@
 
 namespace think\route\dispatch;
 
+use ReflectionMethod;
 use think\Container;
 use think\exception\ClassNotFoundException;
 use think\exception\HttpException;
@@ -54,7 +55,7 @@ class Module extends Dispatch
                 $this->app->init($module);
 
                 // 加载当前模块语言包
-                $this->app['lang']->load($this->app->getAppPath() . $module . '/lang/' . $this->app['request']->langset() . '.php');
+                $this->app['lang']->load($this->app->getAppPath() . $module . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->app['request']->langset() . '.php');
 
                 // 模块请求缓存检查
                 $this->app['request']->cache(
@@ -72,7 +73,7 @@ class Module extends Dispatch
         }
 
         // 当前模块路径
-        $this->app->setModulePath($this->app->getAppPath() . ($module ? $module . '/' : ''));
+        $this->app->setModulePath($this->app->getAppPath() . ($module ? $module . DIRECTORY_SEPARATOR : ''));
 
         // 是否自动转换控制器和操作名
         $convert = is_bool($this->convert) ? $this->convert : $this->app->config('app.url_convert');
@@ -82,10 +83,9 @@ class Module extends Dispatch
 
         // 获取操作名
         $actionName = strip_tags($result[2] ?: $this->app->config('app.default_action'));
-        $actionName = $convert ? strtolower($actionName) : $actionName;
 
         // 设置当前请求的控制器、操作
-        $this->app['request']->controller(Loader::parseName($controller, 1))->action($actionName);
+        $this->app['request']->controller(Loader::parseName($controller, 1));
 
         // 监听module_init
         $this->app['hook']->listen('module_init');
@@ -106,14 +106,24 @@ class Module extends Dispatch
         if (is_callable([$instance, $action])) {
             // 执行操作方法
             $call = [$instance, $action];
+
+            // 严格获取当前操作方法名
+            $reflect    = new ReflectionMethod($instance, $action);
+            $methodName = $reflect->getName();
+            $suffix     = $this->app->config('app.action_suffix');
+            $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName;
+            $this->app['request']->action($actionName);
+
             // 自动获取请求变量
             $vars = $this->app->config('app.url_param_type')
             ? $this->app['request']->route()
             : $this->app['request']->param();
         } elseif (is_callable([$instance, '_empty'])) {
             // 空操作
-            $call = [$instance, '_empty'];
-            $vars = [$actionName];
+            $this->app['request']->action($actionName);
+            $call    = [$instance, '_empty'];
+            $vars    = [$actionName];
+            $reflect = new ReflectionMethod($instance, '_empty');
         } else {
             // 操作不存在
             throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');
@@ -121,6 +131,6 @@ class Module extends Dispatch
 
         $this->app['hook']->listen('action_begin', $call);
 
-        return Container::getInstance()->invokeMethod($call, $vars);
+        return Container::getInstance()->invokeReflectMethod($instance, $reflect, $vars);
     }
 }

+ 1 - 1
vendor/autoload.php

@@ -4,4 +4,4 @@
 
 require_once __DIR__ . '/composer/autoload_real.php';
 
-return ComposerAutoloaderInit5b9bf45703a8bbf57b5d1b4f5a7493a8::getLoader();
+return ComposerAutoloaderInitcf980ce2ebc944e5d847ad4152559ece::getLoader();

+ 7 - 7
vendor/composer/autoload_real.php

@@ -2,7 +2,7 @@
 
 // autoload_real.php @generated by Composer
 
-class ComposerAutoloaderInit5b9bf45703a8bbf57b5d1b4f5a7493a8
+class ComposerAutoloaderInitcf980ce2ebc944e5d847ad4152559ece
 {
     private static $loader;
 
@@ -19,15 +19,15 @@ class ComposerAutoloaderInit5b9bf45703a8bbf57b5d1b4f5a7493a8
             return self::$loader;
         }
 
-        spl_autoload_register(array('ComposerAutoloaderInit5b9bf45703a8bbf57b5d1b4f5a7493a8', 'loadClassLoader'), true, true);
+        spl_autoload_register(array('ComposerAutoloaderInitcf980ce2ebc944e5d847ad4152559ece', 'loadClassLoader'), true, true);
         self::$loader = $loader = new \Composer\Autoload\ClassLoader();
-        spl_autoload_unregister(array('ComposerAutoloaderInit5b9bf45703a8bbf57b5d1b4f5a7493a8', 'loadClassLoader'));
+        spl_autoload_unregister(array('ComposerAutoloaderInitcf980ce2ebc944e5d847ad4152559ece', 'loadClassLoader'));
 
         $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
         if ($useStaticLoader) {
             require_once __DIR__ . '/autoload_static.php';
 
-            call_user_func(\Composer\Autoload\ComposerStaticInit5b9bf45703a8bbf57b5d1b4f5a7493a8::getInitializer($loader));
+            call_user_func(\Composer\Autoload\ComposerStaticInitcf980ce2ebc944e5d847ad4152559ece::getInitializer($loader));
         } else {
             $map = require __DIR__ . '/autoload_namespaces.php';
             foreach ($map as $namespace => $path) {
@@ -48,19 +48,19 @@ class ComposerAutoloaderInit5b9bf45703a8bbf57b5d1b4f5a7493a8
         $loader->register(true);
 
         if ($useStaticLoader) {
-            $includeFiles = Composer\Autoload\ComposerStaticInit5b9bf45703a8bbf57b5d1b4f5a7493a8::$files;
+            $includeFiles = Composer\Autoload\ComposerStaticInitcf980ce2ebc944e5d847ad4152559ece::$files;
         } else {
             $includeFiles = require __DIR__ . '/autoload_files.php';
         }
         foreach ($includeFiles as $fileIdentifier => $file) {
-            composerRequire5b9bf45703a8bbf57b5d1b4f5a7493a8($fileIdentifier, $file);
+            composerRequirecf980ce2ebc944e5d847ad4152559ece($fileIdentifier, $file);
         }
 
         return $loader;
     }
 }
 
-function composerRequire5b9bf45703a8bbf57b5d1b4f5a7493a8($fileIdentifier, $file)
+function composerRequirecf980ce2ebc944e5d847ad4152559ece($fileIdentifier, $file)
 {
     if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
         require $file;

+ 4 - 4
vendor/composer/autoload_static.php

@@ -4,7 +4,7 @@
 
 namespace Composer\Autoload;
 
-class ComposerStaticInit5b9bf45703a8bbf57b5d1b4f5a7493a8
+class ComposerStaticInitcf980ce2ebc944e5d847ad4152559ece
 {
     public static $files = array (
         '1cfd2761b63b0a29ed23657ea394cb2d' => __DIR__ . '/..' . '/topthink/think-captcha/src/helper.php',
@@ -257,9 +257,9 @@ class ComposerStaticInit5b9bf45703a8bbf57b5d1b4f5a7493a8
     public static function getInitializer(ClassLoader $loader)
     {
         return \Closure::bind(function () use ($loader) {
-            $loader->prefixLengthsPsr4 = ComposerStaticInit5b9bf45703a8bbf57b5d1b4f5a7493a8::$prefixLengthsPsr4;
-            $loader->prefixDirsPsr4 = ComposerStaticInit5b9bf45703a8bbf57b5d1b4f5a7493a8::$prefixDirsPsr4;
-            $loader->classMap = ComposerStaticInit5b9bf45703a8bbf57b5d1b4f5a7493a8::$classMap;
+            $loader->prefixLengthsPsr4 = ComposerStaticInitcf980ce2ebc944e5d847ad4152559ece::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInitcf980ce2ebc944e5d847ad4152559ece::$prefixDirsPsr4;
+            $loader->classMap = ComposerStaticInitcf980ce2ebc944e5d847ad4152559ece::$classMap;
 
         }, null, ClassLoader::class);
     }

+ 6 - 6
vendor/composer/installed.json

@@ -175,17 +175,17 @@
     },
     {
         "name": "topthink/framework",
-        "version": "v5.1.5",
-        "version_normalized": "5.1.5.0",
+        "version": "5.1.6",
+        "version_normalized": "5.1.6.0",
         "source": {
             "type": "git",
             "url": "https://github.com/top-think/framework.git",
-            "reference": "f81c4282cdf401be44fbe1a28b3e3df425e3017f"
+            "reference": "98aaf52dd364af7fdecc7214224678d180676b4f"
         },
         "dist": {
             "type": "zip",
-            "url": "https://files.phpcomposer.com/files/top-think/framework/f81c4282cdf401be44fbe1a28b3e3df425e3017f.zip",
-            "reference": "f81c4282cdf401be44fbe1a28b3e3df425e3017f",
+            "url": "https://files.phpcomposer.com/files/top-think/framework/98aaf52dd364af7fdecc7214224678d180676b4f.zip",
+            "reference": "98aaf52dd364af7fdecc7214224678d180676b4f",
             "shasum": ""
         },
         "require": {
@@ -201,7 +201,7 @@
             "sebastian/phpcpd": "2.*",
             "squizlabs/php_codesniffer": "2.*"
         },
-        "time": "2018-02-02T05:39:38+00:00",
+        "time": "2018-03-26T07:10:00+00:00",
         "type": "think-framework",
         "installation-source": "dist",
         "notification-url": "https://packagist.org/downloads/",