node使用MeiliSearch实现内容快速搜索

360 阅读1分钟

Meilisearch是一个基于 rust 开发的,快速的、完全开源的轻量级搜索引擎。它的数据存储基于磁盘与内存映射,不受 RAM 限制。在一定数量级下,搜索速度不逊于 Elasticsearch(ES),所以对于中小型项目来说,MeiliSearch 是一个不错的选择

一,基于docker安装Meilisearch

1,拉取镜像

docker pull getmeili/meilisearch:latest

image.png

2,启动服务

// 执行下面命令, \ 后换行
docker run -itd --rm \
--name meilisearch \
-p 7700:7700 \
-v $(pwd)/data:/meili_data \
-e MEILI_MASTER_KEY='b1xxxxxxxxxxxxxxxxxxxxxxxxxcb' \
getmeili/meilisearch:latest

注意: MEILI_MASTER_KEY:指定 master key,至少 16 字节的 UTF-8 字符串,这是为了保护 Meilisearch 实例免遭未经授权的使用,后继js初始化时需要用到的主密钥

image.png

查看是否运行成功

image.png

也可以浏览器http://ipxxx:7700 查看

image.png

二,在node项目使用

1,安装依赖

npm install meilisearch

2,MeiliSearch实例初始化

const { MeiliSearch } = require('meilisearch');
const _env = process.env.EGG_SERVER_ENV;
const hosts = {
  local: 'http://127.0.0.1:7700',
  gray: 'http://10.x.x.x:7700',
  prod: 'http://10.x.x.x:7700',
}
const meilisearchClient = new MeiliSearch({
  host: hosts[_env],
  apiKey: 'b1xxxxxxxxxxxxxxxxxxxxxxxxxcb' // 上面安装的 MEILI_MASTER_KEY
});

module.exports = meilisearchClient

3,service封装的常用方法

'use strict';

const Service = require('egg').Service;
const meilisearch = require('../utils/meilisearch');

class MeilisearchService extends Service {
   // 添加数据方法,index是模块标识
  async addDocuments(index, docs, config) {
    let res;
    try {
      res = await meilisearch.index(index).addDocuments(docs, config || {});
    } catch (error) {
      res = null;
    }
    return res;
  }
  // 修改数据方法,index是模块标识
  async updateDocuments(index, docs, config) {
    let res;
    try {
      res = await meilisearch.index(index).updateDocuments(docs, config || {});
    } catch (error) {
      res = null;
    }
    return res;
  }
  // 删除数据方法,index是模块标识
  async deleteDocument(index, id) {
    let res;
    try {
      res = await meilisearch.index(index).deleteDocument(id);
    } catch (error) {
      res = null;
    }
    return res;
  }
  // 查询数据方法,index是模块标识
  async search(index, query, options, config) {
    let res;
    try {
      res = await meilisearch.index(index).search(query, options || {}, config || {});
    } catch (error) {
      res = null;
    }
    return res;
  }
}

module.exports = MeilisearchService;

4,例子
在controller中新建或修改时,需要同步添加或修改meilisearch中的数据

// 新增
async add() {
    const { ctx, app, service } = this;
    const reqBody = ctx.request.body;
    try {
        const newData = {
          ...reqBody
        };
        // 数据入库
        const res = await ctx.model.xxx.create(newData);
        // 添加数据进meilisearch
        await service.meilisearch.addDocuments('homeSearch', [res]);
        ctx.success('保存成功');
    } catch (error) {
      ctx.error(4002, error);
    }
}

// 修改
async update() {
    const { ctx, app, service } = this;
    const reqBody = ctx.request.body;
    const updateData = {
      ...reqBody
    }
    try {
      const existItem = await model.findByPk(reqBody.id);
      if (!existItem) {
        return ctx.error(4002, '该资源不存在');
      }
      // 更新数据
      await existItem.update(updateData);
      // 更新meilisearch数据
      await service.meilisearch.updateDocuments('homeSearch', [existItem.dataValues]);
      ctx.success(existItem);
    } catch (error) {
      ctx.error(4002, error);
    }
}

// 搜索
async search() {
    const { ctx, service } = this;
    const { keyword, target, page, pageSize } = ctx.query;
    const options = {
      attributesToRetrieve: [],
      attributesToCrop: ['content'],
      cropMarker: '…',
      attributesToHighlight: ['*'],
      highlightPreTag: '<span class="highlight">', // 高亮标签包裹命中关键词
      highlightPostTag: '</span>',
      filter: ['status=1']
    };
    if (!isNaN(page) && !isNaN(pageSize)) {
      options.limit = +pageSize;
      options.offset = (+page - 1) * (+pageSize);
    }
    if (target) {
      options.filter.push(`typeId=${target}`);
    }
    const resourceSearcher = meilisearchClient.index('homeSearch')
    try {
      // 更新可过滤filter的属性, 要和options的filter配合使用
      await resourceSearcher.updateFilterableAttributes(['status', 'typeId']);
      // 更新搜索设置
      await resourceSearcher.updateSettings({
       // 搜索内容的字段
        searchableAttributes:['title', 'userName', 'content'],
        // searchableAttributes:['*'],
        // 返回字段
        displayedAttributes:['*'],
        // displayedAttributes:['title', 'authorName', 'content'],
      });
      // 搜索资源
      const res = await resourceSearcher.search(keyword, options);
      return ctx.success(res);
    } catch (error) {
      ctx.error(4002, error);
    }
  }

搜索效果:

image.png

meilisearch-js git地址:github.com/meilisearch…