基础搜索 #

搜索基础 #

简单搜索 #

javascript
const algoliasearch = require('algoliasearch');
const client = algoliasearch('APP_ID', 'SEARCH_KEY');
const index = client.initIndex('products');

// 最简单的搜索
const results = await index.search('iphone');

console.log(results);

搜索结果结构 #

javascript
{
  "hits": [...],              // 搜索结果数组
  "nbHits": 150,              // 总匹配数
  "page": 0,                  // 当前页码
  "nbPages": 15,              // 总页数
  "hitsPerPage": 10,          // 每页结果数
  "processingTimeMS": 1,      // 处理时间(毫秒)
  "query": "iphone",          // 搜索词
  "params": "...",            // 搜索参数
  "exhaustiveNbHits": true,   // 是否精确计数
  "exhaustiveFacetsCount": true
}

结果项结构 #

javascript
{
  "objectID": "prod-001",
  "name": "iPhone 15 Pro",
  "brand": "Apple",
  "price": 999,
  
  // 高亮结果
  "_highlightResult": {
    "name": {
      "value": "<em>iPhone</em> 15 Pro",
      "matchLevel": "full",
      "matchedWords": ["iphone"],
      "fullyHighlighted": false
    }
  },
  
  // 代码片段结果
  "_snippetResult": {
    "description": {
      "value": "...latest <em>iPhone</em> with A17 Pro..."
    }
  },
  
  // 排名信息
  "_rankingInfo": {
    "nbTypos": 0,
    "firstMatchedWord": 0,
    "proximityDistance": 1
  }
}

搜索方法 #

使用回调 #

javascript
index.search('iphone', (err, results) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(results.hits);
});

使用Promise #

javascript
index.search('iphone')
  .then(results => {
    console.log(results.hits);
  })
  .catch(err => {
    console.error(err);
  });

使用async/await #

javascript
try {
  const results = await index.search('iphone');
  console.log(results.hits);
} catch (err) {
  console.error(err);
}

搜索参数 #

基本参数 #

javascript
const results = await index.search('iphone', {
  hitsPerPage: 20,    // 每页结果数
  page: 0,            // 页码(从0开始)
  attributesToRetrieve: ['name', 'price', 'imageUrl']  // 返回字段
});

分页搜索 #

javascript
async function searchWithPagination(query, page = 0) {
  const results = await index.search(query, {
    hitsPerPage: 20,
    page: page
  });
  
  return {
    items: results.hits,
    currentPage: results.page,
    totalPages: results.nbPages,
    totalItems: results.nbHits,
    hasNextPage: results.page < results.nbPages - 1,
    hasPrevPage: results.page > 0
  };
}

// 使用
const page1 = await searchWithPagination('iphone', 0);
const page2 = await searchWithPagination('iphone', 1);

限制搜索属性 #

javascript
// 只在name和brand中搜索
const results = await index.search('apple', {
  restrictSearchableAttributes: ['name', 'brand']
});

搜索选项 #

拼写容错 #

javascript
// 启用拼写容错(默认)
const results = await index.search('iphne', {
  typoTolerance: true
});

// 禁用拼写容错
const results = await index.search('iphne', {
  typoTolerance: false
});

忽略复数 #

javascript
// "phone" 可以匹配 "phones"
const results = await index.search('phone', {
  ignorePlurals: true
});

移除停用词 #

javascript
// 移除常见停用词(the, a, an等)
const results = await index.search('the iphone', {
  removeStopWords: true
});

查询类型 #

javascript
// prefixLast(默认):最后一个词前缀匹配
// "iph" 匹配 "iphone"
const results = await index.search('iph', {
  queryType: 'prefixLast'
});

// prefixAll:所有词前缀匹配
const results = await index.search('iph pro', {
  queryType: 'prefixAll'
});

// prefixNone:精确匹配
const results = await index.search('iphone', {
  queryType: 'prefixNone'
});

搜索结果处理 #

处理高亮 #

javascript
function renderHit(hit) {
  const name = hit._highlightResult.name.value;
  const description = hit._snippetResult?.description?.value || hit.description;
  
  return `
    <div class="product">
      <h3>${name}</h3>
      <p>${description}</p>
      <span class="price">$${hit.price}</span>
    </div>
  `;
}

自定义高亮标签 #

javascript
const results = await index.search('iphone', {
  attributesToHighlight: ['name', 'description'],
  highlightPreTag: '<mark>',
  highlightPostTag: '</mark>'
});

// 结果: <mark>iPhone</mark> 15 Pro

处理无结果 #

javascript
async function search(query) {
  const results = await index.search(query);
  
  if (results.hits.length === 0) {
    return {
      success: false,
      message: `没有找到"${query}"相关的结果`,
      suggestions: await getSuggestions(query)
    };
  }
  
  return {
    success: true,
    items: results.hits,
    total: results.nbHits
  };
}

搜索函数封装 #

基础搜索函数 #

javascript
class SearchService {
  constructor(appId, apiKey, indexName) {
    const client = algoliasearch(appId, apiKey);
    this.index = client.initIndex(indexName);
  }
  
  async search(query, options = {}) {
    const defaultOptions = {
      hitsPerPage: 20,
      page: 0,
      attributesToHighlight: ['name', 'description'],
      attributesToRetrieve: ['name', 'price', 'imageUrl', 'brand']
    };
    
    const results = await this.index.search(query, {
      ...defaultOptions,
      ...options
    });
    
    return this.formatResults(results);
  }
  
  formatResults(results) {
    return {
      items: results.hits.map(this.formatHit),
      total: results.nbHits,
      page: results.page,
      totalPages: results.nbPages,
      processingTime: results.processingTimeMS
    };
  }
  
