Ethan 6 лет назад
Родитель
Сommit
53d9ebab75
47 измененных файлов с 1351 добавлено и 575 удалено
  1. 4 4
      application/home/view/about/index.html
  2. 1 1
      application/home/view/index/index.html
  3. BIN
      public/static/home/images/trust1.png
  4. BIN
      public/static/home/images/trust2.png
  5. BIN
      public/static/home/images/trust3.png
  6. 0 6
      public/static/home/js/main.js
  7. 1 1
      thinkphp/base.php
  8. 2 0
      thinkphp/convention.php
  9. 0 6
      thinkphp/helper.php
  10. 2 0
      thinkphp/lang/zh-cn.php
  11. 14 2
      thinkphp/library/think/App.php
  12. 12 2
      thinkphp/library/think/Collection.php
  13. 0 7
      thinkphp/library/think/Controller.php
  14. 22 21
      thinkphp/library/think/Loader.php
  15. 2 2
      thinkphp/library/think/Log.php
  16. 47 7
      thinkphp/library/think/Model.php
  17. 9 1
      thinkphp/library/think/Paginator.php
  18. 129 87
      thinkphp/library/think/Request.php
  19. 1 3
      thinkphp/library/think/Response.php
  20. 177 135
      thinkphp/library/think/Route.php
  21. 40 4
      thinkphp/library/think/Validate.php
  22. 1 1
      thinkphp/library/think/cache/driver/Memcache.php
  23. 1 1
      thinkphp/library/think/cache/driver/Redis.php
  24. 11 2
      thinkphp/library/think/console/command/Clear.php
  25. 66 44
      thinkphp/library/think/db/Builder.php
  26. 40 35
      thinkphp/library/think/db/Connection.php
  27. 48 0
      thinkphp/library/think/db/Expression.php
  28. 235 55
      thinkphp/library/think/db/Query.php
  29. 14 4
      thinkphp/library/think/db/builder/Mysql.php
  30. 8 2
      thinkphp/library/think/db/builder/Pgsql.php
  31. 8 2
      thinkphp/library/think/db/builder/Sqlite.php
  32. 32 18
      thinkphp/library/think/db/builder/Sqlsrv.php
  33. 4 1
      thinkphp/library/think/db/connector/Sqlsrv.php
  34. 190 66
      thinkphp/library/think/log/driver/File.php
  35. 1 1
      thinkphp/library/think/log/driver/Socket.php
  36. 68 8
      thinkphp/library/think/model/relation/BelongsToMany.php
  37. 28 10
      thinkphp/library/think/model/relation/HasMany.php
  38. 12 0
      thinkphp/library/think/model/relation/HasManyThrough.php
  39. 34 6
      thinkphp/library/think/model/relation/MorphMany.php
  40. 37 4
      thinkphp/library/think/model/relation/MorphOne.php
  41. 12 1
      thinkphp/library/think/model/relation/MorphTo.php
  42. 11 0
      thinkphp/library/think/model/relation/OneToOne.php
  43. 4 1
      thinkphp/library/think/template/driver/File.php
  44. 13 17
      thinkphp/library/think/view/driver/Php.php
  45. 3 1
      thinkphp/library/think/view/driver/Think.php
  46. 2 1
      thinkphp/library/traits/model/SoftDelete.php
  47. 5 5
      thinkphp/tpl/think_exception.tpl

+ 4 - 4
application/home/view/about/index.html

@@ -254,15 +254,15 @@ End #Portfolio
         <div class="clearfix" style="display: flex; justify-content: center;">
 
             <div class="col-md-4" style="margin-top: 20px">
-                <img class="img-responsive" src="{$Think.HOME_SITE_ROOT}/images/team4.png" alt="Portfolio Item"  style="width: 48%; float: left">
-                </div>
+                <img class="img-responsive" src="{$Think.HOME_SITE_ROOT}/images/trust1.png" alt="Portfolio Item">
+            </div>
 
             <div class="col-md-4" style="margin-top: 20px">
-                <img class="img-responsive" src="{$Think.HOME_SITE_ROOT}/images/team6.png" alt="Portfolio Item">
+                <img class="img-responsive" src="{$Think.HOME_SITE_ROOT}/images/trust2.png" alt="Portfolio Item">
             </div>
 
             <div class="col-md-4" style="margin-top: 20px">
-                <img class="img-responsive" src="{$Think.HOME_SITE_ROOT}/images/team7.png" alt="Portfolio Item">
+                <img class="img-responsive" src="{$Think.HOME_SITE_ROOT}/images/trust3.png" alt="Portfolio Item">
             </div>
 
         </div>

+ 1 - 1
application/home/view/index/index.html

@@ -798,7 +798,7 @@ End #contact
             <div class="col-md-3 col-sm-12">
                 <div class="footer-social wow fadeInUp">
                     <h4 style="text-align: left;">站点导航</h4>
-                    <ul id="navFoot" class="text-center list-inline">
+                    <ul class="text-center list-inline">
                         {foreach name="navigation" item="value" key="key"}
                             {if $key !== 0}
                                 <li style="width: 45%;">

BIN
public/static/home/images/trust1.png


BIN
public/static/home/images/trust2.png


BIN
public/static/home/images/trust3.png


+ 0 - 6
public/static/home/js/main.js

@@ -32,12 +32,6 @@ $(function(){
         scrollThreshold: 1
     });
 
-    $('#navFoot').onePageNav({
-        filter: ':not(.external)',
-        scrollSpeed: 950,
-        scrollThreshold: 1
-    });
-
     /* Slider Height
     var slideHeight = $(window).height();
     $('#home-carousel .carousel-inner .item, #home-carousel .video-container').css('height',slideHeight);

+ 1 - 1
thinkphp/base.php

@@ -9,7 +9,7 @@
 // | Author: liu21st <liu21st@gmail.com>
 // +----------------------------------------------------------------------
 
-define('THINK_VERSION', '5.0.16');
+define('THINK_VERSION', '5.0.24');
 define('THINK_START_TIME', microtime(true));
 define('THINK_START_MEM', memory_get_usage());
 define('EXT', '.php');

+ 2 - 0
thinkphp/convention.php

@@ -116,6 +116,8 @@ return [
     // +----------------------------------------------------------------------
 
     'template'               => [
+        // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写
+        'auto_rule'    => 1,
         // 模板引擎类型 支持 php think 支持扩展
         'type'         => 'Think',
         // 视图基础目录,配置目录为所有模块的视图起始目录

+ 0 - 6
thinkphp/helper.php

@@ -443,12 +443,6 @@ if (!function_exists('view')) {
      */
     function view($template = '', $vars = [], $replace = [], $code = 200)
     {
-        if ('' === $template) {
-            $trace    = debug_backtrace(false, 2);
-            $suffix   = Config::get('action_suffix');
-            $action   = $suffix ? substr($trace[1]['function'], 0, -strlen($suffix)) : $trace[1]['function'];
-            $template = Loader::parseName($action);
-        }
         return Response::create($template, 'view', $code)->replace($replace)->assign($vars);
     }
 }

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

