高仿“饿了么”Vue项目(二)

高仿“饿了么”Vue项目(二)


  这一讲我们继续研究高仿“饿了么”项目的后台代码,特别是数据库存储部分。


  一、MongoDB的基本概念


  MongoDB中有三个重要概念:Database、Collection、Document,分别是数据库、集合和文档。

  •   Database概念相当于MySQL中的数据库。
  •   Collection概念相当于MySQL中的表。
  •   Document概念相当于MySQL中的记录。


  如下图所示:




  从概念的名称上,不难看出MongoDB有意识地与传统的关系型数据库区别开。一个“表”,在MongoDB中称为“集合”,即一组有关联的数据的集合,但集合中的文档可能格式各不相同。一个“记录”,在MongoDB中称为“文档”。


  每一个文档都有一个_id字段,它类似于Oracle数据库的rowid。关于_id字段,有如下说明:

  •   在Mongo数据库中,存储在集合中的每个文档都需要一个惟一的_id域作为主键。如果插入操作时遗漏了_id域,Mongo会自动给_id域生成一个ObjectId。
  •   ObjectId是以其创建时的时间戳的4字节为首的12字节组成的值,规则如下:
  A、4字节的时间戳
  B、3字节的机器标识符
  C、2字节的进程ID
  D、3字节的计数器,由一个随机数开始


  二、Mongoose


  Mongoose,发音['mɑːŋɡuːs],中文意为猫鼬,它的字形和Mongo很接近。

  Mongoose是node中进行MongoDB开发的接口。


  Mongoose中有以下几个重要概念:Schema、Model、Document,即架构、模型和文档。

  Schema用于定义数据结构,类似于表结构定义。每个Schema(架构)会对应于一个MongoDB中的Collection(集合)。Schema不仅可以定义数据结构,还可以定义方法。

  Model是由Schema编译而成的构造器,相当于一个类。Model可以对集合进行增删改查。

  Document是由Model创建的实例,是一个对象,它相当于一条记录。


  理解这几个概念后,我们就可以开始查看项目的源代码了。


  三、项目源代码


  项目的目录结构如下:




  1. mongodb/db.js


'use strict';

import mongoose from 'mongoose';
import config from 'config-lite';
import chalk from 'chalk';

mongoose.connect(config.url, { useMongoClient: true });
mongoose.Promise = global.Promise;

const db = mongoose.connection;

db.once('open', () => {
  console.log(
    chalk.green('连接数据库成功')
  );
})

db.on('error', function (error) {
  console.error(
    chalk.red('Error in MongoDb connection: ' + error)
  );
  mongoose.disconnect();
});

db.on('close', function () {
  console.log(
    chalk.red('数据库断开,重新连接数据库')
  );
  mongoose.connect(config.url, { server: { auto_reconnect: true } });
});

export default db;


  从代码中大概可以看出,这个文件的作用是连接数据库。app.js会包含这个文件,所以后台程序一启动,就会去连接MongoDB数据库。


  mongoose.connect(uri, options);

  mongoose.connect()方法的作用是连接数据库。uri是数据库连接字符串,在配置文件config/default.js中有定义config.url。options是一些选项,选项的细节我们就不管了。


  Node中所有的异步操作在完成时都会发送一个事件到事件队列。所有这些产生事件的对象都是events.EventEmitter的实例。

  EventEmitter.addListener(event, listener)
  为指定事件添加一个监听器。
  EventEmitter.on(event, listener)
  为指定事件注册一个监听器,接受一个字符串event和一个回调函数。
  EventEmitter.once(event, listener)

  为指定事件注册一个单次监听器,即监听器最多只会触发一次,触发后立刻解除该监听器。


  上面的代码中,为“open”、“close”和“error”事件分别注册了事件监听器。


  在项目的代码中,可能会发现定义变量,很多时候都使用了const关键字,例如:

  const db = mongoose.connection;

  一般情况下,如果该变量只被赋值一次,后面不会再被修改,都建议使用const(常量)。for循环的循环变量可以使用let,原则上如果变量不会被修改,都建议使用const。


  其它插件:

  config-lite是一个轻量的读取配置文件的模块。config-lite会根据环境变量(NODE_ENV)的不同从当前执行进程目录下的config目录加载不同的配置文件。如果不设置NODE_ENV,则读取默认的default配置文件,如果设置了NODE_ENV,则会合并指定的配置文件和default配置文件作为配置,config-lite支持.js、.json、.node、.yml、.yaml后缀的文件。

  chalk,发音[tʃɔk],中文意为粉笔,是一个颜色的插件,可以通过chalk.blue('hello world')为控制台输出的内容提供颜色。


  2. models/admin/admin.js


  models目录下的文件主要用于创建模型(Model)。前面已经说过了Mongoose中有几个重要概念:Schema、Model、Document。通常先创建Schema,再创建Model,那么其它模块就可以使用new Model()语法创建对象了。


'use strict';

import mongoose from 'mongoose'

const Schema = mongoose.Schema;

const adminSchema = new Schema({
	user_name: String,
	password: String,
	id: Number,
	create_time: String,
	admin: { type: String, default: '管理员' },
	status: Number,
	avatar: { type: String, default: 'default.jpg' },
	city: String,
})

adminSchema.index({ id: 1 });

const Admin = mongoose.model('Admin', adminSchema);

export default Admin


  这里,首先创建了一个adminSchema对象,它是new Schema()。Schema描述了“表”的数据结构。

  adminSchema.index({ id: 1 });这句话创建了一个索引,按id升序排列。

  之后,根据Schema,生成了Model,Model可以理解为Java中的实体类。

  mongoose.model()方法的第一个参数是模型名称,第二个参数是Schema对象。

  Mongoose会将集合名称设为模型名称(第一个参数)的小写形式。如果模型名称的最后一个字符是字母,则会变成复数;如果模型名称的最后一个字符是数字,则不变。例如模型名称为"Admin",则集合名称为"admins";如果模型名称为"Model1",则集合名称为"model1"。

  这样,通过指定模型名称,就能够与数据库中的集合名称对应起来。


  3. controller/admin/admin.js


  这个文件是具体的业务操作类。


  由于这个类代码较长,我们截取了一些典型的代码。


  代码一,查询所有管理员:


class Admin {
	async getAllAdmin(req, res, next) {
		const { limit = 20, offset = 0 } = req.query;
		try {
			const allAdmin = await AdminModel.find({}, '-_id -password').sort({ id: -1 }).skip(Number(offset)).limit(Number(limit))
			res.send({
				status: 1,
				data: allAdmin,
			})
		} catch (err) {
			console.log('获取超级管理列表失败', err);
			res.send({
				status: 0,
				type: 'ERROR_GET_ADMIN_LIST',
				message: '获取超级管理列表失败'
			})
		}
	}
}


  这个类中定义的方法都是异步方法,都用async修饰,async是ES7引入的用于异步操作的关键字,await也是。

  async放在方法前,用于声明一个方法是异步方法。await放在方法调用前,表示等待异步方法执行完成,并取得异步方法的返回结果。


  在上面的代码中,用到了Model的一些方法。

  AdminModel.find({}, '-_id -password'),find()方法的作用是在MongoDB中执行查询,参数一是查询条件,是JSON格式的查询条件,参数二是字段过滤,例子中'-_id -password'表示不显示_id字段和password字段。

  接下来的几个方法是对查询结果进行再次处理。

  sort({ id: -1 }),sort()方法的作用是排序,{ id: -1 }表示按id字段降序排列,-1为降序,1为升序。

  skip()和limit()方法常用于分页,skip表示忽略前M条,limit表示只显示N条。

  这一系列方法调用形成链式调用。


  代码二,注册管理员:


			try {
				const admin = await AdminModel.findOne({ user_name })
				if (admin) {
					console.log('该用户已经存在');
					res.send({
						status: 0,
						type: 'USER_HAS_EXIST',
						message: '该用户已经存在',
					})
				} else {
					const adminTip = status == 1 ? '管理员' : '超级管理员'
					const admin_id = await this.getId('admin_id');
					const newpassword = this.encryption(password);
					const newAdmin = {
						user_name,
						password: newpassword,
						id: admin_id,
						create_time: dtime().format('YYYY-MM-DD'),
						admin: adminTip,
						status,
					}
					await AdminModel.create(newAdmin)
					req.session.admin_id = admin_id;
					res.send({
						status: 1,
						message: '注册管理员成功',
					})
				}
			} catch (err) {
				console.log('注册管理员失败', err);
				res.send({
					status: 0,
					type: 'REGISTER_ADMIN_FAILED',
					message: '注册管理员失败',
				})
			}


  在这段代码中,使用了AdminModel.findOne()来查找并返回一条数据,使用了AdminModel.create(),新增一条数据。


  如果你想了解更多关于Mongoose的细节,请查看http://mongoosejs.com/docs/api.html。


猜你喜欢

转载自blog.csdn.net/hanhf/article/details/80179435