nodejs的基本学习(五)——Promise与MongoDB的操作

前言:

承接前面的内容,继续往下走

 

 

 

 


Promise异步回调:

故事背景:

node做高并发操作好,因为node的大部分操作都是异步的,可如果异步操作多了,想给操作建立顺序,就不得不在回调函数中再调用下一个操作函数,例如下面这样:

 const fs=require('fs')
fs.stat('./123.js',(err,stat)=>{
    if(err){
        console.log('文件不存在')
    }else{
        fs.unlink('./123.js',(err)=>{
            console.log(err)
            fs.writeFile('./test.js','xxxx',()=>{
                 ......
            })
        })
    }
})

如此延伸下去,便是回调地狱

为了代码的便捷性,我们要尽量避免回调地狱,那该怎么做呢?基本方法如下:

  1. priomise对象
  2. asyc/awiat(es7中推出)
  3. 蓝鸟插件
  4. …….

今天我们来学习一下 promise

了解promise:

Promise是抽象异步处理对象以及对其进行各种操作的组件。Promise并不是从javaScript中发祥的概念。Promise最初是提出在E语言中,它是基于并列/并行处理设计的一种编程语言。

 创建promise对象

var promise=new Promise(function(resolve,reject){
       //异步处理
       //处理结束后,调用resolve或reject
       resolve('成功处理')
       reject('错误处理')
})

看一个示例会清晰一些,比如我要删除文件123

 const fs=require('fs')
//封装一个函数,内部return一个promise对象
//将一个异步操作封装到promise对象中去
function delfile(){
    return new Promise((resolve,reject)=>{
        //这里来写异步操作
        fs.unlink('./123.js',(err)=>{
            if(err){
                //reject外部是catch的处理函数
                reject('失败了')
            }else{
                //resolve外部是then的处理函数
                //表示ok
                resolve('成功了')
            }
        })
    })
}
delfile()

.then((msg)=>{
    console.log('then:'+msg)
})
.catch((err)=>{
    console.log('err:'+err)
})

封装了一个函数,里面返回了一个Promise对象,构造对象的回调函数中进行了异步处理,在适当的地方调用resolve和reject函数,分别对应在外部的.then和.catch处理。

 

解决回调地狱

链式调用

那么Promise是怎么解决回调地狱问题的呢?依靠链式调用

看下面的示例,先检测文件是否存在,然后再删除该文件:

 const fs=require('fs')
//封装
function isExist(){
    return new Promise((resolve,reject)=>{
        fs.stat('./123.js',(err,stats)=>{
            if(err){
                reject('文件不存在')
            }else{
                resolve('文件存在')
            }
        })
    })
}

function delFile(){
    return new Promise((resolve,reject)=>{
        fs.unlink('./123.js',(err)=>{
            if(err){
                reject('删除失败')
            }else{
                resolve('删除成功')
            }
        })
    })
} 
isExist()
.then((msg)=>{
    console.log("‘是否存在文件’的成功处理:"+msg)
    return delFile()
})
.then((msg)=>{
    console.log("‘删除文件’的成功处理:"+msg)
})
.catch((err)=>{
    console.log(err)
})

上面封装了两个会生成Promise对象的函数,并且都调用了reject和resolve。重点是后面,后面请这样解读:isExist()返回了一个Promise对象,后面的.then就是这个Promise对象的resolve的处理函数,而在这个处理函数中返回了一个delFile(),即又返回了一个Promise对象,所以再下面的那个.then(即第二个.then)就是这个delFile中构造的Promise对象的resolve的处理函数,这就形成了链式调用,而不管有多少.then,.catch只需有一个,只有报错才会进入.catch处理函数。

说几个要注意的点:

  • 别再.then后面加分号
  • 只要有一个.catch即可,链式调用中任何一个reject了,都会直接跳到.catch

终止链式调用

很有趣的现象是,即使一个.then没有返回Promise对象,它后面的.then还是会被调用,那么请问该如何手动终止链式调用:

很简单,在你想要终止的.then里面加入一个语句:

throw new Error("手动终止")

对,这就是抛出了一个异常,这样就会直接跳到.catch中了,即实现了手动终止链式调用

