1 路由中间件
将服务器中的四个路由拆分出去,UI路由和业务逻辑路由
router–>UIRouter.js
/*
* 专门用于管理展示界面的UI路由
* */
//引入Router构造函数
const {Router} = require('express')
//创建一个Router实例(路由器就是一个小型的app)
let router = new Router()
//引入path模块----Node中内置的一个专门用于解决路径问题的库
let {resolve} = require('path')
//用于展示登录界面的路由,无其他任何逻辑 ----- UI路由
router.get('/login',(req,res)=>{
let url = resolve(__dirname,'../public/login.html')
res.sendFile(url)
})
//用于展示注册界面的路由,无其他任何逻辑 ----- UI路由
router.get('/register',(req,res)=>{
let url = resolve(__dirname,'../public/register.html')
res.sendFile(url)
})
module.exports = function () {
return router
}
router–>loginRegisterRouter.js
/*
* 专门用于管理登录、注册的业务路由
* */
//引入Router构造函数
const {Router} = require('express')
//创建一个Router实例(路由器就是一个小型的app)
let router = new Router()
//引入模型对象
let usersModel = require('../model/usersModel')
//用于处理用户的注册请求,有很多业务逻辑(校验数据的有效性等) -------- 业务路由
router.post('/register',(req,res)=>{
/*
{ email: 'kobe@qq.com',
nick_name: 'kobe',
password: '123',
re_password: '123' }
*/
//1.获取用户的输入
const {email,nick_name,password,re_password} = req.body
/*
* 2.校验数据的合法性:(一般是前台和后台同时验证。)
* 2.1:校验成功
* -去数据库中查找该邮箱是否注册过
* 2.1.1:注册过:提示用户邮箱已被占用。
* 2.1.2:未注册:写入数据库
* 2.2:校验失败
* -提示用户具体哪里输入的不正确
* */
//校验邮件的正则表达式
const emailReg = /^[a-zA-Z0-9_]{4,20}@[a-zA-Z0-9]{2,10}\.com$/
//校验昵称的正则表达式
const nickNameReg = /[\u4e00-\u9fa5]/gm
//校验密码的正则表达式
const passwordReg = /^[a-zA-Z0-9_@#.+&]{6,20}$/
//使用正则去校验
if(!emailReg.test(email)){
res.send('邮箱格式不合法,用户名必须4-20位,主机名必须2-10位')
}else if(!nickNameReg.test(nick_name)){
res.send('昵称格式不合法,必须为中文')
}else if(!passwordReg.test(password)){
res.send('密码格式不合法,必须6-20')
}else if( password !== re_password){
res.send('两次输入密码不一致')
}else{
//去数据库中查询该邮箱是否注册过
usersModel.findOne({email},function (err,data) {
if(data){
//如果注册过
//引入计数模块--当达到一个敏感的阈值,触发安全性机制。
console.log(`邮箱为${email}的用户注册失败,因为邮箱重复`)
res.send('该邮箱已被注册,请更换邮箱')
}else{
//如果没有注册过
usersModel.create({email,nick_name,password},function (err) {
if(!err){
//如果写入成功了
console.log(`邮箱为${email}的用户注册成功`)
res.send('注册成功了')
}else{
//如果写入失败了
//引入报警模块,当达到敏感阈值,触发报警。
console.log(err)
res.send('您当前的网络状态不稳定,稍后重试')
}
})
}
})
}
})
//用于处理用户的登录请求,有很多业务逻辑(校验数据的有效性等) -------- 业务路由
router.post('/login',(req,res)=>{
//1.获取输入
const {email,password} = req.body
//2.准备正则
const emailReg = /^[a-zA-Z0-9_]{4,20}@[a-zA-Z0-9]{2,10}\.com$/
const passwordReg = /^[a-zA-Z0-9_@#.+&]{6,20}$/
if(!emailReg.test(email)){
res.send('邮箱格式不合法,用户名必须4-20位,主机名必须2-10位')
}else if(!passwordReg.test(password)){
res.send('密码格式不合法,必须6-20')
}else{
//3.去数据库中查找:
usersModel.findOne({email,password},(err,data)=>{
if(err){
//引入报警模块,当达到敏感阈值,触发报警。
console.log(err)
res.send('网络不稳定,稍后重试')
return
}
if(data){
res.redirect('https://wwww.baidu.com')
return
}
res.send('用户名或密码输入错误!')
})
}
})
module.exports = function () {
return router
}
server.js
//引入express
const express = require('express')
//创建app应用对象
const app = express()
//禁止服务器返回X-Powered-By,为了安全
app.disable('x-powered-by')
//使用内置中间件暴露静态资源,不访问路由直接写文件名+后缀也能看页面
app.use(express.static(__dirname+'/public'))
//引入db模块,用于连接数据库
const db = require('./db/db')
//使用内置中间件用于解析post请求的urlencoded参数
app.use(express.urlencoded({extended:true}))
//引入UI路由器
const UIRouter = require('./router/UIRouter')
//引入登录注册路由器
const loginRegisterRouter = require('./router/loginRegisterRouter')
//逻辑:如果数据库连接成功,随后立即启动服务器,在整个过程中,无论多少次请求,数据库只连接一次。
db(()=>{
//使用UIRouter
app.use(UIRouter())
//使用loginRegisterRouter
app.use(loginRegisterRouter())
//绑定端口监听
app.listen(3000,(err)=>{
if(!err) console.log('服务器启动成功!')
else console.log(err)
})
},(err)=>{
console.log('数据库连接失败',err)
})
2 ejs
让login和register页面的数据动起来,新建文件夹view,用来存放.ejs模板文件,使得前后端有数据交互,再用户post之后,在对应的位置指出所有错误,登陆失败则重新渲染登陆页面,一次性指出所有错误,注册同理
ejs语法:
1.< % % > 里面能写任意js代码,但是不会向浏览器输出任何东西。
2.< %- % > 能够将后端传递过来对象指定key所对应value渲染的页面
3.< %= % > 能够将后端传递过来对象指定key所对应value渲染的页面
注意:需要下载ejs----yarn add ejs
示例
const express = require('express')
const app = express()
//让你的服务器知道你在用哪一个模板引擎-----配置模板引擎
app.set('view engine','ejs')
//让你的服务器知道你的模板在哪个目录下,配置模板目录
app.set('views','./haha')
//如果在express中基于Node搭建的服务器,使用ejs无需引入。
app.get('/show',function (request,response) {
let personArr = [
{name:'peiqi',age:4},
{name:'suxi',age:5},
{name:'peideluo',age:6}
]
response.render('person',{persons:personArr,a:1})
})
app.listen(3000,function (err) {
if (!err) console.log('服务器启动成功了')
else console.log(err)
})
对应的.ejs文件,haha—>person.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>show</title>
</head>
<body>
<!--
ejs语法:
1.< % % > 里面能写任意js代码,但是不会向浏览器输出任何东西。
2.< %- % > 能够将后端传递过来对象指定key所对应value渲染的页面
3.< %= % > 能够将后端传递过来对象指定key所对应value渲染的页面
-->
<h1>我是一个新的页面</h1>
<h1>data</h1>
<!--<div>< %-data% ></div>
<div>< %=data% ></div>-->
<%
for (var i=0; i<persons.length; i++ ){
let item = persons[i] %>
<ul>
<li class="name">姓名:<%=item.name%></li>
<li>年龄:<%=item.age%></li>
</ul>
<%}%>
<h1><%=a%></h1>
</body>
</html>
在登录注册项目中的使用
view–>login.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
<style>
.err{
color: red;
}
</style>
</head>
<body>
<span class="err"><%=errMsg.networkErr%></span>
<span class="err"><%=errMsg.loginErr%></span>
<form action="http://localhost:3000/login" method="post">
邮箱:<input type="email" name="email" value="<%=errMsg.email%>"><span class="err"><%=errMsg.emailErr%></span><br><br>
密码:<input type="password" name="password"><span class="err"><%=errMsg.passwordErr%></span><br><br>
<input type="submit">
</form>
</body>
</html>
view–>register.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册</title>
<style>
.err{
color: red;
}
</style>
</head>
<body>
<span class="err"><%=errMsg.networkErr%></span>
<form action="http://localhost:3000/register" method="post">
邮箱:<input type="email" name="email" id="email"><span class="err"><%=errMsg.emailErr%></span><br><br>
昵称:<input type="text" name="nick_name"><span class="err"><%=errMsg.nickErr%></span><br><br>
密码:<input type="password" name="password"><span class="err"><%=errMsg.passwordErr%></span><br><br>
重复密码:<input type="password" name="re_password"><span class="err"><%=errMsg.rePasswordErr%></span><br><br>
<input type="submit">
</form>
<script type="text/javascript">
</script>
</body>
</html>
server.js
//引入express
const express = require('express')
//创建app应用对象
const app = express()
//禁止服务器返回X-Powered-By,为了安全
app.disable('x-powered-by')
//使用内置中间件暴露静态资源,不访问路由直接写文件名+后缀也能看页面
app.use(express.static(__dirname+'/public'))
//配置模板引擎
app.set('view engine','ejs')
app.set('views','./views')
//引入db模块,用于连接数据库
const db = require('./db/db')
//使用内置中间件用于解析post请求的urlencoded参数
app.use(express.urlencoded({extended:true}))
//引入UI路由器
const UIRouter = require('./router/UIRouter')
//引入登录注册路由器
const loginRegisterRouter = require('./router/loginRegisterRouter')
//逻辑:如果数据库连接成功,随后立即启动服务器,在整个过程中,无论多少次请求,数据库只连接一次。
db(()=>{
//使用UIRouter
app.use(UIRouter())
//使用loginRegisterRouter
app.use(loginRegisterRouter())
//绑定端口监听
app.listen(3000,(err)=>{
if(!err) console.log('服务器启动成功!')
else console.log(err)
})
},(err)=>{
console.log('数据库连接失败',err)
})
router–>UIRouter.js
将res.send(''),变成res.render('login',{})或res.render('register',{})
/*
* 专门用于管理展示界面的UI路由
* */
//引入Router构造函数
const {Router} = require('express')
//创建一个Router实例(路由器就是一个小型的app)
let router = new Router()
//引入path模块----Node中内置的一个专门用于解决路径问题的库
let {resolve} = require('path')
//用于展示登录界面的路由,无其他任何逻辑 ----- UI路由
router.get('/login',(req,res)=>{
const {email} = req.query
res.render('login',{errMsg:{email}})
})
//用于展示注册界面的路由,无其他任何逻辑 ----- UI路由
router.get('/register',(req,res)=>{
res.render('register',{errMsg:{}})
})
module.exports = function () {
return router
}
router–>loginRegisterRouter.js
将res.send(''),变成res.render('login',{})或res.render('register',{}),且用户注册成功后,直接跳转到登录界面并填好电子邮件信息
/*
* 专门用于管理登录、注册的业务路由
* */
//引入Router构造函数
const {Router} = require('express')
//创建一个Router实例(路由器就是一个小型的app)
let router = new Router()
//引入模型对象
let usersModel = require('../model/usersModel')
//用于处理用户的注册请求,有很多业务逻辑(校验数据的有效性等) -------- 业务路由
router.post('/register',(req,res)=>{
/*
{ email: 'kobe@qq.com',
nick_name: 'kobe',
password: '123',
re_password: '123' }
*/
//1.获取用户的输入
const {email,nick_name,password,re_password} = req.body
/*
* 2.校验数据的合法性:(一般是前台和后台同时验证。)
* 2.1:校验成功
* -去数据库中查找该邮箱是否注册过
* 2.1.1:注册过:提示用户邮箱已被占用。
* 2.1.2:未注册:写入数据库
* 2.2:校验失败
* -提示用户具体哪里输入的不正确
* */
//校验邮件的正则表达式
const emailReg = /^[a-zA-Z0-9_]{4,20}@[a-zA-Z0-9]{2,10}\.com$/
//校验昵称的正则表达式
const nickNameReg = /[\u4e00-\u9fa5]/gm
//校验密码的正则表达式
const passwordReg = /^[a-zA-Z0-9_@#.+&]{6,20}$/
const errMsg = {}
//使用正则去校验
if(!emailReg.test(email)){
errMsg.emailErr = '邮箱格式不合法,用户名必须4-20位,主机名必须2-10位'
}
if(!nickNameReg.test(nick_name)){
errMsg.nickErr = '昵称格式不合法,必须为中文'
}
if(!passwordReg.test(password)){
errMsg.passwordErr = '密码格式不合法,必须6-20'
}
if( password !== re_password){
errMsg.rePasswordErr = '两次输入密码不一致'
}
if(JSON.stringify(errMsg) !== '{}'){
//若进入此判断,证明用户一定有输入错误的项,重新“打”回注册页面。
res.render('register',{errMsg})
return
}
//去数据库中查询该邮箱是否注册过
usersModel.findOne({email},function (err,data) {
if(data){
//如果注册过
//引入计数模块--当达到一个敏感的阈值,触发安全性机制。
console.log(`邮箱为${email}的用户注册失败,因为邮箱重复`)
errMsg.emailErr = `${email}邮箱已被注册,请更换邮箱`
res.render('register',{errMsg})
return
}
//如果没有注册过
usersModel.create({email,nick_name,password},function (err) {
if(!err){
//如果写入成功了
console.log(`邮箱为${email}的用户注册成功`)
res.redirect(`/login?email=${email}`)
}else{
//如果写入失败了
//引入报警模块,当达到敏感阈值,触发报警。
console.log(err)
errMsg.networkErr = `您当前的网络状态不稳定,稍后重试`
res.render('register',{errMsg})
}
})
})
})
//用于处理用户的登录请求,有很多业务逻辑(校验数据的有效性等) -------- 业务路由
router.post('/login',(req,res)=>{
//1.获取输入
const {email,password} = req.body
//2.准备正则
const emailReg = /^[a-zA-Z0-9_]{4,20}@[a-zA-Z0-9]{2,10}\.com$/
const passwordReg = /^[a-zA-Z0-9_@#.+&]{6,20}$/
//3.准备一个用于收集错误的对象
const errMsg = {}
if(!emailReg.test(email)){
errMsg.emailErr = '邮箱格式不合法,用户名必须4-20位,主机名必须2-10位'
}
if(!passwordReg.test(password)){
errMsg.passwordErr = '密码格式不合法,必须6-20'
}
if(JSON.stringify(errMsg) !== '{}'){
res.render('login',{errMsg})
return
}
//3.去数据库中查找:
usersModel.findOne({email,password},(err,data)=>{
if(err){
//引入报警模块,当达到敏感阈值,触发报警。
console.log(err)
errMsg.networkErr = '网络不稳定,稍后重试'
res.render('login',{errMsg})
return
}
if(data){
res.redirect('https://wwww.baidu.com')
return
}
errMsg.loginErr = '用户名或密码输入错误!'
res.render('login',{errMsg})
})
})
module.exports = function () {
return router
}
3 实现用户登陆后跳转个人中心页面
views—>userCenter.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>个人中心</title>
</head>
<body>
<h1>hello,<%=nickName%></h1>
</body>
</html>
两种实现方法,方法一:直接在登陆成功后重定向到个人中心页面且通过查询字符串的方式,将用户名传给页面. 需要修改router--->UIRouter.js和router--->loginRegisterRouter.js
此处仅标记关键代码 问题:当你在用户栏输入数据库中任意字符,都会展示相应的个人中心页面(localhost:3000/user_center?nick_name=哈哈),且地址栏暴露信息太多
router—>UIRouter.js
//用于展示个人中心界面的路由,无其他任何逻辑 ----- UI路由
router.get('/user_center',(req,res)=>{
const {nick_name} = req.query
res.render('userCenter',{nickName:nick_name})
})
router—>loginRegisterRouter.js
//用于处理用户的登录请求,有很多业务逻辑(校验数据的有效性等) -------- 业务路由
router.post('/login',(req,res)=>{
//1.获取输入
const {email,password} = req.body
//2.准备正则
const emailReg = /^[a-zA-Z0-9_]{4,20}@[a-zA-Z0-9]{2,10}\.com$/
const passwordReg = /^[a-zA-Z0-9_@#.+&]{6,20}$/
//3.准备一个用于收集错误的对象
const errMsg = {}
if(!emailReg.test(email)){
errMsg.emailErr = '邮箱格式不合法,用户名必须4-20位,主机名必须2-10位'
}
if(!passwordReg.test(password)){
errMsg.passwordErr = '密码格式不合法,必须6-20'
}
if(JSON.stringify(errMsg) !== '{}'){
res.render('login',{errMsg})
return
}
//3.去数据库中查找:
usersModel.findOne({email,password},(err,data)=>{
if(err){
//引入报警模块,当达到敏感阈值,触发报警。
console.log(err)
errMsg.networkErr = '网络不稳定,稍后重试'
res.render('login',{errMsg})
return
}
if(data){
res.redirect(`http://localhost:3000/user_center?nick_name=${data.nick_name}`) //此种方法浏览器的地址栏残留的东西太多了。
return
}
errMsg.loginErr = '用户名或密码输入错误!'
res.render('login',{errMsg})
})
})
方法二:用户重新登陆后重新按照userCenter.ejs渲染页面,需要修改router--->loginRrgisterRouter.js 问题:此种方式会导致浏览器地址栏依然是login--浏览器的地址栏不变化。
//用于处理用户的登录请求,有很多业务逻辑(校验数据的有效性等) -------- 业务路由
router.post('/login',(req,res)=>{
//1.获取输入
const {email,password} = req.body
//2.准备正则
const emailReg = /^[a-zA-Z0-9_]{4,20}@[a-zA-Z0-9]{2,10}\.com$/
const passwordReg = /^[a-zA-Z0-9_@#.+&]{6,20}$/
//3.准备一个用于收集错误的对象
const errMsg = {}
if(!emailReg.test(email)){
errMsg.emailErr = '邮箱格式不合法,用户名必须4-20位,主机名必须2-10位'
}
if(!passwordReg.test(password)){
errMsg.passwordErr = '密码格式不合法,必须6-20'
}
if(JSON.stringify(errMsg) !== '{}'){
res.render('login',{errMsg})
return
}
//3.去数据库中查找:
usersModel.findOne({email,password},(err,data)=>{
if(err){
//引入报警模块,当达到敏感阈值,触发报警。
console.log(err)
errMsg.networkErr = '网络不稳定,稍后重试'
res.render('login',{errMsg})
return
}
if(data){
res.render('userCenter',{nickName:data.nick_name}) //此种方式会导致浏览器地址栏依然是login--浏览器的地址栏不变化。
return
}
errMsg.loginErr = '用户名或密码输入错误!'
res.render('login',{errMsg})
})
})
4 cookie
cookie是一般服务器生成交给客户端(浏览器)保管的,是为了解决http无状态,是后端在返回响应时的同时"种"下(附属操作),客户端下次请求自动携带,分为:会话cookie,持久化cookie
/*
* 关于cookie:
* 1.是什么?
* 本质就是一个【字符串】,里面包含着浏览器和服务器沟通的信息(交互时产生的信息)。
* 存储的形式以:【key-value】的形式存储。
* 浏览器会自动携带该网站的cookie,只要是该网站下的cookie,全部携带。
* 2.分类:
* --会话cookie(关闭浏览器后,会话cookie会自动消失,会话cookie存储在浏览器运行的那块【内存】上)。
* --持久化cookie:(看过期时间,一旦到了过期时间,自动销毁,存储在用户的硬盘上,备注:如果没有到过期时间,同时用户清理了浏览器的缓存,持久化cookie也会消失)。
*
* 3.工作原理:
* --当浏览器第一次请求服务器的时候,服务器可能返回一个或多个cookie给浏览器
* --浏览器判断cookie种类
* --会话cookie:存储在浏览器运行的那块内存上
* --持久化cookie:存储在用户的硬盘上
* --以后请求该网站的时候,自动携带上该网站的所有cookie(无法进行干预)
* --服务器拿到之前自己“种”下cookie,分析里面的内容,校验cookie的合法性,根据cookie里保存的内容,进行具体的业务逻辑。
*
* 4.应用:
* 解决http无状态的问题(例子:7天免登录,一般来说不会单独使用cookie,一般配合后台的session存储使用)
*
* 5.不同的语言、不同的后端架构cookie的具体语法是不一样的,但是cookie原理和工作过程是不变的。
* 备注:cookie不一定只由服务器生成,前端同样可以生成cookie,但是前端生成的cookie几乎没有意义。
*
* 6.对比浏览器的本地存储:
* 1.localStorage:
* (1).保存的数据,只要用户不清除,一直存在
* (2).作为一个中转人,实现跨页签通信。
* (3).保存数据的大小:5MB - 10MB
* 2.sessionStorage:
* (1).保存的数据,关闭浏览器就消失
* (3).保存数据的大小:5MB - 10MB
* 3.cookie:
* (1).分类:会话cookie----关浏览器消失、持久化cookie----到过期时间消失
* (2).保存数据的大小:4K --- 8K
* (3).主要用于解决http无状态(一般配合后端的session会话存储使用)
* (4).浏览器请求服务器时,会自动携带该网站的所有cookie
* */
在node平台使用express框架搭建服务器时,cookie的操作
let express = require('express')
let cookieParser = require('cookie-parser')
let app = express()
app.use(cookieParser())
//demo路由不对cookie进行任何操作
app.get('/demo',function (req,res) {
res.send('我是demo路由给你的反馈,我没有对cookie进行任何的操作')
})
//会话cookie,关闭浏览器即立刻消失
//demo1路由,负责给客户端“种”下一个会话cookie
app.get('/demo1',function (req,res) {
//express中给客户端“种”cookie不需要任何的库
let obj = {school:'atguigu',subject:'qianduan'}
res.cookie('peiqi',JSON.stringify(obj)) //给客户端种下一个会话cookie
res.send('我是demo1路由给你的反馈,我给你种下了一个【会话cookie】,你赶紧去浏览器里看看!')
})
//demo2路由,负责给客户端“种”下一个持久化cookie
app.get('/demo2',function (req,res) {
let obj = {school:'atguigu2',subject:'qianduan2'}
res.cookie('peiqi',JSON.stringify(obj),{maxAge:1000 * 60 * 60 *24 *30}) //给客户端种下一个持久化cookie
res.send('我是demo2路由给你的反馈,我给你种下了一个【持久化cookie】,你赶紧去浏览器里看看!')
})
//demo3路由,负责读取客户端所携带过来的cookie
app.get('/demo3',function (req,res) {
//express中读取客户端携带过来的cookie要借助一个中间件,名为:cookie-parser(yarn add cookie-parser)
console.log(req.cookies);
const {peiqi} = req.cookies
let a = JSON.parse(peiqi)
console.log(a.school)
res.send('我是demo3路由,我读取了你携带过来的cookie,你去服务器控制台看看吧')
})
//demo4路由,负责告诉客户端删除一个cokkie
app.get('/demo4',function (req,res) {
//res.cookie('peiqi','',{maxAge:0})
res.clearCookie('peiqi')
res.send('兄台,我删除了你所保存的key为peiqi的那个cookie')
})
app.listen(3000,function (err) {
if(err) console.log(err)
else console.log('演示cokkie服务器启动成功了')
})