Практика разработки Mongoose - продвинутый уровень

MongoDB Mongoose
В предыдущей статье "Практика разработки Mongoose — основы", мы научились подключаться к базе данных и реализовывать базовые операции CRUD, а сегодня узнаем о некоторых интересных и полезных функциях.
Очки знаний:
  1. показатель
  2. валидатор
  3. Запрос к объединенной таблице
  4. виртуальная собственность
  5. промежуточное ПО
  6. Плагины

1. Индекс

Индекс может ускорить запрос, давайте посмотрим на эффект на примере.
В Mongo Shell мы создаем 10000 единиц данных:
$ mongo
> for (var i = 0; i < 10000; i++) { //

... db.users.insert({'name': 'user' + i}); //

... }
Взгляните на запрос без индексации:
> db.users.find({'name': 'user1000'}).explain()
обрати внимание наnscannedиmillis, представляющее количество запросов и время (мс) соответственно

Теперь давайте выполним запрос после добавления индекса:
> db.articles.ensureIndex({name: 1});
> db.articles.find({'name': 'user1000'}).explain()
Как видно из приведенного выше примера, после добавления индекса можно быстро запросить один элемент, что показывает, что индекс значительно увеличивает скорость запроса.
Давайте посмотрим, как использовать Mongoose для создания:
// modules/articles/articles.model.js
const ArticlesSchema = new Schema({   
  title: {   
    ...   
    index: true   
  }  
}, {collection: 'articles'});

Мы также можем создатьуникальный индекс:
const ArticlesSchema = new Schema({   
  title: {   
    ...    
    index: true,   
    unique: true      
  }  
}, {collection: 'articles'});

Конечно, вы также можете построить индекс единообразно:
ArticlesSchema.index({ name: 1});  
//1 表示正序, -1 表示逆序
составной индекс:
ArticlesSchema.index({name: 1, by: -1});  
ArticlesSchema.index({name: 1, by: -1}, {unique: true});

Примечание. Следует отметить, что при запуске приложения Mongoose автоматически вызывает каждый определенный индекс в схеме.ensureIndex, обязательно сгенерируйте индекс и во всехensureIndexИспускать ' в модели, когда вызов завершается успешно или когда возникает ошибкаindex'событие. Это нормально для сред разработки, но не рекомендуется использовать это в производственных средах.

Мы можем отключить его, используя следующий методensureIndex:
mongoose.connect('mongodb://localhost/blog', { config: { autoIndex: false } });  //推荐  
// or    
mongoose.createConnection('mongodb://localhost/blog', { config: { autoIndex: false } });  //不推荐  
// or  
animalSchema.set('autoIndex', false);  //推荐  
// or  
new Schema({..}, { autoIndex: false }); //不推荐

Примечание. Для каждого добавленного индекса каждая операция записи (вставка, обновление, удаление) будет занимать больше времени. Это связано с тем, что при изменении данных обновляются не только документы, но и все индексы в коллекции. Поэтому mongodb ограничивает каждую коллекцию максимум 64 индексами. Как правило, у вас не должно быть более двух индексов для конкретной коллекции.

2. Валидатор Валидация

правила валидатора:
  • проверка находится вSchemaTypeопределено выше.
  • Валидация — это промежуточное ПО. Валидатор Mongoose какpre('save')Прехуки выполняются по умолчанию в каждом режиме.
  • вы можете вручную использоватьdocumentЗапустите проверку.validate(callback)илиdoc.validateSync().
  • Валидаторы не работают с неопределенными значениями, за исключениемrequiredвалидатор.
  • Проверить асинхронную рекурсию; при вызовеModel#save, также может быть выполнена проверка вложенных документов. Если есть ошибка, ваш обратный вызов Model#save получает ее.
  • Проверка настраивается.

(1) Встроенный валидатор
Mongoose предоставляет несколько встроенных валидаторов.
  • всеSchemaTypeиметь встроенныйrequireвалидатор. Необходимый валидатор для использованияSchemaTypeизcheckrequired()Функция определяет, удовлетворяет ли значение требуемому валидатору.
  • Значения (Числа) имеют наибольшее (man) и минимум (min) валидатор.
  • Строка (строка) имеетenum,match,maxLengthиminLengthвалидатор.

Далее создаем пользователяSchema, добавьте валидаторы в разные поля:
// modules/users/users.model.js


const mongoose = require('mongoose');  
const Schema = mongoose.Schema;   