.then((msg)=>{
    console.log("‘是否存在文件’的成功处理:"+msg)
    return delFile()
})
.then((msg)=>{
    console.log("‘删除文件’的成功处理:"+msg)
})
.then(()=>{
    console.log("1")
    throw new Error("手动终止")
})
.then((msg)=>{
    console.log("2")
})
.then((msg)=>{
    console.log("3")
})
.catch((err)=>{
    console.log(err)
})

输出:

‘是否存在文件’的成功处理:文件存在
‘删除文件’的成功处理:删除成功
1
Error: 手动终止

 


操作MongoDB数据库

在上一篇【node的基本学习】,我们已经安装好了mongodb数据库,现在我们来学习如何用node操作

node操作Mongodb主要依赖于一个对象模型库——Mongoose

Mongoose

Mongoose是在node.js异步环境下对mongodb进行便捷操作的对象模型工具。

官网奉上

首先在VSCode中使用命令:

npm install mongoose

安装mongoose

安装好之后,我们照着官网的步骤来简单连接一下,开启mongodb后,运行如下代码

//导入mongoose库
const mongoose=require('mongoose')

//进行数据库的连接(最后一个是数据库名)
mongoose.connect('mongodb://localhost/test');

//connect() 返回一个状态待定(pending)的连接, 接着我们加上成功提醒和失败警告。
var db = mongoose.connection;   //connection是数据库的连接对象
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
   console.log('数据库连接成功!')
});

输出:

(node:22624) DeprecationWarning: current URL string 
parser is deprecated, and will be removed in a future 
version. To use the new parser, pass option { useNewUrlParser: true } 
to MongoClient.connect.
(node:22624) DeprecationWarning: current Server Discovery
 and Monitoring engine is deprecated, and will be removed
 in a future version. To use the new Server Discover and 
Monitoring engine, pass option { useUnifiedTopology: true } 
to the MongoClient constructor.
数据库连接成功!

倒是数据库连接成功了,但是爆出了两个警告,这是什么原因呢?原来因为一些版本问题,我们需要加一些新的参数。

写成这样既可

//导入mongoose库
const mongoose=require('mongoose')

//进行数据库的连接(第一个参数最后的字段是数据库名)
mongoose.connect('mongodb://localhost/test',
{ useNewUrlParser: true,useUnifiedTopology: true }  );

//connect() 返回一个状态待定(pending)的连接, 接着我们加上成功提醒和失败警告。
var db = mongoose.connection;   //connection是数据库的连接对象
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
   console.log('数据库连接成功!')
});

我们要操作mongodb数据库,就要创建一个schema对象,下面是一个schema对象的示例:

var schema = new Schema({
  name:    String,
  binary:  Buffer,
  living:  Boolean,
  updated: { type: Date, default: Date.now },
  age:     { type: Number, min: 18, max: 65 },
  mixed:   Schema.Types.Mixed,
  _someId: Schema.Types.ObjectId,
  decimal: Schema.Types.Decimal128,
  array:      [],
  ofString:   [String],
  ofNumber:   [Number],
  ofDates:    [Date],
  ofBuffer:   [Buffer],
  ofBoolean:  [Boolean],
  ofMixed:    [Schema.Types.Mixed],
  ofObjectId: [Schema.Types.ObjectId],
  ofArrays:   [[]],
  ofArrayOfNumbers: [[Number]],
  nested: {
    stuff: { type: String, lowercase: true, trim: true }
  }
})

好,我们的步骤很简单:

  1. 建立连接
  2. 创建schema对象
  3. 将schema对象转换成数据模型(Schema对象与集合关联)

代码奉上:

//导入mongoose库
const mongoose=require('mongoose')

//进行数据库的连接(最后一个是数据库名)
mongoose.connect('mongodb://localhost/test',
{ useNewUrlParser: true,useUnifiedTopology: true }  );

//connect() 返回一个状态待定(pending)的连接, 接着我们加上成功提醒和失败警告。
var db = mongoose.connection;   //connection是数据库的连接对象
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
   console.log('数据库连接成功!')
});