@@ -48,6 +48,7 @@ return [
     'KVDB init error'                                           => '没有初始化KVDB,请在SAE管理平台初始化KVDB服务',
     'fields not exists'                                         => '数据表字段不存在',
     'where express error'                                       => '查询表达式错误',
+    'not support data'                                          => '不支持的数据表达式',
     'no data to update'                                         => '没有任何数据需要更新',
     'miss data to insert'                                       => '缺少需要写入的数据',
     'miss complex primary data'                                 => '缺少复合主键数据',
@@ -66,6 +67,7 @@ return [
     'relation data not exists'                                  => '关联数据不存在',
     'relation not support'                                      => '关联不支持',
     'chunk not support order'                                   => 'Chunk不支持调用order方法',
+    'closure not support cache(true)'                           => '使用闭包查询不支持cache(true),请指定缓存Key',
 
     // 上传错误信息
     'unknown upload error'                                      => '未知上传错误!',

+ 14 - 2
thinkphp/library/think/App.php

@@ -551,15 +551,20 @@ class App
 
         // 获取控制器名
         $controller = strip_tags($result[1] ?: $config['default_controller']);
+
         if (!preg_match('/^[A-Za-z](\w|\.)*$/', $controller)) {
             throw new HttpException(404, 'controller not exists:' . $controller);
-
         }
+
         $controller = $convert ? strtolower($controller) : $controller;
 
         // 获取操作名
         $actionName = strip_tags($result[2] ?: $config['default_action']);
-        $actionName = $convert ? strtolower($actionName) : $actionName;
+        if (!empty($config['action_convert'])) {
+            $actionName = Loader::parseName($actionName, 1);
+        } else {
+            $actionName = $convert ? strtolower($actionName) : $actionName;
+        }
 
         // 设置当前请求的控制器、操作
         $request->controller(Loader::parseName($controller, 1))->action($actionName);
@@ -585,6 +590,13 @@ class App
         if (is_callable([$instance, $action])) {
             // 执行操作方法
             $call = [$instance, $action];
+            // 严格获取当前操作方法名
+            $reflect    = new \ReflectionMethod($instance, $action);
+            $methodName = $reflect->getName();
+            $suffix     = $config['action_suffix'];
+            $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName;
+            $request->action($actionName);
+
         } elseif (is_callable([$instance, '_empty'])) {
             // 空操作
             $call = [$instance, '_empty'];

+ 12 - 2
thinkphp/library/think/Collection.php

@@ -99,6 +99,16 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
         return new static(array_keys($this->items));
     }
 
+    /**
+     * 返回数组中所有的值组成的新 Collection 实例
+     * @access public
+     * @return static
+     */
+    public function values()
+    {
+        return new static(array_values($this->items));
+    }
+
     /**
      * 合并数组并返回一个新的 Collection 实例
      * @access public
@@ -273,7 +283,7 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
 
         $result = [];
         foreach ($this->items as $row) {
-            $key    = $value    = null;
+            $key    = $value = null;
             $keySet = $valueSet = false;
 
             if (null !== $indexKey && array_key_exists($indexKey, $row)) {
@@ -309,7 +319,7 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
      */
     public function sort(callable $callback = null)
     {
-        $items = $this->items;
+        $items    = $this->items;
         $callback = $callback ?: function ($a, $b) {
             return $a == $b ? 0 : (($a < $b) ? -1 : 1);
         };

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

@@ -117,13 +117,6 @@ class Controller
      */
     protected function fetch($template = '', $vars = [], $replace = [], $config = [])
     {
-        if ('' === $template) {
-            $trace    = debug_backtrace(false, 2);
-            $suffix   = Config::get('action_suffix');
-            $action   = $suffix ? substr($trace[1]['function'], 0, -strlen($suffix)) : $trace[1]['function'];
-            $template = Loader::parseName($action);
-        }
-
         return $this->view->fetch($template, $vars, $replace, $config);
     }
 

+ 22 - 21
thinkphp/library/think/Loader.php

@@ -23,7 +23,7 @@ class Loader
     /**
      * @var array 类名映射
      */
-    protected static $map = [];
+    protected static $classMap = [];
 
     /**
      * @var array 命名空间别名
@@ -56,9 +56,9 @@ class Loader
     private static $fallbackDirsPsr0 = [];
 
     /**
-     * @var array 自动加载的文件
+     * @var array 需要加载的文件
      */
-    private static $autoloadFiles = [];
+    private static $files = [];
 
     /**
      * 自动加载
@@ -99,8 +99,8 @@ class Loader
     private static function findFile($class)
     {
         // 类库映射
-        if (!empty(self::$map[$class])) {
-            return self::$map[$class];
+        if (!empty(self::$classMap[$class])) {
+            return self::$classMap[$class];
         }
 
         // 查找 PSR-4
@@ -156,7 +156,7 @@ class Loader
         }
 
         // 找不到则设置映射为 false 并返回
-        return self::$map[$class] = false;
+        return self::$classMap[$class] = false;
     }
 
     /**
@@ -169,9 +169,9 @@ class Loader
     public static function addClassMap($class, $map = '')
     {
         if (is_array($class)) {
-            self::$map = array_merge(self::$map, $class);
+            self::$classMap = array_merge(self::$classMap, $class);
         } else {
-            self::$map[$class] = $map;
+            self::$classMap[$class] = $map;
         }
     }
 
@@ -292,12 +292,11 @@ class Loader
                 $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 : [];
+                foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) {
+                    if (property_exists($composerClass, $attr)) {
+                        self::${$attr} = $composerClass::${$attr};
+                    }
+                }
             } else {
                 self::registerComposerLoader();
             }
@@ -348,18 +347,20 @@ class Loader
                 self::addClassMap($classMap);
             }
         }
+
+        if (is_file(VENDOR_PATH . 'composer/autoload_files.php')) {
+            self::$files = require VENDOR_PATH . 'composer/autoload_files.php';
+        }
     }
 
     // 加载composer autofile文件
     public static function loadComposerAutoloadFiles()
     {
-        if (is_file(VENDOR_PATH . 'composer/autoload_files.php')) {
-            $includeFiles = require VENDOR_PATH . 'composer/autoload_files.php';
-            foreach ($includeFiles as $fileIdentifier => $file) {
-                if (empty(self::$autoloadFiles[$fileIdentifier])) {
-                    __require_file($file);
-                    self::$autoloadFiles[$fileIdentifier] = true;
-                }
+        foreach (self::$files as $fileIdentifier => $file) {
+            if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+                __require_file($file);
+
+                $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
             }
         }
     }

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

@@ -176,7 +176,7 @@ class Log
             }
         }
 
-        if ($result = self::$driver->save($log)) {
+        if ($result = self::$driver->save($log, true)) {
             self::$log = [];
         }
 
@@ -211,7 +211,7 @@ class Log
         is_null(self::$driver) && self::init(Config::get('log'));
 
         // 写入日志
-        if ($result = self::$driver->save($log)) {
+        if ($result = self::$driver->save($log, false)) {
             self::$log = [];
         }
 

+ 47 - 7
thinkphp/library/think/Model.php

@@ -94,6 +94,8 @@ abstract class Model implements \JsonSerializable, \ArrayAccess
     protected $type = [];
     // 是否为更新数据
     protected $isUpdate = false;
+    // 是否使用Replace
+    protected $replace = false;
     // 是否强制更新所有数据
     protected $force = false;
     // 更新条件
@@ -116,6 +118,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess
      */
     protected static $initialized = [];
 
+    /**
+     * 是否从主库读取(主从分布式有效)
+     * @var array
+     */
+    protected static $readMaster;
+
     /**
      * 构造方法
      * @access public
@@ -171,6 +179,20 @@ abstract class Model implements \JsonSerializable, \ArrayAccess
         $this->initialize();
     }
 
+    /**
+     * 是否从主库读取数据(主从分布有效)
+     * @access public
+     * @param  bool     $all 是否所有模型生效
+     * @return $this
+     */
+    public function readMaster($all = false)
+    {
+        $model = $all ? '*' : $this->class;
+
+        static::$readMaster[$model] = true;
+        return $this;
+    }
+
     /**
      * 创建模型的查询对象
      * @access protected
@@ -194,6 +216,10 @@ abstract class Model implements \JsonSerializable, \ArrayAccess
         $queryClass = $this->query ?: $con->getConfig('query');
         $query      = new $queryClass($con, $this);
 
+        if (isset(static::$readMaster['*']) || isset(static::$readMaster[$this->class])) {
+            $query->master(true);
+        }
+
         // 设置当前数据表和模型名
         if (!empty($this->table)) {
             $query->setTable($this->table);
@@ -989,6 +1015,18 @@ abstract class Model implements \JsonSerializable, \ArrayAccess
         return false;
     }
 
+    /**
+     * 新增数据是否使用Replace
+     * @access public
+     * @param  bool $replace
+     * @return $this
+     */
+    public function replace($replace = true)
+    {
+        $this->replace = $replace;
+        return $this;
+    }
+
     /**
      * 保存当前数据对象
      * @access public
@@ -1004,19 +1042,21 @@ abstract class Model implements \JsonSerializable, \ArrayAccess
             $data     = [];
         }
 
+        // 数据自动验证
         if (!empty($data)) {
-            // 数据自动验证
             if (!$this->validateData($data)) {
                 return false;
             }
+
             // 数据对象赋值
             foreach ($data as $key => $value) {
                 $this->setAttr($key, $value, $data);
             }
-            if (!empty($where)) {
-                $this->isUpdate    = true;
-                $this->updateWhere = $where;
-            }
+        }
+
+        if (!empty($where)) {
+            $this->isUpdate    = true;
+            $this->updateWhere = $where;
         }
 
         // 自动关联写入
@@ -1139,9 +1179,9 @@ abstract class Model implements \JsonSerializable, \ArrayAccess
             // 检测字段
             $allowFields = $this->checkAllowField(array_merge($this->auto, $this->insert));
             if (!empty($allowFields)) {
-                $result = $this->getQuery()->strict(false)->field($allowFields)->insert($this->data, false, false, $sequence);
+                $result = $this->getQuery()->strict(false)->field($allowFields)->insert($this->data, $this->replace, false, $sequence);
             } else {
-                $result = $this->getQuery()->insert($this->data, false, false, $sequence);
+                $result = $this->getQuery()->insert($this->data, $this->replace, false, $sequence);
             }
 
             // 获取自动增长主键

+ 9 - 1
thinkphp/library/think/Paginator.php

@@ -395,7 +395,15 @@ abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, J
 
     public function __call($name, $arguments)
     {
-        return call_user_func_array([$this->getCollection(), $name], $arguments);
+        $collection = $this->getCollection();
+
+        $result = call_user_func_array([$collection, $name], $arguments);
+
+        if ($result === $collection) {
+            return $this;
+        }
+
+        return $result;
     }
 
 }

+ 129 - 87
thinkphp/library/think/Request.php

@@ -121,6 +121,11 @@ class Request
     protected $cache;
     // 缓存是否检查
     protected $isCheckCache;
+    /**
+     * 是否合并Param
+     * @var bool
+     */
+    protected $mergeParam = false;
 
     /**
      * 构造函数
@@ -155,8 +160,8 @@ class Request
     /**
      * Hook 方法注入
      * @access public
-     * @param string|array  $method 方法名
-     * @param mixed         $callback callable
+     * @param string|array $method   方法名
+     * @param mixed        $callback callable
      * @return void
      */
     public static function hook($method, $callback = null)
@@ -182,16 +187,28 @@ class Request
         return self::$instance;
     }
 
+    /**
+     * 销毁当前请求对象
+     * @access public
+     * @return void
+     */
+    public static function destroy()
+    {
+        if (!is_null(self::$instance)) {
+            self::$instance = null;
+        }
+    }
+
     /**
      * 创建一个URL请求
      * @access public
-     * @param string    $uri URL地址
-     * @param string    $method 请求类型
-     * @param array     $params 请求参数
-     * @param array     $cookie
-     * @param array     $files
-     * @param array     $server
-     * @param string    $content
+     * @param string $uri    URL地址
+     * @param string $method 请求类型
+     * @param array  $params 请求参数
+     * @param array  $cookie
+     * @param array  $files
+     * @param array  $server
+     * @param string $content
      * @return \think\Request
      */
     public static function create($uri, $method = 'GET', $params = [], $cookie = [], $files = [], $server = [], $content = null)
@@ -232,7 +249,7 @@ class Request
             parse_str(html_entity_decode($info['query']), $query);
             if (!empty($params)) {
                 $params      = array_replace($query, $params);
-                $queryString = http_build_query($query, '', '&');
+                $queryString = http_build_query($params, '', '&');
             } else {
                 $params      = $query;
                 $queryString = $info['query'];
@@ -479,8 +496,8 @@ class Request
     /**
      * 设置资源类型
      * @access public
-     * @param string|array  $type 资源类型名
-     * @param string        $val 资源类型
+     * @param string|array $type 资源类型名
+     * @param string       $val  资源类型
      * @return void
      */
     public function mimeType($type, $val = '')
@@ -495,22 +512,28 @@ class Request
     /**
      * 当前的请求类型
      * @access public
-     * @param bool $method  true 获取原始请求类型
+     * @param bool $method true 获取原始请求类型
      * @return string
      */
     public function method($method = false)
     {
         if (true === $method) {
             // 获取原始请求类型
-            return IS_CLI ? 'GET' : (isset($this->server['REQUEST_METHOD']) ? $this->server['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']);
+            return $this->server('REQUEST_METHOD') ?: 'GET';
         } elseif (!$this->method) {
             if (isset($_POST[Config::get('var_method')])) {
-                $this->method = strtoupper($_POST[Config::get('var_method')]);
-                $this->{$this->method}($_POST);
+                $method = strtoupper($_POST[Config::get('var_method')]);
+                if (in_array($method, ['GET', 'POST', 'DELETE', 'PUT', 'PATCH'])) {
+                    $this->method = $method;
+                    $this->{$this->method}($_POST);
+                } else {
+                    $this->method = 'POST';
+                }
+                unset($_POST[Config::get('var_method')]);
             } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
                 $this->method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
             } else {
-                $this->method = IS_CLI ? 'GET' : (isset($this->server['REQUEST_METHOD']) ? $this->server['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']);
+                $this->method = $this->server('REQUEST_METHOD') ?: 'GET';
             }
         }
         return $this->method;
@@ -609,14 +632,14 @@ class Request
     /**
      * 获取当前请求的参数
      * @access public
-     * @param string|array  $name 变量名
-     * @param mixed         $default 默认值
-     * @param string|array  $filter 过滤方法
+     * @param string|array $name    变量名
+     * @param mixed        $default 默认值
+     * @param string|array $filter  过滤方法
      * @return mixed
      */
     public function param($name = '', $default = null, $filter = '')
     {
-        if (empty($this->param)) {
+        if (empty($this->mergeParam)) {
             $method = $this->method(true);
             // 自动获取请求变量
             switch ($method) {
@@ -632,7 +655,8 @@ class Request
                     $vars = [];
             }
             // 当前请求参数和URL地址中的参数合并
-            $this->param = array_merge($this->get(false), $vars, $this->route(false));
+            $this->param      = array_merge($this->param, $this->get(false), $vars, $this->route(false));
+            $this->mergeParam = true;
         }
         if (true === $name) {
             // 获取包含文件上传信息的数组
@@ -646,15 +670,16 @@ class Request
     /**
      * 设置获取路由参数
      * @access public
-     * @param string|array  $name 变量名
-     * @param mixed         $default 默认值
-     * @param string|array  $filter 过滤方法
+     * @param string|array $name    变量名
+     * @param mixed        $default 默认值
+     * @param string|array $filter  过滤方法
      * @return mixed
      */
     public function route($name = '', $default = null, $filter = '')
     {
         if (is_array($name)) {
             $this->param        = [];
+            $this->mergeParam   = false;
             return $this->route = array_merge($this->route, $name);
         }
         return $this->input($this->route, $name, $default, $filter);
@@ -663,9 +688,9 @@ class Request
     /**
      * 设置获取GET参数
      * @access public
-     * @param string|array  $name 变量名
-     * @param mixed         $default 默认值
-     * @param string|array  $filter 过滤方法
+     * @param string|array $name    变量名
+     * @param mixed        $default 默认值
+     * @param string|array $filter  过滤方法
      * @return mixed
      */
     public function get($name = '', $default = null, $filter = '')
@@ -675,6 +700,7 @@ class Request
         }
         if (is_array($name)) {
             $this->param      = [];
+            $this->mergeParam = false;
             return $this->get = array_merge($this->get, $name);
         }
         return $this->input($this->get, $name, $default, $filter);
@@ -683,9 +709,9 @@ class Request
     /**
      * 设置获取POST参数
      * @access public
-     * @param string        $name 变量名
-     * @param mixed         $default 默认值
-     * @param string|array  $filter 过滤方法
+     * @param string       $name    变量名
+     * @param mixed        $default 默认值
+     * @param string|array $filter  过滤方法
      * @return mixed
      */
     public function post($name = '', $default = null, $filter = '')
@@ -700,6 +726,7 @@ class Request
         }
         if (is_array($name)) {
             $this->param       = [];
+            $this->mergeParam  = false;
             return $this->post = array_merge($this->post, $name);
         }
         return $this->input($this->post, $name, $default, $filter);
@@ -708,9 +735,9 @@ class Request
     /**
      * 设置获取PUT参数
      * @access public
-     * @param string|array      $name 变量名
-     * @param mixed             $default 默认值
-     * @param string|array      $filter 过滤方法
+     * @param string|array $name    变量名
+     * @param mixed        $default 默认值
+     * @param string|array $filter  过滤方法
      * @return mixed
      */
     public function put($name = '', $default = null, $filter = '')
@@ -725,6 +752,7 @@ class Request
         }
         if (is_array($name)) {
             $this->param      = [];
+            $this->mergeParam = false;
             return $this->put = is_null($this->put) ? $name : array_merge($this->put, $name);
         }
 
@@ -734,9 +762,9 @@ class Request
     /**
      * 设置获取DELETE参数
      * @access public
-     * @param string|array      $name 变量名
-     * @param mixed             $default 默认值
-     * @param string|array      $filter 过滤方法
+     * @param string|array $name    变量名
+     * @param mixed        $default 默认值
+     * @param string|array $filter  过滤方法
      * @return mixed
      */
     public function delete($name = '', $default = null, $filter = '')
@@ -747,9 +775,9 @@ class Request
     /**
      * 设置获取PATCH参数
      * @access public
-     * @param string|array      $name 变量名
-     * @param mixed             $default 默认值
-     * @param string|array      $filter 过滤方法
+     * @param string|array $name    变量名
+     * @param mixed        $default 默认值
+     * @param string|array $filter  过滤方法
      * @return mixed
      */
     public function patch($name = '', $default = null, $filter = '')
@@ -759,9 +787,9 @@ class Request
 
     /**
      * 获取request变量
-     * @param string        $name 数据名称
-     * @param string        $default 默认值
-     * @param string|array  $filter 过滤方法
+     * @param string       $name    数据名称
+     * @param string       $default 默认值
+     * @param string|array $filter  过滤方法
      * @return mixed
      */
     public function request($name = '', $default = null, $filter = '')
@@ -771,6 +799,7 @@ class Request
         }
         if (is_array($name)) {
             $this->param          = [];
+            $this->mergeParam     = false;
             return $this->request = array_merge($this->request, $name);
         }
         return $this->input($this->request, $name, $default, $filter);
@@ -779,9 +808,9 @@ class Request
     /**
      * 获取session数据
      * @access public
-     * @param string|array  $name 数据名称
-     * @param string        $default 默认值
-     * @param string|array  $filter 过滤方法
+     * @param string|array $name    数据名称
+     * @param string       $default 默认值
+     * @param string|array $filter  过滤方法
      * @return mixed
      */
     public function session($name = '', $default = null, $filter = '')
@@ -798,9 +827,9 @@ class Request
     /**
      * 获取cookie参数
      * @access public
-     * @param string|array  $name 数据名称
-     * @param string        $default 默认值
-     * @param string|array  $filter 过滤方法
+     * @param string|array $name    数据名称
+     * @param string       $default 默认值
+     * @param string|array $filter  过滤方法
      * @return mixed
      */
     public function cookie($name = '', $default = null, $filter = '')
@@ -831,9 +860,9 @@ class Request
     /**
      * 获取server参数
      * @access public
-     * @param string|array  $name 数据名称
-     * @param string        $default 默认值
-     * @param string|array  $filter 过滤方法
+     * @param string|array $name    数据名称
+     * @param string       $default 默认值
+     * @param string|array $filter  过滤方法
      * @return mixed
      */
     public function server($name = '', $default = null, $filter = '')
@@ -909,9 +938,9 @@ class Request
 
     /**
      * 获取环境变量
-     * @param string|array  $name 数据名称
-     * @param string        $default 默认值
-     * @param string|array  $filter 过滤方法
+     * @param string|array $name    数据名称
+     * @param string       $default 默认值
+     * @param string|array $filter  过滤方法
      * @return mixed
      */
     public function env($name = '', $default = null, $filter = '')
@@ -928,8 +957,8 @@ class Request
     /**
      * 设置或者获取当前的Header
      * @access public
-     * @param string|array  $name header名称
-     * @param string        $default 默认值
+     * @param string|array $name    header名称
+     * @param string       $default 默认值
      * @return string
      */
     public function header($name = '', $default = null)
@@ -967,10 +996,10 @@ class Request
 
     /**
      * 获取变量 支持过滤和默认值
-     * @param array         $data 数据源
-     * @param string|false  $name 字段名
-     * @param mixed         $default 默认值
-     * @param string|array  $filter 过滤函数
+     * @param array        $data    数据源
+     * @param string|false $name    字段名
+     * @param mixed        $default 默认值
+     * @param string|array $filter  过滤函数
      * @return mixed
      */
     public function input($data = [], $name = '', $default = null, $filter = '')
@@ -1051,9 +1080,9 @@ class Request
 
     /**
      * 递归过滤给定的值
-     * @param mixed     $value 键值
-     * @param mixed     $key 键名
-     * @param array     $filters 过滤方法+默认值
+     * @param mixed $value   键值
+     * @param mixed $key     键名
+     * @param array $filters 过滤方法+默认值
      * @return mixed
      */
     private function filterValue(&$value, $key, $filters)
@@ -1093,7 +1122,7 @@ class Request
     public function filterExp(&$value)
     {
         // 过滤查询特殊字符
-        if (is_string($value) && preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT LIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i', $value)) {
+        if (is_string($value) && preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT LIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOT EXISTS|NOTEXISTS|EXISTS|NOT NULL|NOTNULL|NULL|BETWEEN TIME|NOT BETWEEN TIME|NOTBETWEEN TIME|NOTIN|NOT IN|IN)$/i', $value)) {
             $value .= ' ';
         }
         // TODO 其他安全过滤
@@ -1138,9 +1167,9 @@ class Request
     /**
      * 是否存在某个请求参数
      * @access public
-     * @param string    $name 变量名
-     * @param string    $type 变量类型
-     * @param bool      $checkEmpty 是否检测空值
+     * @param string $name       变量名
+     * @param string $type       变量类型
+     * @param bool   $checkEmpty 是否检测空值
      * @return mixed
      */
     public function has($name, $type = 'param', $checkEmpty = false)
@@ -1164,8 +1193,8 @@ class Request
     /**
      * 获取指定的参数
      * @access public
-     * @param string|array  $name 变量名
-     * @param string        $type 变量类型
+     * @param string|array $name 变量名
+     * @param string       $type 变量类型
      * @return mixed
      */
     public function only($name, $type = 'param')
@@ -1186,8 +1215,8 @@ class Request
     /**
      * 排除指定参数获取
      * @access public
-     * @param string|array  $name 变量名
-     * @param string        $type 变量类型
+     * @param string|array $name 变量名
+     * @param string       $type 变量类型
      * @return mixed
      */
     public function except($name, $type = 'param')
@@ -1229,7 +1258,7 @@ class Request
     /**
      * 当前是否Ajax请求
      * @access public
-     * @param bool $ajax  true 获取原始ajax请求
+     * @param bool $ajax true 获取原始ajax请求
      * @return bool
      */
     public function isAjax($ajax = false)
@@ -1239,14 +1268,16 @@ class Request
         if (true === $ajax) {
             return $result;
         } else {
-            return $this->param(Config::get('var_ajax')) ? true : $result;
+            $result           = $this->param(Config::get('var_ajax')) ? true : $result;
+            $this->mergeParam = false;
+            return $result;
         }
     }
 
     /**
      * 当前是否Pjax请求
      * @access public
-     * @param bool $pjax  true 获取原始pjax请求
+     * @param bool $pjax true 获取原始pjax请求
      * @return bool
      */
     public function isPjax($pjax = false)
@@ -1255,14 +1286,16 @@ class Request
         if (true === $pjax) {
             return $result;
         } else {
-            return $this->param(Config::get('var_pjax')) ? true : $result;
+            $result           = $this->param(Config::get('var_pjax')) ? true : $result;
+            $this->mergeParam = false;
+            return $result;
         }
     }
 
     /**
      * 获取客户端IP地址
-     * @param integer   $type 返回类型 0 返回IP地址 1 返回IPV4地址数字
-     * @param boolean   $adv 是否进行高级模式获取(有可能被伪装)
+     * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字
+     * @param boolean $adv  是否进行高级模式获取(有可能被伪装)
      * @return mixed
      */
     public function ip($type = 0, $adv = true)
@@ -1273,7 +1306,11 @@ class Request
             return $ip[$type];
         }
 
-        if ($adv) {
+        $httpAgentIp = Config::get('http_agent_ip');
+
+        if ($httpAgentIp && isset($_SERVER[$httpAgentIp])) {
+            $ip = $_SERVER[$httpAgentIp];
+        } elseif ($adv) {
             if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
                 $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
                 $pos = array_search('unknown', $arr);
@@ -1338,14 +1375,18 @@ class Request
     /**
      * 当前请求的host
      * @access public
+     * @param bool $strict true 仅仅获取HOST
      * @return string
      */
-    public function host()
+    public function host($strict = false)
     {
         if (isset($_SERVER['HTTP_X_REAL_HOST'])) {
-            return $_SERVER['HTTP_X_REAL_HOST'];
+            $host = $_SERVER['HTTP_X_REAL_HOST'];
+        } else {
+            $host = $this->server('HTTP_HOST');
         }
-        return $this->server('HTTP_HOST');
+
+        return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host;
     }
 
     /**
@@ -1415,7 +1456,7 @@ class Request
     /**
      * 设置或者获取当前请求的调度信息
      * @access public
-     * @param array  $dispatch 调度信息
+     * @param array $dispatch 调度信息
      * @return array
      */
     public function dispatch($dispatch = null)
@@ -1466,11 +1507,12 @@ 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);
         }
     }
 
