Thinkphp5、6添加Mysql全文搜索功能

正文开始

https://blog.csdn.net/qq_15041159/article/details/108615959

原文件:

thinkphp\library\think\db\Query.php 2处修改。

//在1300行左右
protected function parseWhereExp($logic, $field, $op, $condition, $param = [], $strict = false)
    {
        $logic = strtoupper($logic);
        if ($field instanceof \Closure) {
            $this->options['where'][$logic][] = is_string($op) ? [$op, $field] : $field;
            return;
        }

        if (is_string($field) && !empty($this->options['via']) && !strpos($field, '.')) {
            $field = $this->options['via'] . '.' . $field;
        }

        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) && !in_array(strtolower($op),['match','match in boolean'])) {
            $where[] = ['exp', $this->raw($field)];
            if (is_array($op)) {
                // 参数绑定
                $this->bind($op);
            }
        } elseif (is_null($op) && is_null($condition)) {
            if (is_array($field)) {
                // 数组批量查询
                $where = $field;
                foreach ($where as $k => $val) {
                    $this->options['multi'][$logic][$k][] = $val;
                }
            } elseif ($field && is_string($field)) {
                // 字符串查询
                $where[$field]                            = ['null', ''];
                $this->options['multi'][$logic][$field][] = $where[$field];
            }
        } elseif (is_array($op)) {
            $where[$field] = $param;
        } elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) {
            // null查询
            $where[$field] = [$op, ''];

            $this->options['multi'][$logic][$field][] = $where[$field];
            //******修改:添加相应关键词的排除
        } elseif (is_null($condition)  && !in_array(strtolower($op),['match','match in boolean'])) {
            // 字段相等查询
            $where[$field] = ['eq', $op];
            $this->options['multi'][$logic][$field][] = $where[$field];
        } else {
            if ('exp' == strtolower($op)) {
                $where[$field] = ['exp', $this->raw($condition)];
                // 参数绑定
                if (isset($param[2]) && is_array($param[2])) {
                    $this->bind($param[2]);
                }
            } else {
                if(in_array(strtolower($op),['match','match in boolean'])){
                    $op = strtolower($op);
                }
                $where[$field] = [$op, $condition];
                //echo '$field'.$field.'op'.$op.'$condition'.$condition;
            }
            // 记录一个字段多次查询条件
            $this->options['multi'][$logic][$field][] = $where[$field];
        }

        if (!empty($where)) {
            if (!isset($this->options['where'][$logic])) {
                $this->options['where'][$logic] = [];
            }
            if (is_string($field) && $this->checkMultiField($field, $logic)) {
                $where[$field] = $this->options['multi'][$logic][$field];
            } elseif (is_array($field)) {
                foreach ($field as $key => $val) {
                    if ($this->checkMultiField($key, $logic)) {
                        $where[$key] = $this->options['multi'][$logic][$key];
                    }
                }
            }
            $this->options['where'][$logic] = array_merge($this->options['where'][$logic], $where);
        }
    }

原文件

thinkphp\library\think\db\Builder.php

//*****25行左右
//添加match、match in boolean两个条件关键词

    // 数据库表达式
    protected $exp = ['eq' => '=', 'neq' => '<>', 'gt' => '>', 'egt' => '>=',
        'lt' => '<', 'elt' => '<=', 'notlike' => 'NOT LIKE', 'not like' => 'NOT LIKE',
        'like' => 'LIKE', 'in' => 'IN', 'exp' => 'EXP', 'notin' => 'NOT IN',
        'not in' => 'NOT IN', 'between' => 'BETWEEN', 'not between' => 'NOT BETWEEN',
        'notbetween' => 'NOT BETWEEN', 'exists' => 'EXISTS', 'notexists' => 'NOT EXISTS',
        'not exists' => 'NOT EXISTS', 'null' => 'NULL', 'notnull' => 'NOT NULL',
        'not null' => 'NOT NULL', '> time' => '> TIME', '< time' => '< TIME',
        '>= time' => '>= TIME', '<= time' => '<= TIME', 'between time' => 'BETWEEN TIME',
        'not between time' => 'NOT BETWEEN TIME', 'notbetween time' => 'NOT BETWEEN TIME',
        'match' => 'MATCH','match in boolean' => 'MATCH IN BOOLEAN'];


