前文
本节跟油猴其实关系并不大
但是问的人实在太多了
于是决定提供一个实现简易的模糊搜索的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: '查询失败' });
}
});
})
我们可以简单实验两条
然后查询一下数据
这里需要注意的是Sqlite分词的能力并不如其他的分词强大,仅在于轻便
虽然对于关键词方面的搜寻能力不错,但是在一些情况下尽管相差不大但是会搜索不到,例如
这里我删除了帅和有钱,留下一个相似但是语义又不是特别连贯的,相对搜索能力就较差了
接下来我们做略微的补足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: '修改失败' });
}
});
})
然后尝试修改一下
再次进行查询测试
同理补足删除函数
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: '删除失败' });
}
});
})
测试一下
再次进行查询
那么至此我们的增删改查就全部结束了
关于参考项目的地址为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