@@ -1534,7 +1576,7 @@ class Request
     /**
      * 设置当前地址的请求缓存
      * @access public
-     * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id
+     * @param string $key    缓存标识,支持变量规则 ,例如 item/:name/:id
      * @param mixed  $expire 缓存有效期
      * @param array  $except 缓存排除
      * @param string $tag    缓存标签
@@ -1619,7 +1661,7 @@ class Request
      * 设置当前请求绑定的对象实例
      * @access public
      * @param string|array $name 绑定的对象标识
-     * @param mixed  $obj 绑定的对象实例
+     * @param mixed        $obj  绑定的对象实例
      * @return mixed
      */
     public function bind($name, $obj = null)

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

@@ -69,9 +69,7 @@ class Response
      */
     public static function create($data = '', $type = '', $code = 200, array $header = [], $options = [])
     {
-        $type = empty($type) ? 'null' : strtolower($type);
-
-        $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst($type);
+        $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type));
         if (class_exists($class)) {
             $response = new $class($data, $code, $header, $options);
         } else {

+ 177 - 135
thinkphp/library/think/Route.php

@@ -68,8 +68,8 @@ class Route
     /**
      * 注册变量规则
      * @access public
-     * @param string|array  $name 变量名
-     * @param string        $rule 变量规则
+     * @param string|array $name 变量名
+     * @param string       $rule 变量规则
      * @return void
      */
     public static function pattern($name = null, $rule = '')
@@ -84,10 +84,10 @@ class Route
     /**
      * 注册子域名部署规则
      * @access public
-     * @param string|array  $domain 子域名
-     * @param mixed         $rule 路由规则
-     * @param array         $option 路由参数
-     * @param array         $pattern 变量规则
+     * @param string|array $domain  子域名
+     * @param mixed        $rule    路由规则
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
      * @return void
      */
     public static function domain($domain, $rule = '', $option = [], $pattern = [])
@@ -121,8 +121,8 @@ class Route
     /**
      * 设置路由绑定
      * @access public
-     * @param mixed     $bind 绑定信息
-     * @param string    $type 绑定类型 默认为module 支持 namespace class controller
+     * @param mixed  $bind 绑定信息
+     * @param string $type 绑定类型 默认为module 支持 namespace class controller
      * @return mixed
      */
     public static function bind($bind, $type = 'module')
@@ -133,8 +133,8 @@ class Route
     /**
      * 设置或者获取路由标识
      * @access public
-     * @param string|array     $name 路由命名标识 数组表示批量设置
-     * @param array            $value 路由地址及变量信息
+     * @param string|array $name  路由命名标识 数组表示批量设置
+     * @param array        $value 路由地址及变量信息
      * @return array
      */
     public static function name($name = '', $value = null)
@@ -154,7 +154,7 @@ class Route
     /**
      * 读取路由绑定
      * @access public
-     * @param string    $type 绑定类型
+     * @param string $type 绑定类型
      * @return mixed
      */
     public static function getBind($type)
@@ -165,8 +165,8 @@ class Route
     /**
      * 导入配置文件的路由规则
      * @access public
-     * @param array     $rule 路由规则
-     * @param string    $type 请求类型
+     * @param array  $rule 路由规则
+     * @param string $type 请求类型
      * @return void
      */
     public static function import(array $rule, $type = '*')
@@ -222,11 +222,11 @@ class Route
     /**
      * 注册路由规则
      * @access public
-     * @param string|array  $rule 路由规则
-     * @param string        $route 路由地址
-     * @param string        $type 请求类型
-     * @param array         $option 路由参数
-     * @param array         $pattern 变量规则
+     * @param string|array $rule    路由规则
+     * @param string       $route   路由地址
+     * @param string       $type    请求类型
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
      * @return void
      */
     public static function rule($rule, $route = '', $type = '*', $option = [], $pattern = [])
@@ -270,12 +270,12 @@ class Route
     /**
      * 设置路由规则
      * @access public
-     * @param string|array  $rule 路由规则
-     * @param string        $route 路由地址
-     * @param string        $type 请求类型
-     * @param array         $option 路由参数
-     * @param array         $pattern 变量规则
-     * @param string        $group 所属分组
+     * @param string|array $rule    路由规则
+     * @param string       $route   路由地址
+     * @param string       $type    请求类型
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
+     * @param string       $group   所属分组
      * @return void
      */
     protected static function setRule($rule, $route, $type = '*', $option = [], $pattern = [], $group = '')
@@ -348,7 +348,7 @@ class Route
     /**
      * 设置当前执行的参数信息
      * @access public
-     * @param array    $options 参数信息
+     * @param array $options 参数信息
      * @return mixed
      */
     protected static function setOption($options = [])
@@ -369,7 +369,7 @@ class Route
     /**
      * 获取当前的分组信息
      * @access public
-     * @param string    $type 分组信息名称 name option pattern
+     * @param string $type 分组信息名称 name option pattern
      * @return mixed
      */
     public static function getGroup($type)
@@ -384,9 +384,9 @@ class Route
     /**
      * 设置当前的路由分组
      * @access public
-     * @param string    $name 分组名称
-     * @param array     $option 分组路由参数
-     * @param array     $pattern 分组变量规则
+     * @param string $name    分组名称
+     * @param array  $option  分组路由参数
+     * @param array  $pattern 分组变量规则
      * @return void
      */
     public static function setGroup($name, $option = [], $pattern = [])
@@ -399,10 +399,10 @@ class Route
     /**
      * 注册路由分组
      * @access public
-     * @param string|array      $name 分组名称或者参数
-     * @param array|\Closure    $routes 路由地址
-     * @param array             $option 路由参数
-     * @param array             $pattern 变量规则
+     * @param string|array   $name    分组名称或者参数
+     * @param array|\Closure $routes  路由地址
+     * @param array          $option  路由参数
+     * @param array          $pattern 变量规则
      * @return void
      */
     public static function group($name, $routes, $option = [], $pattern = [])
@@ -487,10 +487,10 @@ class Route
     /**
      * 注册路由
      * @access public
-     * @param string|array  $rule 路由规则
-     * @param string        $route 路由地址
-     * @param array         $option 路由参数
-     * @param array         $pattern 变量规则
+     * @param string|array $rule    路由规则
+     * @param string       $route   路由地址
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
      * @return void
      */
     public static function any($rule, $route = '', $option = [], $pattern = [])
@@ -501,10 +501,10 @@ class Route
     /**
      * 注册GET路由
      * @access public
-     * @param string|array  $rule 路由规则
-     * @param string        $route 路由地址
-     * @param array         $option 路由参数
-     * @param array         $pattern 变量规则
+     * @param string|array $rule    路由规则
+     * @param string       $route   路由地址
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
      * @return void
      */
     public static function get($rule, $route = '', $option = [], $pattern = [])
@@ -515,10 +515,10 @@ class Route
     /**
      * 注册POST路由
      * @access public
-     * @param string|array  $rule 路由规则
-     * @param string        $route 路由地址
-     * @param array         $option 路由参数
-     * @param array         $pattern 变量规则
+     * @param string|array $rule    路由规则
+     * @param string       $route   路由地址
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
      * @return void
      */
     public static function post($rule, $route = '', $option = [], $pattern = [])
@@ -529,10 +529,10 @@ class Route
     /**
      * 注册PUT路由
      * @access public
-     * @param string|array  $rule 路由规则
-     * @param string        $route 路由地址
-     * @param array         $option 路由参数
-     * @param array         $pattern 变量规则
+     * @param string|array $rule    路由规则
+     * @param string       $route   路由地址
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
      * @return void
      */
     public static function put($rule, $route = '', $option = [], $pattern = [])
@@ -543,10 +543,10 @@ class Route
     /**
      * 注册DELETE路由
      * @access public
-     * @param string|array  $rule 路由规则
-     * @param string        $route 路由地址
-     * @param array         $option 路由参数
-     * @param array         $pattern 变量规则
+     * @param string|array $rule    路由规则
+     * @param string       $route   路由地址
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
      * @return void
      */
     public static function delete($rule, $route = '', $option = [], $pattern = [])
@@ -557,10 +557,10 @@ class Route
     /**
      * 注册PATCH路由
      * @access public
-     * @param string|array  $rule 路由规则
-     * @param string        $route 路由地址
-     * @param array         $option 路由参数
-     * @param array         $pattern 变量规则
+     * @param string|array $rule    路由规则
+     * @param string       $route   路由地址
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
      * @return void
      */
     public static function patch($rule, $route = '', $option = [], $pattern = [])
@@ -571,10 +571,10 @@ class Route
     /**
      * 注册资源路由
      * @access public
-     * @param string|array  $rule 路由规则
-     * @param string        $route 路由地址
-     * @param array         $option 路由参数
-     * @param array         $pattern 变量规则
+     * @param string|array $rule    路由规则
+     * @param string       $route   路由地址
+     * @param array        $option  路由参数
+     * @param array        $pattern 变量规则
      * @return void
      */
     public static function resource($rule, $route = '', $option = [], $pattern = [])
@@ -618,10 +618,10 @@ class Route
     /**
      * 注册控制器路由 操作方法对应不同的请求后缀
      * @access public
-     * @param string    $rule 路由规则
-     * @param string    $route 路由地址
-     * @param array     $option 路由参数
-     * @param array     $pattern 变量规则
+     * @param string $rule    路由规则
+     * @param string $route   路由地址
+     * @param array  $option  路由参数
+     * @param array  $pattern 变量规则
      * @return void
      */
     public static function controller($rule, $route = '', $option = [], $pattern = [])
@@ -634,9 +634,9 @@ class Route
     /**
      * 注册别名路由
      * @access public
-     * @param string|array  $rule 路由别名
-     * @param string        $route 路由地址
-     * @param array         $option 路由参数
+     * @param string|array $rule   路由别名
+     * @param string       $route  路由地址
+     * @param array        $option 路由参数
      * @return void
      */
     public static function alias($rule = null, $route = '', $option = [])
@@ -651,8 +651,8 @@ class Route
     /**
      * 设置不同请求类型下面的方法前缀
      * @access public
-     * @param string    $method 请求类型
-     * @param string    $prefix 类型前缀
+     * @param string $method 请求类型
+     * @param string $prefix 类型前缀
      * @return void
      */
     public static function setMethodPrefix($method, $prefix = '')
@@ -667,8 +667,8 @@ class Route
     /**
      * rest方法定义和修改
      * @access public
-     * @param string|array  $name 方法名称
-     * @param array|bool    $resource 资源
+     * @param string|array $name     方法名称
+     * @param array|bool   $resource 资源
      * @return void
      */
     public static function rest($name, $resource = [])
@@ -683,9 +683,9 @@ class Route
     /**
      * 注册未匹配路由规则后的处理
      * @access public
-     * @param string    $route 路由地址
-     * @param string    $method 请求类型
-     * @param array     $option 路由参数
+     * @param string $route  路由地址
+     * @param string $method 请求类型
+     * @param array  $option 路由参数
      * @return void
      */
     public static function miss($route, $method = '*', $option = [])
@@ -696,7 +696,7 @@ class Route
     /**
      * 注册一个自动解析的URL路由
      * @access public
-     * @param string    $route 路由地址
+     * @param string $route 路由地址
      * @return void
      */
     public static function auto($route)
@@ -726,9 +726,9 @@ class Route
     /**
      * 检测子域名部署
      * @access public
-     * @param Request   $request Request请求对象
-     * @param array     $currentRules 当前路由规则
-     * @param string    $method 请求类型
+     * @param Request $request      Request请求对象
+     * @param array   $currentRules 当前路由规则
+     * @param string  $method       请求类型
      * @return void
      */
     public static function checkDomain($request, &$currentRules, $method = 'get')
@@ -737,7 +737,7 @@ class Route
         $rules = self::$rules['domain'];
         // 开启子域名部署 支持二级和三级域名
         if (!empty($rules)) {
-            $host = $request->host();
+            $host = $request->host(true);
             if (isset($rules[$host])) {
                 // 完整域名或者IP配置
                 $item = $rules[$host];
@@ -827,14 +827,23 @@ class Route
     /**
      * 检测URL路由
      * @access public
-     * @param Request   $request Request请求对象
-     * @param string    $url URL地址
-     * @param string    $depr URL分隔符
-     * @param bool      $checkDomain 是否检测域名规则
+     * @param Request $request     Request请求对象
+     * @param string  $url         URL地址
+     * @param string  $depr        URL分隔符
+     * @param bool    $checkDomain 是否检测域名规则
      * @return false|array
      */
     public static function check($request, $url, $depr = '/', $checkDomain = false)
     {
+        //检查解析缓存
+        if (!App::$debug && Config::get('route_check_cache')) {
+            $key = self::getCheckCacheKey($request);
+            if (Cache::has($key)) {
+                list($rule, $route, $pathinfo, $option, $matches) = Cache::get($key);
+                return self::parseRule($rule, $route, $pathinfo, $option, $matches, true);
+            }
+        }
+
         // 分隔符替换 确保路由定义使用统一的分隔符
         $url = str_replace($depr, '|', $url);
 
@@ -888,12 +897,12 @@ class Route
     /**
      * 检测路由规则
      * @access private
-     * @param Request   $request
-     * @param array     $rules 路由规则
-     * @param string    $url URL地址
-     * @param string    $depr URL分割符
-     * @param string    $group 路由分组名
-     * @param array     $options 路由参数(分组)
+     * @param Request $request
+     * @param array   $rules   路由规则
+     * @param string  $url     URL地址
+     * @param string  $depr    URL分割符
+     * @param string  $group   路由分组名
+     * @param array   $options 路由参数(分组)
      * @return mixed
      */
     private static function checkRoute($request, $rules, $url, $depr = '/', $group = '', $options = [])
@@ -971,9 +980,9 @@ class Route
     /**
      * 检测路由别名
      * @access private
-     * @param Request   $request
-     * @param string    $url URL地址
-     * @param string    $depr URL分隔符
+     * @param Request $request
+     * @param string  $url  URL地址
+     * @param string  $depr URL分隔符
      * @return mixed
      */
     private static function checkRouteAlias($request, $url, $depr)
@@ -1018,9 +1027,9 @@ class Route
     /**
      * 检测URL绑定
      * @access private
-     * @param string    $url URL地址
-     * @param array     $rules 路由规则
-     * @param string    $depr URL分隔符
+     * @param string $url   URL地址
+     * @param array  $rules 路由规则
+     * @param string $depr  URL分隔符
      * @return mixed
      */
     private static function checkUrlBind(&$url, &$rules, $depr = '/')
@@ -1049,9 +1058,9 @@ class Route
     /**
      * 绑定到类
      * @access public
-     * @param string    $url URL地址
-     * @param string    $class 类名(带命名空间)
-     * @param string    $depr URL分隔符
+     * @param string $url   URL地址
+     * @param string $class 类名(带命名空间)
+     * @param string $depr  URL分隔符
      * @return array
      */
     public static function bindToClass($url, $class, $depr = '/')
@@ -1068,9 +1077,9 @@ class Route
     /**
      * 绑定到命名空间
      * @access public
-     * @param string    $url URL地址
-     * @param string    $namespace 命名空间
-     * @param string    $depr URL分隔符
+     * @param string $url       URL地址
+     * @param string $namespace 命名空间
+     * @param string $depr      URL分隔符
      * @return array
      */
     public static function bindToNamespace($url, $namespace, $depr = '/')
@@ -1088,9 +1097,9 @@ class Route
     /**
      * 绑定到控制器类
      * @access public
-     * @param string    $url URL地址
-     * @param string    $controller 控制器名 (支持带模块名 index/user )
-     * @param string    $depr URL分隔符
+     * @param string $url        URL地址
+     * @param string $controller 控制器名 (支持带模块名 index/user )
+     * @param string $depr       URL分隔符
      * @return array
      */
     public static function bindToController($url, $controller, $depr = '/')
@@ -1107,9 +1116,9 @@ class Route
     /**
      * 绑定到模块/控制器
      * @access public
-     * @param string    $url URL地址
-     * @param string    $controller 控制器类名(带命名空间)
-     * @param string    $depr URL分隔符
+     * @param string $url        URL地址
+     * @param string $controller 控制器类名(带命名空间)
+     * @param string $depr       URL分隔符
      * @return array
      */
     public static function bindToModule($url, $controller, $depr = '/')
@@ -1126,8 +1135,8 @@ class Route
     /**
      * 路由参数有效性检查
      * @access private
-     * @param array     $option 路由参数
-     * @param Request   $request Request对象
+     * @param array   $option  路由参数
+     * @param Request $request Request对象
      * @return bool
      */
     private static function checkOption($option, $request)
@@ -1153,12 +1162,12 @@ class Route
     /**
      * 检测路由规则
      * @access private
-     * @param string    $rule 路由规则
-     * @param string    $route 路由地址
-     * @param string    $url URL地址
-     * @param array     $pattern 变量规则
-     * @param array     $option 路由参数
-     * @param string    $depr URL分隔符(全局)
+     * @param string $rule    路由规则
+     * @param string $route   路由地址
+     * @param string $url     URL地址
+     * @param array  $pattern 变量规则
+     * @param array  $option  路由参数
+     * @param string $depr    URL分隔符(全局)
      * @return array|false
      */
     private static function checkRule($rule, $route, $url, $pattern, $option, $depr)
@@ -1200,9 +1209,9 @@ class Route
     /**
      * 解析模块的URL地址 [模块/控制器/操作?]参数1=值1&参数2=值2...
      * @access public
-     * @param string    $url URL地址
-     * @param string    $depr URL分隔符
-     * @param bool      $autoSearch 是否自动深度搜索控制器
+     * @param string $url        URL地址
+     * @param string $depr       URL分隔符
+     * @param bool   $autoSearch 是否自动深度搜索控制器
      * @return array
      */
     public static function parseUrl($url, $depr = '/', $autoSearch = false)
@@ -1269,7 +1278,7 @@ class Route
     /**
      * 解析URL的pathinfo参数和变量
      * @access private
-     * @param string    $url URL地址
+     * @param string $url URL地址
      * @return array
      */
     private static function parseUrlPath($url)
@@ -1295,9 +1304,9 @@ class Route
     /**
      * 检测URL和规则路由是否匹配
      * @access private
-     * @param string    $url URL地址
-     * @param string    $rule 路由规则
-     * @param array     $pattern 变量规则
+     * @param string $url     URL地址
+     * @param string $rule    路由规则
+     * @param array  $pattern 变量规则
      * @return array|false
      */
     private static function match($url, $rule, $pattern)
@@ -1370,16 +1379,28 @@ class Route
     /**
      * 解析规则路由
      * @access private
-     * @param string    $rule 路由规则
-     * @param string    $route 路由地址
-     * @param string    $pathinfo URL地址
-     * @param array     $option 路由参数
-     * @param array     $matches 匹配的变量
+     * @param string $rule      路由规则
+     * @param string $route     路由地址
+     * @param string $pathinfo  URL地址
+     * @param array  $option    路由参数
+     * @param array  $matches   匹配的变量
+     * @param bool   $fromCache 通过缓存解析
      * @return array
      */
-    private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = [])
+    private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = [], $fromCache = false)
     {
         $request = Request::instance();
+
+        //保存解析缓存
+        if (Config::get('route_check_cache') && !$fromCache) {
+            try {
+                $key = self::getCheckCacheKey($request);
+                Cache::tag('route_check')->set($key, [$rule, $route, $pathinfo, $option, $matches]);
+            } catch (\Exception $e) {
+
+            }
+        }
+
         // 解析路由规则
         if ($rule) {
             $rule = explode('/', $rule);
@@ -1506,7 +1527,7 @@ class Route
             App::$modulePath = APP_PATH . (Config::get('app_multi_module') ? $request->module() . DS : '');
         } else {
             // 路由到模块/控制器/操作
-            $result = self::parseModule($route);
+            $result = self::parseModule($route, isset($option['convert']) ? $option['convert'] : false);
         }
         // 开启请求缓存
         if ($request->isGet() && isset($option['cache'])) {
@@ -1526,10 +1547,11 @@ class Route
     /**
      * 解析URL地址为 模块/控制器/操作
      * @access private
-     * @param string    $url URL地址
+     * @param string $url     URL地址
+     * @param bool   $convert 是否自动转换URL地址
      * @return array
      */
-    private static function parseModule($url)
+    private static function parseModule($url, $convert = false)
     {
         list($path, $var) = self::parseUrlPath($url);
         $action           = array_pop($path);
@@ -1543,14 +1565,14 @@ class Route
         // 设置当前请求的路由变量
         Request::instance()->route($var);
         // 路由到模块/控制器/操作
-        return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => false];
+        return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => $convert];
     }
 
     /**
      * 解析URL地址中的参数Request对象
      * @access private
-     * @param string    $url 路由规则
-     * @param array     $var 变量
+     * @param string $url 路由规则
+     * @param array  $var 变量
      * @return void
      */
     private static function parseUrlParams($url, &$var = [])
@@ -1600,4 +1622,24 @@ class Route
         }
         return $var;
     }