//使用schema对象来操作数据库
var Schema=mongoose.Schema;
//创建Schema对象
var userSchema=new Schema({
    user:{
        type:String,
        required:true
    },
    passwd:{
        type:String,
        required:true
    },
    age:Number,
    sex:{
        type:Number,
        delfault:0
    }
})

//将schema对象转换成数据模型
//该数据对象和集合关联,第一个参数就是集合名,第二个参数是Schema对象
//User的操作就是针对us这张表(集合)了
var User = mongoose.model('us', userSchema);
//模型对象调用操作函数
User.insertMany({
    user:'xiaoming',
    passwd:'123',
    age:17
})
.then((data)=>{
    console.log(data)
    console.log('插入成功')
})
.catch((err)=>{
    console.log('插入失败')
})

上面的代码模拟在数据库test中创建集合us并且插入数据,回到mongoDB端使用命令

use test   //选中数据库
show collections    //展示集合
da.集合名.find()     //查看集合中的数据

来验证是否插入成功

另外,上面我们又用到了.then和.catch,没错,insertMany函数会返回一个Promise对象

关于Model对象的更多操作,可以利用这个文档以及Ctrl+f 搜索

再补充一点,调皮的mongodb有时会将你创建的集合自动变成复数

增删改查

上面演示了“增”

User.remove()
.then((data)=>{
    console.log(data)
    console.log('删除成功')
})
.catch((err)=>{
    console.log('删除失败')
})

var User = mongoose.model('us', userSchema);
User.update({
    user:'xiaoming',
    passwd:'321',
    age:27
})
.then((data)=>{
    console.log(data)
    console.log('修改成功')
})
.catch((err)=>{
    console.log('修改失败')
})

我们来看看查:

全部查询:

var User = mongoose.model('us', userSchema);
User.find()
.then((data)=>{
    console.log(data)
    console.log('查询成功')
})
.catch((err)=>{
    console.log('查询失败')
})

条件查询:

var User = mongoose.model('us', userSchema);
User.find({age:17})
.then((data)=>{
    console.log(data)
    console.log('查询成功')
})
.catch((err)=>{
    console.log('查询失败')
})

条件查询及时没找到符合条件的,只要没报错,都会走.then,只有报错了才会走.catch

上面这些都是简单操作,更为丰富的函数都在官方文档中,找Model的函数

 

模拟登陆

学数据库就是这样,学完增删改查,就要来学登陆注册。

废话不多说了,我们来模拟登陆

先写好服务器模板

const express=require('express')
const app=express()

app.listen(3000,()=>{
    console.log('server start')
})

记得我们上次的步骤到哪里了吗?我们现在将整个项目改一下,让连接数据库的操作分文件来做

新目录结构如下:

server就是我们的主文件(启动文件)。

db.js是上次学mongodb的文件,这个不必在意。

db目录专门用来放我们数据库相关的文件

db/connect.js文件负责与mongodb的连接

db/model目录专门用来放每一个“集合”

userModel.js就是处理user集合文件

#connect.js
//导入mongoose库
const mongoose=require('mongoose')
//进行数据库的连接(最后一个是数据库名)
mongoose.connect('mongodb://localhost/test',
{ useNewUrlParser: true,useUnifiedTopology: true }  );
//connect() 返回一个状态待定(pending)的连接, 接着我们加上成功提醒和失败警告。
var db = mongoose.connection;   //connection是数据库的连接对象
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
   console.log('数据库连接成功!')
});


#userModel.js
const mongoose=require('mongoose')
//使用schema对象来操作数据库
var Schema=mongoose.Schema;
//创建Schema对象
var userSchema=new Schema({
    user:{
        type:String,
        required:true
    },
    passwd:{
        type:String,
        required:true
    },
    age:Number,
    sex:{
        type:Number,
        delfault:0
    }
}) 
//将schema对象转换成数据模型
//该数据对象和集合关联,第一个参数就是集合名,第二个参数是Schema对象
//User的操作就是针对us这张表(集合)了
var User = mongoose.model('user', userSchema);
//将Schema转化后的数据模型Model对象抛出
module.exports=User


