上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖

【油猴开发指南】实现Node.js的轻量级模糊搜索

[复制链接]
  • TA的每日心情
    慵懒
    2024-10-28 07:07
  • 签到天数: 193 天

    [LV.7]常住居民III

    712

    主题

    5959

    回帖

    6758

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6758

    荣誉开发者喜迎中秋油中2周年生态建设者

    发表于 2024-10-17 02:55:29 | 显示全部楼层 | 阅读模式

    前文

    本节跟油猴其实关系并不大
    但是问的人实在太多了
    于是决定提供一个实现简易的模糊搜索的Node.js例子
    但是并没有进行权限控制
    感兴趣可以基于这之前继续实现

    参考项目的地址为https://github.com/lihengdao666/sqlite-fuzzy-search-demo

    正文

    首先输入npm init填一些项目信息起手
    然后因为我们需要提供题目的收录和查询,轻量一点我选择了express作为HTTP服务器的库
    输入npm install express --save安装一下
    直接上官网抄一下例子https://expressjs.com/en/guide/routing.html
    创建一个index.js文件

    const express = require('express')
    const app = express()
    
    app.use(express.json())
    app.post('/submit', (req, res) => {
      res.send('POST request to the homepage')
    })
    
    app.post('/search', (req, res) => {
      res.send('POST request to the homepage')
    })
    
    app.listen(3000, () => {
      console.log(`fuzzySearch app listening on port !`)
    })

    就得到了一个基本的http服务器,然后我们需要进行字段过滤,数据库选型

    我们先进行最简单的字段过滤吧

    字段过滤

    经过两分钟的深思熟虑,最后选择了express-validator库

    先输入npm install express-validator安装一下

    参考一下官方文档,可以改造成了

    这样就可以自动校验不合法的提交请求,我们写的是默认POST

    提交题目数据为question,answer,type三个字段,搜寻数据的时候仅提交question字段

    app.post('/submit', [
      check('question').notEmpty(),
      check('answer').notEmpty(),
      check('type').notEmpty(),
    ], (req, res) => {
      const result = validationResult(req);
      if (! result.isEmpty()) {
        return res.send(`filed is not valid!`);
      }
      res.send('POST request to the homepage')
    })
    
    app.post('/search', [
      check('question').notEmpty(),
    ], (req, res) => {
      const result = validationResult(req);
      if (! result.isEmpty()) {
        return res.send(`filed is not valid!`);
      }
      res.send('POST request to the homepage')
    })

    那我们就完成了HTTP服务器的搭建,正式开始分词问题了

    分词选型

    由于ES和其他数据库的分词的配置较为复杂,需要从头进行搭建

    而我们作为例子追求的并不是供几十万人的同时使用,而是一个小型的,可以随地大小建的Demo

    方便大家可以为了解决一些临时的需求快速上手

    所以最终选择了Sqlite + 分词插件作为技术选型

    由于默认的Sqlite的中文分词存在些许问题,所以经过搜索,最终选择了wangfenjin大佬的分词插件

    具体文档可以参考wangfenjin/simple

    我们首先去Release 下载好对应的文件

    放到项目目录中,然后输入npm i sqlite3安装sqlite依赖包

    需要注意的是sqlite3依赖于python,而python目前3.12+会导致sqlite3的依赖安装失败

    所以建议降级python到3.11版本,教程使用的版本是3.11.7

    分词初始化

    参考Github的例子我们可以很轻易的写出Sqlite分词的初始化代码

    function startSqlite() {
      db.serialize(function () {
        db.loadExtension(path.resolve('./', "simple"));
        db.run("select jieba_dict(?)", path.resolve('./', "dict"));
        db.run("CREATE VIRTUAL TABLE if not exists fuzzySearch USING fts5(question,answer,type UNINDEXED, tokenize = 'simple')");
      })
    }

    那么接下来我们可以很轻易的写出简易的分词搜索的API

    首先是提交的

      app.post('/submit', [
        check('question').notEmpty(),
        check('answer').notEmpty(),
        check('type').notEmpty(),
      ], (req, res) => {
        const result = validationResult(req);
        if (!result.isEmpty()) {
          return res.json({ status: 'error', message: '参数不合法' });
        }
        const { question, answer, type } = req.body
        db.run("insert into fuzzySearch(question,answer,type) values (?,?,?)", [question, answer, type], (err) => {
          if (err == null) {
            res.json({ status: 'success', message: '上传成功' });
          } else {
            console.log("submit insert error :", err)
            res.json({ status: 'error', message: '上传失败' });
          }
        });
    
      })

    然后是查询的

      app.post('/search', [
        check('question').notEmpty(),
      ], (req, res) => {
        const result = validationResult(req);
        if (!result.isEmpty()) {
          return res.send(`filed is not valid!`);
        }
        const { question } = req.body
        db.all("select rowid as id,question,answer,type from fuzzySearch where question match jieba_query(?) LIMIT 0,10", [question], (err, rows) => {
          if (err == null) {
            console.log(rows)
            res.json({ status: 'success', message: rows });
          } else {
            console.log("submit insert error :", err)
            res.json({ status: 'error', message: '查询失败' });
          }
        });
      })

    我们可以简单实验两条
    图片.png
    图片.png
    然后查询一下数据
    图片.png
    图片.png
    这里需要注意的是Sqlite分词的能力并不如其他的分词强大,仅在于轻便
    虽然对于关键词方面的搜寻能力不错,但是在一些情况下尽管相差不大但是会搜索不到,例如
    这里我删除了帅和有钱,留下一个相似但是语义又不是特别连贯的,相对搜索能力就较差了
    图片.png

    接下来我们做略微的补足valieKey用于做api的鉴权,而suffix用于增加修改和删除的API的入口复杂度

    const valieKey = `bbs.tampermonkey.net.cn`
    const suffix = `Racsw`
      app.post('/update' + suffix, [
        check('id').notEmpty(),
        check('question').notEmpty(),
        check('type').notEmpty(),
        check('answer').notEmpty(),
        check('key').notEmpty(),
      ], (req, res) => {
        const result = validationResult(req);
        if (!result.isEmpty()) {
          return res.json({ status: 'error', message: '参数不合法' });
        }
        const { id, question, type, answer, key } = req.body
        if (key !== valieKey) {
          return res.json({ status: 'error', message: 'key无效' });
        }
        db.run("UPDATE fuzzySearch SET question = ?,answer = ?,type = ? WHERE rowid = ?", [question, answer, type, id], (err) => {
          if (err == null) {
            res.json({ status: 'success', message: '修改成功' });
          } else {
            console.log("submit update error :", err)
            res.json({ status: 'error', message: '修改失败' });
          }
        });
      })

    然后尝试修改一下
    image.png
    再次进行查询测试
    image.png
    同理补足删除函数

    const valieKey = `bbs.tampermonkey.net.cn`
    const suffix = `Racsw`
      app.post('/delete' + suffix, [
        check('id').notEmpty(),
        check('key').notEmpty(),
      ], (req, res) => {
        const result = validationResult(req);
        if (!result.isEmpty()) {
          return res.json({ status: 'error', message: '参数不合法' });
        }
        const { id, key } = req.body
        if (key !== valieKey) {
          return res.json({ status: 'error', message: 'key无效' });
        }
        db.run("DELETE FROM fuzzySearch WHERE rowid = ?", [id], (err) => {
          if (err == null) {
            res.json({ status: 'success', message: '删除成功' });
          } else {
            console.log("submit update error :", err)
            res.json({ status: 'error', message: '删除失败' });
          }
        });
      })

    测试一下

    image.png

    再次进行查询

    image.png

    那么至此我们的增删改查就全部结束了

    关于参考项目的地址为https://github.com/lihengdao666/sqlite-fuzzy-search-demo

    参考文章

    https://blog.xulihang.me/sqlite-full-text-search/
    https://www.misterma.com/archives/892/
    https://www.cnblogs.com/wangfenjin/p/14425659.html

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
  • TA的每日心情
    开心
    5 小时前
  • 签到天数: 213 天

    [LV.7]常住居民III

    305

    主题

    4189

    回帖

    4056

    积分

    管理员

    积分
    4056

    管理员荣誉开发者油中2周年生态建设者喜迎中秋油中3周年挑战者 lv2

    发表于 2024-10-17 09:36:58 | 显示全部楼层
    跟油猴没关系就别往这里面塞了吧。。。。。。
    上不慕古,下不肖俗。为疏为懒,不敢为狂。为拙为愚,不敢为恶。
    回复

    使用道具 举报

    发表回复

    本版积分规则

    快速回复 返回顶部 返回列表