+
+    /**
+     * 获取路由解析缓存的key
+     * @param Request $request
+     * @return string
+     */
+    private static function getCheckCacheKey(Request $request)
+    {
+        static $key;
+
+        if (empty($key)) {
+            if ($callback = Config::get('route_check_cache_key')) {
+                $key = call_user_func($callback, $request);
+            } else {
+                $key = "{$request->host(true)}|{$request->method()}|{$request->path()}";
+            }
+        }
+
+        return $key;
+    }
 }

+ 40 - 4
thinkphp/library/think/Validate.php

@@ -67,6 +67,8 @@ class Validate
         'min'         => 'min size of :attribute must be :rule',
         'after'       => ':attribute cannot be less than :rule',
         'before'      => ':attribute cannot exceed :rule',
+        'afterWith'   => ':attribute cannot be less than :rule',
+        'beforeWith'  => ':attribute cannot exceed :rule',
         'expire'      => ':attribute not within :rule',
         'allowIp'     => 'access IP is not allowed',
         'denyIp'      => 'access IP denied',
@@ -878,12 +880,16 @@ class Validate
             // 支持多个字段验证
             $fields = explode('^', $key);
             foreach ($fields as $key) {
-                $map[$key] = $data[$key];
+                if (isset($data[$key])) {
+                    $map[$key] = $data[$key];
+                }
             }
         } elseif (strpos($key, '=')) {
             parse_str($key, $map);
-        } else {
+        } elseif (isset($data[$field])) {
             $map[$key] = $data[$field];
+        } else {
+            $map = [];
         }
 
         $pk = isset($rule[3]) ? $rule[3] : $db->getPk();
@@ -1113,9 +1119,10 @@ class Validate
      * @access protected
      * @param mixed     $value  字段值
      * @param mixed     $rule  验证规则
+     * @param array     $data  数据
      * @return bool
      */
-    protected function after($value, $rule)
+    protected function after($value, $rule, $data)
     {
         return strtotime($value) >= strtotime($rule);
     }
@@ -1125,13 +1132,42 @@ class Validate
      * @access protected
      * @param mixed     $value  字段值
      * @param mixed     $rule  验证规则
+     * @param array     $data  数据
      * @return bool
      */
-    protected function before($value, $rule)
+    protected function before($value, $rule, $data)
     {
         return strtotime($value) <= strtotime($rule);
     }
 
+    /**
+     * 验证日期字段
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @param array     $data  数据
+     * @return bool
+     */
+    protected function afterWith($value, $rule, $data)
+    {
+        $rule = $this->getDataValue($data, $rule);
+        return !is_null($rule) && strtotime($value) >= strtotime($rule);
+    }
+
+    /**
+     * 验证日期字段
+     * @access protected
+     * @param mixed     $value  字段值
+     * @param mixed     $rule  验证规则
+     * @param array     $data  数据
+     * @return bool
+     */
+    protected function beforeWith($value, $rule, $data)
+    {
+        $rule = $this->getDataValue($data, $rule);
+        return !is_null($rule) && strtotime($value) <= strtotime($rule);
+    }
+
     /**
      * 验证有效期
      * @access protected

+ 1 - 1
thinkphp/library/think/cache/driver/Memcache.php

@@ -63,7 +63,7 @@ class Memcache extends Driver
     public function has($name)
     {
         $key = $this->getCacheKey($name);
-        return $this->handler->get($key) ? true : false;
+        return false !== $this->handler->get($key);
     }
 
     /**

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

@@ -70,7 +70,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));
     }
 
     /**

+ 11 - 2
thinkphp/library/think/console/command/Clear.php

@@ -10,8 +10,10 @@
 // +----------------------------------------------------------------------
 namespace think\console\command;
 
+use think\Cache;
 use think\console\Command;
 use think\console\Input;
+use think\console\input\Argument;
 use think\console\input\Option;
 use think\console\Output;
 
@@ -22,6 +24,7 @@ class Clear extends Command
         // 指令配置
         $this
             ->setName('clear')
+            ->addArgument('type', Argument::OPTIONAL, 'type to clear', null)
             ->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null)
             ->setDescription('Clear runtime file');
     }
@@ -30,8 +33,14 @@ class Clear extends Command
     {
         $path = $input->getOption('path') ?: RUNTIME_PATH;
 
-        if (is_dir($path)) {
-            $this->clearPath($path);
+        $type = $input->getArgument('type');
+
+        if ($type == 'route') {
+            Cache::clear('route_check');
+        } else {
+            if (is_dir($path)) {
+                $this->clearPath($path);
+            }
         }
 
         $output->writeln("<info>Clear Successed</info>");

+ 66 - 44
thinkphp/library/think/db/Builder.php

@@ -11,7 +11,6 @@
 
 namespace think\db;
 
-use BadMethodCallException;
 use PDO;
 use think\Exception;
 
@@ -99,8 +98,15 @@ abstract class Builder
 
         $result = [];
         foreach ($data as $key => $val) {
-            $item = $this->parseKey($key, $options);
-            if (is_object($val) && method_exists($val, '__toString')) {
+            if ('*' != $options['field'] && !in_array($key, $fields, true)) {
+                continue;
+            }
+
+            $item = $this->parseKey($key, $options, true);
+            if ($val instanceof Expression) {
+                $result[$item] = $val->getValue();
+                continue;
+            } elseif (is_object($val) && method_exists($val, '__toString')) {
                 // 对象数据写入
                 $val = $val->__toString();
             }
@@ -111,20 +117,15 @@ abstract class Builder
             } elseif (is_null($val)) {
                 $result[$item] = 'NULL';
             } elseif (is_array($val) && !empty($val)) {
-                switch ($val[0]) {
-                    case 'exp':
-                        $result[$item] = $val[1];
-                        break;
+                switch (strtolower($val[0])) {
                     case 'inc':
-                        if ($key == $val[1]) {
-                            $result[$item] = $this->parseKey($val[1]) . '+' . floatval($val[2]);
-                        }
+                        $result[$item] = $item . '+' . floatval($val[1]);
                         break;
                     case 'dec':
-                        if ($key == $val[1]) {
-                            $result[$item] = $this->parseKey($val[1]) . '-' . floatval($val[2]);
-                        }
+                        $result[$item] = $item . '-' . floatval($val[1]);
                         break;
+                    case 'exp':
+                        throw new Exception('not support data:[' . $val[0] . ']');
                 }
             } elseif (is_scalar($val)) {
                 // 过滤非标量数据
@@ -147,7 +148,7 @@ abstract class Builder
      * @param array  $options
      * @return string
      */