//*****260行左右
//修改where表达式的拼接方案,适配无关键词的情况
//4处修改
    public function buildWhere($where, $options)
    {
        if (empty($where)) {
            $where = [];
        }

        if ($where instanceof Query) {
            return $this->buildWhere($where->getOptions('where'), $options);
        }

        $whereStr = '';
        $binds    = $this->query->getFieldsBind($options['table']);
        foreach ($where as $key => $val) {
            $str = [];
            foreach ($val as $field => $value) {
                if($value == ['like',0]){
                    echo json_encode($options);
                }
                if ($value instanceof Expression) {
                    $str[] = '( ' . $value->getValue() . ' )';
                } elseif ($value instanceof \Closure) {
                    // 使用闭包查询
                    $query = new Query($this->connection);
                    call_user_func_array($value, [ & $query]);
                    $whereClause = $this->buildWhere($query->getOptions('where'), $options);
                    if (!empty($whereClause)) {
                        $str[] = '( ' . $whereClause . ' )';
                    }
                } elseif (strpos($field, '|')) {
                    // 不同字段使用相同查询条件(OR)
                    $array = explode('|', $field);
                    $item  = [];
                    foreach ($array as $k) {
                        $item[] = $this->parseWhereItem($k, $value, '', $options, $binds);
                    }
                    $str[] = '( ' . implode(' OR ', $item) . ' )';
                } elseif (strpos($field, '&')) {
                    // 不同字段使用相同查询条件(AND)
                    $array = explode('&', $field);
                    $item  = [];
                    foreach ($array as $k) {
                        $item[] = $this->parseWhereItem($k, $value, '', $options, $binds);
                    }
                    $str[] = '( ' . implode(' AND ', $item) . ' )';
                } elseif (is_array($value) && in_array($value[0],['match','match in boolean'])){
                    if(!$value[1]){
                        $str[] = '';
                    }else {
                        $field = is_string($field) ? $field : '';
                        $str[] = $this->parseWhereItem($field, $value, $key, $options, $binds);
                    }
                }
                else {
                    // 对字段使用表达式查询
                    $field = is_string($field) ? $field : '';
                    $str[] = $this->parseWhereItem($field, $value, $key, $options, $binds);
                }
            }

        $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($key) + 1) : implode(' ', $str);

        return $whereStr;
    }

    // where子单元分析
    protected function parseWhereItem($field, $val, $rule = '', $options = [], $binds = [], $bindName = null)
    {
        // 查询规则和条件
        if (!is_array($val)) {
            $val = is_null($val) ? ['null', ''] : ['=', $val];
        }
        list($exp, $value) = $val;
        //=========== 修改:全文搜索跳过字段分析
        if(in_array($exp,['match','match in boolean'])) {
            $key = $field;
        }else{
            // 字段分析
            $key = $field ? $this->parseKey($field, $options, true) : '';
        }

        // 对一个字段使用多个查询条件
        if (is_array($exp)) {
            $item = array_pop($val);
            // 传入 or 或者 and
            if (is_string($item) && in_array($item, ['AND', 'and', 'OR', 'or'])) {
                $rule = $item;
            } else {
                array_push($val, $item);
            }
            foreach ($val as $k => $item) {
                $bindName = 'where_' . str_replace('.', '_', $field) . '_' . $k;
                $str[]    = $this->parseWhereItem($field, $item, $rule, $options, $binds, $bindName);
            }
            return '( ' . implode(' ' . $rule . ' ', $str) . ' )';
        }

        // 检测操作符
        if (!in_array($exp, $this->exp)) {
            $exp = strtolower($exp);
            if (isset($this->exp[$exp])) {
                $exp = $this->exp[$exp];
            } else {
                throw new Exception('where express error:' . $exp);
            }
        }
        $bindName = $bindName ?: 'where_' . $rule . '_' . str_replace(['.', '-'], '_', $field);
        if (preg_match('/\W/', $bindName)) {
            // 处理带非单词字符的字段名
            $bindName = md5($bindName);
        }

        if ($value instanceof Expression) {

        } elseif (is_object($value) && method_exists($value, '__toString')) {
            // 对象数据写入
            $value = $value->__toString();
        }

        $bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR;
        if (is_scalar($value) && array_key_exists($field, $binds) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) {
            if (strpos($value, ':') !== 0 || !$this->query->isBind(substr($value, 1))) {
                if ($this->query->isBind($bindName)) {
                    $bindName .= '_' . str_replace('.', '_', uniqid('', true));
                }
                $this->query->bind($bindName, $value, $bindType);
                $value = ':' . $bindName;
            }
        }

        $whereStr = '';
        if (in_array($exp, ['=', '<>', '>', '>=', '<', '<='])) {
            // 比较运算
            if ($value instanceof \Closure) {
                $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value);
            } else {
                $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field);
            }
        } elseif ('LIKE' == $exp || 'NOT LIKE' == $exp) {
            // 模糊匹配
            if (is_array($value)) {
                foreach ($value as $item) {
                    $array[] = $key . ' ' . $exp . ' ' . $this->parseValue($item, $field);
                }
                $logic = isset($val[2]) ? $val[2] : 'AND';
                $whereStr .= '(' . implode($array, ' ' . strtoupper($logic) . ' ') . ')';
            } else {
                $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field);
            }
        } elseif ('EXP' == $exp) {
            // 表达式查询
            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;
        } elseif (in_array($exp, ['NOT IN', 'IN'])) {
            // IN 查询
            if ($value instanceof \Closure) {
                $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value);
            } else {
                $value = array_unique(is_array($value) ? $value : explode(',', $value));
                if (array_key_exists($field, $binds)) {
                    $bind  = [];
                    $array = [];
                    $i     = 0;
                    foreach ($value as $v) {
                        $i++;
                        if ($this->query->isBind($bindName . '_in_' . $i)) {
                            $bindKey = $bindName . '_in_' . uniqid() . '_' . $i;
                        } else {
                            $bindKey = $bindName . '_in_' . $i;
                        }
                        $bind[$bindKey] = [$v, $bindType];
                        $array[]        = ':' . $bindKey;
                    }
                    $this->query->bind($bind);
                    $zone = implode(',', $array);
                } else {
                    $zone = implode(',', $this->parseValue($value, $field));
                }
                $whereStr .= $key . ' ' . $exp . ' (' . (empty($zone) ? "''" : $zone) . ')';
            }
        } elseif (in_array($exp, ['NOT BETWEEN', 'BETWEEN'])) {
            // BETWEEN 查询
            $data = is_array($value) ? $value : explode(',', $value);
            if (array_key_exists($field, $binds)) {
                if ($this->query->isBind($bindName . '_between_1')) {
                    $bindKey1 = $bindName . '_between_1' . uniqid();
                    $bindKey2 = $bindName . '_between_2' . uniqid();
                } else {
                    $bindKey1 = $bindName . '_between_1';
                    $bindKey2 = $bindName . '_between_2';
                }
                $bind = [
                    $bindKey1 => [$data[0], $bindType],
                    $bindKey2 => [$data[1], $bindType],
                ];
                $this->query->bind($bind);
                $between = ':' . $bindKey1 . ' AND :' . $bindKey2;
            } else {
                $between = $this->parseValue($data[0], $field) . ' AND ' . $this->parseValue($data[1], $field);
            }
            $whereStr .= $key . ' ' . $exp . ' ' . $between;
        } elseif (in_array($exp, ['NOT EXISTS', 'EXISTS'])) {
            // EXISTS 查询
            if ($value instanceof \Closure) {
                $whereStr .= $exp . ' ' . $this->parseClosure($value);
            } else {
                $whereStr .= $exp . ' (' . $value . ')';
            }
        } elseif (in_array($exp, ['< TIME', '> TIME', '<= TIME', '>= TIME'])) {
            $whereStr .= $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($value, $field, $options, $bindName, $bindType);
        } elseif (in_array($exp, ['BETWEEN TIME', 'NOT BETWEEN TIME'])) {
            if (is_string($value)) {
                $value = explode(',', $value);
            }

            $whereStr .= $key . ' ' . substr($exp, 0, -4) . $this->parseDateTime($value[0], $field, $options, $bindName . '_between_1', $bindType) . ' AND ' . $this->parseDateTime($value[1], $field, $options, $bindName . '_between_2', $bindType);
        } elseif ($exp == 'MATCH'){
            //=========== 修改: 全文搜索1
            if(!$value){
                $whereStr .= '';
            }
            if ($value instanceof \Closure) {
                $whereStr .= 'MATCH(' . $key . ') AGAINST (' . $this->parseClosure($value) . ')';
            } else {
                $whereStr .= 'MATCH(' . $key . ') AGAINST (' . $this->parseValue($value, $field) . ')';
            }
        } elseif ($exp == 'MATCH IN BOOLEAN'){
            // =========== 修改: 全文搜索2

            if(!$value){
                $whereStr .= '';
            }
            if ($value instanceof \Closure) {
                $whereStr .= 'MATCH(' . $key . ') AGAINST (' . $this->parseClosure($value) . ' IN BOOLEAN MODE)';
            } else {
                $whereStr .= 'MATCH(' . $key . ') AGAINST (' . $this->parseValue($value, $field) . ' IN BOOLEAN MODE)';
            }
        }

        return $whereStr;
    }


