ThinkPHP 配合 MySQL 全文索引实现文章关键字搜索

ThinkPHP 配合 MySQL 全文索引实现文章关键字搜索

一、前提条件

  1. MySQL 版本 ≥ 5.6(InnoDB 引擎从该版本开始支持全文索引,推荐 8.0 对中文分词支持更完善)
  2. 数据库表引擎必须为 InnoDB,字符集使用 utf8mb4
  3. ThinkPHP 已完成基础安装与数据库连接配置

二、数据库表设计与全文索引创建

1.创建文章表(含全文索引)

针对中文搜索,必须使用 ngram 中文分词解析器,否则默认按空格分词的规则无法适配中文。

CREATE TABLE `article` (
  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '文章ID',
  `title` varchar(200) NOT NULL COMMENT '文章标题',
  `content` text NOT NULL COMMENT '文章内容',
  `create_time` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
  `update_time` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间',
  `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态 1正常 0禁用',
  PRIMARY KEY (`id`),
  -- 创建全文索引:字段顺序必须与后续搜索的MATCH字段完全一致
  FULLTEXT INDEX `ft_title_content` (`title`, `content`) WITH PARSER ngram
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章表';

2.已有表添加全文索引

若表已存在,执行以下 SQL 添加索引:

ALTER TABLE `article` ADD FULLTEXT INDEX `ft_title_content` (`title`, `content`) WITH PARSER ngram;

3.中文分词核心配置

MySQL 的 ngram 分词默认最小词长为 2(即仅匹配≥2 个字符的关键词),若需要支持单字搜索:

修改 MySQL 配置文件 my.cnf(或 my.ini):

innodb_ngram_token_size=1

重启 MySQL 服务,重建全文索引生效。

三.创建模型

app/model 目录下创建 Article.php,封装全文搜索逻辑:

<?php
namespace app\model;

use think\Model;

class Article extends Model
{
    // 对应表名(不含前缀)
    protected $name = 'article';
    // 主键
    protected $pk = 'id';
    // 自动写入时间戳
    protected $autoWriteTimestamp = true;
    // 时间格式
    protected $dateFormat = 'Y-m-d H:i:s';

    /**
     * 全文搜索条件范围
     * @param  object $query   查询对象
     * @param  string $keyword 搜索关键字
     * @return object
     */
    public function scopeKeywordSearch($query, $keyword)
    {
        if (!empty(trim($keyword))) {
            // MATCH字段必须与全文索引的字段、顺序完全一致
            // IN BOOLEAN MODE 布尔模式,支持灵活的搜索语法
            $query->whereRaw(
                "MATCH(title,content) AGAINST(? IN BOOLEAN MODE)",
                [trim($keyword)]
            );
            // 普通场景可使用默认自然语言模式:
            // $query->whereRaw("MATCH(title,content) AGAINST(?)", [trim($keyword)]);
        }
    }

    /**
     * 附加全文相关性得分(用于排序)
     * @param  object $query   查询对象
     * @param  string $keyword 搜索关键字
     * @return object
     */
    public function scopeWithRelevance($query, $keyword)
    {
        if (!empty(trim($keyword))) {
            $query->field(
                "*, MATCH(title,content) AGAINST(? IN BOOLEAN MODE) as relevance",
                [trim($keyword)]
            );
        }
    }
}

评论

发表回复