egg-mongoose使用总结

前言

本项目基于: egg.js

安装配置

1.安装依赖

npm install egg-mongoose --save

2.开启插件

# /config/plugin.ts
import { EggPlugin } from 'egg';

const plugin: EggPlugin = {
  mongoose: {
    enable: true,
    package: 'egg-mongoose',
  },
};

export default plugin;

3.插件配置

# /config/config.default.ts
import { EggAppConfig, PowerPartial } from 'egg';

export default () => {
  const config = {} as PowerPartial<EggAppConfig>;

  config.mongoose = {
    url: process.env.MONGO_URL || 'mongodb://localhost:27017/blog',
    options: {
      poolSize: 40,
    },
  };

  return {
    ...config,
  };
};

本项目锁使用的环境:

"egg-mongoose": "3.2.0"

node -v
v10.5.0

分割线,下面是重点

使用

一.创建 Model(模型)

eggjs 在 /app/model/ 下定义数据模型

以我的 blog 项目为例,假设有一个 tag 表、article 表,article 表通过 tag_id 关联 tag 表如下:

1.tag 表

# /app/model/tag.ts
module.exports = app => {
  const mongoose = app.mongoose;
  const Schema = mongoose.Schema;
  const PostSchema = new Schema({
    tag_name: {
      type: String,
      required: true,
    },
    created_time: {
      type: Date,
      default: new Date(),
    },
    updated_time: {
      type: Date,
      default: new Date(),
    },
  });
  return mongoose.model('Tag', PostSchema);
};

2.article 表

通过 tag_id 关联 tag 表

# /app/model/article.ts
module.exports = app => {
  const mongoose = app.mongoose;
  const Schema = mongoose.Schema;
  const PostSchema = new Schema({
    tag_id: {
      type: Schema.Types.ObjectId,
      required: true,
      ref: 'Tag',
    },
    title: {
      type: String,
      required: true,
    },
    status: {
      type: Boolean,
      default: false,
    },
    content: {
      type: String,
      default: '',
    },
    weather: {
      type: String,
      default: '',
    },
    image: {
      type: String,
      default: '',
    },
    images: {
      type: Array,
      default: [],
    },
    pv: {
      type: Number,
      default: 0,
    },
    created_time: {
      type: Date,
      default: new Date(),
    },
    updated_time: {
      type: Date,
      default: new Date(),
    },
  });
  return mongoose.model('Article', PostSchema);
};

更多 schema 使用:mongoose schema

二.简单常用

eggjs 中,一般在 service 层操作 model 层,返回的是一个 promise,so 可以直接用 await 同步编程

我们先定义 search 为查询条件

1.增

新增一篇文章

# article 为对象
const article: Article;
this.ctx.model.Article.create(article);

批量新增

# article 为数组
const article: Array<Article>;
this.ctx.model.Article.create(article);

2.删

删除一篇文章

this.ctx.model.Article.deleteOne(search);

删除多篇文章

this.ctx.model.Article.remove(search);

3.查

查找一篇文章

this.ctx.model.Article.findOne(search);

查找多篇

# search 为空 或者空对象返回全部
this.ctx.model.Article.find(search);

分页查找 skip、limit

this.ctx.model.Article.find(search)
  .sort({ _id: -1 }) # 按照创建时间倒序
  .skip(page_size * (current_page - 1)) # 跳过前n个数据
  .limit(page_size); # 限制n个数据

4.改

替换文章内容

# 注意,是直接全部替换为 new_data
# findOneAndUpdate默认返回旧的数据
# 若需要部分更新,应使用 $set 操作符
return await this.ctx.model.Article.findOneAndUpdate(search, new_data);

返回修改后最新的数据

# 注意第三个参数
return await this.ctx.model.Article.findOneAndUpdate(search, new_data, { new: true });

再次分割线,下面是重点中的重点

三.操作符

$set

$set 对指定文档中的某些键进行更新,如果这个字段不存在则创建它

# 修改文章 pv为 10000
const operation: any = {
  $set: {
    pv: 10000,
  },
};
return await this.ctx.model.Article.findOneAndUpdate(search, operation);

gt、lt、gte、lte、$ne

  • (>) 大于 $gt
  • (<) 小于 $lt
  • (>=) 大于等于 $gte
  • (<= ) 小于等于 $lte
  • (!==) 不等于 $ne
# 查pv大于10的文章
const search = {
  pv: {
    $gt: 1000,
  },
};
this.ctx.model.Article.find(search);