修改文件: thinkphp/library/think/db/builder/Mysql.php protected function parseKey($key, $options = [], $strict = false) 函数下

 if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) {
            throw new Exception('not support data:' . $key);
        }
        if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key))) {
                $key = '`' . $key . '`';
        }

        改成:

 if ($strict && !preg_match('/^[\w\.,\*]+$/', $key)) {
            throw new Exception('not support data:' . $key);
        }
        if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key))) {
            if(strstr($key, ',')) {
                $keyArray = explode(',', $key);
                $tmpList = [];
                foreach ($keyArray as $key_) {
                    $tmpList[] = '`'.$key_.'`';
                }
                $key = join(',', $tmpList);
            } else {
                $key = '`' . $key . '`';
            }
        }

添加后的Thinkphp链式操作示例 以上的SQL可以用Thinkphp的链式操作生成,而且,如果$searchtext为空的情况下,我的代码将不会生成相关的where语句,较符合实际需求

$total =  model('Article')
            ->where('title,summary','MATCH IN BOOLEAN',$searchtext)
            ->where('status',1)
            ->count();
$subQuery = Db::table('Article')
            ->where('title,summary','match in boolean',$searchtext)
            ->where('post.status',1)
            ->limit($offset, $limit)
            ->order($sort, $order)
            ->select();

使用方法就是

->where(‘字段名称’,‘MATCH IN BOOLEAN’,‘要搜索的词’)

或 ->where(‘字段名称’,‘MATCH’,‘要搜索的词’)

正文结束

Mysql Innodb 引擎优化 参数 SQL查询~ 存在一个表而不在另一个表中的数据