MongoDB本身是支持全文搜索的,但是遗憾的是,它不支持中文.英文的分词较为简单,基本上是按空格拆分即可,这就是MongoDB内置的默认分词器.但是中文都是连着的,没有空格分隔,那该怎么拆分呢?很简单,用中文分词器拆分一下就好了.
下面可以先看一下我做了中文分词以后保存在MongoDB里面的测试数据,可以看到影子字段titleToken和contentToken,在英文时几乎没变(其实还是清理掉了一些停顿字符的),在中文时,就自动在每个词之间加入了空格.比如就将”裸辞”拆分成了”裸 辞”.如果我们不对搜索关键词也做分词,直接搜索”裸辞”,是搜不到的.
All articles:
{
  _id: new ObjectId('672036818456f3ef4799b21f'),
  title: 'Getting Started with TypeScript',
  content: 'TypeScript is a superset of JavaScript...',
  titleToken: 'Getting Started with TypeScript',
  contentToken: 'TypeScript is a superset of JavaScript',
  __v: 0
}
{
  _id: new ObjectId('672036818456f3ef4799b220'),
  title: 'Getting Started with MongoDB',
  content: 'MongoDB is a NoSQL database...',
  titleToken: 'Getting Started with MongoDB',
  contentToken: 'MongoDB is a NoSQL database',
  __v: 0
}
{
  _id: new ObjectId('672036818456f3ef4799b221'),
  title: '裸辞后独立开发产品上线五天开始盈利,我是怎么做的',
  content: 'Learn advanced techniques in MongoDB...',
  titleToken: '裸 辞 后 独立 开发 产品 上线 五天 开始 盈利 我 是 怎么 做 的',
  contentToken: 'Learn advanced techniques in MongoDB',
  __v: 0
}
{
  _id: new ObjectId('672036818456f3ef4799b222'),
  title: 'TypeScript',
  content: 'TypeScript TypeScript TypeScript',
  titleToken: 'TypeScript',
  contentToken: 'TypeScript TypeScript TypeScript',
  __v: 0
}
{
  _id: new ObjectId('672036818456f3ef4799b223'),
  title: 'Mongodb建立文本索引时,会对提取所有文本的关键字建立索引,因而会造成一定的性能问题。所以对于结构化的字段,建议用普通的关系查询,如果需要对大段的文本进行搜索,才考虑用全文搜索。',
  content: 'Learn advanced techniques in MongoDB...',
  titleToken: 'Mongodb 建立 文本 索引 时 会 对 提取 所有 文本 的 关键字 建立 索引 因而 会 造成 一定 的 性能 问题 所以 对于 结构化 的 字 段 建议 用 普通 的 关系 查询 如果 需要 对 大段 的 文本 进行 搜索 才 考虑 用 全文 搜索',
  contentToken: 'Learn advanced techniques in MongoDB',
  __v: 0
}
import mongoose, { Schema } from "mongoose";
import nodejieba from "nodejieba";
import dotenv from "dotenv";
// 从环境变量中获取MongoDB连接字符串
dotenv.config();
const uri = process.env.MONGO_URI || "";
// 定义 Article Schema
const articleSchema = new Schema({
    title: String,
    content: String,
    titleToken: String,
    contentToken: String,
});
// 定义 title 和 content 的影子字段的全文索引
articleSchema.index({ titleToken: "text", contentToken: "text" });
// 定义 pre('validate') 中间件,在创建Document对象时对其进行分词,并将分词结果保存到相应的影子字段中
articleSchema.pre("validate", function (next) {
    if (!this.titleToken) {
        this.titleToken = bigramTokenize(this.title);
    }
    if (!this.contentToken) {
        this.contentToken = bigramTokenize(this.content);
    }
    next();
});
// 创建 Article Model
const Article = mongoose.model("Article", articleSchema);
// 定义分词函数
function bigramTokenize(str) {
    // 使用正则表达式将字符串中的中文和英文分开
    const regex = /([\\u4e00-\\u9fa5]+|[a-zA-Z]+)/g;
    const tokens = str.match(regex);
    // 对中文部分进行分词,英文部分保持不变
    const result = tokens.map((token) => {
        return /^[\\u4e00-\\u9fa5]+$/.test(token)
            ? nodejieba.cut(token).join(" ")
            : token;
    });
    return result.join(" ");
}
async function main() {
    try {
        // 连接到 MongoDB
        await mongoose.connect(uri, {
            dbName: "test",
            autoIndex: true,
            autoCreate: true,
        });
        console.log("Connected to MongoDB");
        // 先删除所有数据
        await Article.deleteMany({});
        console.log("Deleted all articles");
        //再删除所有索引
        Article.collection.dropIndexes();
        console.log("Dropped all indexes");
        // 重新用schema创建索引
        await Article.ensureIndexes();
        console.log("ReCreated indexes");
        // 再插入一些示例数据
        const articles = [
            new Article({
                title: "Getting Started with TypeScript",
                content: "TypeScript is a superset of JavaScript...",
            }),
            new Article({
                title: "Getting Started with MongoDB",
                content: "MongoDB is a NoSQL database...",
            }),
            new Article({
                title: "裸辞后独立开发产品上线五天开始盈利,我是怎么做的",
                content: "Learn advanced techniques in MongoDB...",
            }),
            new Article({
                title: "TypeScript",
                content: "TypeScript TypeScript TypeScript",
            }),
            new Article({
                title: "Mongodb建立文本索引时,会对提取所有文本的关键字建立索引,因而会造成一定的性能问题。所以对于结构化的字段,建议用普通的关系查询,如果需要对大段的文本进行搜索,才考虑用全文搜索。",
                content: "Learn advanced techniques in MongoDB...",
            }),
        ];
        await Article.insertMany(articles);
        console.log("Inserted example articles");
        // 执行全文搜索查询
        await searchKeyword("TypeScript");
        await searchKeyword("独立开发产品");
        await searchKeyword("因而会造成一定的性能问题");
        await searchKeyword("我怎么做的");
        // 打印当前的所有文章
        const allItems = await Article.find();
        console.log("All articles:");
        allItems.forEach((item) => {
            console.log(item);
        });
    } catch (err) {
        console.error("Error:", err);
    } finally {
        // 关闭连接
        await mongoose.connection.close();
        console.log("Disconnected from MongoDB");
    }
}
async function searchKeyword(searchKeyword: string) {
    const filteredResult = await Article.aggregate([
        // 对搜索关键词也要进行分词
        {
            $match: {
                $text: { $search: bigramTokenize(searchKeyword) },
            },
        },
        // 搜索结果中保留下的字段
        {
            $project: {
                title: 1,
                content: 1,
                score: { $meta: "textScore" },
            },
        },
        // 过滤出匹配分数大于1的文章
        {
            $match: {
                score: { $gt: 1 },
            },
        },
        // 按匹配分数排序
        {
            $sort: {
                score: { $meta: "textScore" },
            },
        },
    ]);
    console.log(`Search results for keyword "${searchKeyword}":`);
    filteredResult.forEach((article: any) => {
        console.log(article);
    });
}
main();
可以看到,基本符合预期
Search results for keyword "TypeScript":
{
  _id: new ObjectId('672036b4f9e3829bff610653'),
  title: 'TypeScript',
  content: 'TypeScript TypeScript TypeScript',
  score: 2.85
}
{
  _id: new ObjectId('672036b4f9e3829bff610650'),
  title: 'Getting Started with TypeScript',
  content: 'TypeScript is a superset of JavaScript...',
  score: 1.3333333333333333
}
Search results for keyword "独立开发产品":
{
  _id: new ObjectId('672036b4f9e3829bff610652'),
  title: '裸辞后独立开发产品上线五天开始盈利,我是怎么做的',
  content: 'Learn advanced techniques in MongoDB...',
  score: 1.6
}
Search results for keyword "因而会造成一定的性能问题":
{
  _id: new ObjectId('672036b4f9e3829bff610654'),
  title: 'Mongodb建立文本索引时,会对提取所有文本的关键字建立索引,因而会造成一定的性能问题。所以对于结构化的字段,建议用普通的关系查询,如果需要对大段的文本进行搜索,才考虑用全文搜索。',
  content: 'Learn advanced techniques in MongoDB...',
  score: 4.411005434782609
}
Search results for keyword "我怎么做的":
{
  _id: new ObjectId('672036b4f9e3829bff610652'),
  title: '裸辞后独立开发产品上线五天开始盈利,我是怎么做的',
  content: 'Learn advanced techniques in MongoDB...',
  score: 2.1333333333333333
}
{
  _id: new ObjectId('672036b4f9e3829bff610654'),
  title: 'Mongodb建立文本索引时,会对提取所有文本的关键字建立索引,因而会造成一定的性能问题。所以对于结构化的字段,建议用普通的关系查询,如果需要对大段的文本进行搜索,才考虑用全文搜索。',
  content: 'Learn advanced techniques in MongoDB...',
  score: 1.0740489130434783
}