-    protected function parseKey($key, $options = [])
+    protected function parseKey($key, $options = [], $strict = false)
     {
         return $key;
     }
@@ -188,8 +189,10 @@ abstract class Builder
             // 支持 'field1'=>'field2' 这样的字段别名定义
             $array = [];
             foreach ($fields as $key => $field) {
-                if (!is_numeric($key)) {
-                    $array[] = $this->parseKey($key, $options) . ' AS ' . $this->parseKey($field, $options);
+                if ($field instanceof Expression) {
+                    $array[] = $field->getValue();
+                } elseif (!is_numeric($key)) {
+                    $array[] = $this->parseKey($key, $options) . ' AS ' . $this->parseKey($field, $options, true);
                 } else {
                     $array[] = $this->parseKey($field, $options);
                 }
@@ -268,7 +271,9 @@ abstract class Builder
         foreach ($where as $key => $val) {
             $str = [];
             foreach ($val as $field => $value) {
-                if ($value instanceof \Closure) {
+                if ($value instanceof Expression) {
+                    $str[] = ' ' . $key . ' ( ' . $value->getValue() . ' )';
+                } elseif ($value instanceof \Closure) {
                     // 使用闭包查询
                     $query = new Query($this->connection);
                     call_user_func_array($value, [ & $query]);
@@ -309,7 +314,7 @@ abstract class Builder
     protected function parseWhereItem($field, $val, $rule = '', $options = [], $binds = [], $bindName = null)
     {
         // 字段分析
-        $key = $field ? $this->parseKey($field, $options) : '';
+        $key = $field ? $this->parseKey($field, $options, true) : '';
 
         // 查询规则和条件
         if (!is_array($val)) {
@@ -348,7 +353,9 @@ abstract class Builder
             $bindName = md5($bindName);
         }
 
-        if (is_object($value) && method_exists($value, '__toString')) {
+        if ($value instanceof Expression) {
+
+        } elseif (is_object($value) && method_exists($value, '__toString')) {
             // 对象数据写入
             $value = $value->__toString();
         }
@@ -385,7 +392,11 @@ abstract class Builder
             }
         } elseif ('EXP' == $exp) {
             // 表达式查询
-            $whereStr .= '( ' . $key . ' ' . $value . ' )';
+            if ($value instanceof Expression) {
+                $whereStr .= '( ' . $key . ' ' . $value->getValue() . ' )';
+            } else {
+                throw new Exception('where express error:' . $exp);
+            }
         } elseif (in_array($exp, ['NOT NULL', 'NULL'])) {
             // NULL 查询
             $whereStr .= $key . ' IS ' . $exp;
@@ -503,6 +514,11 @@ abstract class Builder
             }
         }
         $bindName = $bindName ?: $key;
+
+        if ($this->query->isBind($bindName)) {
+            $bindName .= '_' . str_replace('.', '_', uniqid('', true));
+        }
+
         $this->query->bind($bindName, $value, $bindType);
         return ':' . $bindName;
     }
@@ -533,7 +549,9 @@ abstract class Builder
                 list($table, $type, $on) = $item;
                 $condition               = [];
                 foreach ((array) $on as $val) {
-                    if (strpos($val, '=')) {
+                    if ($val instanceof Expression) {
+                        $condition[] = $val->getValue();
+                    } elseif (strpos($val, '=')) {
                         list($val1, $val2) = explode('=', $val, 2);
                         $condition[]       = $this->parseKey($val1, $options) . '=' . $this->parseKey($val2, $options);
                     } else {
@@ -557,28 +575,29 @@ abstract class Builder
      */
     protected function parseOrder($order, $options = [])
     {
-        if (is_array($order)) {
-            $array = [];
-            foreach ($order as $key => $val) {
+        if (empty($order)) {
+            return '';
+        }
+
+        $array = [];
+        foreach ($order as $key => $val) {
+            if ($val instanceof Expression) {
+                $array[] = $val->getValue();
+            } elseif ('[rand]' == $val) {
+                $array[] = $this->parseRand();
+            } else {
                 if (is_numeric($key)) {
-                    if ('[rand]' == $val) {
-                        if (method_exists($this, 'parseRand')) {
-                            $array[] = $this->parseRand();
-                        } else {
-                            throw new BadMethodCallException('method not exists:' . get_class($this) . '-> parseRand');
-                        }
-                    } elseif (false === strpos($val, '(')) {
-                        $array[] = $this->parseKey($val, $options);
-                    } else {
-                        $array[] = $val;
-                    }
+                    list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' ');
                 } else {
-                    $sort    = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : '';
-                    $array[] = $this->parseKey($key, $options) . ' ' . $sort;
+                    $sort = $val;
                 }
+                $sort    = strtoupper($sort);
+                $sort    = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : '';
+                $array[] = $this->parseKey($key, $options, true) . $sort;
             }
-            $order = implode(',', $array);
         }
+        $order = implode(',', $array);
+
         return !empty($order) ? ' ORDER BY ' . $order : '';
     }
 
@@ -612,6 +631,9 @@ abstract class Builder
      */
     protected function parseComment($comment)
     {
+        if (false !== strpos($comment, '*/')) {
+            $comment = strstr($comment, '*/', true);
+        }
         return !empty($comment) ? ' /* ' . $comment . ' */' : '';
     }
 
@@ -661,11 +683,7 @@ abstract class Builder
             return '';
         }
 
-        if (is_array($index)) {
-            $index = join(",", $index);
-        }
-
-        return sprintf(" FORCE INDEX ( %s ) ", $index);
+        return sprintf(" FORCE INDEX ( %s ) ", is_array($index) ? implode(',', $index) : $index);
     }
 
     /**
@@ -783,10 +801,14 @@ abstract class Builder
             $values[] = 'SELECT ' . implode(',', $value);
 
             if (!isset($insertFields)) {
-                $insertFields = array_map([$this, 'parseKey'], array_keys($data));
+                $insertFields = array_keys($data);
             }
         }
 
+        foreach ($insertFields as $field) {
+            $fields[] = $this->parseKey($field, $options, true);
+        }
+
         return str_replace(
             ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'],
             [

+ 40 - 35
thinkphp/library/think/db/Connection.php

@@ -90,6 +90,8 @@ abstract class Connection
         'master_num'      => 1,
         // 指定从服务器序号
         'slave_no'        => '',
+        // 模型写入后自动读取主服务器
+        'read_master'     => false,
         // 是否严格检查字段是否存在
         'fields_strict'   => true,
         // 数据返回类型
@@ -359,14 +361,9 @@ abstract class Connection
             // 调试开始
             $this->debug(true);
 
-            // 释放前次的查询结果
-            if (!empty($this->PDOStatement)) {
-                $this->free();
-            }
             // 预处理
-            if (empty($this->PDOStatement)) {
-                $this->PDOStatement = $this->linkID->prepare($sql);
-            }
+            $this->PDOStatement = $this->linkID->prepare($sql);
+
             // 是否为存储过程调用
             $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']);
             // 参数绑定
@@ -378,7 +375,7 @@ abstract class Connection
             // 执行查询
             $this->PDOStatement->execute();
             // 调试结束
-            $this->debug(false);
+            $this->debug(false, '', $master);
             // 返回结果集
             return $this->getResult($pdo, $procedure);
         } catch (\PDOException $e) {
@@ -402,13 +399,14 @@ abstract class Connection
     /**
      * 执行语句
      * @access public
-     * @param string        $sql sql指令
-     * @param array         $bind 参数绑定
+     * @param  string        $sql sql指令
+     * @param  array         $bind 参数绑定
+     * @param  Query         $query 查询对象
      * @return int
      * @throws PDOException
      * @throws \Exception
      */
-    public function execute($sql, $bind = [])
+    public function execute($sql, $bind = [], Query $query = null)
     {
         $this->initConnect(true);
         if (!$this->linkID) {
@@ -426,14 +424,9 @@ abstract class Connection
             // 调试开始
             $this->debug(true);
 
-            //释放前次的查询结果
-            if (!empty($this->PDOStatement) && $this->PDOStatement->queryString != $sql) {
-                $this->free();
-            }
             // 预处理
-            if (empty($this->PDOStatement)) {
-                $this->PDOStatement = $this->linkID->prepare($sql);
-            }
+            $this->PDOStatement = $this->linkID->prepare($sql);
+
             // 是否为存储过程调用
             $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']);
             // 参数绑定
@@ -445,23 +438,27 @@ abstract class Connection
             // 执行语句
             $this->PDOStatement->execute();
             // 调试结束
-            $this->debug(false);
+            $this->debug(false, '', true);
+
+            if ($query && !empty($this->config['deploy']) && !empty($this->config['read_master'])) {
+                $query->readMaster();
+            }
 
             $this->numRows = $this->PDOStatement->rowCount();
             return $this->numRows;
         } catch (\PDOException $e) {
             if ($this->isBreak($e)) {
-                return $this->close()->execute($sql, $bind);
+                return $this->close()->execute($sql, $bind, $query);
             }
             throw new PDOException($e, $this->config, $this->getLastsql());
         } catch (\Throwable $e) {
             if ($this->isBreak($e)) {
-                return $this->close()->execute($sql, $bind);
+                return $this->close()->execute($sql, $bind, $query);
             }
             throw $e;
         } catch (\Exception $e) {
             if ($this->isBreak($e)) {
-                return $this->close()->execute($sql, $bind);
+                return $this->close()->execute($sql, $bind, $query);
             }
             throw $e;
         }
@@ -652,18 +649,15 @@ abstract class Connection
                 );
             }
 
-        } catch (\PDOException $e) {
-            if ($this->isBreak($e)) {
-                return $this->close()->startTrans();
-            }
-            throw $e;
         } catch (\Exception $e) {
             if ($this->isBreak($e)) {
+                --$this->transTimes;
                 return $this->close()->startTrans();
             }
             throw $e;
         } catch (\Error $e) {
             if ($this->isBreak($e)) {
+                --$this->transTimes;
                 return $this->close()->startTrans();
             }
             throw $e;
@@ -744,7 +738,7 @@ abstract class Connection
      * @param array $sqlArray SQL批处理指令
      * @return boolean
      */
-    public function batchQuery($sqlArray = [], $bind = [])
+    public function batchQuery($sqlArray = [], $bind = [], Query $query = null)
     {
         if (!is_array($sqlArray)) {
             return false;
@@ -753,7 +747,7 @@ abstract class Connection
         $this->startTrans();
         try {
             foreach ($sqlArray as $sql) {
-                $this->execute($sql, $bind);
+                $this->execute($sql, $bind, $query);
             }
             // 提交事务
             $this->commit();
@@ -797,6 +791,8 @@ abstract class Connection
         $this->linkWrite = null;
         $this->linkRead  = null;
         $this->links     = [];
+        // 释放查询
+        $this->free();
         return $this;
     }
 
@@ -904,9 +900,10 @@ abstract class Connection
      * @access protected
      * @param boolean $start 调试开始标记 true 开始 false 结束
      * @param string  $sql 执行的SQL语句 留空自动获取
+     * @param boolean $master 主从标记
      * @return void
      */
-    protected function debug($start, $sql = '')
+    protected function debug($start, $sql = '', $master = false)
     {
         if (!empty($this->config['debug'])) {
             // 开启数据库调试模式
@@ -923,7 +920,7 @@ abstract class Connection
                     $result = $this->getExplain($sql);
                 }
                 // SQL监听
-                $this->trigger($sql, $runtime, $result);
+                $this->trigger($sql, $runtime, $result, $master);
             }
         }
     }
@@ -945,19 +942,27 @@ abstract class Connection
      * @param string    $sql SQL语句
      * @param float     $runtime SQL运行时间
      * @param mixed     $explain SQL分析
-     * @return bool
+     * @param  bool     $master 主从标记
+     * @return void
      */
-    protected function trigger($sql, $runtime, $explain = [])
+    protected function trigger($sql, $runtime, $explain = [], $master = false)
     {
         if (!empty(self::$event)) {
             foreach (self::$event as $callback) {
                 if (is_callable($callback)) {
-                    call_user_func_array($callback, [$sql, $runtime, $explain]);
+                    call_user_func_array($callback, [$sql, $runtime, $explain, $master]);
                 }
             }
         } else {
             // 未注册监听则记录到日志中
-            Log::record('[ SQL ] ' . $sql . ' [ RunTime:' . $runtime . 's ]', 'sql');
+            if ($this->config['deploy']) {
+                // 分布式记录当前操作的主从
+                $master = $master ? 'master|' : 'slave|';
+            } else {
+                $master = '';
+            }
+
+            Log::record('[ SQL ] ' . $sql . ' [ ' . $master . 'RunTime:' . $runtime . 's ]', 'sql');
             if (!empty($explain)) {
                 Log::record('[ EXPLAIN : ' . var_export($explain, true) . ' ]', 'sql');
             }

+ 48 - 0
thinkphp/library/think/db/Expression.php

@@ -0,0 +1,48 @@
+<?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\db;
+
+class Expression
+{
+    /**
+     * 查询表达式
+     *
+     * @var string
+     */
+    protected $value;
+
+    /**
+     * 创建一个查询表达式
+     *
+     * @param  string  $value
+     * @return void
+     */
+    public function __construct($value)
+    {
+        $this->value = $value;
+    }
+
+    /**
+     * 获取表达式
+     *
+     * @return string
+     */
+    public function getValue()
+    {
+        return $this->value;
+    }
+
+    public function __toString()
+    {
+        return (string) $this->value;
+    }
+}

+ 235 - 55
thinkphp/library/think/db/Query.php

@@ -53,6 +53,8 @@ class Query
     protected static $info = [];
     // 回调事件
     private static $event = [];
+    // 读取主库
+    protected static $readMaster = [];
 
     /**
      * 构造函数
@@ -90,6 +92,13 @@ class Query
             $name         = Loader::parseName(substr($method, 10));
             $where[$name] = $args[0];
             return $this->where($where)->value($args[1]);
+        } elseif ($this->model && method_exists($this->model, 'scope' . $method)) {
+            // 动态调用命名范围
+            $method = 'scope' . $method;
+            array_unshift($args, $this);
+
+            call_user_func_array([$this->model, $method], $args);
+            return $this;
         } else {
             throw new Exception('method not exist:' . __CLASS__ . '->' . $method);
         }
@@ -140,6 +149,25 @@ class Query
         return $this->model;
     }
 
+    /**
+     * 设置后续从主库读取数据
+     * @access public
+     * @param  bool $allTable
+     * @return void
+     */
+    public function readMaster($allTable = false)
+    {
+        if ($allTable) {
+            $table = '*';
+        } else {
+            $table = isset($this->options['table']) ? $this->options['table'] : $this->getTable();
+        }
+
+        static::$readMaster[$table] = true;
+
+        return $this;
+    }
+
     /**
      * 获取当前的builder实例对象
      * @access public
@@ -238,7 +266,7 @@ class Query
      */
     public function execute($sql, $bind = [])
     {
-        return $this->connection->execute($sql, $bind);
+        return $this->connection->execute($sql, $bind, $this);
     }
 
     /**
@@ -415,12 +443,13 @@ class Query
                 // 返回SQL语句
                 return $pdo;
             }
+
             $result = $pdo->fetchColumn();
             if ($force) {
-                $result += 0;
+                $result = (float) $result;
             }
 
-            if (isset($cache)) {
+            if (isset($cache) && false !== $result) {
                 // 缓存数据
                 $this->cacheData($key, $result, $cache);
             }
@@ -510,13 +539,43 @@ class Query
     public function count($field = '*')
     {
         if (isset($this->options['group'])) {
+            if (!preg_match('/^[\w\.\*]+$/', $field)) {
+                throw new Exception('not support data:' . $field);
+            }
             // 支持GROUP
             $options = $this->getOptions();
             $subSql  = $this->options($options)->field('count(' . $field . ')')->bind($this->bind)->buildSql();
-            return $this->table([$subSql => '_group_count_'])->value('COUNT(*) AS tp_count', 0, true);
+
+            $count = $this->table([$subSql => '_group_count_'])->value('COUNT(*) AS tp_count', 0, true);
+        } else {
+            $count = $this->aggregate('COUNT', $field, true);
         }
 
-        return $this->value('COUNT(' . $field . ') AS tp_count', 0, true);
+        return is_string($count) ? $count : (int) $count;
+
+    }
+
+    /**
+     * 聚合查询
+     * @access public
+     * @param  string $aggregate    聚合方法
+     * @param  string $field        字段名
+     * @param  bool   $force        强制转为数字类型
+     * @return mixed
+     */
+    public function aggregate($aggregate, $field, $force = false)
+    {
+        if (0 === stripos($field, 'DISTINCT ')) {
+            list($distinct, $field) = explode(' ', $field);
+        }
+
+        if (!preg_match('/^[\w\.\+\-\*]+$/', $field)) {
+            throw new Exception('not support data:' . $field);
+        }
+
+        $result = $this->value($aggregate . '(' . (!empty($distinct) ? 'DISTINCT ' : '') . $field . ') AS tp_' . strtolower($aggregate), 0, $force);
+
+        return $result;
     }
 
     /**
@@ -527,7 +586,7 @@ class Query
      */
     public function sum($field)
     {
-        return $this->value('SUM(' . $field . ') AS tp_sum', 0, true);
+        return $this->aggregate('SUM', $field, true);
     }
 
     /**
@@ -539,7 +598,7 @@ class Query
      */
     public function min($field, $force = true)
     {
-        return $this->value('MIN(' . $field . ') AS tp_min', 0, $force);
+        return $this->aggregate('MIN', $field, $force);
     }
 
     /**
@@ -551,7 +610,7 @@ class Query
      */
     public function max($field, $force = true)
     {
-        return $this->value('MAX(' . $field . ') AS tp_max', 0, $force);
+        return $this->aggregate('MAX', $field, $force);
     }
 
     /**
@@ -562,7 +621,7 @@ class Query
      */
     public function avg($field)
     {
-        return $this->value('AVG(' . $field . ') AS tp_avg', 0, true);
+        return $this->aggregate('AVG', $field, true);
     }
 
     /**
@@ -609,7 +668,7 @@ class Query
                 return true;
             }
         }
-        return $this->setField($field, ['inc', $field, $step]);
+        return $this->setField($field, ['inc', $step]);
     }
 
     /**
@@ -637,9 +696,9 @@ class Query
                 $this->options = [];
                 return true;
             }
-            return $this->setField($field, ['inc', $field, $step]);
+            return $this->setField($field, ['inc', $step]);
         }
-        return $this->setField($field, ['dec', $field, $step]);
+        return $this->setField($field, ['dec', $step]);
     }
 
     /**
@@ -769,8 +828,15 @@ class Query
     {
         if (empty($field)) {
             return $this;
+        } elseif ($field instanceof Expression) {
+            $this->options['field'][] = $field;
+            return $this;
         }
+
         if (is_string($field)) {
+            if (preg_match('/[\<\'\"\(]/', $field)) {
+                return $this->fieldRaw($field);
+            }
             $field = array_map('trim', explode(',', $field));
         }
         if (true === $field) {
@@ -800,6 +866,24 @@ class Query
         return $this;
     }
 
+    /**
+     * 表达式方式指定查询字段
+     * @access public
+     * @param  string $field    字段名
+     * @param  array  $bind     参数绑定
+     * @return $this
+     */
+    public function fieldRaw($field, array $bind = [])
+    {
+        $this->options['field'][] = $this->raw($field);
+
+        if ($bind) {
+            $this->bind($bind);
+        }
+
+        return $this;
+    }
+
     /**
      * 设置数据
      * @access public
@@ -828,7 +912,7 @@ class Query
     {
         $fields = is_string($field) ? explode(',', $field) : $field;
         foreach ($fields as $field) {
-            $this->data($field, ['inc', $field, $step]);
+            $this->data($field, ['inc', $step]);
         }
         return $this;
     }
@@ -844,7 +928,7 @@ class Query
     {
         $fields = is_string($field) ? explode(',', $field) : $field;
         foreach ($fields as $field) {
-            $this->data($field, ['dec', $field, $step]);
+            $this->data($field, ['dec', $step]);
         }
         return $this;
     }
@@ -858,16 +942,27 @@ class Query
      */
     public function exp($field, $value)
     {
-        $this->data($field, ['exp', $value]);
+        $this->data($field, $this->raw($value));
         return $this;
     }
 
+    /**
+     * 使用表达式设置数据
+     * @access public
+     * @param  mixed $value 表达式
+     * @return Expression
+     */
+    public function raw($value)
+    {
+        return new Expression($value);
+    }
+
     /**
      * 指定JOIN查询字段
      * @access public
      * @param string|array $table 数据表
      * @param string|array $field 查询字段
-     * @param string|array $on    JOIN条件
+     * @param mixed        $on    JOIN条件
      * @param string       $type  JOIN类型
      * @return $this
      */
@@ -975,6 +1070,37 @@ class Query
         return $this;
     }
 
+    /**
+     * 指定表达式查询条件
+     * @access public
+     * @param  string $where  查询条件
+     * @param  array  $bind   参数绑定
+     * @param  string $logic  查询逻辑 and or xor
+     * @return $this
+     */
+    public function whereRaw($where, $bind = [], $logic = 'AND')
+    {
+        $this->options['where'][$logic][] = $this->raw($where);
+
+        if ($bind) {
+            $this->bind($bind);
+        }
+
+        return $this;
+    }
+
+    /**
+     * 指定表达式查询条件 OR
+     * @access public
+     * @param  string $where  查询条件
+     * @param  array  $bind   参数绑定
+     * @return $this
+     */
+    public function whereOrRaw($where, $bind = [])
+    {
+        return $this->whereRaw($where, $bind, 'OR');
+    }
+
     /**
      * 指定Null查询条件
      * @access public
@@ -1121,7 +1247,7 @@ class Query
      */
     public function whereExp($field, $condition, $logic = 'AND')
     {
-        $this->parseWhereExp($logic, $field, 'exp', $condition, [], true);
+        $this->parseWhereExp($logic, $field, 'exp', $this->raw($condition), [], true);
         return $this;
     }
 
@@ -1163,14 +1289,16 @@ class Query
             $field = $this->options['via'] . '.' . $field;
         }
 
-        if ($strict) {
+        if ($field instanceof Expression) {
+            return $this->whereRaw($field, is_array($op) ? $op : []);
+        } elseif ($strict) {
             // 使用严格模式查询
             $where[$field] = [$op, $condition];
 
             // 记录一个字段多次查询条件
             $this->options['multi'][$logic][$field][] = $where[$field];
         } elseif (is_string($field) && preg_match('/[,=\>\<\'\"\(\s]/', $field)) {
-            $where[] = ['exp', $field];
+            $where[] = ['exp', $this->raw($field)];
             if (is_array($op)) {
                 // 参数绑定
                 $this->bind($op);
@@ -1191,21 +1319,28 @@ class Query
             $where[$field] = $param;
         } elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) {
             // null查询
-            $where[$field]                            = [$op, ''];
+            $where[$field] = [$op, ''];
+
             $this->options['multi'][$logic][$field][] = $where[$field];
         } elseif (is_null($condition)) {
             // 字段相等查询
-            $where[$field]                            = ['eq', $op];
+            $where[$field] = ['eq', $op];
+
             $this->options['multi'][$logic][$field][] = $where[$field];
         } else {
-            $where[$field] = [$op, $condition, isset($param[2]) ? $param[2] : null];
-            if ('exp' == strtolower($op) && isset($param[2]) && is_array($param[2])) {
+            if ('exp' == strtolower($op)) {
+                $where[$field] = ['exp', $this->raw($condition)];
                 // 参数绑定
-                $this->bind($param[2]);
+                if (isset($param[2]) && is_array($param[2])) {
+                    $this->bind($param[2]);
+                }
+            } else {
+                $where[$field] = [$op, $condition];
             }
             // 记录一个字段多次查询条件
             $this->options['multi'][$logic][$field][] = $where[$field];
         }
+
         if (!empty($where)) {
             if (!isset($this->options['where'][$logic])) {
                 $this->options['where'][$logic] = [];
@@ -1423,31 +1558,59 @@ 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;
-                }
-                $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 (!isset($this->options['order'])) {
-                $this->options['order'] = [];
+        if (empty($field)) {
+            return $this;
+        } elseif ($field instanceof Expression) {
+            $this->options['order'][] = $field;
+            return $this;
+        }
+
+        if (is_string($field)) {
+            if (!empty($this->options['via'])) {
+                $field = $this->options['via'] . '.' . $field;
             }
-            if (is_array($field)) {
-                $this->options['order'] = array_merge($this->options['order'], $field);
+            if (strpos($field, ',')) {
+                $field = array_map('trim', explode(',', $field));
             } else {
-                $this->options['order'][] = $field;
+                $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 (!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;
+    }
+
+    /**
+     * 表达式方式指定Field排序
+     * @access public
+     * @param  string $field 排序字段
+     * @param  array  $bind  参数绑定
+     * @return $this
+     */
+    public function orderRaw($field, array $bind = [])
+    {
+        $this->options['order'][] = $this->raw($field);
+
+        if ($bind) {
+            $this->bind($bind);
         }
+
         return $this;
     }
 
@@ -1967,14 +2130,23 @@ class Query
                 $this->field('*');
             }
             foreach ($relations as $key => $relation) {
-                $closure = false;
+                $closure = $name = null;
                 if ($relation instanceof \Closure) {
                     $closure  = $relation;
                     $relation = $key;
+                } elseif (!is_int($key)) {
+                    $name     = $relation;
+                    $relation = $key;
                 }
                 $relation = Loader::parseName($relation, 1, false);
-                $count    = '(' . $this->model->$relation()->getRelationCountQuery($closure) . ')';
-                $this->field([$count => Loader::parseName($relation) . '_count']);
+
+                $count = '(' . $this->model->$relation()->getRelationCountQuery($closure, $name) . ')';
+
+                if (empty($name)) {
+                    $name = Loader::parseName($relation) . '_count';
+                }
+
+                $this->field([$count => $name]);
             }
         }
         return $this;
@@ -2100,7 +2272,7 @@ class Query
         }
 
         // 执行操作
-        $result = 0 === $sql ? 0 : $this->execute($sql, $bind);
+        $result = 0 === $sql ? 0 : $this->execute($sql, $bind, $this);
         if ($result) {
             $sequence  = $sequence ?: (isset($options['sequence']) ? $options['sequence'] : null);
             $lastInsId = $this->getLastInsID($sequence);
@@ -2166,10 +2338,10 @@ class Query
             return $this->connection->getRealSql($sql, $bind);
         } elseif (is_array($sql)) {
             // 执行操作
-            return $this->batchQuery($sql, $bind);
+            return $this->batchQuery($sql, $bind, $this);
         } else {
             // 执行操作
-            return $this->execute($sql, $bind);
+            return $this->execute($sql, $bind, $this);
         }
     }
 
@@ -2195,7 +2367,7 @@ class Query
             return $this->connection->getRealSql($sql, $bind);
         } else {
             // 执行操作
-            return $this->execute($sql, $bind);
+            return $this->execute($sql, $bind, $this);
         }
     }
 
@@ -2262,7 +2434,7 @@ class Query
                 Cache::clear($options['cache']['tag']);
             }
             // 执行操作
-            $result = '' == $sql ? 0 : $this->execute($sql, $bind);
+            $result = '' == $sql ? 0 : $this->execute($sql, $bind, $this);
             if ($result) {
                 if (is_string($pk) && isset($where[$pk])) {
                     $data[$pk] = $where[$pk];
@@ -2436,8 +2608,12 @@ class Query
 
         if (isset($data)) {
             return 'think:' . $prefix . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data;
-        } else {
+        }
+
+        try {
             return md5($prefix . serialize($options) . serialize($bind));
+        } catch (\Exception $e) {
+            throw new Exception('closure not support cache(true)');
         }
     }
 
@@ -2730,7 +2906,7 @@ class Query
             Cache::clear($options['cache']['tag']);
         }
         // 执行操作
-        $result = $this->execute($sql, $bind);
+        $result = $this->execute($sql, $bind, $this);
         if ($result) {
             if (!is_array($data) && is_string($pk) && isset($key) && strpos($key, '|')) {
                 list($a, $val) = explode('|', $key);
@@ -2815,6 +2991,10 @@ class Query
             }
         }
 
+        if (isset(static::$readMaster['*']) || (is_string($options['table']) && isset(static::$readMaster[$options['table']]))) {
+            $options['master'] = true;
+        }
+
         foreach (['join', 'union', 'group', 'having', 'limit', 'order', 'force', 'comment'] as $name) {
             if (!isset($options[$name])) {
                 $options[$name] = '';

+ 14 - 4
thinkphp/library/think/db/builder/Mysql.php

@@ -82,17 +82,23 @@ class Mysql extends Builder
     /**
      * 字段和表名处理
      * @access protected
-     * @param string $key
+     * @param mixed  $key
      * @param array  $options
      * @return string
      */
-    protected function parseKey($key, $options = [])
+    protected function parseKey($key, $options = [], $strict = false)
     {
+        if (is_numeric($key)) {
+            return $key;
+        } elseif ($key instanceof Expression) {
+            return $key->getValue();
+        }
+
         $key = trim($key);
         if (strpos($key, '$.') && false === strpos($key, '(')) {
             // JSON字段支持
             list($field, $name) = explode('$.', $key);
-            $key                = 'json_extract(' . $field . ', \'$.' . $name . '\')';
+            return 'json_extract(' . $field . ', \'$.' . $name . '\')';
         } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) {
             list($table, $key) = explode('.', $key, 2);
             if ('__TABLE__' == $table) {
@@ -102,7 +108,11 @@ class Mysql extends Builder
                 $table = $options['alias'][$table];
             }
         }
-        if (!preg_match('/[,\'\"\*\(\)`.\s]/', $key)) {
+
+        if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) {
+            throw new Exception('not support data:' . $key);
+        }
+        if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key))) {
             $key = '`' . $key . '`';
         }
         if (isset($table)) {

+ 8 - 2
thinkphp/library/think/db/builder/Pgsql.php

@@ -44,12 +44,18 @@ class Pgsql extends Builder
     /**
      * 字段和表名处理
      * @access protected
-     * @param string $key
+     * @param mixed  $key
      * @param array  $options
      * @return string
      */
-    protected function parseKey($key, $options = [])
+    protected function parseKey($key, $options = [], $strict = false)
     {
+        if (is_numeric($key)) {
+            return $key;
+        } elseif ($key instanceof Expression) {
+            return $key->getValue();
+        }
+
         $key = trim($key);
         if (strpos($key, '$.') && false === strpos($key, '(')) {
             // JSON字段支持

+ 8 - 2
thinkphp/library/think/db/builder/Sqlite.php

@@ -52,12 +52,18 @@ class Sqlite extends Builder
     /**
      * 字段和表名处理
      * @access protected
-     * @param string $key
+     * @param mixed  $key
      * @param array  $options
      * @return string
      */
-    protected function parseKey($key, $options = [])
+    protected function parseKey($key, $options = [], $strict = false)
     {
+        if (is_numeric($key)) {
+            return $key;
+        } elseif ($key instanceof Expression) {
+            return $key->getValue();
+        }
+
         $key = trim($key);
         if (strpos($key, '.')) {
             list($table, $key) = explode('.', $key, 2);

+ 32 - 18
thinkphp/library/think/db/builder/Sqlsrv.php

@@ -12,6 +12,7 @@
 namespace think\db\builder;
 
 use think\db\Builder;
+use think\db\Expression;
 
 /**
  * Sqlsrv数据库驱动
@@ -34,25 +35,29 @@ class Sqlsrv extends Builder
      */
     protected function parseOrder($order, $options = [])
     {
-        if (is_array($order)) {
-            $array = [];
-            foreach ($order as $key => $val) {
-                if (is_numeric($key)) {
-                    if (false === strpos($val, '(')) {
-                        $array[] = $this->parseKey($val, $options);
-                    } elseif ('[rand]' == $val) {
-                        $array[] = $this->parseRand();
-                    } else {
-                        $array[] = $val;
-                    }
+        if (empty($order)) {
+            return ' ORDER BY rand()';
+        }
+
+        $array = [];
+        foreach ($order as $key => $val) {
+            if ($val instanceof Expression) {
+                $array[] = $val->getValue();
+            } elseif (is_numeric($key)) {
+                if (false === strpos($val, '(')) {
+                    $array[] = $this->parseKey($val, $options);
+                } elseif ('[rand]' == $val) {
+                    $array[] = $this->parseRand();
                 } else {
-                    $sort    = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : '';
-                    $array[] = $this->parseKey($key, $options) . ' ' . $sort;
+                    $array[] = $val;
                 }
+            } else {
+                $sort    = in_array(strtolower(trim($val)), ['asc', 'desc'], true) ? ' ' . $val : '';
+                $array[] = $this->parseKey($key, $options, true) . ' ' . $sort;
             }
-            $order = implode(',', $array);
         }
-        return !empty($order) ? ' ORDER BY ' . $order : ' ORDER BY rand()';
+
+        return ' ORDER BY ' . implode(',', $array);
     }
 
     /**
@@ -68,12 +73,17 @@ class Sqlsrv extends Builder
     /**
      * 字段和表名处理
      * @access protected
-     * @param string $key
+     * @param mixed  $key
      * @param array  $options
      * @return string
      */
-    protected function parseKey($key, $options = [])
+    protected function parseKey($key, $options = [], $strict = false)
     {
+        if (is_numeric($key)) {
+            return $key;
+        } elseif ($key instanceof Expression) {
+            return $key->getValue();
+        }
         $key = trim($key);
         if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) {
             list($table, $key) = explode('.', $key, 2);
@@ -84,7 +94,11 @@ class Sqlsrv extends Builder
                 $table = $options['alias'][$table];
             }
         }
-        if (!is_numeric($key) && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) {
+
+        if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) {
+            throw new Exception('not support data:' . $key);
+        }
+        if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)\[.\s]/', $key))) {
             $key = '[' . $key . ']';
         }
         if (isset($table)) {

+ 4 - 1
thinkphp/library/think/db/connector/Sqlsrv.php

@@ -50,7 +50,10 @@ class Sqlsrv extends Connection
     public function getFields($tableName)
     {
         list($tableName) = explode(' ', $tableName);
-        $sql             = "SELECT   column_name,   data_type,   column_default,   is_nullable
+        $tableNames      = explode('.', $tableName);
+        $tableName       = isset($tableNames[1]) ? $tableNames[1] : $tableNames[0];
+
+        $sql = "SELECT   column_name,   data_type,   column_default,   is_nullable
         FROM    information_schema.tables AS t
         JOIN    information_schema.columns AS c
         ON  t.table_catalog = c.table_catalog

+ 190 - 66
thinkphp/library/think/log/driver/File.php

@@ -26,10 +26,9 @@ class File
         'path'        => LOG_PATH,
         'apart_level' => [],
         'max_files'   => 0,
+        'json'        => false,
     ];
 
-    protected $writed = [];
-
     // 实例化并传入参数
     public function __construct($config = [])
     {
@@ -41,106 +40,231 @@ class File
     /**
      * 日志写入接口
      * @access public
-     * @param array $log 日志信息
+     * @param  array    $log 日志信息
+     * @param  bool     $append 是否追加请求信息
      * @return bool
      */
-    public function save(array $log = [])
+    public function save(array $log = [], $append = false)
+    {
+        $destination = $this->getMasterLogFile();
+
+        $path = dirname($destination);
+        !is_dir($path) && mkdir($path, 0755, true);
+
+        $info = [];
+        foreach ($log as $type => $val) {
+
+            foreach ($val as $msg) {
+                if (!is_string($msg)) {
+                    $msg = var_export($msg, true);
+                }
+
+                $info[$type][] = $this->config['json'] ? $msg : '[ ' . $type . ' ] ' . $msg;
+            }
+
+            if (!$this->config['json'] && (true === $this->config['apart_level'] || in_array($type, $this->config['apart_level']))) {
+                // 独立记录的日志级别
+                $filename = $this->getApartLevelFile($path, $type);
+
+                $this->write($info[$type], $filename, true, $append);
+                unset($info[$type]);
+            }
+        }
+
+        if ($info) {
+            return $this->write($info, $destination, false, $append);
+        }
+
+        return true;
+    }
+
+    /**
+     * 获取主日志文件名
+     * @access public
+     * @return string
+     */
+    protected function getMasterLogFile()
     {
         if ($this->config['single']) {
-            $destination = $this->config['path'] . 'single.log';
+            $name = is_string($this->config['single']) ? $this->config['single'] : 'single';
+
+            $destination = $this->config['path'] . $name . '.log';
         } else {
-            $cli = IS_CLI ? '_cli' : '';
+            $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]);
+                try {
+                    if (count($files) > $this->config['max_files']) {
+                        unlink($files[0]);
+                    }
+                } catch (\Exception $e) {
                 }
             } else {
-                $filename = date('Ym') . '/' . date('d') . $cli . '.log';
+                $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . $cli . '.log';
             }
 
             $destination = $this->config['path'] . $filename;
         }
 
-        $path = dirname($destination);
-        !is_dir($path) && mkdir($path, 0755, true);
+        return $destination;
+    }
 
-        $info = '';
-        foreach ($log as $type => $val) {
-            $level = '';
-            foreach ($val as $msg) {
-                if (!is_string($msg)) {
-                    $msg = var_export($msg, true);
-                }
-                $level .= '[ ' . $type . ' ] ' . $msg . "\r\n";
-            }
-            if (in_array($type, $this->config['apart_level'])) {
-                // 独立记录的日志级别
-                if ($this->config['single']) {
-                    $filename = $path . DS . $type . '.log';
-                } elseif ($this->config['max_files']) {
-                    $filename = $path . DS . date('Ymd') . '_' . $type . $cli . '.log';
-                } else {
-                    $filename = $path . DS . date('d') . '_' . $type . $cli . '.log';
-                }
-                $this->write($level, $filename, true);
-            } else {
-                $info .= $level;
-            }
+    /**
+     * 获取独立日志文件名
+     * @access public
+     * @param  string $path 日志目录
+     * @param  string $type 日志类型
+     * @return string
+     */
+    protected function getApartLevelFile($path, $type)
+    {
+        $cli = PHP_SAPI == 'cli' ? '_cli' : '';
+
+        if ($this->config['single']) {
+            $name = is_string($this->config['single']) ? $this->config['single'] : 'single';
+
+            $name .= '_' . $type;
+        } elseif ($this->config['max_files']) {
+            $name = date('Ymd') . '_' . $type . $cli;
+        } else {
+            $name = date('d') . '_' . $type . $cli;
         }
-        if ($info) {
-            return $this->write($info, $destination);
+
+        return $path . DIRECTORY_SEPARATOR . $name . '.log';
+    }
+
+    /**
+     * 日志写入
+     * @access protected
+     * @param  array     $message 日志信息
+     * @param  string    $destination 日志文件
+     * @param  bool      $apart 是否独立文件写入
+     * @param  bool      $append 是否追加请求信息
+     * @return bool
+     */
+    protected function write($message, $destination, $apart = false, $append = false)
+    {
+        // 检测日志文件大小,超过配置大小则备份日志文件重新生成
+        $this->checkLogSize($destination);
+
+        // 日志信息封装
+        $info['timestamp'] = date($this->config['time_format']);
+
+        foreach ($message as $type => $msg) {
+            $info[$type] = is_array($msg) ? implode("\r\n", $msg) : $msg;
         }
-        return true;
+
+        if (PHP_SAPI == 'cli') {
+            $message = $this->parseCliLog($info);
+        } else {
+            // 添加调试日志
+            $this->getDebugLog($info, $append, $apart);
+
+            $message = $this->parseLog($info);
+        }
+
+        return error_log($message, 3, $destination);
     }
 
-    protected function write($message, $destination, $apart = false)
+    /**
+     * 检查日志文件大小并自动生成备份文件
+     * @access protected
+     * @param  string    $destination 日志文件
+     * @return void
+     */
+    protected function checkLogSize($destination)
     {
-        //检测日志文件大小,超过配置大小则备份日志文件重新生成
         if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) {
             try {
-                rename($destination, dirname($destination) . DS . time() . '-' . basename($destination));
+                rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination));
             } catch (\Exception $e) {
             }
-            $this->writed[$destination] = false;
         }
+    }
+
+    /**
+     * CLI日志解析
+     * @access protected
+     * @param  array     $info 日志信息
+     * @return string
+     */
+    protected function parseCliLog($info)
+    {
+        if ($this->config['json']) {
+            $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n";
+        } else {
+            $now = $info['timestamp'];
+            unset($info['timestamp']);
+
+            $message = implode("\r\n", $info);
+
+            $message = "[{$now}]" . $message . "\r\n";
+        }
+
+        return $message;
+    }
+
+    /**
+     * 解析日志
+     * @access protected
+     * @param  array     $info 日志信息
+     * @return string
+     */
+    protected function parseLog($info)
+    {
+        $request     = Request::instance();
+        $requestInfo = [
+            'ip'     => $request->ip(),
+            'method' => $request->method(),
+            'host'   => $request->host(),
+            'uri'    => $request->url(),
+        ];
+
+        if ($this->config['json']) {
+            $info = $requestInfo + $info;
+            return json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n";
+        }
+
+        array_unshift($info, "---------------------------------------------------------------\r\n[{$info['timestamp']}] {$requestInfo['ip']} {$requestInfo['method']} {$requestInfo['host']}{$requestInfo['uri']}");
+        unset($info['timestamp']);
+
+        return implode("\r\n", $info) . "\r\n";
+    }
+
+    protected function getDebugLog(&$info, $append, $apart)
+    {
+        if (App::$debug && $append) {
 
-        if (empty($this->writed[$destination]) && !IS_CLI) {
-            if (App::$debug && !$apart) {
+            if ($this->config['json']) {
                 // 获取基本信息
-                if (isset($_SERVER['HTTP_HOST'])) {
-                    $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
-                } else {
-                    $current_uri = "cmd:" . implode(' ', $_SERVER['argv']);
-                }
+                $runtime = round(microtime(true) - THINK_START_TIME, 10);
+                $reqs    = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞';
 
-                $runtime    = round(microtime(true) - THINK_START_TIME, 10);
-                $reqs       = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞';
-                $time_str   = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]';
                 $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2);
+
+                $info = [
+                    'runtime' => number_format($runtime, 6) . 's',
+                    'reqs'    => $reqs . 'req/s',
+                    'memory'  => $memory_use . 'kb',
+                    'file'    => count(get_included_files()),
+                ] + $info;
+
+            } elseif (!$apart) {
+                // 增加额外的调试信息
+                $runtime = round(microtime(true) - THINK_START_TIME, 10);
+                $reqs    = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞';
+
+                $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2);
+
+                $time_str   = '[运行时间:' . number_format($runtime, 6) . 's] [吞吐率:' . $reqs . 'req/s]';
                 $memory_str = ' [内存消耗:' . $memory_use . 'kb]';
                 $file_load  = ' [文件加载:' . count(get_included_files()) . ']';
 
-                $message = '[ info ] ' . $current_uri . $time_str . $memory_str . $file_load . "\r\n" . $message;
+                array_unshift($info, $time_str . $memory_str . $file_load);
             }
-            $now     = date($this->config['time_format']);
-            $ip      = Request::instance()->ip();
-            $method  = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI';
-            $uri     = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
-            $message = "---------------------------------------------------------------\r\n[{$now}] {$ip} {$method} {$uri}\r\n" . $message;
-
-            $this->writed[$destination] = true;
-        }
-
-        if (IS_CLI) {
-            $now     = date($this->config['time_format']);
-            $message = "[{$now}]" . $message;
         }
-
-        return error_log($message, 3, $destination);
     }
-
 }

+ 1 - 1
thinkphp/library/think/log/driver/Socket.php

@@ -60,7 +60,7 @@ class Socket
      * @param array     $log 日志信息
      * @return bool
      */
-    public function save(array $log = [])
+    public function save(array $log = [], $append = false)
     {
         if (!$this->check()) {
             return false;

+ 68 - 8
thinkphp/library/think/model/relation/BelongsToMany.php

@@ -12,6 +12,7 @@
 namespace think\model\relation;
 
 use think\Collection;
+use think\Db;
 use think\db\Query;
 use think\Exception;
 use think\Loader;
@@ -28,6 +29,8 @@ class BelongsToMany extends Relation
     protected $pivotName;
     // 中间表模型对象
     protected $pivot;
+    // 中间表数据名称
+    protected $pivotDataName = 'pivot';
 
     /**
      * 构造函数
@@ -70,17 +73,43 @@ class BelongsToMany extends Relation
     }
 
     /**
-     * 实例化中间表模型
+     * 设置中间表数据名称
+     * @access public
+     * @param  string $name
+     * @return $this
+     */
+    public function pivotDataName($name)
+    {
+        $this->pivotDataName = $name;
+        return $this;
+    }
+
+    /**
+     * 获取中间表更新条件
      * @param $data
+     * @return array
+     */
+    protected function getUpdateWhere($data)
+    {
+        return [
+            $this->localKey   => $data[$this->localKey],
+            $this->foreignKey => $data[$this->foreignKey],
+        ];
+    }
+
+    /**
+     * 实例化中间表模型
+     * @param  array    $data
+     * @param  bool     $isUpdate
      * @return Pivot
      * @throws Exception
      */
-    protected function newPivot($data = [])
+    protected function newPivot($data = [], $isUpdate = false)
     {
         $class = $this->pivotName ?: '\\think\\model\\Pivot';
         $pivot = new $class($data, $this->parent, $this->middle);
         if ($pivot instanceof Pivot) {
-            return $pivot;
+            return $isUpdate ? $pivot->isUpdate(true, $this->getUpdateWhere($data)) : $pivot;
         } else {
             throw new Exception('pivot model must extends: \think\model\Pivot');
         }
@@ -103,7 +132,7 @@ class BelongsToMany extends Relation
                     }
                 }
             }
-            $model->setRelation('pivot', $this->newPivot($pivot));
+            $model->setRelation($this->pivotDataName, $this->newPivot($pivot, true));
         }
     }
 
@@ -331,14 +360,22 @@ class BelongsToMany extends Relation
      * 获取关联统计子查询
      * @access public
      * @param \Closure $closure 闭包
+     * @param string   $name    统计数据别名
      * @return string
      */
-    public function getRelationCountQuery($closure)
+    public function getRelationCountQuery($closure, &$name = null)
     {
+        if ($closure) {
+            $return = call_user_func_array($closure, [ & $this->query]);
+            if ($return && is_string($return)) {
+                $name = $return;
+            }
+        }
+
         return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [
             'pivot.' . $this->localKey => [
                 'exp',
-                '=' . $this->parent->getTable() . '.' . $this->parent->getPk(),
+                Db::raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()),
             ],
         ])->fetchSql()->count();
     }
@@ -369,7 +406,7 @@ class BelongsToMany extends Relation
                     }
                 }
             }