or、and、not、nor

  • $or 满足任一表达式
  • $and 同时满足多个表达式
  • $not 不满足表达式
  • $nor 不满足任一表达式

查找 pv 大于 10 且公开的文章

const search: any = {
  $and: [
    { pv: { $gt: 10 } },
    { status: true },
  ],
};
this.ctx.model.Article.find(search);

$inc

$inc 用来增加和减少已有键的值,只能用于 Number 类型。对于不存在的键,会自动创建相应的键,并且值为给定的值

文章 pv 自增

const operation: any = {
  $inc: {
    pv: 1,
  },
};
this.ctx.model.Article.findOneAndUpdate(search, operation);

$push、$pull

$push 向已有的数组末尾添加一个元素,如果没有就创建一个新的数组

$pull 会删除掉数组中符合条件的元素

const operation: any = {
  $push: {
    images: {
      content: 'hello world',
    },
  },
};
await this.ctx.model.Article.findOneAndUpdate(search, operation);

const operation: any = {
  $pull: {
    images: {
      content: 'hello world',
    },
  },
};
await this.ctx.model.Article.findOneAndUpdate(search, operation);

$in、$nin

  • $in 包含
  • $nin 不包含

$in 类似与 js Araay includes 方法,与数组中任意一个值相等

查找 pv 在[1,2,3]的文章

const search: any = {
  pv: {
    $in: [ 1, 2, 3 ],
  },
};
this.ctx.model.Article.find(search);

$type

$type 匹配数据类型

详细的类型请:MongoDB $type 操作符

匹配 status 字段类型为 boolean

const search: any = {
  status: {
    $type: 8,
  },
};
this.ctx.model.Article.find(search);

$exists

$exists 判断字段是否存在

查找 status 字段存在的文章

const search: any = {
  status: {
    $exists: true,
  },
};
this.ctx.model.Article.find(search);

$regex

$regex 正则匹配内容

正则匹配内容 123

const search: any = {
  content: { $regex: '123', $options: 'i' },
};
this.ctx.model.Article.find(search);

实际上,也可以直接用字面量

const search: any = {
  content: /123/g,
};
this.ctx.model.Article.find(search);

$where

$where 类似于 mysql 的 where

查找 pv 大于 20 的文章

const search: any = {
  $where: 'this.pv>20',
};
this.ctx.model.Article.find(search);

四.aggregate 聚合

MongoDB 的聚合管道将 MongoDB 文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。

常用的管道操作符:

1.$match

$match 用于过滤数据,只输出符合条件的文档。

this.ctx.model.Article.aggregate([
  { $match: search },
]);

2.$project

$project 用于修改输入文档的结构,可以用来重命名、增加或删除域

修改文档结构,只输出 content 字段

this.ctx.model.Article.aggregate([
  { $match: search },
  {
    $project: {
      content: 1,
    },
  },
]);

3.$limit、$skip

  • $limi: 限制返回的文档数
  • $skip:跳过指定数量的文档

常用的分页查找

this.ctx.model.Article.aggregate([
  { $match: search },
  { $skip: page_size * (current_page - 1) },
  { $limit: page_size },
]);

4.$group

$group 将集合中的文档分组,用于统计结果。类似于 mysql group by

统计每个标签的文章总数 count

 this.ctx.model.Article.aggregate([
  {
    $group: {
      _id: '$tag_id',
      count: {
        $sum: 1,
      },
    },
  },
]);

5.$sort

$sort 将输入文档排序后输出

按照文章修改时间倒序

this.ctx.model.Article.aggregate([
  { $match: search },
  { $sort: { updated_time: -1 } },
]);

6.$lookup、$unwind

  • $unwind 将 Array 类型字段拆分成多条,每条包含数组中的一个值

  • $lookup mongo3.2 版本新增,用于实现联表查询

有几个参数:

语法 解释
from 源数据表
localField 待 Join 的数据表
foreignField Join 的数据表的 match 字段
as 为输出文档的新增值命名

查找文章详情,根据 tag_id 查找 tag 表的详情

this.ctx.model.Article.aggregate([
  {
    $lookup: {
      from: 'tags',
      localField: 'tag_id',
      foreignField: '_id',
      as: 'tag_info',
    },
  },
  { $unwind: '$tag_info' },
]);

END

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,635评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,628评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,971评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,986评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,006评论 6 394
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,784评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,475评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,364评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,860评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,008评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,152评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,829评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,490评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,035评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,428评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,127评论 2 356