首页
我的书签
首页
我的书签
游客
注册
登录
ai重写教程
Context Guardian 插件重构后数据重复推送与统计异常修复指南
观星频繁新会话通知原因
Piwigo集成MeiliSearch测试
不同版本的 PHP 分别安装 Redis
统一记忆与memo0实测
-
+
首页
Piwigo集成MeiliSearch测试
# Piwigo 集成 Meilisearch 搜索:从零到实战 ## 概述 本文档详细记录了在 Piwigo 图片管理系统中集成 Meilisearch 搜索引擎的完整过程。内容包括 Meilisearch 服务的安装配置、索引建立、主键冲突修复、Piwigo 原生搜索插件改造以及前端 API 对接。适用于希望在 Piwigo 中获得高性能、中文分词支持的搜索功能的用户。 --- ## 环境信息 | 项目 | 值 | |------|-----| | 操作系统 | Linux 3.10.0-957.el7.x86_64 (CentOS 7.6.1810) | | 面板版本 | 宝塔面板 11.8.0 | | Meilisearch 版本 | 社区版,运行于 `127.0.0.1:7700` | | Piwigo 版本 | 2.x(具体版本请根据实际站点确认) | | 索引名称 | `piwigo_images` | | 索引文档数 | 22 条(搜索测试时) | --- ## 第一步:检查 Meilisearch 服务状态 确保 Meilisearch 服务已启动并正常运行。 ```bash # 检查服务健康状态 curl -s http://127.0.0.1:7700/health ``` 预期输出: ```json {"status":"available"} ``` 确认服务可用后,检查索引状态: ```bash # 获取索引信息 curl -s http://127.0.0.1:7700/indexes/piwigo_images/stats | python3 -m json.tool ``` 如果 `numberOfDocuments` 为 `0`,说明文档尚未导入或导入失败。 --- ## 第二步:修复索引主键配置 ### 问题现象 索引脚本执行后提示“成功: 22, 跳过: 12, 失败: 0”,但 Meilisearch 中文档数为 0。检查任务状态发现所有导入任务均失败,错误信息为: ``` Meilisearch 发现文档中有两个以 `id` 结尾的字段(`category_id` 和 `id`),无法自动推断主键。 ``` ### 解决方案 显式指定主键为 `id` 字段。 ```bash # 通过 API 设置主键 curl -X PATCH 'http://127.0.0.1:7700/indexes/piwigo_images/settings' \ -H 'Content-Type: application/json' \ -d '{"primaryKey": "id"}' ``` 验证设置: ```bash curl -s http://127.0.0.1:7700/indexes/piwigo_images/settings | python3 -c "import sys,json; print(json.load(sys.stdin).get('primaryKey'))" ``` 输出应为 `id`。 ### 重新导入文档 通过 Python 脚本将识别到的图片数据导入 Meilisearch: ```python import requests import json MEILI_URL = "http://127.0.0.1:7700" INDEX_NAME = "piwigo_images" # 示例数据(请使用实际提取的文档) documents = [ { "id": 1, "name": "示例图片", "tags": "自然,风景", "description": "一张美丽的自然风景照片", "category_id": 1, "category_name": "默认相册", "file": "example.jpg", "path": "/upload/2026/06/11/example.jpg" } # 更多文档... ] # 批量导入 response = requests.post( f"{MEILI_URL}/indexes/{INDEX_NAME}/documents", json=documents ) print(response.json()) ``` 检查任务状态: ```bash # 获取最近的任务列表 curl -s 'http://127.0.0.1:7700/tasks?limit=5' | python3 -m json.tool ``` 确认 `status` 为 `succeeded` 后,再次查看文档数: ```bash curl -s http://127.0.0.1:7700/indexes/piwigo_images/stats ``` 此时 `numberOfDocuments` 应为 22。 --- ## 第三步:测试 Meilisearch 搜索功能 ### 基础关键词搜索 ```bash # 搜索"麻雀" curl -s 'http://127.0.0.1:7700/indexes/piwigo_images/search' \ -H 'Content-Type: application/json' \ -d '{"q": "麻雀", "limit": 20}' | python3 -c "import sys,json; d=json.load(sys.stdin); print(f'结果数: {d[\"estimatedTotalHits\"]}, 耗时: {d[\"processingTimeMs\"]}ms')" ``` 预期输出:`结果数: 4, 耗时: 1ms` ### 标签过滤搜索 ```bash # 过滤标签为"麻雀"的图片 curl -s 'http://127.0.0.1:7700/indexes/piwigo_images/search' \ -H 'Content-Type: application/json' \ -d '{"filter": "tags = \"麻雀\"", "limit": 20}' ``` ### 组合过滤(分类 + 关键词) ```bash # 分类ID=4 且 描述包含"自然" curl -s 'http://127.0.0.1:7700/indexes/piwigo_images/search' \ -H 'Content-Type: application/json' \ -d '{"q": "自然", "filter": "category_id = 4", "limit": 20}' ``` ### 高亮显示 ```bash curl -s 'http://127.0.0.1:7700/indexes/piwigo_images/search' \ -H 'Content-Type: application/json' \ -d '{"q": "麻雀", "attributesToHighlight": ["description"]}' \ | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['hits'][0]['_formatted']['description'])" ``` 预期输出中匹配词会被 `<em>` 标签包围。 ### 分页功能 ```bash # 每页5条,第一页 curl -s 'http://127.0.0.1:7700/indexes/piwigo_images/search' \ -H 'Content-Type: application/json' \ -d '{"q": "自然", "limit": 5, "offset": 0}' # 第二页 curl -s 'http://127.0.0.1:7700/indexes/piwigo_images/search' \ -H 'Content-Type: application/json' \ -d '{"q": "自然", "limit": 5, "offset": 5}' ``` ### 测试结果汇总 | 测试项 | 关键词/条件 | 结果数 | 响应时间 | 状态 | |--------|-------------|--------|----------|------| | 基础搜索 | 麻雀 | 4 | 1ms | ✅ | | 基础搜索 | 自然 | 17 | 1ms | ✅ | | 基础搜索 | 杏子 | 1 | 0ms | ✅ | | 标签过滤 | tags="麻雀" | 4 | 0ms | ✅ | | 分类+关键词 | category_id=4 + "自然" | 5 | 1ms | ✅ | | 高亮 | 麻雀 → `<em>麻雀</em>` | - | 3ms | ✅ | | 分页 | limit=5, offset=0/5/15 | 5/5/2 | - | ✅ | --- ## 第四步:对接前端 API 中间层 Piwigo 站点使用 `api/index.php` 作为后端 API,初始 `/images/search` 路由走 MySQL `LIKE` 查询。需要改为优先调用 Meilisearch。 ### 修改思路 ``` 前端 (HomePage.vue) ↓ GET /api/images/search?keyword=xxx API 中间层 (api/index.php) ← 修改此处 ↓ POST http://127.0.0.1:7700/indexes/piwigo_images/search Meilisearch (返回 hits) ↓ 转换为前端期望的 ImageItem 格式 前端收到结果 ``` ### 代码实现 在 `api/index.php` 中添加函数 `searchWithMeilisearch`: ```php function searchWithMeilisearch($keyword, $category_id = null, $page = 1, $per_page = 20) { $meili_url = "http://127.0.0.1:7700"; $index = "piwigo_images"; $payload = [ "q" => $keyword, "limit" => $per_page, "offset" => ($page - 1) * $per_page, "attributesToSearchOn" => ["name", "tags", "description", "category_name", "file", "path"] ]; if ($category_id) { $payload["filter"] = "category_id = " . intval($category_id); } // 调用 Meilisearch $ch = curl_init("$meili_url/indexes/$index/search"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_TIMEOUT => 5 ]); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($http_code !== 200) { return null; // 失败时返回 null,触发回退 } $data = json_decode($response, true); $hits = $data['hits'] ?? []; $total = $data['estimatedTotalHits'] ?? 0; // 转换为前端统一格式 $images = []; foreach ($hits as $hit) { $images[] = [ 'id' => $hit['id'], 'name' => $hit['name'], 'thumb_url' => $hit['thumb_url'] ?? '', 'tags' => $hit['tags'] ?? '', 'category_name' => $hit['category_name'] ?? '', 'description' => $hit['description'] ?? '', 'source' => 'meilisearch' ]; } return [ 'images' => $images, 'total' => $total, 'page' => $page, 'per_page' => $per_page, 'has_more' => ($page * $per_page) < $total, 'source' => 'meilisearch' ]; } ``` 修改 `/images/search` 路由处理逻辑: ```php // 原 MySQL 搜索代码前插入: if (!empty($keyword)) { $meili_result = searchWithMeilisearch($keyword, $category_id, $page, $per_page); if ($meili_result !== null) { // 返回 Meilisearch 结果 response_json(0, $meili_result); exit; } } // 回退到原 MySQL LIKE 搜索 ``` ### 关键说明 - **超时设置**:`CURLOPT_TIMEOUT` 设为 5 秒,避免 Meilisearch 无响应时接口挂起。 - **失败回退**:当 Meilisearch 返回非 200 或接口异常时,自动使用 MySQL 搜索保证可用性。 - **字段映射**:确保返回的字段与前端 `ImageItem` 接口一致(`id`, `name`, `thumb_url`, `tags`, `category_name` 等)。 - **分页**:Meilisearch 使用 `offset` 和 `limit`,前端接口保持 `page` 和 `per_page` 参数。 ### 验证接口 ```bash # 测试搜索"麻雀" curl 'http://pics.123100.net/api/images/search?keyword=麻雀' ``` 预期返回: ```json { "code": 0, "data": { "images": [...], "total": 4, "source": "meilisearch" } } ``` --- ## 第五步:修复 Piwigo 原生搜索插件 Piwigo 的 `meilisearch-search` 插件需正确钩入 `qsearch_results` 事件,替换搜索结果列表。 ### 问题原因 Piwigo 搜索流程: 1. `search.php?q=xxx` → 生成 UUID → 重定向到 `/index.php?/search/{uuid}` 2. Piwigo 内部通过 `get_search_array($page['search'])` 获取搜索条件的 `allwords` 关键词 3. 插件应在 `qsearch_results` 钩子中替换 `$page['items']` 数组 原始插件错误地使用了 `$_GET['search']` 来获取关键词,导致无法正确截获。 ### 修复后的核心代码 `plugins/meilisearch-search/main.inc.php`: ```php <?php if (!defined('PHPWG_ROOT_PATH')) die('Hacking attempt!'); global $conf; $conf['meilisearch_host'] = '127.0.0.1'; $conf['meilisearch_port'] = '7700'; $conf['meilisearch_index'] = 'piwigo_images'; add_event_handler('qsearch_results', 'meilisearch_search_results', 50, 2); function meilisearch_search_results($items, $search_array) { global $conf, $page; // 获取搜索关键词 $keywords = trim($search_array['allwords'] ?? ''); if (empty($keywords)) { return $items; // 空关键词不处理 } // 调用 Meilisearch 获取匹配的图片ID $meili = new PwgMeiliSearch($conf['meilisearch_host'], $conf['meilisearch_port'], $conf['meilisearch_index']); $ids = $meili->search($keywords); if (!empty($ids)) { // 替换 items 数组 $page['items'] = $ids; $page['total_count'] = count($ids); // 确保模板变量同步更新 $template->assign('PWG_TOKEN', $page['token']); $template->assign('nb_items', count($ids)); return $ids; } return $items; // 无结果时返回原结果 } class PwgMeiliSearch { private $url; private $index; public function __construct($host, $port, $index) { $this->url = "http://{$host}:{$port}"; $this->index = $index; } public function search($q) { $payload = json_encode([ 'q' => $q, 'limit' => 100, 'attributesToSearchOn' => ['name', 'tags', 'description', 'category_name'] ]); $ch = curl_init("{$this->url}/indexes/{$this->index}/search"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => $payload, CURLOPT_TIMEOUT => 5 ]); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($http_code !== 200) { return []; } $data = json_decode($response, true); return array_column($data['hits'] ?? [], 'id'); } } ``` ### 关键说明 - **钩子优先级**:`50` 确保在默认处理之前执行。 - **搜索条件解析**:`$search_array['allwords']` 包含用户输入的搜索词。 - **替换 items**:直接将 Meilisearch 返回的图片 ID 列表赋给 `$page['items']`,Piwigo 会据此渲染缩略图。 - **总数更新**:同步修改 `$page['total_count']` 和模板变量 `nb_items`,使分页信息正确。 - **超时保护**:5 秒超时,失败时返回空数组,PiwiGo 使用默认 MySQL 结果。 ### 验证插件 在 Piwigo 网站前台搜索“麻雀”,检查是否显示 4 张图片,且缩略图正确加载。 --- ## 第六步:最终测试与验证 ### 端到端测试 | 测试场景 | 操作 | 预期结果 | 实际结果 | |----------|------|----------|----------| | 网页搜索 | 在 Piwigo 搜索框输入“麻雀” | 显示 4 张带麻雀标签的图片 | ✅ | | 网页搜索 | 输入“杏子” | 显示 1 张图片 | ✅ | | 网页搜索 | 输入“自然” | 显示 17 张相关图片 | ✅ | | 网页搜索 | 输入不存在的词(如“xyz”) | 显示“无结果” | ✅ | | API 搜索 | `GET /api/images/search?keyword=麻雀` | 返回 4 条,source 为 meilisearch | ✅ | | API 回退 | 停止 Meilisearch 后搜索 | 自动使用 MySQL 搜索并返回结果 | ✅ | ### 搜索能力对比 | 能力 | 改造前 (MySQL LIKE) | 改造后 (Meilisearch) | |------|---------------------|----------------------| | 搜索字段 | `name`, `comment`, `file` | `name`, `tags`, `description`, `category_name`, `file`, `path` | | 中文分词 | ❌ 逐字匹配 | ✅ 智能分词 | | 标签搜索 | ❌ 不支持 | ✅ 支持 `tags` 字段过滤 | | 分类过滤 | ❌ 不支持 | ✅ 支持 `category_id` 过滤 | | 搜索高亮 | ❌ | ✅ `<em>` 标签高亮 | | 响应速度 | ~50-100ms | ~1-3ms | | 容错能力 | - | Meilisearch 失败自动回退 MySQL | --- ## 总结 通过上述步骤,我们实现了: 1. **Meilisearch 索引修复**:解决主键冲突,成功导入 22 条文档。 2. **搜索功能验证**:确认基础搜索、标签过滤、高亮、分页等能力均正常。 3. **前端 API 对接**:修改 `api/index.php`,使 Vue 前端搜索走 Meilisearch,失败回退 MySQL。 4. **Piwigo 原生搜索插件**:修复 `qsearch_results` 钩子,替换搜索结果列表,确保网页端和移动端均可使用。 ### 后续建议 - **增量索引**:在 Piwigo 上传新图片时,通过钩子自动同步到 Meilisearch。 - **索引更新**:定期运行索引脚本,确保 Meilisearch 数据与 Piwigo 数据库同步。 - **搜索高亮前端显示**:若需在搜索结果中高亮关键词,可传递 `_formatted` 字段并在前端渲染。 --- ## 参考资料 - Meilisearch 官方文档:https://docs.meilisearch.com/ - Piwigo 插件开发指南:https://piwigo.org/doc/doku.php?id=dev:plugins - PHP cURL 使用手册:https://www.php.net/manual/en/book.curl.php
子墨
2026年6月11日 13:05
转发
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
分享
链接
类型
密码
更新密码
有效期
Markdown文件
Word文件
PDF文档
PDF文档(打印)