本文目录导读:

在PHP项目中实现全文搜索,可以根据项目规模和需求选择不同的方案,以下是几种常见且实用的实现方式:
MySQL 内置全文搜索(中小型项目)
创建全文索引
-- 建表时创建全文索引
CREATE TABLE articles (
id INT AUTO_INCREMENT PRIMARY KEY,VARCHAR(200),
content TEXT,
FULLTEXT INDEX ft_index (title, content)
) ENGINE=InnoDB;
-- 或给已有表添加
ALTER TABLE articles ADD FULLTEXT INDEX ft_index (title, content);
PHP 查询示例
class FullTextSearch {
private $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function search($keyword, $page = 1, $perPage = 10) {
// 防止SQL注入
$keyword = trim($keyword);
if (empty($keyword)) return [];
$offset = ($page - 1) * $perPage;
// 自然语言模式搜索
$sql = "SELECT id, title,
MATCH(title, content) AGAINST(:keyword IN NATURAL LANGUAGE MODE) AS relevance,
SUBSTRING(content, LOCATE(:keyword2, content), 200) AS snippet
FROM articles
WHERE MATCH(title, content) AGAINST(:keyword3 IN NATURAL LANGUAGE MODE)
ORDER BY relevance DESC
LIMIT :limit OFFSET :offset";
$stmt = $this->pdo->prepare($sql);
$stmt->bindValue(':keyword', $keyword);
$stmt->bindValue(':keyword2', $keyword);
$stmt->bindValue(':keyword3', $keyword);
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function searchCount($keyword) {
$sql = "SELECT COUNT(*) as total
FROM articles
WHERE MATCH(title, content) AGAINST(:keyword IN NATURAL LANGUAGE MODE)";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':keyword' => $keyword]);
return $stmt->fetch(PDO::FETCH_ASSOC)['total'];
}
}
布尔模式搜索(支持高级语法)
public function advancedSearch($query) {
// 支持: +必须包含 -排除 *通配符 ""精确短语
$sql = "SELECT * FROM articles
WHERE MATCH(title, content) AGAINST(:query IN BOOLEAN MODE)";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':query' => $query]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// 用法
$results = $search->advancedSearch('+PHP -MySQL "全文搜索"');
Elasticsearch 全文搜索(大型项目)
安装与配置
# 安装 Elasticsearch 客户端 composer require elasticsearch/elasticsearch
PHP 实现
require 'vendor/autoload.php';
use Elasticsearch\ClientBuilder;
class ElasticSearchService {
private $client;
public function __construct() {
$this->client = ClientBuilder::create()
->setHosts(['http://localhost:9200'])
->build();
}
// 创建索引
public function createIndex($indexName) {
$params = [
'index' => $indexName,
'body' => [
'settings' => [
'analysis' => [
'analyzer' => [
'my_analyzer' => [
'type' => 'standard',
'tokenizer' => 'ik_smart' // 中文分词
]
]
]
],
'mappings' => [
'properties' => [
'title' => [
'type' => 'text',
'analyzer' => 'ik_max_word',
'search_analyzer' => 'ik_smart'
],
'content' => [
'type' => 'text',
'analyzer' => 'ik_max_word',
'search_analyzer' => 'ik_smart'
],
'category' => ['type' => 'keyword'],
'created_at' => ['type' => 'date']
]
]
]
];
return $this->client->indices()->create($params);
}
// 索引文档
public function indexDocument($index, $id, $document) {
$params = [
'index' => $index,
'id' => $id,
'body' => $document
];
return $this->client->index($params);
}
// 批量索引
public function bulkIndex($index, $documents) {
$params = ['body' => []];
foreach ($documents as $id => $doc) {
$params['body'][] = [
'index' => [
'_index' => $index,
'_id' => $id
]
];
$params['body'][] = $doc;
}
return $this->client->bulk($params);
}
// 搜索
public function search($index, $query, $page = 1, $perPage = 10) {
$from = ($page - 1) * $perPage;
$params = [
'index' => $index,
'body' => [
'query' => [
'bool' => [
'must' => [
'multi_match' => [
'query' => $query,
'fields' => ['title^3', 'content'], // title权重更高
'type' => 'best_fields',
'fuzziness' => 'AUTO' // 模糊搜索
]
],
'filter' => [
// 可以添加过滤条件
// 'term' => ['category' => '技术']
]
]
],
'highlight' => [
'pre_tags' => ['<mark>'],
'post_tags' => ['</mark>'],
'fields' => [
'title' => ['fragment_size' => 50],
'content' => ['fragment_size' => 200, 'number_of_fragments' => 2]
]
],
'from' => $from,
'size' => $perPage,
'sort' => [
'_score' => ['order' => 'desc']
]
]
];
$response = $this->client->search($params);
return [
'total' => $response['hits']['total']['value'],
'results' => $this->formatResults($response['hits']['hits'])
];
}
private function formatResults($hits) {
$results = [];
foreach ($hits as $hit) {
$result = $hit['_source'];
$result['_score'] = $hit['_score'];
$result['highlight'] = $hit['highlight'] ?? [];
$results[] = $result;
}
return $results;
}
}
TNTSearch(轻量级替代方案)
安装
composer require team-tnt/tntsearch
实现
use TeamTNT\TNTSearch\TNTSearch;
class TNTSearchService {
private $tnt;
public function __construct() {
$this->tnt = new TNTSearch();
$this->tnt->loadConfig([
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'your_db',
'username' => 'root',
'password' => '',
'storage' => '/path/to/index/storage'
]);
}
// 创建索引
public function createIndex($table, $fields) {
$indexer = $this->tnt->createIndex("{$table}.index");
$indexer->query("SELECT id, " . implode(',', $fields) . " FROM {$table}");
$indexer->run();
}
// 搜索
public function search($query, $page = 1, $perPage = 10) {
$this->tnt->selectIndex("articles.index");
$this->tnt->fuzziness = true; // 启用模糊搜索
$this->tnt->fuzzy_distance = 2; // 编辑距离
$res = $this->tnt->search($query, $perPage);
return [
'total' => $res['hits'],
'ids' => $res['ids'],
'suggestions' => $res['suggestions'] ?? []
];
}
}
Sphinx 搜索引擎(高性能)
配置示例
# /etc/sphinx/sphinx.conf
source article_src
{
type = mysql
sql_host = localhost
sql_user = root
sql_pass =
sql_db = your_db
sql_query = SELECT id, title, content, created_at FROM articles
sql_attr_timestamp = created_at
}
index article_index
{
source = article_src
path = /var/lib/sphinx/data/article_index
charset_table = 0..9, A..Z->a..z, _, a..z, U+410..U+42F->U+430..U+44F, U+430..U+44F
}
PHP 调用
class SphinxSearch {
private $client;
public function __construct() {
$this->client = new mysqli('localhost', '', '', '', 9306);
if ($this->client->connect_error) {
throw new Exception("Sphinx连接失败: " . $this->client->connect_error);
}
}
public function search($query, $page = 1, $perPage = 10) {
$offset = ($page - 1) * $perPage;
// 转义特殊字符
$query = str_replace(['\\', '|', '(', ')', '-', '!', '@', '~', '"', '&', '/', '^', '$', '='],
['\\\\', '\\|', '\\(', '\\)', '\\-', '\\!', '\\@', '\\~', '\\"', '\\&', '\\/', '\\^', '\\$', '\\='],
$query);
$sql = "SELECT *, WEIGHT() as weight
FROM article_index
WHERE MATCH('$query')
ORDER BY weight DESC
LIMIT $offset, $perPage";
$result = $this->client->query($sql);
return $result->fetch_all(MYSQLI_ASSOC);
}
}
完整搜索系统示例
// 搜索服务接口
interface SearchInterface {
public function index($id, $data);
public function search($query, $page, $perPage);
public function delete($id);
public function update($id, $data);
}
// 工厂模式
class SearchFactory {
public static function create($type = 'mysql') {
switch ($type) {
case 'elasticsearch':
return new ElasticSearchService();
case 'tntsearch':
return new TNTSearchService();
case 'sphinx':
return new SphinxSearch();
default:
return new FullTextSearch($pdo);
}
}
}
// 控制器示例
class SearchController {
private $searchService;
public function __construct($searchType = 'mysql') {
$this->searchService = SearchFactory::create($searchType);
}
public function search(Request $request) {
$keyword = $request->input('q');
$page = $request->input('page', 1);
$perPage = $request->input('per_page', 20);
try {
$results = $this->searchService->search($keyword, $page, $perPage);
return [
'success' => true,
'data' => $results,
'meta' => [
'query' => $keyword,
'page' => $page,
'total' => $results['total'] ?? 0
]
];
} catch (Exception $e) {
return [
'success' => false,
'error' => $e->getMessage()
];
}
}
public function reindex() {
// 从数据库获取所有数据
$articles = Article::all();
foreach ($articles as $article) {
$this->searchService->index($article->id, [
'title' => $article->title,
'content' => $article->content,
'category' => $article->category,
'created_at' => $article->created_at->timestamp
]);
}
return ['success' => true, 'message' => '索引更新完成'];
}
}
选择建议
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| MySQL FULLTEXT | 中小型项目(<10万数据) | 无需额外服务,简单 | 性能有限,不支持中文分词 |
| Elasticsearch | 大型项目、高并发 | 分布式、实时、功能强大 | 需要额外维护集群 |
| TNTSearch | 轻量级、嵌入式 | 无需外部依赖 | 功能相对简单 |
| Sphinx | 高性能、大数据量 | 索引速度快 | 配置复杂 |
最佳实践
- 中文搜索:建议使用 Elasticsearch + IK 分词器,或 MySQL + 中文分词插件
- 搜索优化:实现搜索建议、拼写纠正、相关搜索
- 缓存策略:对热门搜索进行缓存
- 性能监控:记录搜索日志,分析搜索行为
- 数据同步:建立索引更新机制,保持数据一致性
选择哪种方案取决于你的项目规模、技术栈和性能要求,对于大多数中小型项目,MySQL全文搜索加上一些优化措施就足够了。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播