const UsersSchema = new Schema({   
  name: {   
    type: String,   
    required: true,   
    minlength: 3,   
    maxlength: 6   
  },   
  age: {   
    type: Number,   
    min: 18,   
    max: 30,   
    required: true   
  },   
  sex: {   
    type: String,   
    enum: {   
      values: ['male', 'female'],   
      message: '`{PATH}` 是 `{VALUE}`, 您必须确认您的性别!'   
    },   
    required: true   
  }  
}, {collection: 'users'});   


mongoose.model('users', UsersSchema);
В приведенном выше коде имя является обязательным элементом с минимальной длиной 3 и максимальной длиной 6; возраст является обязательным элементом с минимальным значением 18 и максимальным значением 30; пол является обязательным элементом и должен быть мужским или женским. .

Если вы внимательно посмотрите на приведенный выше код, я думаю, вы тоже его видели.{PATH}и{VALUE},что это?
На самом деле в сообщении об ошибке валидатора (message), мы можем использовать 5 встроенных переменных:

  • {PATH}: имя ключа
  •  {VALUE}: текущее значение ключа
  • {TYPE}: тип валидатора, например min, regexp и т. д.
  • {MIN}: минимальное значение, существуют только значения
  • {MAX}: максимальное значение, существуют только значения


Для встроенного валидатора мы также можем настроить его сообщение об ошибке, например:
min: [6, "自定义错误提示"]  
required: [true, "必须项"]

(2) Пользовательский валидатор

Далее мы создаем мобильный аутентификатор:

// modules/common/validation.js


module.exports = {   
  phone(v) {   
    return /1[3|5|8]\d{9}/.test(v);   
  }  
};

мы поднимаемсяUsersSchemaдобавитьphoneполе, затем добавьте собственный валидатор:
// modules/users/users.model.js


...  
const validation = require('../common/validation');   


const UsersSchema = new Schema({   
  ...   
  phone: {   
    type: String,   
    validate: {   
      validator: validation.phone,   
      message: '`{PATH}` 必须是有效的11位手机号码!'   
    },   
    required: true   
  }  
}, {collection: 'users'});  
...

Мы также можем использовать массивы для добавления нескольких валидаторов:
[  
  {   
    validator: validation.phone,   
    message: '`{PATH}` 必须是有效的11位手机号码!'   
  }
]

(3) Сообщение об ошибке
Когда проверка не пройдена, Errors возвращает объект ошибки, который на самом делеValidatorErrorобъект. каждыйValidatorErrorимеютkind, path, value, messageproperties, мы можем получить каждое сообщение об ошибке:
const user = new UsersModel(req.body);
user.save((err, result) => {
  if (err) {
    console.error(err.errors['name']['message']);

    return res.status(400)
      .send({
        message: err
       });
  } else {
    res.jsonp(user);
  }
})

Мы также можем получить ошибки проверки асинхронно:
const user = new UsersModel();  
const errors = user.validateSync();

Помимо добавления валидаторов на схему, мы также можем использоватьvalidate()метод:
UsersSchema.path('phone').validate(validation.phone, '`{PATH}` 必须是有效的11位手机号码!');
В приведенном выше коде мы добавляем валидатор мобильного телефона validation.phone к полевому телефону и добавляем сообщение об ошибке, которое совпадает с определением на схеме.

По умолчанию валидаторы толькоsaveдействие сработает, но когда После Mongoose 4.x мы также можем включить валидаторы update() и findoneandupdate(), просто добавимrunValidatorsУстановите значение true (по умолчанию false):
const opts = { runValidators: true };   
UsersModel.update({}, { name: 'Superman' }, opts, (err) => { });

Примечание. Средство проверки обновлений работает только для * $set * $unset * $push (>= 4.8.0) * $addToSet (>= 4.8.0) * $pull (>= 4.12.0) * $pullAll (>= 4.12.0)

3. Запрос к объединенной таблице

Если вы использовали MySql, вы должны были использовать егоjoin, используется для соединения запроса таблицы, но не в Mongoosejoin, но предоставляет более удобный и быстрый способ:Population(Мангуста >= 3,2).

Обобщить в коротких словахPopulationИспользование: определите поле (по) в коллекции (статьях), которое указывает на поле _id другой коллекции (пользователей).
const ArticlesSchema = new Schema({   
  ...   
  by: { type: Schema.Types.ObjectId, ref: 'users' },   
  ...  
}, { collection: 'articles' });   
mongoose.model('articles', ArticlesSchema);   


const UsersSchema = new Schema({   
  ...  
}, {collection: 'users'});   
mongoose.model('users', UsersSchema);
Уведомление:refЗначением является модель (model), а не имя коллекции.