#server.js
const express=require('express')
//引入db即执行了connect文件的操作
const db=require('./db/connect.js')
const app=express()
app.listen(3000,()=>{
    console.log('server start')
}) 

开启数据库后,就可以正常连接了(现在还没有对user集合做操作)

我们先来做注册功能。为了方便我们的接口管理,我们再来做路由处理,新建一个目录router负责处理路由,目录下先新建一个文件userRouter.js来处理用户相关的

#userRouter.js
const express=require('express')
const router=express.Router()

router.post('/reg',(reg,res)=>{

    res.send('test OK!')
})


module.exports=router

在主文件引入路由,并作为中间件使用,另外配置好body-parser中间件,方便解析POST数据

#server.js
const express=require('express')
//引入db即执行了connect文件的操作
const db=require('./db/connect.js')
const app=express()

//安装body-parser来解析POST数据
const bodyparser=require('body-parser')
app.use(bodyparser.urlencoded({extends:false}))
app.use(bodyparser.json())
//引入路由
const userRouter=require('./router/userRouter')

app.use('/user',userRouter)

app.listen(3000,()=>{
    console.log('server start')
}) 

用postman测试一下,没问题,是通的。

好,接下来利用User.js暴露的数据模型对象来操作数据库,模拟注册

#userRouter.js
const express=require('express')
const router=express.Router()
const User=require('../db/model/userModel')

router.post('/reg',(req,res)=>{
    //获取数据
    let {us,ps}=req.body
    if(us&&ps){
       //数据库操作,开始!
       User.insertMany({
           user:us,
           passwd:ps,   
       })
       .then(()=>{
           res.send({err:0,msg:'注册成功!'})
       })
       .catch(()=>{
           res.send({err:0,msg:'注册失败!'})
       })
    }else{
        return res.send({err:-1,msg:'参数错误'})
    }
})
module.exports=router

mongodb自动给集合名变了复数,别惊讶

同样的道理,就把登录也写出来了

#userRouter.js
const express=require('express')
const router=express.Router()
const User=require('../db/model/userModel')

router.post('/reg',(req,res)=>{
    //获取数据
    let {us,ps}=req.body
    if(us&&ps){
       //数据库操作,开始!
       User.insertMany({
           user:us,
           passwd:ps,   
       })
       .then(()=>{
           res.send({err:0,msg:'注册成功!'})
       })
       .catch(()=>{
           res.send({err:0,msg:'注册失败!'})
       })
    }else{
        return res.send({err:-1,msg:'参数错误'})
    }
})
router.post('/login',(req,res)=>{
    let {us,ps}=req.body
    if(!us||!ps){
        return res.send({err:-1,msg:"参数错误"})
    }
    User.find({
        user:us,
        passwd:ps
    })
    .then((data)=>{
        if(data.length>0){
            res.send({err:0,msg:"登录成功!"})
        }else{
            res.send({err:-2,msg:"用户名或密码不正确"})
        }
    })
    .catch((err)=>{
        return res.send({err:-1,msg:"内部错误"})
    })
})

module.exports=router

 

邮件模块封装

首先我们先更新一下路由文件,解决重复注册的问题

#userRouter.js
const express=require('express')
const router=express.Router()
const User=require('../db/model/userModel')

router.post('/reg',(req,res)=>{
    //获取数据
    let {us,ps}=req.body
    if(us&&ps){
        //检测是否用户名重复
        User.find({
            user:us
        })
        .then((data)=>{
            if(data.length===0){
                //可以注册
                return User.insertMany({
                    user:us,
                    passwd:ps,   
                })
            }else{
                 res.send({err:-3,msg:"用户名已被注册"})
            }
        })
       .then(()=>{
           res.send({err:0,msg:'注册成功!'})
       })
       .catch(()=>{
           res.send({err:-2,msg:'注册失败!'})
       })
    }else{
        return res.send({err:-1,msg:'参数错误'})
    }
})
router.post('/login',(req,res)=>{
    let {us,ps}=req.body
    if(!us||!ps){
        return res.send({err:-1,msg:"参数错误"})
    }
    User.find({
        user:us,
        passwd:ps
    })
    .then((data)=>{
        if(data.length>0){
            res.send({err:0,msg:"登录成功!"})
        }else{
            res.send({err:-2,msg:"用户名或密码不正确"})
        }
    })
    .catch((err)=>{
        return res.send({err:-1,msg:"内部错误"})
    }) 
})

