官网文档:https://github.com/sideway/joi/blob/v13.6.0/API.md注意:该文章只适用于13.6.0版本,最新版本语法已经变了,所以安装时需要指定版本。作用:它是一个验证器,可以验证传入的值是否合法,验证格式可以自定义,它提供了很多方法,可以直接调用方法来验证。0x01使用:1、下载Joi模块npminstalljoi@13.6.02、引入Joi模块constJoi=require("joi");3、定义验证规则:声明一个变量(schema),这个变量的值是一个对象,在对象内部定义的就是验证规则。属性:表单传过来的name名称值:验证内容,例如最小长度,最大长度…如果被验证的对象中出现规则以外的内容,验证就会失败。constschema={username:Joi.string().min(2).max(20).required().error(newError("传入的数据不合法!")),//...}注意:必须将类型写在最前面,不同类型有不同的验证规则。使用规则验证表单传过来的数据:使用Joi.validate(被验证的对象,验证规则)方法来验证。Joi.validate(req.body,schema);它返回一个Promise对象。如果验证通过:返回被验证的表单数据对象(req.body这个对象)。如果验证失败:抛出一个异常。asyncfunctionrun(){try{constt=awaitJoi.validate(req.body,schema);console.log(t);}catch(e){console.log(e.message)}}run();0x02方法:string():字符串类型。alphanum():只能包含az、A-Z、0-9,不能包含其他特殊字符。regex():这个方法接收一个正则表达式,用这个参数来验证这个字段。min():字符串的小长度。max():字符串的最大长度。required():这个属性是必填属性。[Joi.string(),Joi.number()]:这两种类型都可以,如果不是这两种,则不通过。email():必须符合邮箱格式。error(newError("错误信息")):如果不符合验证规则,则会抛出这个错误。valid('a','b'):只能是这个参数内中的值,不能是其他的。number():数值类型。integer():必须是整数。min():数值最小数(注意:不是长度)max():数字最大数(注意:不是长度)
作用:在访问后台页面时,如果不做处理,是直接可以访问的,这样是不行的。使用登录拦截可以使用户必须登录,然后才可以访问后台的其他页面。方法:使用app.use()中间件来拦截访问后台的所有请求。拦截后进行判断:(访问的如果不是/login页面)并且(session是空的)。那么就应该让它重定向到/login页面进行登录否则直接next()继续向下匹配就行。app.use("/admin",function(req,res,next){if((req.url=="/register")){next();}else{if((req.url!="/login")&&(req.session.user==null)){res.redirect("/admin/login");}else{next();}}})
它可以实现session功能。注意:它需要写在路由的上面。步骤:1、下载express-session库。npminstallexpress-session2、导入包。constsession=require("express-session");3、使用app.use()中间件来拦截所有请求,并且将请求交给session来处理。在app.use()中间件中调用session这个方法。在这个方法中需要传入一个对象,对象有一个键值对。键:secret值:密钥(自定义的),这个密钥是用于加密cookie信息的。app.use(session({secret:"keyboardcat",//密钥resave:false,saveUninitialized:true}));1、方法内部会为请求对象(req)下面添加一个属性,属性的名字叫做session,session属性的值是用于保存一个对象,这个对象保存的就是用户用户信息。2、方法内部会在session存储数据时,生成sessionID,这个sessionID是唯一标识。然后将sessionID存储在客户端的cookie当中,在客户端再次访问服务端的时候,这个方法会拿到客户端传过来的cookie,并从cookie中提取出sessionID,根据提取出来的这个sessionID中,找到用户信息。req.session.username=ret.username;//将这个用户昵称信息存储到session中//ret是数据库中的用户信息
该模块为加密模块:加密方式:哈希加密(hash)。哈希加密是单程加密方式,只可以加密,不可以解密。注意:该方法都是异步API,需要使用await来等待一下,不要忘记await需要配合async使用哦!!0x01密码加密步骤:1、首先下载bcrypt这个库。npminstallbcrypt2、然后导包。constbcrypt=require("bcrypt");3、使用genSalt()方法生成‘盐’。参数:加密复杂程度,越大越复杂。默认为:10,当然,越大也越慢。letsalt=awaitbcrypt.genSalt(10);4、使用hash()方法对密码进行加密。第一个参数:用户输入的明文密码。第二个参数:使用genSalt()生成的‘盐’。letpass=awaitbcrypt.hash("明文密码",salt);0x02密码比对:虽然不能解密,但是它提供了一个密码比对的方法,可以判断密码输入的是否正确。方法:awaitbcrypt.compare('明文密码','加密密码');明文密码就是用户现在输入的密码。加密密码是从数据库中读取出来的已经被加密的密码。返回值:返回一个Boolean值。比对正确:true比对错误:false//查询数据库User.findOne({//只查询用户输入的账号account:req.body.account})//由于bcrypt.compare使用了await,所以这里使用async。.then(asyncfunction(ret){//判断查询返回的是否是null,如果是null就是没有数据,那么返回账号密码错误。if(ret){//如果返回的对象,就是存在,然后密码比对,返回Boolean值。if(awaitbcrypt.compare(req.body.password,ret.password)){//返回true说明比对成功res.send({message:"登录成功!",code:0});//返回false说明比对失败,返回账号密码错误}else{res.send({message:"账号或密码错误!",code:1});}}else{res.send({message:"账号或密码错误!",code:1});}});
promisify方法:该方法可以将异步回调方法改成promise实例的方法。需要满足以下两个条件:1、最后一个参数是函数2、回调函数的参数为(err,result),前面是可能的错误,后面是正常的结果以fs.readFile()方法为例://导入fs模块constfs=reuqire("fs");//引入uril模块中的promisift方法constpromisify=require("util").promisift;//这里不要直接调用,将它赋值给变量,用变量调用//将目标方法作为参数传递给promisify方法constreadFile=promisify(fs.readFile);//接下来使用返回的readFile来读取文件即可asyncfunctionrun(){vara=awaitread("a.txt","utf8");console.log(a);//a文件中的内容}//调用run()
0x01path模块方法:返回路径中的最后一部分:path.basename(p,[ext]);//p:路径//ext:输入一个文件后缀名,如果匹配到该路径中有该后缀名,则省略该后缀名,只显示文件名。path.basename("c:a/b/index.js",[ext]);//index.jspath.basename("c:a/b/index.js",".js");//index返回路径中代表文件夹的部分:path.dirname(p);path.dirname("c:a/b/index.js");//c:a/b返回路径中文件的后缀名:path.extname(p);path.dirname("c:a/b/index.js");//.jspath.dirname("c:a/b/index.");//.path.dirname("c:a/b/index");//返回空字符串判断参数是否一个绝对路径:path.isAbsolute(path);path.isAbsoulute("c:/a/b/index.js");//true-绝对路径path.isAbsoulute("./c/index.js");//false-相对路径将路劲解析为一个对象:path.parse(pathString);path.parse("c:/a/b/index.js");//解析出来的对象{root:'c:/',dir:'c:/a/b',base:'index.js',ext:'.js',name:'index'}路径拼接:path.join();0x02dirname和filename下面这两个属性是Node中的全局变量:__dirname获取当前该文件,所在目录的,绝对路径,(不包含自己)__filename获取当前该文件,所在的,绝对路径,(包含自己)在文件操作中,使用相对路径是不可靠的,因为在Node中,操作的路径被设计为,相对于执行node命令所处的路径。例如:这个文件夹中有一个app.js,还有一个public文件夹,里面有一个a.txt。需要在app.js中将a.txt中的数据读出来。如果使用相对路径,那么当我退出32593这个文件夹,跳转到上一级目录:Users文件夹,执行node32593/app.js那么这个时候就会在Users这个文件中中找a.txt。为了解决这个问题,只需要将把相对路径更改为绝对路径就可以了。这个时候就可以使用__dirname来动态获取该文件现在所在的目录。补充:模块中的标识路径(require)和这里的路径没关系,不受影响。constfs=require("fs");constpath=require("path");fs.readFile(path.join(__dirname,"./public/a.txt"),"utf8",function(err,data){if(err){returnconsole.log("读取失败!",err);}console.log(data);});
0x01什么是中间件?在默认情况下,当客户端发送请求时,服务器会进行依次到下的匹配,匹配成功就不再向下匹配。而中间件可以拥有继续向下匹配的权限,这需要在参数中添加一个参数:nextnext是一个封装好的方法,它可以控制是否继续向下匹配。向下匹配的前提是:该中间件没有返回给客户端数据,否则就会报错了。这就相当于一个生产线,当手机进入第一个工序时,这个工序对它进行加工,加工完成后继续向下流(这就相当于next),如果这个工序检测到了这个产品不合格,那么就不让它流入下一个了。在下面这个案例中,首先匹配到第一个get。这个get并没有结束返回,而是调用next()方法继续向下匹配。然后匹配到第二个get,在同一次请求中,req对象时同一个,然后返回给客户端req对象中的foo属性,执行完毕。app.get("/admin",function(req,res,next){req.foo="bar";next();});app.get("/admin",function(req,res,next){res.send(req.foo);});0x02app.use()中间件用法:在Express框架中,提供了app.use()这个方法,这个方法不区分到底是get还是post请求,它会匹配所有请求方式。它可以传入一个请求地址,然后这个中间件只针对这个请求地址有效。也可以不传入请求地址,就只写一个回调函数,然后它会不管你是什么请求地址,都会经过这个中间件。该中间件只针对/admin地址有效,然后向下匹配第二个中间件。app.use("/admin",function(req,res,next){//处理内容next();//向下匹配});该中间价不管是什么请求,都会经过这里。app.use(function(req,res,next){//处理函数next();});0x03错误处理中间件:在程序执行的过程中,不可避免的会出现一些无法预料的错误,比如文件读取失败,数据库连接失败,错误处理中间件是一个集中处理错误的地方。它一共有四个参数:err、req、res、nextapp.use(function(err,req,res,next){res.status(500).send("服务器发生未知错误:",err.message);})//当发生错误时,会执行这个错误处理中间件。错误处理中间件只能捕获到同步代码出错(thrownewErroe(“错误信息”))。如果在异步代码中出现了错误,这时需要手动触发错误处理中间件,这时调用next()方法并且将错误信息当作参数即可。app.get("/index",function(req,res,next){thrownewError("出现错误了.");next("出现错误了.");//同步代码可以手动抛出一个异常让错误处理中间件来捕获,也可以手动调用next()。})app.get("/index",function(req,res,next){fs.readFile("./a.txt","utf8",function(err,data){if(err){next(err);//在异步代码中只能通过next()来调用错误处理中间件。}})})注意:错误处理中间件一般写在最后面。
导入:使用{{include‘./文件路径’}}可以导入网页,例如:页头和页脚是单独的文件,然后通过模板引擎将它引入。{{include'./header.html'}}//引入页头<div><h1>Hi~</h1></div>{{include'./footer.html'}}//引入页脚继承:1、首先需要创建一个模板文件(layoyt.html),在该文件中留好坑。使用{{block‘名称’}}来留一个坑。{{block'名称'}}<h1>我是h1标题</h1><!--这里可以写也可以不写,写了就是默认内容-->{{/block}}2、然后在子文件中继承这个模板文件,然后填坑(相当于重写)。{{extend'./layout.html'}}<!--继承这个文件-->{{block'名称'}}<!--这里可以重写继承过来的内容-->{{/block}}模板文件:<htmllang="zh"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="viewport"content="width=device-width,initial-scale=1.0"><title>Document</title><!--模板继承:css开始-->{{block'css'}}{{/block}}<!--模板继承:css结束--></head><body><!--引入页头开始-->{{include'./header.html'}}<!--引入页头结束--><!--模板继承:主体内容开始-->{{block'content'}}{{/block}}<!--模板继承:主题内容结束--><!--引入页脚开始-->{{include'./footer.html'}}<!--引入页脚结束--><!--模板继承:script开始-->{{block'script'}}{{/block}}<!--模板继承:script结束--></body></html>首页:<!--继承layout这个布局-->{{extend'./layout.html'}}<!--该页面的css-->{{block'css'}}<style>body{background-color:aqua;}</style>{{/block}}<!--该页面的主体内容-->{{block'content'}}<div><h1>fda</h1></div>{{/block}}<!--该页面的script-->{{block'script'}}<script>alert("我是index页面");</script>{{/block}}
例如:文章中要显示作者的名字,那么这个文章表和用户表就产生了关联。步骤:1、在文章表中设置一个用户字段,然后设置类型,但是用户__ID的类型是特殊的。//在文章中设置用户字段author:{//设置类型type:mongoose.Schema.Types.ObjectId,}type(类型):mongoose.Schema.Types.ObjectId2、接着使用ref这个属性来关联用户表的模型。//在文章中设置用户字段author:{//设置类型type:mongoose.Schema.Types.ObjectId,//关联用户表ref:"User"}3、在给文章中author这个字段传入数据时,需要传入一个用户表中的用户__ID。Post.create({author:"61134a307a0157187cb1707d"//传入一个用户表中的id})4、插入完成后,如果直接使用find()方法来查询,author这个字段默认还是显示这个__id,我们需要在find()方法后调用populate("字段")这个方法,在这个方法的参数中传入关联其他表的字段。Post.find().populate("author").then(function(ret){console.log(ret);})//查询显示的内容[{_id:61134bbd37309e0b60aadc42,title:'标题1',content:'内容1',//将这个__id的文档显示了出来author:{_id:61134a307a0157187cb1707d,name:'王大拿',age:30,sex:'男',__v:0},__v:0}]