когда используешьpopulate()метод, Mongoose автоматически вставит запрошенное значение в соответствующее поле. Например, мы хотим запросить автора статьи:
// modules/articles/articles.controller.js


exports.getAuthorByArticleid = (req, res) => {   
  ArticlesModel.findById(req.query.id)   
    .populate('by')   
    .exec(function (err, story) {   
      if (err) {   
        return res.status(400).send({   
          message: '更新失败',   
          data: []   
        });   
      } else { 
        res.jsonp({   
          data: [story]   
        })  
      } 
  });  
};
Запрошенное значение будет вставлено в поле by:
{"data":[{"_id":"5a02d5c41f76646d9d369628","title":"123","content":"123","by":{"_id":"5a02d4831515cd6a62a3bc65","name":"Hot","phone":"13123123123","age":21,"sex":"male","__v":0},"articleId":"hfceahcc","__v":0,"modifyOn":"2017-11-08T10:00:36.962Z"}]}

Вы также можете указать второй параметр, чтобы вернуть указанное значение:
populate('by', 'name')    


// {"data":[{"_id":"5a02d5c41f76646d9d369628","title":"123","content":"123","by":{"_id":"5a02d4831515cd6a62a3bc65","name":"Hot"},"articleId":"hfceahcc","__v":0,"modifyOn":"2017-11-08T10:00:36.962Z"}]}

Вернуть несколько значений:
populate('by', 'name phone')  


// {"data":[{"_id":"5a02d5c41f76646d9d369628","title":"123","content":"123","by":{"_id":"5a02d4831515cd6a62a3bc65","name":"Hot","phone":"13123123123"},"articleId":"hfceahcc","__v":0,"modifyOn":"2017-11-08T10:00:36.962Z"}]}

Не возвращать некоторые значения (ключ с префиксом -):
populate('by', 'name -_id')  


// {"data":[{"_id":"5a02d5c41f76646d9d369628","title":"123","content":"123","by":{"name":"Hot"},"articleId":"hfceahcc","__v":0,"modifyOn":"2017-11-08T10:00:36.962Z"}]}

Мы также можем выполнить некоторую обработку данных возвращенной таблицы ассоциаций:
populate({   
  path: 'by',   
  match: { age: { $gte: 21 }},   
  select: 'name',   
  options: { limit: 5 }   
})
Приведенный выше код указывает, что возраст запроса меньше или равен 21, отображается только поле имени и имеется не более 5 фрагментов данных.

4. Виртуальное свойство VirtualType

Виртуальные свойства не хранятся в MongoDB, и с их помощью мы можем форматировать и настраивать значения составных свойств.

Добавляем адрес пользователям:
// modules/users/users.model.js


const UsersSchema = new Schema({   
  ...   
  address: {   
    city: {type: String},   
    street: {type: String}   
  }  
}, {collection: 'users'});

Если мы хотим получить полный адрес, мы использовали объединение по одному, но теперь мы можем определить виртуальные свойства и получить их одним словом:
const address = UsersSchema.virtual('address.full');   


address.get(function () {   
  return this.address.city + ' ' + this.address.street;  
});

Создайте маршрут доступа:
app.route('/api/users/address')   
  .get(usersController.getAddress);

Метод getAddress:
exports.getAddress = (req, res) => {   
  UsersModel.findById(req.query.id, (err, result) => {   
    if (err) {   
      return err.status(400).send({   
        message: '用户不存在',   
        data: []   
      });   
    } else {   
      console.log(result);   
      res.jsonp(result.address.full);    
    }   
  })  
}
При вызове этого интерфейса вы увидите, что в консоли нет выводаfullЭто собственность, но мы можем ее получить, это виртуальная собственность.

иsetметод:
address.set(function(v) {   
  const split = v.split(' ');   
  this.address.city = split[0];   
  this.address.street = split[1];  
});

по определениюsetметод, мы можем быстро присвоить значения двум полям:
const body = {   
  name: 'abcd',   
  phone: '13123123123',   
  age: 20,   
  sex: 'male'   
};   
const user = new UsersModel(body);   
user.address.full = 'beijing 100号';   
user.save();


5. Промежуточное ПО


Промежуточное ПО (также известное как пре- и пост-перехватчики) — это функции, которые передают управление во время выполнения асинхронной функции.中间件вschemaуказывается на уровне. В плагинах, о которых мы расскажем позже, промежуточное ПО также очень важно.


Mongoose 4.0 имеет 2 типа промежуточного программного обеспечения:ПО промежуточного слоя документаиПО промежуточного слоя запросов.


