本文目录导读:

PHP API 搜索建议通常通过 AJAX 异步请求 + 后端模糊查询 来实现,下面我会提供一个完整的实现方案,包括前端和后端代码。
数据库设计
假设我们有一个 products 表:
CREATE TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
category VARCHAR(100),
price DECIMAL(10,2),
status TINYINT DEFAULT 1,
INDEX idx_name (name)
);
PHP API 后端实现
1 搜索建议 API (search_suggest.php)
<?php
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET');
// 数据库连接配置
$host = 'localhost';
$dbname = 'your_database';
$username = 'your_username';
$password = 'your_password';
try {
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
echo json_encode(['error' => '数据库连接失败']);
exit;
}
// 获取搜索关键词
$keyword = isset($_GET['q']) ? trim($_GET['q']) : '';
if (empty($keyword)) {
echo json_encode(['suggestions' => []]);
exit;
}
// 防止SQL注入,使用预处理语句
$stmt = $pdo->prepare("
SELECT DISTINCT name, category
FROM products
WHERE name LIKE :keyword
OR category LIKE :keyword
ORDER BY
CASE
WHEN name LIKE :exact THEN 0
WHEN name LIKE :start THEN 1
ELSE 2
END,
name ASC
LIMIT 10
");
$likeKeyword = '%' . $keyword . '%';
$exactKeyword = $keyword . '%';
$startKeyword = $keyword . '%';
$stmt->execute([
':keyword' => $likeKeyword,
':exact' => $exactKeyword,
':start' => $startKeyword
]);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 格式化返回数据
$suggestions = [];
foreach ($results as $row) {
$suggestions[] = [
'value' => $row['name'],
'category' => $row['category'],
'label' => highlightKeyword($row['name'], $keyword)
];
}
echo json_encode([
'success' => true,
'suggestions' => $suggestions,
'keyword' => $keyword
]);
/**
* 高亮搜索关键词
*/
function highlightKeyword($text, $keyword) {
return preg_replace('/(' . preg_quote($keyword, '/') . ')/iu', '<strong>$1</strong>', $text);
}
2 优化版本:使用缓存提升性能
<?php
// search_suggest_cached.php
// 设置缓存
$cacheFile = __DIR__ . '/cache/suggestions_' . md5($keyword) . '.json';
$cacheTime = 300; // 5分钟缓存
if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < $cacheTime) {
echo file_get_contents($cacheFile);
exit;
}
// ... 数据库查询代码 ...
// 保存到缓存
file_put_contents($cacheFile, json_encode($response));
echo json_encode($response);
前端实现
1 HTML + JavaScript 实现
<!DOCTYPE html>
<html>
<head>
<style>
.search-container {
position: relative;
width: 300px;
}
.search-input {
width: 100%;
padding: 10px;
font-size: 16px;
border: 2px solid #ddd;
border-radius: 4px;
}
.suggestions-box {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #ddd;
border-top: none;
max-height: 300px;
overflow-y: auto;
display: none;
z-index: 1000;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.suggestion-item {
padding: 10px;
cursor: pointer;
border-bottom: 1px solid #eee;
}
.suggestion-item:hover {
background: #f5f5f5;
}
.suggestion-item strong {
color: #ff6b6b;
}
.no-results {
padding: 10px;
color: #999;
text-align: center;
}
</style>
</head>
<body>
<div class="search-container">
<input type="text"
id="searchInput"
class="search-input"
placeholder="搜索产品..."
autocomplete="off">
<div id="suggestionsBox" class="suggestions-box"></div>
</div>
<script>
class SearchSuggest {
constructor(options) {
this.input = document.getElementById(options.inputId);
this.suggestionsBox = document.getElementById(options.suggestionsBox);
this.apiUrl = options.apiUrl;
this.debounceTimer = null;
this.debounceDelay = options.debounceDelay || 300;
this.minChars = options.minChars || 2;
this.maxSuggestions = options.maxSuggestions || 10;
this.init();
}
init() {
// 输入事件监听
this.input.addEventListener('input', (e) => {
this.debounceSearch(e.target.value);
});
// 失焦隐藏建议框
this.input.addEventListener('blur', () => {
setTimeout(() => this.hideSuggestions(), 200);
});
// 获取焦点时如果有关键词则显示
this.input.addEventListener('focus', () => {
if (this.input.value.length >= this.minChars) {
this.search(this.input.value);
}
});
// 键盘事件
this.input.addEventListener('keydown', (e) => {
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
e.preventDefault();
this.navigateSuggestions(e.key);
} else if (e.key === 'Enter') {
this.selectCurrentSuggestion();
} else if (e.key === 'Escape') {
this.hideSuggestions();
}
});
}
debounceSearch(value) {
clearTimeout(this.debounceTimer);
if (value.length < this.minChars) {
this.hideSuggestions();
return;
}
this.debounceTimer = setTimeout(() => {
this.search(value);
}, this.debounceDelay);
}
async search(keyword) {
try {
const response = await fetch(`${this.apiUrl}?q=${encodeURIComponent(keyword)}`);
const data = await response.json();
if (data.success) {
this.displaySuggestions(data.suggestions);
}
} catch (error) {
console.error('搜索建议请求失败:', error);
}
}
displaySuggestions(suggestions) {
if (!suggestions || suggestions.length === 0) {
this.suggestionsBox.innerHTML = '<div class="no-results">无匹配结果</div>';
this.showSuggestions();
return;
}
// 限制显示数量
const limitedSuggestions = suggestions.slice(0, this.maxSuggestions);
let html = '';
limitedSuggestions.forEach((item, index) => {
html += `
<div class="suggestion-item" data-index="${index}" data-value="${item.value}">
<div>${item.label}</div>
<small style="color: #999;">${item.category || ''}</small>
</div>
`;
});
this.suggestionsBox.innerHTML = html;
this.showSuggestions();
// 点击选择
this.suggestionsBox.querySelectorAll('.suggestion-item').forEach(item => {
item.addEventListener('mousedown', (e) => {
this.input.value = item.dataset.value;
this.hideSuggestions();
// 触发选择事件
this.onSelect && this.onSelect(item.dataset.value);
});
});
// 鼠标悬停效果
this.currentIndex = -1;
this.items = this.suggestionsBox.querySelectorAll('.suggestion-item');
}
navigateSuggestions(key) {
if (!this.items || this.items.length === 0) return;
// 移除当前高亮
if (this.currentIndex >= 0 && this.items[this.currentIndex]) {
this.items[this.currentIndex].style.background = '';
}
if (key === 'ArrowDown') {
this.currentIndex = Math.min(this.currentIndex + 1, this.items.length - 1);
} else if (key === 'ArrowUp') {
this.currentIndex = Math.max(this.currentIndex - 1, -1);
}
// 高亮当前项
if (this.currentIndex >= 0 && this.items[this.currentIndex]) {
this.items[this.currentIndex].style.background = '#f0f0f0';
// 滚动到可见区域
this.items[this.currentIndex].scrollIntoView({
block: 'nearest'
});
}
}
selectCurrentSuggestion() {
if (this.currentIndex >= 0 && this.items[this.currentIndex]) {
const value = this.items[this.currentIndex].dataset.value;
this.input.value = value;
this.hideSuggestions();
this.onSelect && this.onSelect(value);
}
}
showSuggestions() {
this.suggestionsBox.style.display = 'block';
}
hideSuggestions() {
this.suggestionsBox.style.display = 'none';
this.currentIndex = -1;
}
// 设置选择回调
onSelect(callback) {
this.onSelect = callback;
}
}
// 使用示例
const searchSuggest = new SearchSuggest({
inputId: 'searchInput',
suggestionsBox: 'suggestionsBox',
apiUrl: 'search_suggest.php',
debounceDelay: 300,
minChars: 2,
maxSuggestions: 10
});
// 选择回调
searchSuggest.onSelect((value) => {
console.log('选择了:', value);
// 这里可以执行搜索或跳转
});
</script>
</body>
</html>
高级优化方案
1 使用 Redis 缓存
<?php
// 使用 Redis 缓存搜索建议
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$cacheKey = 'suggest:' . md5($keyword);
$cached = $redis->get($cacheKey);
if ($cached) {
echo $cached;
exit;
}
// 执行数据库查询
// ...
// 缓存结果,设置过期时间为300秒
$redis->setex($cacheKey, 300, json_encode($response));
echo json_encode($response);
2 使用 Elasticsearch 实现高级搜索
<?php
require 'vendor/autoload.php';
use Elasticsearch\ClientBuilder;
$client = ClientBuilder::create()->build();
$params = [
'index' => 'products',
'body' => [
'size' => 10,
'query' => [
'multi_match' => [
'query' => $keyword,
'fields' => ['name^3', 'category^2', 'description'],
'type' => 'phrase_prefix',
'fuzziness' => 'AUTO'
]
],
'suggest' => [
'name-suggest' => [
'text' => $keyword,
'term' => [
'field' => 'name'
]
]
]
]
];
$response = $client->search($params);
性能优化建议
- 添加索引:确保搜索字段有适当的索引
- 限制结果数量:通常返回5-10条建议
- 使用缓存:减少数据库查询
- 防抖处理:减少请求频率
- 压缩数据:使用gzip压缩返回数据
- CDN加速:静态资源使用CDN
完整示例下载
您可以将上述代码保存为以下文件:
search_suggest.php- PHP APIindex.html- 前端页面
这个实现方案具有良好的用户体验和性能表现,适用于大多数搜索建议场景。