module.exports=router

好,最简单的注册登录验证已经完成,下面我们加入邮箱验证功能

新建一个目录 utils,用来存放我们封装的工具

在utils下新建文件mail.js,这个模块将会用来处理验证码的数据逻辑

还记得我们上次用插件实现的发送邮件吗?我们来利用这段代码

//引入第三方模块
const nodemailer = require("nodemailer");

//创建发送邮件的请求对象
  let transporter = nodemailer.createTransport({
    host: "smtp.qq.com",  //发送方邮箱服务器主机
    port: 465,
    secure: true, // 端口为465的话这个就为true,否则为false
    auth: {
      user: '654321@qq.com', //发送方的邮箱地址
      pass: 'tfzrbasdfdjbccc' //MTP验证码
    }
  });

//创建了一个邮件信息对象
let mailobj={
    from: '"圣诞老哥" <654321@qq.com>', //发送人和地址
    to: "123456@qq.com", // 接收方
    subject: "红玫瑰", //发送标题
    text: "梦里梦到醒不来的梦,红线里被软禁的红", //发送文本(文本信息与html只能有一个)
    //html: "<b>Hello world?</b>" //发送页面
  }
//发送邮件
transporter.sendMail(mailobj); 

复制代码到我们的mail.js文件中,并修改,并且向外暴露,然后在userRouter中利用这个模块

#mail.js
//引入第三方模块
const nodemailer = require("nodemailer");

//创建发送邮件的请求对象
  let transporter = nodemailer.createTransport({
    host: "smtp.qq.com",  //发送方邮箱服务器主机
    port: 465,
    secure: true, // 端口为465的话这个就为true,否则为false
    auth: {
      user: '123456@qq.com', //发送方的邮箱地址
      pass: 'qfmwabaij' //MTP验证码
    }
  });

function send(mail,code){
    //创建了一个邮件信息对象
    let mailobj={
        from: '"就是你要注册?" <123456@qq.com>', //发送人和地址
        to: mail, // 接收方
        subject: "注册验证码", //发送标题
        //反单引号即可以使用模板字符
        text: `你的验证码 ${code},有效期五分钟`, //发送文本(文本信息与html只能有一个)
        //html: "<b>Hello world?</b>" //发送页面
    }
    //发送邮件
    return new Promise((resolve,reject)=>{
        //发送邮件
        transporter.sendMail(mailobj,(err,data)=>{
            if(err){
                reject()
            }else{
                resolve()
            }
        });  
    })
}
//抛出对象,将方法放在对象中
module.exports={send}


#userRouter.js
const express=require('express')
const router=express.Router()
const User=require('../db/model/userModel')
//邮箱发送
const mail=require('../utils/mail')

router.post('/reg',(req,res)=>{
    //获取数据
    let {us,ps}=req.body
    if(us&&ps){
        //检测是否用户名重复
        User.find({
            user:us
        })
        .then((data)=>{
            if(data.length===0){
                //可以注册
                return User.insertMany({
                    user:us,
                     passwd:ps,   
                })
            }else{
                 res.send({err:-3,msg:"用户名已被注册"})
            }
        })
       .then(()=>{
           res.send({err:0,msg:'注册成功!'})
       })
       .catch(()=>{
           res.send({err:-2,msg:'注册失败!'})
       })
    }else{
        return res.send({err:-1,msg:'参数错误'})
    }
})
router.post('/login',(req,res)=>{
    let {us,ps}=req.body
    if(!us||!ps){
        return res.send({err:-1,msg:"参数错误"})
    }
    User.find({
        user:us,
        passwd:ps
    })
    .then((data)=>{
        if(data.length>0){
            res.send({err:0,msg:"登录成功!"})
        }else{
            res.send({err:-2,msg:"用户名或密码不正确"})
        }
    })
    .catch((err)=>{
        return res.send({err:-1,msg:"内部错误"})
    }) 
})

