多索引搜索 #
多索引搜索概述 #
多索引搜索允许同时查询多个索引,适用于:
- 跨类型搜索(商品、文章、用户)
- 分类搜索(不同分类使用不同索引)
- 多语言搜索(每种语言一个索引)
基本用法 #
单个客户端搜索 #
javascript
const client = algoliasearch('APP_ID', 'SEARCH_KEY');
const queries = [
{
indexName: 'products',
query: 'iphone',
params: {
hitsPerPage: 5
}
},
{
indexName: 'articles',
query: 'iphone',
params: {
hitsPerPage: 3
}
},
{
indexName: 'users',
query: 'iphone',
params: {
hitsPerPage: 3
}
}
];
const { results } = await client.multipleQueries(queries);
console.log(results);
// [
// { hits: [...], nbHits: 100, ... }, // products结果
// { hits: [...], nbHits: 50, ... }, // articles结果
// { hits: [...], nbHits: 10, ... } // users结果
// ]
结果结构 #
javascript
{
results: [
{
index: 'products',
hits: [...],
nbHits: 100,
page: 0,
hitsPerPage: 5
},
{
index: 'articles',
hits: [...],
nbHits: 50,
page: 0,
hitsPerPage: 3
}
]
}
搜索策略 #
并行搜索(默认) #
javascript
const { results } = await client.multipleQueries(queries, {
strategy: 'none' // 并行执行所有查询
});
停止于足够结果 #
javascript
const { results } = await client.multipleQueries(queries, {
strategy: 'stopIfEnoughMatches' // 找到足够结果后停止
});
实际应用 #
跨类型搜索 #
javascript
async function federatedSearch(query) {
const queries = [
{
indexName: 'products',
query,
params: {
hitsPerPage: 5,
attributesToHighlight: ['name', 'description'],
attributesToRetrieve: ['name', 'price', 'image', 'category']
}
},
{
indexName: 'articles',
query,
params: {
hitsPerPage: 3,
attributesToHighlight: ['title', 'content'],
attributesToRetrieve: ['title', 'excerpt', 'author', 'publishedAt']
}
},
{
indexName: 'users',
query,
params: {
hitsPerPage: 3,
attributesToHighlight: ['name', 'bio'],
attributesToRetrieve: ['name', 'avatar', 'bio']
}
}
];
const { results } = await client.multipleQueries(queries);
return {
products: results[0],
articles: results[1],
users: results[2]
};
}
渲染结果 #
javascript
function renderFederatedResults(data) {
const { products, articles, users } = data;
let html = '';
// 商品结果
if (products.hits.length > 0) {
html += `
<section class="result-section">
<h3>商品 (${products.nbHits})</h3>
<div class="items">
${products.hits.map(hit => `
<div class="product-item">
<img src="${hit.image}" alt="${hit.name}">
<div class="info">
<h4>${hit._highlightResult.name.value}</h4>
<span class="price">$${hit.price}</span>
</div>
</div>
`).join('')}
</div>
${products.nbHits > 5 ? `<a href="/search?type=products&q=${query}">查看全部 ${products.nbHits} 个商品</a>` : ''}
</section>
`;
}
// 文章结果
if (articles.hits.length > 0) {
html += `
<section class="result-section">
<h3>文章 (${articles.nbHits})</h3>
<div class="items">
${articles.hits.map(hit => `
<div class="article-item">
<h4>${hit._highlightResult.title.value}</h4>
<p>${hit.excerpt}</p>
<span class="author">${hit.author}</span>
</div>
`).join('')}
</div>
</section>
`;
}
// 用户结果
if (users.hits.length > 0) {
html += `
<section class="result-section">
<h3>用户 (${users.nbHits})</h3>
<div class="items">
${users.hits.map(hit => `
<div class="user-item">
<img src="${hit.avatar}" alt="${hit.name}">
<div class="info">
<h4>${hit._highlightResult.name.value}</h4>
<p>${hit.bio}</p>
</div>
</div>
`).join('')}
</div>
</section>
`;
}
return html;
}
多索引分页 #
分页查询 #
javascript
async function paginatedMultiSearch(query, pages = {}) {
const queries = [
{
indexName: 'products',
query,
params: {
page: pages.products || 0,
hitsPerPage: 10
}
},
{
indexName: 'articles',
query,
params: {
page: pages.articles || 0,
hitsPerPage: 5
}
}
];
return await client.multipleQueries(queries);
}
无限滚动 #
javascript
class MultiIndexInfiniteScroll {
constructor(query, indices) {
this.query = query;
this.indices = indices.map(index => ({
name: index,
page: 0,
hasMore: true
}));
}
async loadMore() {
const queries = this.indices
.filter(idx => idx.hasMore)
.map(idx => ({
indexName: idx.name,
query: this.query,
params: {
page: idx.page,
hitsPerPage: 10
}
}));
if (queries.length === 0) return null;
const { results } = await client.multipleQueries(queries);
results.forEach((result, i) => {
const index = this.indices.find(idx => idx.name === queries[i].indexName);
index.page++;
index.hasMore = result.page < result.nbPages - 1;
});
return results;
}
}
多索引过滤 #
不同过滤条件 #
javascript
const queries = [
{
indexName: 'products',
query: 'phone',
params: {
filters: 'inStock:true',
hitsPerPage: 5
}
},
{
indexName: 'articles',
query: 'phone',
params: {
filters: 'status:published',
hitsPerPage: 3
}
}
];
统一过滤 #
javascript
function buildMultiSearchQueries(query, filters) {
const baseParams = {
query,
hitsPerPage: 5
};
return [
{
indexName: 'products',
params: {
...baseParams,
filters: filters.products || ''
}
},
{
indexName: 'articles',
params: {
...baseParams,
filters: filters.articles || ''
}
}
];
}
使用InstantSearch #
多索引配置 #
javascript
import instantsearch from 'instantsearch.js';
import { configure, index } from 'instantsearch.js/es/widgets';
const search = instantsearch({
indexName: 'products',
searchClient: client
});
search.addWidgets([
configure({
hitsPerPage: 5
}),
// 商品索引
index({
indexName: 'products',
indexId: 'products'
}).addWidgets([
hits({
container: '#products-hits',
templates: {
item: (hit) => `<div>${hit.name}</div>`
}
})
]),
// 文章索引
index({
indexName: 'articles',
indexId: 'articles'
}).addWidgets([
hits({
container: '#articles-hits',
templates: {
item: (hit) => `<div>${hit.title}</div>`
}
})
])
]);
search.start();
性能优化 #
并行请求 #
javascript
// multipleQueries自动并行执行
// 无需手动Promise.all
限制索引数量 #
javascript
// 建议:最多同时搜索5个索引
const queries = [
{ indexName: 'index1', query, params },
{ indexName: 'index2', query, params },
{ indexName: 'index3', query, params }
];
条件查询 #
javascript
async function smartMultiSearch(query, types) {
const queries = [];
if (types.includes('products')) {
queries.push({
indexName: 'products',
query,
params: { hitsPerPage: 5 }
});
}
if (types.includes('articles')) {
queries.push({
indexName: 'articles',
query,
params: { hitsPerPage: 3 }
});
}
return await client.multipleQueries(queries);
}
完整示例 #
全站搜索 #
javascript
class SiteSearch {
constructor(client) {
this.client = client;
this.indices = ['products', 'articles', 'users', 'categories'];
}
async search(query, options = {}) {
const {
hitsPerPage = 5,
filters = {},
facets = {}
} = options;
const queries = this.indices.map(indexName => ({
indexName,
query,
params: {
hitsPerPage,
filters: filters[indexName] || '',
facets: facets[indexName] || [],
attributesToHighlight: this.getHighlightAttributes(indexName),
attributesToRetrieve: this.getRetrieveAttributes(indexName)
}
}));
const { results } = await this.client.multipleQueries(queries);
return this.formatResults(results);
}
getHighlightAttributes(indexName) {
const attrs = {
products: ['name', 'description', 'brand'],
articles: ['title', 'content'],
users: ['name', 'bio'],
categories: ['name', 'description']
};
return attrs[indexName] || [];
}
getRetrieveAttributes(indexName) {
const attrs = {
products: ['name', 'price', 'image', 'brand', 'category'],
articles: ['title', 'excerpt', 'author', 'publishedAt'],
users: ['name', 'avatar', 'bio'],
categories: ['name', 'icon']
};
return attrs[indexName] || [];
}
formatResults(results) {
const formatted = {};
results.forEach(result => {
const indexName = result.index || result.params?.indexName;
formatted[indexName] = {
hits: result.hits,
nbHits: result.nbHits,
page: result.page,
nbPages: result.nbPages,
hasMore: result.page < result.nbPages - 1
};
});
return formatted;
}
getTotalHits(results) {
return Object.values(results).reduce((sum, r) => sum + r.nbHits, 0);
}
hasResults(results) {
return Object.values(results).some(r => r.hits.length > 0);
}
}
// 使用
const siteSearch = new SiteSearch(client);
const results = await siteSearch.search('iphone');
console.log(`找到 ${siteSearch.getTotalHits(results)} 条结果`);
总结 #
多索引搜索要点:
| 要点 | 说明 |
|---|---|
| 方法 | multipleQueries |
| 参数 | indexName, query, params |
| 策略 | none(并行), stopIfEnoughMatches |
| 应用 | 跨类型搜索、分类搜索 |
| 优化 | 限制索引数、条件查询 |
接下来,让我们学习 A/B测试。
最后更新:2026-03-28