这里用的数据库是mongodb
第1先做一个数据库类
db.js
javascript
const {MongoClient}=require('mongodb');
class ClientDB{
#urlPath='mongodb://ychj:123456@localhost:27017/?authSource=employees';
constructor(dbName,collectionName){
this.dbName=dbName;
this.collName=collectionName;
this.client=new MongoClient(this.#urlPath);
this.connection=null;
}
async connect(){
if(!this.connection)
{
this.connection=await this.client.connect();
console.log('Connected to MongoDB.');
const db=this.client.db(this.dbName);
this.collection=db.collection(this.collName);
}
}
async disconnect(){
if(this.connection)
{
await this.client.close();
this.connection=null;
console.log('Connection closed');
}
}
async insertOne(obj){
await this.connect();
try{
let result=await this.collection.insertOne(obj);
if(result.acknowledged)
{
return result.insertedId;
}
}catch(err)
{
console.log('Insert Error:',err);
throw err;
}
}
async deleteOne(obj)
{
await this.connect();
try{
let result=await this.collection.deleteOne(obj);
if(result.deletedCount===1)
{
return true;
}else
{
return false;
}
}catch(err){
console.error('Delete Error:',err);
throw err;
}
}
/**
* 统计查询对象有多少条结果
* 这里只要大于0则返回true
* @param {object} obj //查询对象
* @returns
*/
async countDocuments(obj){
await this.connect();
try{
let result=await this.collection.countDocuments(obj);
console.log(result);
if(result>0)
{
return true;
}else
{
return false;
}
}catch(err)
{
console.error("Count documents Error:",err);
throw err;
}
}
/**
* 查找多结果
* @param {Object} obj //查询对象
* @returns
*/
async find(obj)
{
await this.connect();
try{
let result=await this.collection.find(obj).toArray();
if(result.length>=1)
{
return result;
}else
{
return false;
}
}catch(err)
{
console.error("find documents Error:",err);
throw new Error(err);
}
}
/**
*
* @param {Object} upCondition 更新的条件对象
* @param {Object} updateData 要更新的数据对象
*/
async updateOne(upCondition,updateData){
await this.connect();
try{
let result=await this.collection.updateOne(upCondition,updateData);
if(result.acknowledged && result.modifiedCount===1)
{
return true;
}else
{
return false;
}
}catch(err)
{
console.error(err);
throw new Error(err);
}
}
}
module.exports=ClientDB;
第2写一个根据搜索词,从数据库里面找出相关词,并生成一个临时的json文件,用数据流的方式将所有搜索到的json文件写入临时文件内
注意:数据库里面的文档都是进行了分词,用了jieba这个Nodejs库进行的分词,搜索出结果后要把文档内的空格全删除掉
searchJieba.js
javascript
const fs=require("fs");
const ClientDB=require('../db');
const db=new ClientDB("employees","employees");
async function searchDocuments(words)
{
console.log(words);
try{
const docs=await db.find({$text:{"$search":words}});
if(docs===false)
{
return;
}
//创建随机的临时文件名
const outputFile=getRandomFileName();
//对大文件的数据流,为什么要用数据流,因为搜索出来的结果如果非常大,如上千条,我们不能存储在内存中,而是存在一个临时的随机文件中
//避免占用或撑爆我们的内存,所以直接写入临时文件当中,然后显示 的时候再读取
const writeableStream=fs.createWriteStream(outputFile);
//将读取到的搜索结果存储为json文件格式,用一个数组将其包含当中
writeableStream.write("[");
let isFile=true;
docs.forEach((doc)=>{
if(!isFile)
{
writeableStream.write(",\n");
}
const formattitle=doc.title.replace(/\s+/g,'');
const formatcontent=doc.content.split('\n').map(paragraph=>paragraph.replace(/\s+/g,'')).join("\n");
const id=doc.id;
//将各属性id,title,content组合成对象形式,然后再转化为json格式写入临时的json文件中
const result={id:id,title:formattitle,content:formatcontent};
writeableStream.write(JSON.stringify(result));
isFile=false;
});
writeableStream.write("]");
writeableStream.end();
//返回临时文件名字
return outputFile;
}finally{
await db.disconnect();
}
}
//生成临时的随机json文件
function getRandomFileName(){
return `output_${Math.random().toString(36).substring(2,9)}.json`
}
exports.searchDocuments=searchDocuments;
第3做接口文件,方便前端调用
javascript
const express=require("express");
const app=express();
const {searchDocuments}=require("./searchJieba");
const fs=require("fs");
const readline=require("readline");
const cors=require("cors");
const path=require("path");
app.use(cors({
origin:"http://localhost:5173"
}))
app.use(express.json());
app.post("/api/search",async (req,res)=>{
let {words}=req.body;
console.log(words);
try{
const outputFile=await searchDocuments(words);
console.log("文件名:",outputFile);
if(outputFile===undefined)
{
const msg={stat:404,message:"未查询到相关结果"};
const msgJson=JSON.stringify(msg);
res.send(msgJson);
}else
{
const fileStream=fs.createReadStream(path.join(__dirname,outputFile));
const rl=readline.createInterface({
input:fileStream,
crlfDelay:Infinity
});
//按行读取所有的json文件内容
let jsonData='';
rl.on('line',line=>{
jsonData+=line;
});
rl.on('close',()=>{
try{
console.log(jsonData);
res.send(jsonData);
//延时删除临时文件,避免文件数据还未传输完,这边已经删除了临时文件
if(fs.existsSync(outputFile))
{
new Promise((resolve,reject)=>{
setTimeout(()=>{
fs.unlink(outputFile,(err)=>{
if(err)reject(err);
else resolve(`文件${outputFile}已经删除`);
});
},5000);//延时5秒
})
.then(console.log)
.catch(console.error);
}
}catch(err)
{
console.error('发送json数据给前端出错:',err);
res.status(400).end('向前端发送json数据出错');
}
})
}
}catch(err)
{
console.error('查询搜索词出错:',err);
res.status(400).end('查询出错');
}
});
app.listen(1337,"localhost",()=>{
console.log('服务端口已经开启监听');
});
第4步是用vue写前端文件
javascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>测试用vite显示搜索前端的VUE页面,然后调用后端API搜索出来的数据</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.13/vue.js"></script>
</head>
<body>
<div id="app">
<my-search></my-search>
</div>
<script>
const showChild={
props:["responseData"],
template:`<div>
<ul>
<li v-for="item in responseData" :key="item.id">
<div>ID: {{item.id}}</div>
<div>标题:{{item.title}}</div>
<div>内容:{{item.content}}</div>
</li>
</ul>
</div>`,
};
const Search={
template:`<div>
<form @submit.prevent="handleSubmit">
<div>
<label for="searchPage">搜索</label>
<input type="text" id="searchPage" v-model="searchData">
<button :disabled="isLoading">{{isLoading ? "搜索中..." : "搜索"}}</button>
</div>
</form>
<div v-if="!isValied" style="color:red;font-size:14px;">{{searchWordsErr}}</div>
<div v-if="!isSearchResult">{{searchWordsNull}} </div>
<my-child :responseData="responseData" v-if="isSearchResult"></my-child>
</div>`,
data(){
return{
searchData:'',
isLoading:false,
searchWordsErr:'',
responseData:'',
searchWordsNull:'',
isSearchResult:true
}
},
computed:{
isValied(){
return this.searchData.length >= 2;
}
},
methods:{
handleSubmit(){
this.valiedSearchWords(this.searchData);
if(!this.isValied)return;
this.isLoading=true;
let searchWords={words:this.searchData};
console.log(searchWords);
try{
fetch("http://localhost:1337/api/search",{
method:"post",
body:JSON.stringify(searchWords),
headers:{"Content-Type":"application/json"}
})
.then(response=>{
if(response.ok)
{
return response.json();
}
})
.then(data=>{
if(data.stat===404)
{
console.log(data.message);
this.isSearchResult=false;
this.searchWordsNull=data.message;
return;
}
this.isSearchResult=true;
this.responseData=data;
})
.catch(error=>{
console.log('搜索出错:',error);
})
.finally(()=>{
this.isLoading=false;
})
}catch(err)
{
console.error("获取后端数据出错:",err);
}
},
valiedSearchWords(words){
if(words.length<2)
{
this.searchWordsErr="搜索必须大于2个字符";
}else
{
this.searchWordsErr='';
}
}
},
watch:{
searchData(newVal)
{
this.valiedSearchWords(newVal);
}
},
components:{
'my-child':showChild
}
};
new Vue({
el:"#app",
components:{
"mySearch":Search
}
});
</script>
</body>
</html>
这样就是一个用vue写的搜索页面,后端是一个express调用接口
顺便也把插入文档的代码也贴出来,数据库类和上面的db.js 是一样的
第1步是写插入数据库的文件代码
insertJieba.js
javascript
const {Jieba}=require("@node-rs/jieba")
const {dict}=require("@node-rs/jieba/dict");
const ClientDB=require("../db");
const db=new ClientDB("employees","employees");
async function insertDocument(titledata,contentdata)
{
try{
const jieba=Jieba.withDict(dict);
const titleWords=jieba.cut(titledata).join(' ');
const contentWords=contentdata.split("\n").map(paragraph=>jieba.cut(paragraph).join(" "));
const randomId=RandomId(1,10000);
const result=await db.insertOne({id:randomId,title:titleWords,content:contentWords.join("\n")});
if(result){
return true;
}else
{
return false;
}
}catch(error)
{
console.error(error);
throw new Error(error);
}
}
/**
* 用洗牌方法随机生成一个不重复的数字ID
* @param {num} min
* @param {num} max
* @returns
*/
function RandomId(min, max) {
// 创建一个从 min 到 max 的数组
const numbers = Array.from({ length: max - min + 1 }, (_, i) => i + min);
// 使用 Fisher-Yates 洗牌算法打乱数组
for (let i = numbers.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[numbers[i], numbers[j]] = [numbers[j], numbers[i]];
}
// 返回第一个元素作为ID
return numbers[0];
}
exports.insertDocument=insertDocument;
第2步是用express写接口文件,接口文件是接收前端数据,然后传给后端的insertJieba.js写入数据库的中间件
150.js
javascript
const express=require("express");
const cors=require("cors");
const app=express();
const {insertDocument}=require('./insertJieba');
app.use(cors({
origin:"http://localhost:5173"
}))
app.use(express.json());
app.post("/api/participle",async (req,res)=>{
let {title,content}=req.body;
try{
let result=await insertDocument(title,content);
let resData={};
if(result)
{
resData={msg:'数据插入成功',status:true};
}else
{
resData={msg:"数据插入失败",status:false};
}
res.send(JSON.stringify(resData));
}catch(error)
{
console.error('Insert Documents Error:',error);
res.status(404).end('插入分词发生错误');
}
});
app.listen(1337,"localhost",()=>{
console.log("服务端口已经开启监听");
});
第3步是前端文件,即vue写的插入title和content
150.html
javascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>测试用vite显示vue前端页面,然后调用后端API数据</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.13/vue.js"></script>
</head>
<body>
<div id="app">
<my-content></my-content>
</div>
<script>
const Content={
template:`<div>
<form @submit.prevent="handleSubmit">
<div>
<label for="addTitle">标题</label>
<input type="text" name="title" id="addTitle" v-model="title" @input="valiedTitle" required>
<span v-show="!titleErr.stat" :style="titleStyle">{{titleErr.msg}}</span>
</div>
<div>
<label for="addContent">内容</label>
<textarea id="addContent" rows="10" cols="45" v-model="content" @input="valiedContent" placeholder="请输入内容" ></textarea>
<span v-show="!contentErr.stat" :style="contentStyle">{{contentErr.msg}}</span>
</div>
<div>
<button id="btn" :disabled="isLoading">{{isLoading ? "提交中..." : "提交"}}</button>
</div>
</form>
<div id="output">{{responseData}}</div>
</div>`,
data(){
return{
title:'',
content:'',
isLoading:false,
responseData:'',
titleErr:{stat:true,msg:''},
contentErr:{stat:true,msg:''},
titleStyle:{
color:"red",
fontSize:"14px",
marignLeft:"10px"
},
contentStyle:{
color:"red",
fontSize:"14px",
marignLeft:"10px"
}
}
},
methods:{
handleSubmit(){
this.valiedTitle(this.title);
this.valiedContent(this.content);
if(!this.titleErr.stat || !this.contentErr.stat)
{
return;
}
this.isLoading=true;
let obj={title:this.title,content:this.content};
fetch("http://localhost:1337/api/participle",{
method:"post",
body:JSON.stringify(obj),
headers:{"Content-Type":"application/json"}
})
.then(response=>response.json())
.then(data=>{
const {status,msg}=data;
if(status)
{
this.responseData=msg;
this.title='';
this.content='';
}else
{
this.responseData=msg;
}
})
.catch(error=>{
console.error(error);
this.responseData="提交失败:"+error;
})
.finally(()=>{
this.isLoading=false;
})
},
valiedTitle(newVal){
if(newVal.length<5)
{
this.titleErr.msg="标题至少4个字符";
this.titleErr.stat=false;
}else
{
this.titleErr.msg='';
this.titleErr.stat=true;
}
},
valiedContent(newVal)
{
if(newVal.length<5)
{
this.contentErr.msg='内容至少4个字符';
this.contentErr.stat=false;
}else
{
this.contentErr.msg='';
this.contentErr.stat=true;
}
}
},
watch:{
title(newVal){
this.valiedTitle(newVal);
},
content(newVal){
this.valiedContent(newVal);
}
}
};
new Vue({
el:"#app",
components:{
"myContent":Content
}
})
</script>
</body>
</html>
运行方式:在vite中运行前端文件,html后缀的文件,在node中运行express写的文件