在Egg中使用MongoDB及Mongoose

新公司的项目中用了Egg和MongoDB及mongoose。最近有空来系统的梳理一下。

参考资料&扩展阅读:

  • Mongoose中文文档
  • mongoose学习笔记(超详细)
  • Mongoose中文文档-Mongoose的API

Egg链接MongoDB

Egg 使用 egg-mongoose来方便的跟数据库进行链接

// eggProject/server/config/config.default.ts

// 单数据库链接
mongoose: {
 url: process.env.MONGODB_URL || 'mongodb://127.0.0.1:27017/laurel',
},

// 多数据库链接
mongoose: {
 clients: {
   // clientId, access the client instance by app.mongooseDB.get('clientId')
   // 数据库1
   db1: {
     url: process.env.MONGODB_URL || 'mongodb://127.0.0.1:27017/db1',
     options: {},
   },
   // 数据库2
   db2: {
     url: process.env.MONGODB_URL || 'mongodb://127.0.0.1:27017/db2',
     options: {},
   },
 },
},
// 多数据库链接时,在model中要指定是哪个
module.exports = app => {
  const mongoose = app.mongoose;
  const Schema = mongoose.Schema;
  const conn = app.mongooseDB.get('db1'); 

  const UserSchema = new Schema({
    userName: { type: String  },
    password: { type: String  },
  });

  return conn.model('User', UserSchema);
}

在model中定义schema

export default app => {
  const mongoose = app.mongoose;
  const Schema = mongoose.Schema;
  // 连接多个数据库时要在这里指定是哪个数据库
  const connect = app.mongooseDB.get('db1');

  const UserSchema = new Schema(
    {
      name: { type: String },
      account: { type: String, unique: true }, // 唯一
      password: { type: String },
      comments: [{ body: String, date: Date }], // 对象数组
      tags: { type: [String], index: true } // 字符串数组,对字段增加索引
      meta: { // 对象
        votes: Number,
        favs:  Number
      },
      h: { type: String, alias: 'home' }, // 别名,使用时无缝连接
    },
    // schema 的选项options
    {
      usePushEach: true, // 解决 Unknown modifier: $pushAll 报错,作用原理未知
      timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' }, // 生成时间
    },
  );

  // 还可以定义实例方法,定义方法时 不要使用 箭头函数。
  UserSchema.method.someFun = function(cb) {
	return this.model('Animal').find({ type: this.type }, cb);
  }


  // mongoose会自动给下边的user加上s,也就是说真正的表名应该是users
  return connect.model('user', UserSchema);
  // 如果不想这样,可以写第三个参数手动指定表名如下,这样表名就是user了。
  return connect.model('user', UserSchema, 'user');
};

虚拟值(Virtual)

Virtuals 是 document 的属性,但是不会被保存到 MongoDB,所以也不能用来查询。 getter 可以用于格式化和组合字段数据, setter 可以很方便地分解一个值到多个字段。

personSchema.virtual('fullName').
  // 定义一个虚拟值 fullName,他并不会被保存到数据库里,只会把first和last拼接出来返回
  get(function() { return this.name.first + ' ' + this.name.last; }).
  // 定义虚拟值如果是 setter,会把值自动拆分到其他属性
  set(function(v) {
    this.name.first = v.substr(0, v.indexOf(' '));
    this.name.last = v.substr(v.indexOf(' ') + 1);
  });

注意:如果对 document 使用 toJSON() 或 toObject(),默认不包括虚拟值, 你需要额外向 toObject() 或者 toJSON() 传入参数 { virtuals: true }。

Schema 选项

Schemas 有很多可配置选项,你可以在构造时传入或者直接 set:

new Schema({..}, options);

// or

var schema = new Schema({..});
schema.set(option, value);

常见选项:

  • collection: 默认的collection名称是model的复数形式,上边说过,在这里也可以自定义
  • id: 默认true,Mongoose会默认生成一个虚拟值id,指向数据库的_id,但会转成字符串返回
  • minimize: Mongoose默认不保存空对象,若该项设为false将保存空对象
  • strict:默认为true,不能保存schema里没有声明的属性
  • toJSON: 有时候需要对获取的数据进行转换(transform),比如时区转换或Decimal128类型转为string,就可以使用toJSON来完成
  • toObject: 类似toJSON
  • versionKey:版本锁设置在每一个文档(document)上,由mogoose生成。默认的值是__v,但是可以自定义。
  • timestamps:会自动生成createdAt和updatedAt,还可以自定义名字

SchemaTypes

SchemaType 处理字段路径各种属性的定义

