如何实现Drupal 8站点外部API与内部内容的联合搜索?
在Drupal 8中实现内部内容+外部API内容的联合搜索
要实现同时检索Drupal内部基础页面和外部API文章的搜索功能,核心是自定义搜索逻辑,同时拉取两类数据源并整合结果,下面是具体实现方案:
一、核心思路
不用完全替换Drupal核心搜索,而是新增一个联合搜索的处理逻辑:接收用户搜索关键词后,分别查询Drupal内部内容和外部API,将两类结果标准化格式后统一渲染,同时保留权限校验、缓存等关键机制。
二、具体实现步骤
1. 创建自定义模块
先搭个基础模块(比如命名为combined_search),用来承载所有自定义搜索逻辑,避免修改核心或第三方模块。
2. 处理内部内容检索
利用Drupal的实体查询机制,快速筛选匹配关键词的内部节点(比如基础页面page类型),同时做权限校验:
// 获取用户输入的关键词 $keyword = \Drupal::request()->query->get('q'); $escaped_keyword = \Drupal\Database\Database::getConnection()->escapeLike($keyword); // 查询发布状态、匹配关键词的基础页面 $query = \Drupal::entityQuery('node') ->condition('type', 'page') ->condition('status', 1) ->condition( \Drupal\Core\Database\Query\Condition::orConditionGroup() ->condition('title', "%{$escaped_keyword}%", 'LIKE') ->condition('body.value', "%{$escaped_keyword}%", 'LIKE') ); $nids = $query->execute(); $internal_nodes = \Drupal\node\Entity\Node::loadMultiple($nids); // 过滤用户无权限查看的节点 $internal_results = []; foreach ($internal_nodes as $node) { if ($node->access('view')) { $internal_results[] = [ 'type' => 'internal', 'title' => $node->getTitle(), 'url' => $node->toUrl()->toString(), 'body' => strip_tags($node->get('body')->value), 'created' => $node->getCreatedTime(), ]; } }
3. 调用外部API获取结果
用Drupal内置的Guzzle客户端请求外部API,处理请求失败的异常:
$external_results = []; $client = \Drupal::httpClient(); try { $response = $client->get('https://your-external-api.com/search', [ 'query' => [ 'q' => $keyword, 'limit' => 10, // 和内部结果数量保持一致 ], 'timeout' => 5, ]); $api_data = json_decode((string)$response->getBody(), TRUE); // 标准化外部结果格式,和内部结果对齐 foreach ($api_data['items'] ?? [] as $item) { $external_results[] = [ 'type' => 'external', 'title' => $item['title'], 'url' => $item['link'], 'body' => strip_tags($item['summary']), 'created' => strtotime($item['publish_date']), ]; } } catch (\GuzzleHttp\Exception\RequestException $e) { // 记录日志,避免影响内部搜索结果展示 \Drupal::logger('combined_search')->error('External API request failed: @msg', ['@msg' => $e->getMessage()]); }
4. 整合并渲染结果
把内部和外部结果合并,可按发布时间排序,然后用自定义模板渲染:
// 合并结果并按发布时间倒序排序 $all_results = array_merge($internal_results, $external_results); usort($all_results, function($a, $b) { return $b['created'] - $a['created']; }); // 渲染自定义模板 return [ '#theme' => 'combined_search_results', '#results' => $all_results, '#keyword' => $keyword, ];
同时在模块的.module文件中注册模板:
function combined_search_theme($existing, $type, $theme, $path) { return [ 'combined_search_results' => [ 'variables' => ['results' => [], 'keyword' => ''], 'template' => 'combined-search-results', ], ]; }
模板文件templates/combined-search-results.html.twig示例:
{% if results %} <h2>搜索结果(关键词: {{ keyword }})</h2> <ul class="search-results"> {% for result in results %} <li class="result-item {{ result.type }}"> {% if result.type == 'external' %} <span class="badge">外部文章</span> {% else %} <span class="badge">本站页面</span> {% endif %} <h3><a href="{{ result.url }}">{{ result.title }}</a></h3> <p>{{ result.body|truncate(200) }}</p> <small>{{ result.created|format_date('medium') }}</small> </li> {% endfor %} </ul> {% else %} <p>未找到匹配 "{{ keyword }}" 的内容</p> {% endif %}
5. 配置搜索表单
修改默认搜索表单的提交路径,指向我们的联合搜索路由:
function combined_search_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) { if ($form_id == 'search_form') { $form['#action'] = \Drupal\Core\Url::fromRoute('combined_search.index')->toString(); } }
在routing.yml中定义路由:
combined_search.index: path: '/combined-search' defaults: _controller: '\Drupal\combined_search\Controller\SearchController::index' _title: '联合搜索' requirements: _permission: 'access content'
三、优化建议
- 缓存外部结果:用Drupal的缓存API存储外部API返回的数据,比如
\Drupal\Core\Cache\Cache::set("external_search_{$keyword}", $external_results, time() + 3600),减少API调用频次。 - 分页支持:如果结果数量多,给内部查询和外部API都加上分页参数,合并后统一分页展示。
- 相关性排序:内部内容可以用Drupal的搜索评分(如果启用核心搜索模块),外部API如果支持相关性排序就传递参数,提升结果精准度。
内容的提问来源于stack exchange,提问作者Steve James
相关产品推荐
相关产品推荐

