PHPAPI搜索建议怎么实现

wen PHP项目 8

本文目录导读:

PHPAPI搜索建议怎么实现

  1. 数据库设计
  2. PHP API 后端实现
  3. 前端实现
  4. 高级优化方案
  5. 性能优化建议
  6. 完整示例下载

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

性能优化建议

  1. 添加索引:确保搜索字段有适当的索引
  2. 限制结果数量:通常返回5-10条建议
  3. 使用缓存:减少数据库查询
  4. 防抖处理:减少请求频率
  5. 压缩数据:使用gzip压缩返回数据
  6. CDN加速:静态资源使用CDN

完整示例下载

您可以将上述代码保存为以下文件:

  • search_suggest.php - PHP API
  • index.html - 前端页面

这个实现方案具有良好的用户体验和性能表现,适用于大多数搜索建议场景。

抱歉,评论功能暂时关闭!