//发送邮箱验证码的接口 
router.post('/getMailCode',(req,res)=>{
     let {Email}=req.body 
     if(!Email){ 
         return res.send({err:-1,msg:"邮箱输入错误"}) 
    }else{ 
        //产生随机验证码 
        let code=parseInt(Math.random()*10000) 
        mail.send(Email,code)         
        .then(()=>{             
             res.send({err:0,msg:"验证码发送成功!"})        
        })         
        .catch(()=>{             
            res.send({err:-1,msg:"验证码发送失败!"})       
        }) 
    } 
})

module.exports=router 

POST把请求发送给127.0.0.1:3000/getMailCode后,邮件被收到即成功了!

到这里,我们已经实现通过一个接口向一个邮件发送一串随机验证码了,下面我们来保存这个验证码到内存中。

修改后面那个/getMailCode 接口即可


//通过内存保存验证码
let codes=[]
//发送邮箱验证码的接口 
router.post('/getMailCode',(req,res)=>{
     let {Email}=req.body 
     //产生随机验证码 
     let code=parseInt(Math.random()*10000) 
     if(!Email){ 
         return res.send({err:-1,msg:"邮箱输入错误"}) 
    }else{ 
        mail.send(Email,code)         
        .then(()=>{             
             res.send({err:0,msg:"验证码发送成功!"})    
             //发送成功则把验证码存到内存中
             codes[Email]=code    
             console.log(codes)  
        })         
        .catch(()=>{             
            res.send({err:-1,msg:"验证码发送失败!"})       
        }) 
    } 
})

这样,我们就实现了保存邮箱对应的验证码,下面的问题就是在注册的过程中核对验证码

我们修改注册接口,最后整个userRouter.js文件如下:

const express=require('express')
const router=express.Router()
const User=require('../db/model/userModel')
//邮箱发送
const mail=require('../utils/mail')

//通过内存保存验证码
let codes=[]
router.post('/reg',(req,res)=>{
    //获取数据
    let {us,ps,code}=req.body
    if(us&&ps&&code){
        //目前先让用户以邮箱作为用户名
        if(codes[us]!=code)
        {
            return res.send({err:-4,msg:"验证码错误,请重试!"})
        }
        //检测是否用户名重复
        User.find({
            user:us
        })
        .then((data)=>{
            if(data.length===0){
                //可以注册
                return User.insertMany({
                    user:us,
                    passwd:ps,   
                })
            }else{
                 res.send({err:-3,msg:"用户名已被注册"})
            }
        })
       .then(()=>{
           res.send({err:0,msg:'注册成功!'})
       })
       .catch(()=>{
           res.send({err:-2,msg:'注册失败!'})
       })
    }else{
        return res.send({err:-1,msg:'参数错误'})
    }
})
router.post('/login',(req,res)=>{
    let {us,ps}=req.body
    if(!us||!ps){
        return res.send({err:-1,msg:"参数错误"})
    }
    User.find({
        user:us,
        passwd:ps
    })
    .then((data)=>{
        if(data.length>0){
            res.send({err:0,msg:"登录成功!"})
        }else{
            res.send({err:-2,msg:"用户名或密码不正确"})
        }
    })
    .catch((err)=>{
        return res.send({err:-1,msg:"内部错误"})
    }) 
})

//发送邮箱验证码的接口 
router.post('/getMailCode',(req,res)=>{
     let {Email}=req.body 
     //产生随机验证码 
     let code=parseInt(Math.random()*10000) 
     if(!Email){ 
         return res.send({err:-1,msg:"邮箱输入错误"}) 
    }else{ 
        mail.send(Email,code)         
        .then(()=>{             
             res.send({err:0,msg:"验证码发送成功!"})    
             //发送成功则把验证码存到内存中
             codes[Email]=code    
             console.log(codes)  
        })         
        .catch(()=>{             
            res.send({err:-1,msg:"验证码发送失败!"})       
        }) 
    } 
})

module.exports=router

我们上面的判断验证码与code是否相等处用了!=,这样即使字符串与数字相同也算相同

 

 


 

 

 

商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢

 

 

发表评论