-            $set->setRelation('pivot', $this->newPivot($pivot));
+            $set->setRelation($this->pivotDataName, $this->newPivot($pivot, true));
             $data[$pivot[$this->localKey]][] = $set;
         }
         return $data;
@@ -472,7 +509,7 @@ class BelongsToMany extends Relation
             foreach ($ids as $id) {
                 $pivot[$this->foreignKey] = $id;
                 $this->pivot->insert($pivot, true);
-                $result[] = $this->newPivot($pivot);
+                $result[] = $this->newPivot($pivot, true);
             }
             if (count($result) == 1) {
                 // 返回中间表模型对象
@@ -484,6 +521,29 @@ class BelongsToMany extends Relation
         }
     }
 
+    /**
+     * 判断是否存在关联数据
+     * @access public
+     * @param  mixed $data  数据 可以使用关联模型对象 或者 关联对象的主键
+     * @return Pivot
+     * @throws Exception
+     */
+    public function attached($data)
+    {
+        if ($data instanceof Model) {
+            $relationFk = $data->getPk();
+            $id         = $data->$relationFk;
+        } else {
+            $id = $data;
+        }
+
+        $pk = $this->parent->getPk();
+
+        $pivot = $this->pivot->where($this->localKey, $this->parent->$pk)->where($this->foreignKey, $id)->find();
+
+        return $pivot ?: false;
+    }
+
     /**
      * 解除关联的一个中间表数据
      * @access public

+ 28 - 10
thinkphp/library/think/model/relation/HasMany.php

@@ -143,7 +143,7 @@ class HasMany extends Relation
             if ($closure) {
                 call_user_func_array($closure, [ & $this->query]);
             }
-            $count = $this->query->where([$this->foreignKey => $result->$localKey])->count();
+            $count = $this->query->where($this->foreignKey, $result->$localKey)->count();
         }
         return $count;
     }
@@ -152,20 +152,19 @@ class HasMany extends Relation
      * 创建关联统计子查询
      * @access public
      * @param \Closure $closure 闭包
+     * @param string   $name    统计数据别名
      * @return string
      */