  formatHit(hit) {
    return {
      id: hit.objectID,
      name: hit._highlightResult.name.value,
      price: hit.price,
      image: hit.imageUrl,
      brand: hit.brand
    };
  }
}

// 使用
const searchService = new SearchService('APP_ID', 'SEARCH_KEY', 'products');
const results = await searchService.search('iphone');

高级搜索函数 #

javascript
async function advancedSearch(options) {
  const {
    query = '',
    filters = '',
    facets = [],
    page = 0,
    hitsPerPage = 20,
    sortBy = 'default'
  } = options;
  
  // 选择索引(支持排序)
  const indexName = sortBy === 'default' 
    ? 'products' 
    : `products_${sortBy}`;
  
  const index = client.initIndex(indexName);
  
  const searchParams = {
    query,
    page,
    hitsPerPage,
    attributesToHighlight: ['name', 'description'],
    attributesToSnippet: ['description:100']
  };
  
  if (filters) {
    searchParams.filters = filters;
  }
  
  if (facets.length > 0) {
    searchParams.facets = facets;
  }
  
  return await index.search(searchParams);
}

搜索状态管理 #

搜索状态对象 #

javascript
const searchState = {
  query: '',
  page: 0,
  filters: {},
  sortBy: 'default',
  hitsPerPage: 20,
  
  setQuery(query) {
    this.query = query;
    this.page = 0;
  },
  
  setPage(page) {
    this.page = page;
  },
  
  setFilter(key, value) {
    this.filters[key] = value;
    this.page = 0;
  },
  
  removeFilter(key) {
    delete this.filters[key];
    this.page = 0;
  },
  
  buildFilterString() {
    return Object.entries(this.filters)
      .map(([key, value]) => `${key}:${value}`)
      .join(' AND ');
  }
};

执行搜索 #

javascript
async function executeSearch(state) {
  const filterString = state.buildFilterString();
  
  const results = await index.search(state.query, {
    page: state.page,
    hitsPerPage: state.hitsPerPage,
    filters: filterString,
    facets: ['brand', 'category', 'price_range']
  });
  
  return {
    ...results,
    state: { ...state }
  };
}

搜索优化 #

防抖处理 #

javascript
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

const debouncedSearch = debounce(async (query) => {
  const results = await index.search(query);
  renderResults(results);
}, 300);

// 输入时调用
inputElement.addEventListener('input', (e) => {
  debouncedSearch(e.target.value);
});

缓存结果 #

javascript
const searchCache = new Map();

async function cachedSearch(query, options = {}) {
  const cacheKey = JSON.stringify({ query, ...options });
  
  if (searchCache.has(cacheKey)) {
    return searchCache.get(cacheKey);
  }
  
  const results = await index.search(query, options);
  searchCache.set(cacheKey, results);
  
  // 限制缓存大小
  if (searchCache.size > 100) {
    const firstKey = searchCache.keys().next().value;
    searchCache.delete(firstKey);
  }
  
  return results;
}

预取结果 #

javascript
// 预取热门搜索
async function prefetchPopularSearches() {
  const popularQueries = ['iphone', 'samsung', 'laptop', 'headphones'];
  
  await Promise.all(
    popularQueries.map(query => 
      index.search(query, { hitsPerPage: 5 })
    )
  );
}

错误处理 #

基本错误处理 #

javascript
async function safeSearch(query) {
  try {
    const results = await index.search(query);
    return { success: true, data: results };
  } catch (error) {
    console.error('Search failed:', error);
    return { 
      success: false, 
      error: error.message,
      data: null 
    };
  }
}

重试机制 #

javascript
async function searchWithRetry(query, maxRetries = 3) {
  let lastError;
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await index.search(query);
    } catch (error) {
      lastError = error;
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
    }
  }
  
  throw lastError;
}

搜索示例 #

电商搜索 #

javascript
async function productSearch(query, options = {}) {
  const {
    category,
    brand,
    minPrice,
    maxPrice,
    page = 0
  } = options;
  
  const filters = [];
  
  if (category) filters.push(`category:${category}`);
  if (brand) filters.push(`brand:${brand}`);
  if (minPrice !== undefined) filters.push(`price >= ${minPrice}`);
  if (maxPrice !== undefined) filters.push(`price <= ${maxPrice}`);
  
  return await index.search(query, {
    filters: filters.join(' AND '),
    page,
    hitsPerPage: 20,
    facets: ['brand', 'category', 'price_range'],
    attributesToHighlight: ['name', 'description'],
    customRanking: ['desc(rating)', 'desc(popularity)']
  });
}

内容搜索 #

javascript
async function contentSearch(query, options = {}) {
  const {
    type,
    author,
    tags,
    page = 0
  } = options;
  
  const filters = [];
  
  if (type) filters.push(`type:${type}`);
  if (author) filters.push(`author.id:${author}`);
  if (tags?.length) {
    filters.push(`tags:${tags.join(' OR tags:')}`);
  }
  
  return await index.search(query, {
    filters: filters.join(' AND '),
    page,
    hitsPerPage: 10,
    restrictSearchableAttributes: ['title', 'content', 'tags'],
    attributesToHighlight: ['title', 'content'],
    attributesToSnippet: ['content:200'],
    removeStopWords: true,
    ignorePlurals: true
  });
}

总结 #

基础搜索要点:

要点 说明
搜索方法 search()方法
结果结构 hits, nbHits, page等
参数配置 hitsPerPage, page, filters等
结果处理 高亮、分页、无结果处理
优化技巧 防抖、缓存、预取
错误处理 try/catch、重试机制

接下来,让我们学习 分面搜索

最后更新:2026-03-28