ПО промежуточного слоя документа поддерживает следующие методы документа:

  • init
  • validate
  • save
  • remove


ПО промежуточного слоя запросов поддерживает следующие модели и методы запросов:

  • count
  • find
  • findOneAndRemove
  • findOneAndUpdate
  • update


Промежуточное ПО для документов и поддержка промежуточного ПО для запросов前置и后置крюк.


(1) Pre (передний крючок)


Есть два типа передних крючков,串行(serial)и并行(parallel).


Серийный


Последовательные мидлвары выполняются один за другим, и только при вызове функции next() в предыдущем мидлваре будет выполнен последний мидлвар.

var schema = new Schema(..);  
schema.pre('save', function(next) {     
  next();  
});


Параллельно


Операция в методе ловушки не будет выполняться до тех пор, пока не будет завершено каждое промежуточное ПО.

var schema = new Schema(..);   


schema.pre('save', true, function(next, done) {   
  next();   
  setTimeout(done, 100);  
});


обработка ошибок: Если какое-либо промежуточное ПО вызывает next или done с аргументом неправильного типа, поток прерывается и ошибка передается обратному вызову.


(2) ПО промежуточного слоя


После выполнения пост-промежуточного ПО метод ловушки и все предварительное промежуточное ПО завершены. Пост-хук — это способ зарегистрировать традиционные прослушиватели событий для этих методов, которые можно рассматривать как подсказку после завершения операции.

schema.post('init', function(doc) {   
  console.log('%s has been initialized from the db', doc._id);  
});  
schema.post('validate', function(doc) {   
  console.log('%s has been validated (but not saved yet)', doc._id);  
});  
schema.post('save', function(doc) {   
  console.log('%s has been saved', doc._id);  
});  
schema.post('remove', function(doc) {   
  console.log('%s has been removed', doc._id);  
});


6. Плагины


Мы можем расширить функциональность Schema в виде плагинов.


Например, когда мы редактируем статью, мы обычно добавляем время последнего редактирования.Хотя мы можем вручную обновлять ее каждый раз, когда мы ее редактируем, мы можем автоматически обновлять ее через плагины.

// modules/common/plugins.js  
module.exports = {   
  lastModified(schema, options) {   
    schema.add({ lastMod: Date });    


    schema.pre('save', function (next) {   
      this.lastMod = new Date;   
      next()   
    })   
  }  
}   


// modules/articles/articles.model.js

const plugins = require('../common/plugins');  
ArticlesSchema.plugin(plugins.lastModified);

Когда вы нажмете кнопку обновления на странице, вы обнаружите, что поле lastMod было добавлено в измененный документ статьи, и каждое изменение будет автоматически обновляться:

//{ "_id" : ObjectId("5a02d5c41f76646d9d369628"), "title" : "4321", "content" : "123", "by" : ObjectId("5a02d4831515cd6a62a3bc65"), "articleId" : "hfceahcc", "modifyOn" : ISODate("2017-11-08T10:00:36.962Z"), "__v" : 0, "lastMod" : ISODate("2017-11-09T03:36:11.027Z") }


Метод plugin() также может передавать второй параметр (для опций второго параметра, переданных в первом параметре (plugins.lastModified)) для передачи дополнительных параметров:

ArticlesSchema.plugin(plugins.lastModified, {index: true});   


module.exports = {   
  lastModified(schema, options) {   
    ...   
    console.log(options.index);   
  }  
}


Глобальные плагины


только у мангуста естьplugin()Функция для регистрации плагинов для каждой схемы:

const plugins = require('../common/plugins');  
mongoose.plugin(plugins.lastModified);


Суммировать


Благодаря этой статье у нас есть очки знаний:

  • Как построить индекс, уникальный индекс, составной индекс
  • Научился использовать встроенные валидаторы, пользовательские валидаторы, получать ошибки валидации
  • Эффективный запрос таблицы соединений
  • Форматирование и объединение данных с виртуальными свойствами
  • Используйте промежуточное ПО и плагины для интеграции повторно используемого кода


Справочная документация:

Валидатор:мангуст это .com/docs/valid A…

Запрос к объединенной таблице:мангуст это .com/docs/popra…

Виртуальная недвижимость:мангуст - это просто .com/docs/API Контракт...

Промежуточное ПО:мангуст это .com/docs/middle…

Плагин:мангуст находится .com/docs/plugin…


Если у вас есть какие-либо вопросы или комментарии, пожалуйста, оставьте сообщение в области комментариев ниже!