Merge remote-tracking branch 'origin/master'

This commit is contained in:
zhaoxiang 2018-05-08 18:10:10 +08:00
commit dd1b2a2ce2
30 changed files with 458 additions and 231 deletions

View File

@ -3,9 +3,9 @@
# ApiAdmin
[![ApiAdmin](https://img.shields.io/hexpm/l/plug.svg)](http://www.apiadmin.org/)
[![ApiAdmin](https://img.shields.io/badge/release-v3.0.7-blue.svg)](http://www.apiadmin.org/)
[![ApiAdmin](https://img.shields.io/badge/release-v3.0.8-blue.svg)](http://www.apiadmin.org/)
[![ApiAdmin](https://img.shields.io/wercker/ci/wercker/docs.svg)](http://www.apiadmin.org/)
[![ApiAdmin](https://img.shields.io/badge/ApiAdmin-v3.0.7-brightgreen.svg)](http://www.apiadmin.org/)
[![ApiAdmin](https://img.shields.io/badge/ApiAdmin-v3.0.8-brightgreen.svg)](http://www.apiadmin.org/)
## 前端页面
ApiAdmin3.0是一个前后端完全分离的项目前端采用Vue构建如需要可视化配置的请移步[ApiAdmin-WEB](https://gitee.com/apiadmin/ApiAdmin-WEB)
@ -31,7 +31,7 @@ ApiAdmin3.0是一个前后端完全分离的项目前端采用Vue构建
**项目构成**
- ThinkPHP v5.0.16
- ThinkPHP v5.0.19
- Vue 2.0
- semanticUI
- ...
@ -44,8 +44,7 @@ ApiAdmin3.0是一个前后端完全分离的项目前端采用Vue构建
4. 灵活的参数规则设定
5. 支持三方Api无缝融合
6. 本地二次开发友好
7. 使用Datatables完成数据JS加载
8. ...
7. ...
```
ApiAdminPHP部分

2
application/config.php Executable file → Normal file
View File

@ -191,7 +191,7 @@ return [
// 缓存保存目录
'path' => CACHE_PATH,
// 缓存前缀
'prefix' => 'ApiAdmin:',
'prefix' => 'ApiAdmin_',
// 缓存有效期 0表示永久缓存
'expire' => 0,
],

View File

@ -1,8 +0,0 @@
<?php
/**
* Api路由
*/
use think\Route;
Route::miss('api/Index/index');
$afterBehavior = ['\app\api\behavior\ApiAuth', '\app\api\behavior\ApiPermission', '\app\api\behavior\RequestFilter'];

View File

@ -287,7 +287,7 @@ LOCK TABLES `admin_user` WRITE;
INSERT INTO `admin_user` (`id`, `username`, `nickname`, `password`, `regTime`, `regIp`, `updateTime`, `status`, `openId`)
VALUES
(1,'root','root','953dbb5a8a45ae6000e30f29d78dcc68',1519453594,3663623043,1520173599,1,NULL);
(1,'root','root','912601e4ad1b308c9ae41877cf6ca754',1519453594,3663623043,1520173599,1,NULL);
/*!40000 ALTER TABLE `admin_user` ENABLE KEYS */;
UNLOCK TABLES;

View File

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

View File

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

View File

@ -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);
}
}

View File

@ -66,6 +66,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' => '未知上传错误!',

View File

@ -555,7 +555,11 @@ class App
// 获取操作名
$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);
@ -581,6 +585,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'];

View File

@ -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);
}

View File

@ -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;
}
}
}

View File

@ -116,6 +116,12 @@ abstract class Model implements \JsonSerializable, \ArrayAccess
*/
protected static $initialized = [];
/**
* 是否从主库读取(主从分布式有效)
* @var array
*/
protected static $readMaster;
/**
* 构造方法
* @access public
@ -171,6 +177,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 +214,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);

View File

@ -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;
}
}

View File

@ -232,7 +232,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'];
@ -1093,7 +1093,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 其他安全过滤
@ -1273,7 +1273,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 +1342,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;
}
/**
@ -1466,11 +1474,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);
}
}

View File

@ -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 {

View File

@ -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];
@ -1506,7 +1506,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'])) {
@ -1527,9 +1527,10 @@ class Route
* 解析URL地址为 模块/控制器/操作
* @access private
* @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,7 +1544,7 @@ class Route
// 设置当前请求的路由变量
Request::instance()->route($var);
// 路由到模块/控制器/操作
return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => false];
return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => $convert];
}
/**

View File

@ -239,7 +239,7 @@ class Url
$rootDomain = Config::get('url_domain_root');
if (true === $domain) {
// 自动判断域名
$domain = Config::get('app_host') ?: $request->host();
$domain = Config::get('app_host') ?: $request->host(true);
$domains = Route::rules('domain');
if ($domains) {

View File

@ -11,7 +11,6 @@
namespace think\db;
use BadMethodCallException;
use PDO;
use think\Exception;
@ -99,8 +98,11 @@ abstract class Builder
$result = [];
foreach ($data as $key => $val) {
$item = $this->parseKey($key, $options);
if (is_object($val) && method_exists($val, '__toString')) {
$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();
}
@ -112,18 +114,11 @@ abstract class Builder
$result[$item] = 'NULL';
} elseif (is_array($val) && !empty($val)) {
switch ($val[0]) {
case 'exp':
$result[$item] = $val[1];
break;
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;
}
} elseif (is_scalar($val)) {
@ -147,7 +142,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 +183,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,6 +265,10 @@ abstract class Builder
foreach ($where as $key => $val) {
$str = [];
foreach ($val as $field => $value) {
if ($value instanceof Expression) {
$str[] = ' ' . $key . ' ( ' . $value->getValue() . ' )';
continue;
}
if ($value instanceof \Closure) {
// 使用闭包查询
$query = new Query($this->connection);
@ -309,7 +310,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 +349,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 +388,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 +510,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 +545,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 +571,29 @@ abstract class Builder
*/
protected function parseOrder($order, $options = [])
{
if (is_array($order)) {
$array = [];
foreach ($order as $key => $val) {
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;
}
} else {
$sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : '';
$array[] = $this->parseKey($key, $options) . ' ' . $sort;
}
}
$order = implode(',', $array);
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)) {
list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' ');
} else {
$sort = $val;
}
$sort = strtoupper($sort);
$sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : '';
$array[] = $this->parseKey($key, $options, true) . $sort;
}
}
$order = implode(',', $array);
return !empty($order) ? ' ORDER BY ' . $order : '';
}
@ -612,6 +627,9 @@ abstract class Builder
*/
protected function parseComment($comment)
{
if (false !== strpos($comment, '*/')) {
$comment = strstr($coment, '*/', true);
}
return !empty($comment) ? ' /* ' . $comment . ' */' : '';
}
@ -661,11 +679,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 +797,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($query, $field, true);
}
return str_replace(
['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'],
[

View File

@ -90,6 +90,8 @@ abstract class Connection
'master_num' => 1,
// 指定从服务器序号
'slave_no' => '',
// 模型写入后自动读取主服务器
'read_master' => false,
// 是否严格检查字段是否存在
'fields_strict' => true,
// 数据返回类型
@ -378,7 +380,7 @@ abstract class Connection
// 执行查询
$this->PDOStatement->execute();
// 调试结束
$this->debug(false);
$this->debug(false, '', $master);
// 返回结果集
return $this->getResult($pdo, $procedure);
} catch (\PDOException $e) {
@ -402,13 +404,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) {
@ -445,23 +448,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;
}
@ -744,7 +751,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 +760,7 @@ abstract class Connection
$this->startTrans();
try {
foreach ($sqlArray as $sql) {
$this->execute($sql, $bind);
$this->execute($sql, $bind, $query);
}
// 提交事务
$this->commit();
@ -904,9 +911,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 +931,7 @@ abstract class Connection
$result = $this->getExplain($sql);
}
// SQL监听
$this->trigger($sql, $runtime, $result);
$this->trigger($sql, $runtime, $result, $master);
}
}
}
@ -945,19 +953,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');
}

View File

@ -53,6 +53,8 @@ class Query
protected static $info = [];
// 回调事件
private static $event = [];
// 读取主库
private static $readMaster = [];
/**
* 构造函数
@ -140,6 +142,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 +259,7 @@ class Query
*/
public function execute($sql, $bind = [])
{
return $this->connection->execute($sql, $bind);
return $this->connection->execute($sql, $bind, $this);
}
/**
@ -609,7 +630,7 @@ class Query
return true;
}
}
return $this->setField($field, ['inc', $field, $step]);
return $this->setField($field, ['inc', $step]);
}
/**
@ -637,9 +658,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 +790,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 +828,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 +874,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 +890,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 +904,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 +1032,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 +1209,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 +1251,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 +1281,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 +1520,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 (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 (!isset($this->options['order'])) {
$this->options['order'] = [];
}
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;
}
@ -2100,7 +2225,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 +2291,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 +2320,7 @@ class Query
return $this->connection->getRealSql($sql, $bind);
} else {
// 执行操作
return $this->execute($sql, $bind);
return $this->execute($sql, $bind, $this);
}
}
@ -2262,7 +2387,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 +2561,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 +2859,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 +2944,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] = '';

View File

@ -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,8 @@ class Mysql extends Builder
$table = $options['alias'][$table];
}
}
if (!preg_match('/[,\'\"\*\(\)`.\s]/', $key)) {
if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key))) {
$key = '`' . $key . '`';
}
if (isset($table)) {

View File

@ -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字段支持

View File

@ -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);

View File

@ -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;
}
} else {
$sort = in_array(strtolower(trim($val)), ['asc', 'desc']) ? ' ' . $val : '';
$array[] = $this->parseKey($key, $options) . ' ' . $sort;
}
}
$order = implode(',', $array);
if (empty($order)) {
return ' ORDER BY rand()';
}
return !empty($order) ? ' ORDER BY ' . $order : ' 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 {
$array[] = $val;
}
} else {
$sort = in_array(strtolower(trim($val)), ['asc', 'desc'], true) ? ' ' . $val : '';
$array[] = $this->parseKey($key, $options, true) . ' ' . $sort;
}
}
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,7 @@ class Sqlsrv extends Builder
$table = $options['alias'][$table];
}
}
if (!is_numeric($key) && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) {
if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)\[.\s]/', $key))) {
$key = '[' . $key . ']';
}
if (isset($table)) {

View File

@ -12,6 +12,7 @@
namespace think\model\relation;
use think\Collection;
use think\Db;
use think\db\Query;
use think\Exception;
use think\Loader;
@ -338,7 +339,7 @@ class BelongsToMany extends Relation
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();
}

View File

@ -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;
}
@ -160,12 +160,7 @@ class HasMany extends Relation
call_user_func_array($closure, [ & $this->query]);
}
$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();
}
/**

View File

@ -11,6 +11,7 @@
namespace think\model\relation;
use think\Db;
use think\db\Query;
use think\Exception;
use think\Loader;
@ -201,7 +202,7 @@ class MorphMany extends Relation
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();

View File

@ -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;
}
/**

View File

@ -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;
}

View File

@ -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;
}