-    public function getRelationCountQuery($closure)
+    public function getRelationCountQuery($closure, &$name = null)
     {
         if ($closure) {
-            call_user_func_array($closure, [ & $this->query]);
+            $return = call_user_func_array($closure, [ & $this->query]);
+            if ($return && is_string($return)) {
+                $name = $return;
+            }
         }
         $localKey = $this->localKey ?: $this->parent->getPk();
-        return $this->query->where([
-            $this->foreignKey => [
-                'exp',
-                '=' . $this->parent->getTable() . '.' . $localKey,
-            ],
-        ])->fetchSql()->count();
+        return $this->query->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $localKey)->fetchSql()->count();
     }
 
     /**
@@ -206,12 +205,31 @@ class HasMany extends Relation
         if ($data instanceof Model) {
             $data = $data->getData();
         }
+
         // 保存关联表数据
-        $model                   = new $this->model;
         $data[$this->foreignKey] = $this->parent->{$this->localKey};
+
+        $model = new $this->model();
         return $model->save($data) ? $model : false;
     }
 
+    /**
+     * 创建关联对象实例
+     * @param array $data
+     * @return Model
+     */
+    public function make($data = [])
+    {
+        if ($data instanceof Model) {
+            $data = $data->getData();
+        }
+
+        // 保存关联表数据
+        $data[$this->foreignKey] = $this->parent->{$this->localKey};
+
+        return new $this->model($data);
+    }
+
     /**
      * 批量保存当前关联数据对象
      * @access public

+ 12 - 0
thinkphp/library/think/model/relation/HasManyThrough.php

@@ -120,6 +120,18 @@ class HasManyThrough extends Relation
     public function relationCount($result, $closure)
     {}
 
+    /**
+     * 创建关联统计子查询
+     * @access public
+     * @param \Closure $closure 闭包
+     * @param string   $name    统计数据别名
+     * @return string
+     */
+    public function getRelationCountQuery($closure, &$name = null)
+    {
+        throw new Exception('relation not support: withCount');
+    }
+
     /**
      * 执行基础查询(进执行一次)
      * @access protected

+ 34 - 6
thinkphp/library/think/model/relation/MorphMany.php

@@ -11,6 +11,7 @@
 
 namespace think\model\relation;
 
+use think\Db;
 use think\db\Query;
 use think\Exception;
 use think\Loader;
@@ -187,21 +188,25 @@ class MorphMany extends Relation
     }
 
     /**
-     * 获取关联统计子查询
+     * 创建关联统计子查询
      * @access public
      * @param \Closure $closure 闭包
+     * @param string   $name    统计数据别名
      * @return string
      */
