承接前面的内容,继续往下走
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',()=>{
......
})
})
}
})
如此延伸下去,便是回调地狱
为了代码的便捷性,我们要尽量避免回调地狱,那该怎么做呢?基本方法如下:
今天我们来学习一下 promise
Promise是抽象异步处理对象以及对其进行各种操作的组件。Promise并不是从javaScript中发祥的概念。Promise最初是提出在E语言中,它是基于并列/并行处理设计的一种编程语言。
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没有返回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: 手动终止
在上一篇【node的基本学习】,我们已经安装好了mongodb数据库,现在我们来学习如何用node操作
node操作Mongodb主要依赖于一个对象模型库——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 }
}
})
好,我们的步骤很简单:
代码奉上:
//导入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: '[email protected]', //发送方的邮箱地址
pass: 'tfzrbasdfdjbccc' //MTP验证码
}
});
//创建了一个邮件信息对象
let mailobj={
from: '"圣诞老哥" <[email protected]>', //发送人和地址
to: "[email protected]", // 接收方
subject: "红玫瑰", //发送标题
text: "梦里梦到醒不来的梦,红线里被软禁的红", //发送文本(文本信息与html只能有一个)
//html: "Hello world?" //发送页面
}
//发送邮件
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: '[email protected]', //发送方的邮箱地址
pass: 'qfmwabaij' //MTP验证码
}
});
function send(mail,code){
//创建了一个邮件信息对象
let mailobj={
from: '"就是你要注册?" <[email protected]>', //发送人和地址
to: mail, // 接收方
subject: "注册验证码", //发送标题
//反单引号即可以使用模板字符
text: `你的验证码 ${code},有效期五分钟`, //发送文本(文本信息与html只能有一个)
//html: "Hello world?" //发送页面
}
//发送邮件
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是否相等处用了!=,这样即使字符串与数字相同也算相同
欢迎访问我的博客:is-hash.com
商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