以下是 mongoose 的所有合法 SchemaTypes:

  • String
  • Number
  • Date
  • Buffer
  • Boolean
  • Mixed
  • ObjectId
  • Array
  • Decimal128 // 保证数字不丢失精度。可以配合bigNumber.js库做计算
// 可以简单的直接写类型
var schema1 = new Schema({
  test: String // `test` is a path of type String
});
// 也可以写个对象,type表示类型。这样还能加其他属性
var schema2 = new Schema({
  test: {
    type: String,
    lowercase: true, // 比如存入数据库时都转成小写字母
  }
});

全部字段可用的选项:

  • required: 布尔值或函数 如果值为真,为此属性添加 required 验证器
  • default: 任何值或函数 设置此路径默认值。如果是函数,函数返回值为默认值
  • select: 布尔值 指定 query 的默认 projections
  • validate: 函数 adds a validator function for this property
  • get: 函数 使用 Object.defineProperty() 定义自定义 getter
  • set: 函数 使用 Object.defineProperty() 定义自定义 setter
  • alias: 字符串 仅mongoose >= 4.10.0。 为该字段路径定义虚拟值 gets/sets

索引相关:

  • index: 布尔值 是否对这个属性创建索引
  • unique: 布尔值 是否对这个属性创建唯一索引
  • sparse: 布尔值 是否对这个属性创建稀疏索引

对字符串:

  • lowercase: 布尔值 是否在保存前对此值调用 .toLowerCase()
  • uppercase: 布尔值 是否在保存前对此值调用 .toUpperCase()
  • trim: 布尔值 是否在保存前对此值调用 .trim()
  • match: 正则表达式 创建验证器检查这个值是否匹配给定正则表达式
  • enum: 数组 创建验证器检查这个值是否包含于给定数组

对数字:

  • min: 数值 创建验证器检查属性是否大于或等于该值
  • max: 数值 创建验证器检查属性是否小于或等于该值

对日期:

  • min: Date
  • max: Date

Models

常用方法:

  • create
  • remove
  • update
  • find
  • findOne
  • findOneAndUpdate
  • findById
  • findByIdAndUpdate

Queries 查询

详见:Mongoose中文文档-Mongoose的API

Aggregate

用于构建聚合管道的聚合构造函数。通过Model.aggregate()构造使用。

详见: Aggregate

Aggregate能够在查询数据时做各种复杂的操作,包括查询,过滤,排序,分组,增加字段组合计算等等,十分强大!下面是例子。

但是,下面的例子中,如果addFields中增加的字段很通用,其实可以试着使用Schema中的虚拟值(Virtual),通过设置setter和getter也能达到了类似的效果,并且写法也比这个更友好。这是我梳理总结时思考出来的,在之后的开发中,要尽可能使用更简单的方式。

const result = await ctx.model.xxxx.aggregate([
  {
    $match: { type: 'recharge' }, // 匹配出符合条件的数据
  },
  {
    $sort: { createdAt: -1 }, // 按数据生成时间倒序排列
  },
  {
    $addFields: { // 增加项:
      // payAmount 用户支付的金额 = 数量 amount * 价格 price * 订单折扣 discount
      payAmount: { $multiply: ['$amount', '$price', '$discount'] },
      // waitUnfreezeAmount 待解冻本金 = amount * 锁仓率 freezeRatio - 已解冻本金 $unfreezeAmount
      waitUnfreezeAmount: {
        $subtract: [{ $multiply: ['$amount', '$freezeRatio'] }, '$unfreezeAmount'],
      },
      // interest 已产生利息 = 已解冻利息 unfreezeInterest + 待解冻利息 waitUnfreezeInterest
      interest: { $add: ['$unfreezeInterest', '$waitUnfreezeInterest'] },
    },
  },
  // 根据币种分组,求和
  {
    $group: {
      _id: '$coin',
      totalPayPerCoin: {
        // 该币种的支付数量总数
        $sum: '$payAmount',
      },
      totalInterestPerCoin: {
        // 该币种的已产生利息
        $sum: '$interest',
      },
      waitUnfreezePerCoin: {
        // 该币种的待解冻本金
        $sum: '$waitUnfreezeAmount',
      },
      unfreezePerCoin: {
        // 该币种的待解冻本金
        $sum: '$unfreezeAmount',
      },
      totalLUDPerCoin: {
        // 该币种兑换的总数
        $sum: '$amount',
      },
      countPerCoin: {
        // 该币种参与兑换总人数
        $sum: 1,
      },
    },
  },
]);

你可能感兴趣的:(学习笔记,数据库)