-    public function getRelationCountQuery($closure)
+    public function getRelationCountQuery($closure, &$name = null)
     {
         if ($closure) {
-            call_user_func_array($closure, [ & $this->query]);
+            $return = call_user_func_array($closure, [ & $this->query]);
+            if ($return && is_string($return)) {
+                $name = $return;
+            }
         }
 
         return $this->query->where([
             $this->morphKey  => [
                 'exp',
-                '=' . $this->parent->getTable() . '.' . $this->parent->getPk(),
+                Db::raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()),
             ],
             $this->morphType => $this->type,
         ])->fetchSql()->count();
@@ -243,13 +248,36 @@ class MorphMany extends Relation
         if ($data instanceof Model) {
             $data = $data->getData();
         }
+
+        // 保存关联表数据
+        $pk = $this->parent->getPk();
+
+        $data[$this->morphKey]  = $this->parent->$pk;
+        $data[$this->morphType] = $this->type;
+
+        $model = new $this->model();
+
+        return $model->save() ? $model : false;
+    }
+
+    /**
+     * 创建关联对象实例
+     * @param array $data
+     * @return Model
+     */
+    public function make($data = [])
+    {
+        if ($data instanceof Model) {
+            $data = $data->getData();
+        }
+
         // 保存关联表数据
         $pk = $this->parent->getPk();
 
-        $model                  = new $this->model;
         $data[$this->morphKey]  = $this->parent->$pk;
         $data[$this->morphType] = $this->type;
-        return $model->save($data) ? $model : false;
+
+        return new $this->model($data);
     }
 
     /**

+ 37 - 4
thinkphp/library/think/model/relation/MorphOne.php

@@ -81,8 +81,8 @@ class MorphOne extends Relation
     /**
      * 根据关联条件查询当前模型
      * @access public
-     * @param  mixed  $where 查询条件(数组或者闭包)
-     * @param  mixed  $fields   字段
+     * @param  mixed $where  查询条件(数组或者闭包)
+     * @param  mixed $fields 字段
      * @return Query
      */
     public function hasWhere($where = [], $fields = null)
@@ -198,6 +198,28 @@ class MorphOne extends Relation
      * @return Model|false
      */
     public function save($data)
+    {
+        if ($data instanceof Model) {
+            $data = $data->getData();
+        }
+
+        // 保存关联表数据
+        $pk = $this->parent->getPk();
+
+        $data[$this->morphKey]  = $this->parent->$pk;
+        $data[$this->morphType] = $this->type;
+
+        $model = new $this->model();
+
+        return $model->save() ? $model : false;
+    }
+
+    /**
+     * 创建关联对象实例
+     * @param array $data
+     * @return Model
+     */
+    public function make($data = [])
     {
         if ($data instanceof Model) {
             $data = $data->getData();
@@ -205,10 +227,10 @@ class MorphOne extends Relation
         // 保存关联表数据
         $pk = $this->parent->getPk();
 
-        $model                  = new $this->model;
         $data[$this->morphKey]  = $this->parent->$pk;
         $data[$this->morphType] = $this->type;
-        return $model->save($data) ? $model : false;
+
+        return new $this->model($data);
     }
 
     /**
@@ -227,4 +249,15 @@ class MorphOne extends Relation
         }
     }
 
+    /**
+     * 创建关联统计子查询
+     * @access public
+     * @param \Closure $closure 闭包
+     * @param string   $name    统计数据别名
+     * @return string
+     */
+    public function getRelationCountQuery($closure, &$name = null)
+    {
+        throw new Exception('relation not support: withCount');
+    }
 }

+ 12 - 1
thinkphp/library/think/model/relation/MorphTo.php

@@ -105,7 +105,7 @@ class MorphTo extends Relation
 
     /**
      * 解析模型的完整命名空间
-     * @access public
+     * @access protected
      * @param string $model 模型名(或者完整类名)
      * @return string
      */
@@ -285,4 +285,15 @@ class MorphTo extends Relation
         return $this->parent->setRelation($this->relation, null);
     }
 
+    /**
+     * 创建关联统计子查询
+     * @access public
+     * @param \Closure $closure 闭包
+     * @param string   $name    统计数据别名
+     * @return string
+     */
+    public function getRelationCountQuery($closure, &$name = null)
+    {
+        throw new Exception('relation not support: withCount');
+    }
 }

+ 11 - 0
thinkphp/library/think/model/relation/OneToOne.php

@@ -323,4 +323,15 @@ abstract class OneToOne extends Relation
         return $data;
     }
 
+    /**
+     * 创建关联统计子查询
+     * @access public
+     * @param \Closure $closure 闭包
+     * @param string   $name    统计数据别名
+     * @return string
+     */
+    public function getRelationCountQuery($closure, &$name = null)
+    {
+        throw new Exception('relation not support: withCount');
+    }
 }

+ 4 - 1
thinkphp/library/think/template/driver/File.php

@@ -15,6 +15,8 @@ use think\Exception;
 
 class File
 {
+    protected $cacheFile;
+
     /**
      * 写入编译缓存
      * @param string $cacheFile 缓存的文件名
@@ -42,12 +44,13 @@ class File
      */
     public function read($cacheFile, $vars = [])
     {
+        $this->cacheFile = $cacheFile;
         if (!empty($vars) && is_array($vars)) {
             // 模板阵列变量分解成为独立变量
             extract($vars, EXTR_OVERWRITE);
         }
         //载入模版缓存文件
-        include $cacheFile;
+        include $this->cacheFile;
     }
 
     /**

+ 13 - 17
thinkphp/library/think/view/driver/Php.php

@@ -29,7 +29,11 @@ class Php
         'view_suffix' => 'php',
         // 模板文件名分隔符
         'view_depr'   => DS,
+        // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写
+        'auto_rule'   => 1,
     ];
+    protected $template;
+    protected $content;
 
     public function __construct($config = [])
     {
@@ -68,16 +72,12 @@ class Php
         if (!is_file($template)) {
             throw new TemplateNotFoundException('template not exists:' . $template, $template);
         }
+        $this->template = $template;
         // 记录视图信息
         App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info');
-        if (isset($data['template'])) {
-            $__template__ = $template;
-            extract($data, EXTR_OVERWRITE);
-            include $__template__;
-        } else {
-            extract($data, EXTR_OVERWRITE);
-            include $template;
-        }
+
+        extract($data, EXTR_OVERWRITE);
+        include $this->template;
     }
 
     /**
@@ -89,14 +89,10 @@ class Php
      */
     public function display($content, $data = [])
     {
-        if (isset($data['content'])) {
-            $__content__ = $content;
-            extract($data, EXTR_OVERWRITE);
-            eval('?>' . $__content__);
-        } else {
-            extract($data, EXTR_OVERWRITE);
-            eval('?>' . $content);
-        }
+        $this->content = $content;
+
+        extract($data, EXTR_OVERWRITE);
+        eval('?>' . $this->content);
     }
 
     /**
@@ -132,7 +128,7 @@ class Php
             if ($controller) {
                 if ('' == $template) {
                     // 如果模板文件名为空 按照默认规则定位
-                    $template = str_replace('.', DS, $controller) . $depr . $request->action();
+                    $template = str_replace('.', DS, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action());
                 } elseif (false === strpos($template, $depr)) {
                     $template = str_replace('.', DS, $controller) . $depr . $template;
                 }

+ 3 - 1
thinkphp/library/think/view/driver/Think.php

@@ -34,6 +34,8 @@ class Think
         'view_depr'   => DS,
         // 是否开启模板编译缓存,设为false则每次都会重新编译
         'tpl_cache'   => true,
+        // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写
+        'auto_rule'   => 1,
     ];
 
     public function __construct($config = [])
@@ -127,7 +129,7 @@ class Think
             if ($controller) {
                 if ('' == $template) {
                     // 如果模板文件名为空 按照默认规则定位
-                    $template = str_replace('.', DS, $controller) . $depr . $request->action();
+                    $template = str_replace('.', DS, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action());
                 } elseif (false === strpos($template, $depr)) {
                     $template = str_replace('.', DS, $controller) . $depr . $template;
                 }

+ 2 - 1
thinkphp/library/traits/model/SoftDelete.php

@@ -2,6 +2,7 @@
 
 namespace traits\model;
 
+use think\Collection;
 use think\db\Query;
 use think\Model;
 
@@ -111,7 +112,7 @@ trait SoftDelete
         }
 
         // 包含软删除数据
-        $query = self::withTrashed();
+        $query = (new static())->db(false);
         if (is_array($data) && key($data) !== 0) {
             $query->where($data);
             $data = null;

+ 5 - 5
thinkphp/tpl/think_exception.tpl

@@ -79,7 +79,7 @@
 <html>
 <head>
     <meta charset="UTF-8">
-    <title>404页面</title>
+    <title><?php echo \think\Lang::get('System Error'); ?></title>
     <meta name="robots" content="noindex,nofollow" />
     <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
     <style>
@@ -439,11 +439,11 @@
     </div>
     <?php } ?>
 
-    <!-- <div class="copyright">
+    <div class="copyright">
         <a title="官方网站" href="http://www.thinkphp.cn">ThinkPHP</a> 
-        <span>V<?php //echo THINK_VERSION; ?></span> 
-        <span>{ 十年磨一剑-为API开发设计11111的高性能框架 }</span>
-    </div> -->
+        <span>V<?php echo THINK_VERSION; ?></span> 
+        <span>{ 十年磨一剑-为API开发设计的高性能框架 }</span>
+    </div>
     <?php if(\think\App::$debug) { ?>
     <script>
         var LINE = <?php echo $line; ?>;