olp中的搜索功能

OLP项目搜索功能实现

#tech / dev / project #type / howto #status / seed

olp中的搜索功能

目标

在 OLP 项目中实现高效的搜索功能,支持:

  • 关键词搜索(标题、内容、标签)
  • 多条件筛选与组合查询
  • 搜索结果高亮与分页
  • 搜索历史与热门推荐

前置条件

  • OLP 项目前后端分离架构已就绪
  • 后端数据库已配置(MySQL / PostgreSQL / Elasticsearch)
  • 了解防抖(debounce)与请求取消机制
  • 了解前后端搜索方案的取舍

技术方案

方案对比

方案适用场景优点缺点
前端过滤数据量小(<1000条)无网络请求,即时响应数据量大时卡顿
后端模糊查询中等数据量实现简单SQL LIKE 性能差
Elasticsearch大数据量/全文搜索高性能,支持分词架构复杂,需维护

OLP 项目采用 前后端分离 方案:前端负责筛选参数的获取与传递,后端根据参数在数据库中查询并返回结果。

步骤

1. 搜索框组件

<template>
  <div class="search-box">
    <el-input
      v-model="keyword"
      placeholder="搜索..."
      clearable
      @input="onInput"
    >
      <template #prefix>
        <el-icon><Search /></el-icon>
      </template>
    </el-input>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { useDebounceFn } from '@vueuse/core'

const keyword = ref('')
const emit = defineEmits<{
  search: [keyword: string]
}>()

const onInput = useDebounceFn(() => {
  emit('search', keyword.value)
}, 300)
</script>

2. 防抖与请求取消

输入时使用防抖避免频繁请求,同时取消上一次未完成的请求:

import { useDebounceFn } from '@vueuse/core'
import axios from 'axios'

let abortController: AbortController | null = null

const debouncedSearch = useDebounceFn(async (keyword: string) => {
  abortController?.abort()
  abortController = new AbortController()

  try {
    const res = await api.search(keyword, { signal: abortController.signal })
    results.value = res.data
  } catch (e) {
    if (e.name !== 'AbortError') throw e
  }
}, 300)

3. 后端 API 设计

GET /api/search?q={keyword}&type={type}&page={page}&size={size}

Response:
{
  "total": 42,
  "page": 1,
  "size": 20,
  "items": [
    { "id": "...", "title": "...", "excerpt": "...", "highlight": "..." }
  ]
}

后端实现要点:

  • 使用数据库全文索引或 Elasticsearch 分词查询
  • 返回匹配片段(excerpt)和高亮标记(highlight)
  • 支持分页以控制单次返回数据量

4. 结果展示与高亮

<template>
  <div class="search-results">
    <div v-for="item in results.items" :key="item.id" class="result-item">
      <h3 v-html="highlightText(item.title, keyword)"></h3>
      <p v-html="highlightText(item.excerpt, keyword)"></p>
    </div>
  </div>
</template>

<script setup lang="ts">
function highlightText(text: string, keyword: string): string {
  if (!keyword) return text
  const regex = new RegExp(`(${keyword})`, 'gi')
  return text.replace(regex, '<mark>$1</mark>')
}
</script>

5. 搜索历史与推荐

const searchHistory = useStorage<string[]>('search-history', [])

function addToHistory(keyword: string) {
  searchHistory.value = [
    keyword,
    ...searchHistory.value.filter(k => k !== keyword)
  ].slice(0, 10)
}

验证

  • 输入关键词后 300ms 内不重复请求(防抖生效)
  • 快速切换输入时旧请求被取消
  • 搜索结果正确展示,匹配内容高亮显示
  • 分页功能正常,翻页后结果正确
  • 搜索历史正确记录和展示
  • 空关键词时不发起搜索请求

常见问题

Q: 搜索结果不准确?

检查后端查询逻辑。SQL LIKE ‘%keyword%’ 无法处理分词场景,建议引入全文索引(MySQL FULLTEXT)或 Elasticsearch。

Q: 搜索响应慢?

  • 检查是否缺少数据库索引
  • 使用 Elasticsearch 替代 SQL 模糊查询
  • 对搜索结果做缓存(热门关键词缓存 5 分钟)
  • 限制返回字段,避免 SELECT *

Q: 中文搜索分词不准确?

使用支持中文分词的搜索引擎,如 Elasticsearch + IK 分词器,或 MySQL 的 ngram parser。

创建于 2025/1/1 更新于 2026/5/27