前端第18天

Express

一.初识Express

1.概述

Express是基于Node.js平台,快速,开放,极简的Web开发框架

通俗理解:和Node.js内置的http模块类似,是专门用来创建Web服务器的。是基于http封装出来的。

作用:对于前端程序员Express能做Web网站服务器(专门提供Web网站资源的服务器)和API接口服务器(专门对外提供API接口的服务器),使用它我们可以方便快速的创建这两个服务器。

2.Express基本使用

下载express:npm install express@4.17.1

监听GET请求

通过app.get(参数1,参数2)方法,可以监听客户端的GET请求

参数1:客户端请求的url地址

参数2:请求对应的处理函数

​ req:请求对象 包含了与请求相关的属性与方法

​ res:响应对象 包含了与响应相关的属性与方法

监听POST请求

通过app.post(参数1,参数2)方法,可以监听客户端的POST请求

参数1:客户端请求的url地址

参数2:请求对应的处理函数

​ req:请求对象 包含了与请求相关的属性与方法

​ res:响应对象 包含了与响应相关的属性与方法

把内容响应给客户端

通过res.send()方法,可以把处理好的内容,发送给客户端。可以响应Json对象或者一段文本字符串

获取URL中携带的查询参数

通过req.query对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数

获取URL中的动态参数

通过req.params对象,可以访问到URL中,通过: 匹配到的动态参数

PS:冒号后面的参数是自己去命名的只要合理合法行,返回的是对应的键。可以有多个动态参数

//导入express
const express = require('express')//创建web服务器
const web = express()//监听客户端的get和post请求,并向客户端响应具体的内容
web.get('/user', (req, res) => {//调用express提供的res.send方法向客户端响应一个Json对象res.send({ name: 'zs', age: 20, gender: '男' });
})web.post('/user', (req, res) => {res.send('请求成功!')
})web.get('/', (req, res) => {//通过req.query可以获取到客户端发送过来的 查询参数 默认为空对象console.log(req.query);res.send(req.query); //{ name: 'zs', age: '20' }
})//这里的:id是一个动态参数
web.get('/user/:id/:username', (req, res) => {//req.params是动态匹配到的参数,默认是空对象console.log(req.params);res.send(req.params)
})//启动web服务器
web.listen(80, () => {console.log("express server running at http://127.0.0.1");
})//用postman测试

3.托管静态资源

express.static()

可以非常方便的创建一个静态资源服务器,将public目录下的图片,CSS文件,JavaScript文件对外开放访问了

可以多次调用就可以托管多个静态资源目录了!

挂在路径前缀

在访问静态资源访问路径之前,挂载路径前缀,则可以使用如下方式

//导入express
const express = require('express')//创建web服务器
const web = express()//将clock文件下的所有资源变成静态资源
//多个express.static方法时会从上至下依次执行 上面没有再找下面目录中的文件 上面有就不找下面的了
web.use(express.static('./file')) //http://127.0.0.1/index.html
web.use(express.static('./clock'))
//挂在路径前缀
web.use('/clock', express.static('./clock')) //http://127.0.0.1/clock/index.html//启动web服务器
web.listen(80, () => {console.log("express server running at http://127.0.0.1");
})

4.nodemon

监听项目变动,不用每一次修改都重新启动!

使用nodemon

nodemon js文件:来启动项目,监听项目,修改保存完会自动重启项目。非常好用~

二.Express路由

1.概述

广义上讲,路由就是映射关系!

在Express中,路由指的是客户端的请求与服务器处理函数之间的映射关系。由三部分组成:请求的类型、请求的URl地址、处理函数,格式如下:app.METHOD(PATH,HANDLER)

METHOD:请求的方式 GET/POST

PATH:请求的URL地址

HANDLER:回调函数

每当客户端发起的请求到达服务器之后,需要先经过路由的匹配,只有匹配成功后才会调用对应的处理函数。

在匹配时,会按照路由的顺序进行匹配,如果请求的类型和URL同时匹配成功,则Express会将这次请求,转给对应的function函数进行处理。

const express = require('express')
const app = express()//挂载路由
app.get('/', (req, res) => {res.send('hello world!')
})//挂载路由
app.post('/', (req, res) => {res.send('post request')
})app.listen(80, () => {console.log('http://127.0.0.1');
})

2.路由的使用

模块化路由

为了方便对路由进行模块化管理,不建议直接将路由挂载在app上,而是推荐将路由抽离为单独的模块

调用路由模块

const express = require('express')
const app = express()//导入一个路由模块
const router = require('./router.js');//注册路由模块
//app.use()函数的作用就是用来注册全局中间件
app.use(router)//添加统一的资源访问前缀app.use('/api', router)/* //挂载路由
app.get('/', (req, res) => {res.send('hello world!')
})//挂载路由
app.post('/', (req, res) => {res.send('post request')
}) */app.listen(80, () => {console.log('http://127.0.0.1');
})

提取出来的路由模块

//创建一个路由模块
const express = require('express');
//创建路由对象
const router = express.Router()//3.挂载具体路由
router.get('/user/list', (req, res) => {res.send('get user list!')
})
router.post('/user/add', (req, res) => {res.send('add new user!')
})//4.向外导出路由对象
module.exports = router

三.Express中间件

1.概述

中间件特指业务流程的中间处理环节,必须有上一级的输入和对下一级的输出

中间件调用流程:当一个请求达到Express的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理!

中间件的格式:本质上就是一个function处理函数,Express中间件的函数中,函数的参数列表必须包含next参数(本质是个函数)。在路由处理函数中只包含req和res。

next函数的作用:实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或者路由

2.Express中间件的使用

定义中间件函数
const express = require('express')
const app = express()//定义一个中间件函数
const mw = function(req, res, next) {console.log('这是一个中间件函数!');//把流转关系转交给下一个路由或中间件next()
}app.listen(80, () => {console.log('http;//127.0.0.1');
})
全局生效中间件

客户端发起的任何请求,到达服务器之后,都会触发中间件,叫做全局生效的中间件

通过app.use(中间件函数),既可以定义一个全局生效的中间件。

const express = require('express')
const app = express()//定义一个中间件函数
const mw = function(req, res, next) {console.log('这是一个中间件函数!');//把流转关系转交给下一个路由或中间件next()
}//全局生效的中间件
app.use(mw)//中间件的简写形式
/* app.use(function(req,res,next){console.log('这是一个中间件函数!');//把流转关系转交给下一个路由或中间件next()
}) *///挂载路由
app.get('/', (req, res) => {res.send('hello world!')
})//挂载路由
app.get('/user', (req, res) => {res.send('user page.')
})app.listen(80, () => {console.log('http;//127.0.0.1');
})
中间件的作用

多个中间件之间可以共享req和res。基于这样的特征,我们可以在上游的中间件中,统一为req或res对象添加自定义的属性或方法,供下游的中间件或路由进行使用。

const express = require('express');
const app = express()app.use((req, res, next) => {//获取请求到达服务器的时间const time = new Date();//为req挂在自定义属性 从而把时间共享给所有路由req.start = time;next()
})//路由
app.get('/', (req, res) => {res.send('Home page! ' + req.start);
})//路由
app.get('/user', (req, res) => {res.send('user page! ' + req.start);
})app.listen(80, () => {console.log('http://127.0.0.1');
})

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QvBvg5W2-1676954686005)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230214153107662.png)]

定义多个全局中间件

可以使用app.use()定义多个全局中间件,客户端请求达到服务器之后,会按照中间件定义的先后顺序依次进行调用

const express = require('express');
const app = express()//多个中间件 会按照先后顺序执行
app.use((req, res, next) => {console.log('第一个中间件');next()
})app.use((req, res, next) => {console.log('第二个中间件');next()
})//路由
app.get('/', (req, res) => {res.send('Home page! ');
})//路由
app.get('/user', (req, res) => {res.send('user page! ');
})app.listen(80, () => {console.log('http://127.0.0.1');
})

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ktCwXi4r-1676954686006)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230214153534528.png)]

局部生效的中间件

不使用app.use()定义的中间件,叫做局部生效的中间件

const express = require('express')
const app = express()//没有用app.use() 说明这是个局部中间件
const mw1 = (req, res, next) => {console.log('调用了局部生效的路由!');next()
}//创建路由
//在url地址和函数之间加一个局部中间件 可以让中间价影响这个路由
app.get('/', mw1, (req, res) => {res.send('Home pag')
})//创建路由
app.get('/user', (req, res) => {res.send('User pag')
})app.listen(80, (req, res) => {console.log('http://127.0.0.1');
})

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XqgKr8xm-1676954686006)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230214154614255.png)]

定义多个局部生效的中间件

调用顺序从前到后,用 逗号隔开 或者 用[]数组的形式 存放

const express = require('express')
const app = express()//没有用app.use() 说明这是个局部中间件
const mw1 = (req, res, next) => {console.log('调用了第一个局部生效的路由!');next()
}const mw2 = (req, res, next) => {console.log('调用了第二个局部生效的路由!');next()
}//创建路由
//在url地址和函数之间加一个局部中间件 可以让中间价影响这个路由
//也可以这么写![mw1, mw2] 数组的形式!
app.get('/', mw1, mw2, (req, res) => {res.send('Home pag')
})//创建路由
app.get('/user', (req, res) => {res.send('User pag')
})app.listen(80, (req, res) => {console.log('http://127.0.0.1');
})
使用总结:

1.在路由的前面定义中间件

2.客户端发过来的请求,可以连续调用多个中间件进行处理

3.执行完中间件的业务代码后,要执行next()函数!!!

4.防止代码逻辑混乱调用完next()函数之后不要再写任何代码!

5.连续调用多个中间件时,多个中间件之间,共享req和res对象

3.中间价的分类

为了方便理解和记忆中间件的使用,Express官方把常见的中间件用法,分为了5大类:

应用级别中间件

通过app.use()或app.get()或app.post(),绑定到app实例上的中间件

路由级别中间件

绑定到express.Router()实例上的中间件,叫做路由级别中间件。和应用级没有差别,只不过,应用级中间件绑定在app实例上,路由级中间件绑定在router实例上

错误级别中间件

作用:用来捕获整个项目中发生异常错误,从而防止项目异常崩溃

格式:错误级别中间件的function处理函数,必须由4个参数,形参顺序从前到后,分别是(err,req,res,next),一定要定义在所有路由的最后面!!!

const express = require('express')
const app = express()//定义路由
app.get('/', (req, res) => {//人为制造错误throw new Error('服务器内部出现错误!')res.send('Home pag')
})//定义一个错误级别中间件 捕获整个项目的异常错误,从而防止程序崩溃
app.use((err, req, res, next) => {console.log('发生了错误!' + err.message);res.send('Erroe :' + err.message)
})app.listen(80, (req, res) => {console.log('http://127.0.0.1');
})

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W71eaNNM-1676954686007)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230214165824014.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SMCjW8fu-1676954686007)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230214165917800.png)]

Express内置的中间件

从Express 4.16.0 版本开始,Express内置3个常用的中间件,提高了Express项目的开发效率和体验

express.static:快速托管静态资源的内置中间件,例如:HTML文件、图片、CSS样式等,没有兼容性

express.json:解析JSON格式的请求体数据,有兼容性,在4.16.0+版本可用!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ang1CcBj-1676954686007)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230214180509867.png)]

const express = require('express')
const app = express()//通过express.json()这个中间件,解析表单中JSON格式的数据
app.use(express.json())//通过express.urlencoded()这个中间件,解析表单中url-encoded格式的数据
app.use(express.urlencoded({ extended: false }))//定义路由
app.post('/user', (req, res) => {//在服务器可以用req.body这个属性,来接收客户端发送过来的请全体数据//默认情况下,如果不配置解析表单的中间件,则req.body默认等于undefinedconsole.log(req.body);//{ name: 'zs', age: 18 }res.send('Home pag')
})app.post('/book', (req, res) => {//在服务器端可以通过req.body这个属性 获得url-encoded格式的数据console.log(req.body);//[Object: null prototype] { bookname: '水浒传', author: '施耐庵' }res.send('Book ok.');
})app.listen(80, (req, res) => {console.log('http://127.0.0.1');
})

express.urlencoded:解析URL-encoded格式的请求体数据,有兼容性,在4.16.0+版本可用。(代码在上面!)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sS9tadpO-1676954686008)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230214181628047.png)]

第三方中间件

非Express官方内置的,而是由第三方开发出来的中间件。在项目中,可以按需求下载并配置。提高开发的效率。Express中的就是按照这个封装出来的!

例如:body-parser:在express!4.16.0之前版本中用这个解析请求体中的数据

const express = require('express')
const app = express()//导入解析表单数据的中间件
const parser = require('body-parser');
//使用app.use()注册中间件
//解析url数据
app.use(parser.urlencoded({ extended: false }))
//解析JSON数据
app.use(parser.json())//定义路由
app.post('/user', (req, res) => {//在服务器可以用req.body这个属性,来接收客户端发送过来的请全体数据//默认情况下,如果不配置解析表单的中间件,则req.body默认等于undefinedconsole.log(req.body); //{ name: 'zs', age: 18 }res.send('Home pag')
})app.post('/book', (req, res) => {//在服务器端可以通过req.body这个属性 获得url-encoded格式的数据console.log(req.body);//[Object: null prototype] { bookname: '水浒传', author: '施耐庵' }res.send('Book ok.');
})app.listen(80, (req, res) => {console.log('http://127.0.0.1');
})

4.自定义中间件

手动模拟一个类似于express.urlencoded这样的中间件,来解析POST提交到服务器的表单数据。

调用自定义中间件.js

const express = require('express');
//导入自己封装的中间件模块
const app = express()
const cbp = require('./custom-body-parser')//将自定义的中间件函数,注册为全局可用的中间件
app.use(cbp)app.post('/user', (req, res) => {res.send(req.body)console.log(req.body);
})app.listen(80, () => {console.log('Express server running at http://127.0.0.1');
})

自定义中间件:custom-body-parser.js

//导入Node.js内置的 querystring 模块
const qs = require('querystring')//定义一个中间件解析表单数据的
const bodyParser = (req, res, next) => {//定义中间件具体的业务逻辑//定义一个字符串拼接,用来存储客户端发来的请全体数据let str = '';//监听req.data事件req.on('data', (chunk) => {str += chunk;})//监听req.end事件req.on('end', () => {//在str中存放的是完成的请求体数据//console.log(str);//把字符串格式的请求体数据,解析成对象格式const body = qs.parse(str)req.body = bodynext()})
}module.exports = bodyParser

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H6jHbPFV-1676954686008)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230214193637049.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D2Ce1EZV-1676954686009)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230214193728547.png)]

四.使用Express写接口

1.创建一个基本的服务器

启动服务器,调用路由apiRouter.js

// 导入express模块;
const express = require('express');
// 创建express的服务器实例;
const app = express();//配置表单数据的中间件
app.use(express.urlencoded({ extended: false }))//导入路由模块
const router = require('./14apiRouter');
//把路由模块,注册到app上
app.use('/api', router)// 调用app.listen方法,在指定的端口启动web服务器;
app.listen(80, () => {console.log('Express server running at http:127.0.0.1');
})

apiRouter.js:创建GET和POST请求

// 导入express模块;
const express = require('express');
// 创建express的服务器实例;
const router = express.Router()//postman相当于客户端
//get请求
router.get('/get', (req, res) => {//通过req.query获取客户端通过查询字符串,发送到服务器的数据const query = req.query;//调用res.send() 方法向客户端 响应处理的结果res.send({status: 0, //0表示处理成功 1表示处理失败msg: 'get请求成功', //状态的描述data: query //需要响应给客户端的数据})
})//post请求
router.post('/post', (req, res) => {//获取客户端通过请求体,发送到服务器的数据的uel-encoded数据const body = req.body;//调用res.send() 方法,把数据响应给客户端res.send({status: 0, //0表示处理成功 1表示处理失败msg: 'get请求成功', //状态的描述data: body //需要响应给客户端的数据})
})module.exports = router

测试GET和POST

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v3ftvIPj-1676954686009)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230214222829859.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hb4oPnh4-1676954686009)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230214222855664.png)]

2.CORS跨域资源共享

测试接口跨域问题.html

DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Documenttitle><script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js">script>
head><body><button id="btnGET">GETbutton><button id="btnPOST">POSTbutton><script>$(function() {//1.测试GET接口$('#btnGET').on('click', function() {$.ajax({type: 'GET',url: 'http://127.0.0.1/api/get',data: {name: 'zs',age: 18},success: function(res) {console.log(res);}})});//2.测试POST接口$('#btnPOST').on('click', function() {$.ajax({type: 'POST',url: 'http://127.0.0.1/api/post',data: {bookname: '水浒传',authoe: '施耐庵'},success: function(res) {console.log(res);}})});})script>
body>html>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dsgUxXKk-1676954686010)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230214224558946.png)]

使用cors中间件解决跨域问题

cors是Express的第一个第三方中间件,通过安装和配置cors中间件,可以很方便的解决跨域的问题。

CORS使用步骤

1.运行npm install cors 安装中间件

2.使用const cors = require(‘cors’):导入中间件

3.在路由前调用app.use(cors()) 配置中间件

//一定要在路由之前配置cors这个中间件,解决接口跨域的问题。写在启动服务器的js文件中
const cors = require('cors');app.use(cors())

成功解决跨域问题!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tAIGiwdy-1676954686010)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230214225855579.png)]

CORS概述

跨域资源共享,有一系列HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止前端JS代码跨域获取资源

浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了CORS相关的HTTP响应头,就可以接触浏览器端的跨域访问限制

CORS注意事项

1.CORS主要在服务器进行配置。客户端浏览器无须做任何额外的配置,即可开启CORS的接口

2.CORS在浏览器中有兼容性,只有支持XMLRequest Level2 的浏览器,才能正常访问开启了CORS的服务器接口。

CORS响应头部

1.Access-Control-Allow-Origin

origin:参数值指定了允许访问该资源的外域URL

//只允许来自百度的请求
res.setHesder('Access-Control-Allow-Origin', 'http://baidu.cn'); 
//允许来自任何域的请求
res.setHesder('Access-Control-Allow-Origin', '*'); 

2.Access-Control-Allow-Headers

默认情况下CORS仅支持客户端向服务器发送的如下9个请求头:Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width、Content-Tyoe(值仅限于text/plain、multipart/form-data、application/x-www-form-urlencoded三者之一)
如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过Access-Control-Allow-Headers对额外的请求头进行说明,否则请求会失效

//允许客户端发送Content-Type请求头和 X-Custom-Header请求头
//注意:多个请求头之间使用英文的逗号进行分割
res.setHeader('Access-Control-Allow-Headers','Content-Type, X-Custom-Header')

3.Access-Control-Allow-Methods

CORS仅支持客户端发起的GET、POST、HEAD请求。如果客户端希望通过PUT或者DELETE等方式请求服务器资源,则需要在服务器端,通过Access-Control-Allow-Methods来指明实际请求所允许使用的HTTP方法

//只允许POST、GET、DELETE、HEAD请求方法
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, HEAD')
//允许所有的HTTP请求方式
res.setHeader('Access-Control-Allow-Methods', '*')
CORS请求的分类

客户端在请求CORS接口时,根据请求方式和请求头的不同,可以将CORS的请求分为两大类,

简单请求

同时满足下面两个条件的

请求的方式属于:GET、POST、HEAD之一

HTTP头部信息不超过一下几个字段:无自定义头部字段,Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width、Content-Tyoe(值仅限于text/plain、multipart/form-data、application/x-www-form-urlencoded三者之一)。

预检请求

概念:在浏览器与服务器正式通信前,浏览器会先发送OPTION请求进行预检,以获知服务器是否允许该实际请求,所以这一次的OPTION请求被称为“预检请求”。服务器成功响应预检请求后,才会发出真正的请求,并携带真实的数据。

只要符合下面的任何一个条件,都需要预检请求

请求的方式属于:GET、POST、HEAD之外的请求Method类型

请求头中包含自定义头部字段

向服务器发送了application/json格式的数据

区别

简单请求客户端与服务器之间只会发生一次请求。

预检请求的特点:客户端与服务器之间会发生两次请求,OPTION预检请求成功后,才会发起真正的请求

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wbRjLL3D-1676954686010)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230216150332154.png)]

3.JSONP接口

创建JSONP接口的注意事项

如果项目中已经配置了CORS跨域资源共享,为了防止冲突,必须在配置CORS中间件之前声明JSONP的接口。否则JSONP接口会被处理成开启了CORS的接口。

实现JSONP接口的步骤

1.获取客户端发送过来的回调函数的名字

2.得到要通过JSONP形式发送给客户端的数据

3.根据前两步得到的数据,拼接出一个函数调用的字符串

4.把上一步拼接出来的字符串响应给客户端的script标签进行解析

//必须在配置CORS中间件之前,配置JSONP接口
//JSONP模块没有放到路由模块上 所以得手动加个api 保证一致
app.get('/api/jsonp', (req, res) => {//JSONP具体实现 //1.获取客户端发送过来的回调函数的名字const funcName = req.query.callback;//2.得到要通过JSONP形式发送给客户端的数据const data = {name: 'zs', age: 12};//3.根据前两步得到的数据,拼接出一个函数调用的字符串const scriptStr = `${funcName}(${JSON.stringify(data)})`;//4.把上一步拼接出来的字符串响应给客户端的script标签进行解析res.send(scriptStr)
})

数据库与身份认证

一.数据库的基本概念

1.概述

数据库是用来组织、存储和管理数据的仓库。

为了方便管理互联网世界中的数据,就有了数据库管理系统的概念。用户可以对数据库中的数据进行新增、查询、更新和删除等操作

2.常见的数据库分类

MySQL数据库:使用最广泛,社区版开源免费。Oracle数据库。SQL Server数据库:关系型数据库

Mongodb数据库:非关系型数据库,新型数据库

3.传统型数据库的数据组织

数据以什么样的结构进行储存,在传统型数据库中,数据的组成结构分为**数据库(database)、数据表(table)、数据行(row)、字段(field)**这4大部分。每一个字段都要有对应的数据类型

实际开发中库、表、行、字段的关系

1.在项目开发中,一般情况下,每个项目都对应独立数据库

2.不同的数据要存储在不同表中,例如用户数据存储到users表中,图书数据存储到books表中。

3.每个表中具体储存什么数据是由字段来决定的!

4.表中的行,代表每一条具体数据

二.MySQL的基本使用

1.创建一个数据库

DataType数据类型:

int整型

carchar(len)字符串型

tinyint(1) 布尔值

字段的特殊标识:
PK(Primary Key):主键、唯一标识

NN(Not Null):值不能为空

UQ(Unique):值唯一

AI(Auto Increment):值自动增长

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZR9kTinZ-1676954686011)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230215133020085.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0UGAZz0G-1676954686011)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230215133212180.png)]

2.使用SQL管理数据库

SQL语言概述

全称为结构化查询语言,专门用来访问和处理数据库,以编程的形式操作数据库里面的数据。

关键点:SQL是一门数据库编程语言,使用SQL语言编写出来的代码叫做SQL语句,只能在关系型数据库中使用,非关系型数据库中不能使用SQL语言!

作用:增删改查数据

where条件判断

and满足两边的条件or满足一边的

order by 字段排序:默认升序asc,desc降序排列

-- 从users表中把username和password对应的数据查询出来
-- SELECT username, `password` from users-- 向users表中 插入新数据,username的值和password的值
-- INSERT into users (username, password) VALUES ('up','7968')-- 更新某一行中的一个列
-- UPDATE users SET `password`='888888' WHERE id=1;-- 更新id为2的用户,把用户密码更新为123 状态更新为1
-- UPDATE users set `password`='123', `status`=1 WHERE id=2;-- 删除id为5的用户
-- DELETE FROM users WHERE id = 5;-- 使用or来显示所有状态为1或username为zs的用户
-- SELECT*FROM users where `status`=0 or username='wbg';-- 对users表中的数据,按照status字段进行升序排序 默认
-- SELECT * FROM users ORDER BY status ASC;-- 对users表中的数据,按照id字段进行降序排序
-- SELECT * FROM users ORDER BY id	DESC;-- 对user表中的数据先按照status进行降序排序,再按照username字母的顺序升序排序
-- SELECT * FROM users ORDER BY status DESC, username ASC;-- 查询 status状态为0一共有几条
-- SELECT COUNT(*) FROM users where `status`=0;-- 将count(*)的列名改为total as可以给任何东西取名
-- SELECT COUNT(*) as total from users where status=0;-- 查询users全部的数据
SELECT * FROM users;

三.在Express中操作MySQL

步骤

1.安装MySQL数据库的第三方模块

(mysql)npm install mysql:在指定的包下运行

2.通过mysql模块连接到MySQL数据库

操作数据库.js:配置mysql模块

//1.导入mysql模块
const mysql = require('mysql')//2.建立连接
const db = mysql.createConnection({host: '127.0.0.1', //url地址user: 'root', //账号password: 'root', //密码database: 'my_db01' //数据库名字
})//测试mysql模块能否正常工作
db.query('SELECT 1', (err, results) => {//mysql工作期间报错了if (err) return console.log(err.message);//成功执行sql语句console.log(results); //[ RowDataPacket { '1': 1 } ] 成功!
})
3.通过mysql模块执行sql语句

操作数据库.js

//1.导入mysql模块
const mysql = require('mysql')//2.建立连接
const db = mysql.createConnection({host: '127.0.0.1', //url地址user: 'root', //账号password: 'root', //密码database: 'my_db01' //数据库名字
})//测试mysql模块能否正常工作
/* db.query('SELECT 1', (err, results) => {//mysql工作期间报错了if (err) return console.log(err.message);//成功执行sql语句console.log(results); //[ RowDataPacket { '1': 1 } ] 成功!
}) *///查询user表中所有的数据
/* const userStr = 'select * from users';
db.query(userStr, (err, results) => {//查询数据失败if (err) return console.log(err.message);//查询数据成功//注意:如果执行的是select查询语句,执行的results结果是数组!console.log(results);
}) *///添加数据 users 表中的数据对象
/* const user = { username: 'edg', password: 'qishi' };
//?表示占位符 待执行的sql语句
const sqlStr = 'insert into users (id,username, password) value (?,?)'//使用数组的形式,依次为?占位符指定具体的值
db.query(sqlStr, [user.username, user.password], (err, results) => {if (err) return console.log(err.message); //失败//results是一个对象 有affectedRows判断是否插入数据成功if (results.affectedRows === 1) { console.log('插入数据成功!'); } //成功
}) *///样式插入数据的便捷方式
/* const user = { username: 'rng', password: 'huangzhu' };
//定义待执行的SQL语句,数据对象的每一个属性和数据表的字段一一对应可以使用这种sql语句注入
const sqlStr = 'insert into users set ?';
//执行sql语句
db.query(sqlStr, user, (err, results) => {if (err) return console.log(err.message);if (results.affectedRows === 1) {console.log('插入数据成功!');}
}) *///演示如何更新用户信息
/* const user = { id: 1, username: 'al', password: 'siongmao' };
//定义sql语句
const sqlStr = 'update users set username=?, password=? where id= ?';
//执行sql语句
db.query(sqlStr, [user.username, user.password, user.id], (err, results) => {if (err) return console.log(err.message);//执行了update语句后,执行结果也是一个对象 可以通过results.affectedRows判断是否更新成功if (results.affectedRows === 1) {console.log('更新成功!');}
}) *///更新数据的便携方式
/* const user = { id: 1, username: 'al team', password: 'xiongmao' };
//要执行的sql语句
const str = 'update users set ? where id = ?';
//调用jquery方法执行
db.query(str, [user, user.id], (err, results) => {if (err) return console.log(err.message);if (results.affectedRows === 1) {console.log('更新成功!');}
}) *///删除表中数据 要根据id唯一标识进行删除
/* const str = 'delete from users where id=?';
//调用query方法执行
//问号替换的时候只有一个问号,可以不用加[]数组
db.query(str, 1, (err, results) => {if (err) return console.log(err.message);//指定delete语句之后,结果也是一个对象,也会包含affectedRows属性判断是否执行成功if (results.affectedRows === 1) {console.log('删除成功!');}
}) *///建议使用标记删除!!!!
//标记删除-设置类似status这样的状态字段,来标记当前这条数据是否被删除
const str = 'update users set status=1 where id=?';
db.query(str, 6, (err, results) => {if (err) return console.log(err.message);//指定delete语句之后,结果也是一个对象,也会包含affectedRows属性判断是否执行成功if (results.affectedRows === 1) {console.log('删除成功!');}
})

四.前后端身份认证

开发模式分为两种

1.基于服务器渲染的传统Web开发模式

服务器发送给客户端的HTML页面,是在服务器通过字符串的拼接,动态生成的。因此,客户端不需要使用Ajax这样的技术额外请求页面的数据。

优点:前端耗时少,因为服务器负责动态生成界面,前端浏览器只需要渲染页面即可

有利于SEO,因为服务器响应的是完整的HTML页面内容,所以爬虫更容易获得信息,更有利于SEO

缺点:占用服务器资源。即使服务器完成了HTML内容的拼接,如果请求较多,会对服务器造成访问压力

不利于前后端分离,开发效率低,使服务器端渲染,则无法进行分工合作,尤其对于前端复杂度高的项目,不利于项目高效开发

2.基于前后端分离的新型Web开发模式

依赖于Ajax技术的广泛使用。简言之,前后端分离的Web开发模式,就是后端只负责提供API接口,前端使用Ajax调用接口的开发模式

优点:开发体验好,前端专注于UI界面的开发,后端专注于api开发,且后端有更多选择性

用户体验好。Ajax技术的广泛应用,极大提高了用户的体验,可以轻松实现页面的局部刷新

减轻了服务器端的渲染压力。因为页面最终是在每个用户的浏览器生成的。

缺点:不利于SEO,完整的HTML页面需要在客户端动态拼接完成,所以爬虫无法爬取页面的有效信息。

如何选择开发模式

企业网站,主要功能是展示而没有复杂的交互,并且需要良好的SEO,这是要用基于服务器渲染的传统Web开发模式

而类似后台管理项目,交互性比较强,不需要考虑SEO,那么使用基于前后端分离的新型Web开发模式。

为了同时兼顾首页的渲染速度和前后端分离的开发效率,一些网站采用了首屏服务器渲染 + 其他页面前后端分离的开发模式。

2.身份认证

身份认证是指通过一定的手段,完成对用户身份的确认。在web开发中,也涉及到用户的身份认证,例如各大网站的手机验证码登录、邮箱密码、二维码登录等。

目的:确认当前所声称的用户为某种身份的用户,确实是所生成的用户。

不同开发模式下的身份认证:

服务器端渲染推荐使用Session认证机制

前后端分离推荐使用JWT认证机制

3.Session认证机制

HTTP协议的无状态性

客户端每次HTTP请求都是独立的,连续多个请求之间没有直接关系,服务器不会主动保留每次HTTP请求的状态

突破HTTP无状态性的限制:通过Cookie

Cookie

存储在用户浏览器中一段不超过4KB的字符串。由一个名称、一个值和其他几个用于控制Cookie有效期、安全性、适用范围的可选属性组成的。

不同域名下的Cookie各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的Cookie一同发送到服务器。

特性:自动发送,域名独立,过期时限,4KB限制

Cookie在身份认证中的作用

客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证Cookie,客户端会自动将Cookie保存在浏览器中。随后当客户端每次请求服务器的时候,浏览器会自动将身份认证相关的Cookie,通过请求头的形式发送给服务器,服务器既可以验明客户端的身份

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RwOwTguY-1676954686011)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230216102755299.png)]

Cookie不具有安全性

由于Cookie是储存在浏览器中,而且浏览器也提供了读写Cookie的API,因此Cookie很容易被伪造,不具有安全性。因此不建议服务器将重要的隐私数据,通过Cookie的形式发送给浏览器

提高身份认证的安全性-Session

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JviPP9Us-1676954686011)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230216102805071.png)]

在Express中使用Session认证
安装express-session中间件

npm install express-session

配置express-session中间件

express-session中间件安装成功后,需要通过app.use()来注册session中间件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q1O1eIBQ-1676954686012)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230216123102531.png)]

app.js

// 导入express模块;
const express = require('express');
// 创建express的服务器实例;
const app = express();//配置Session中间件
const session = require('express-session');app.use(session({secret: 'tyx',resave: false,saveUninitialized: true,
}))//托管静态资源
app.use(express.static('./pages'));
// 解析 POST 提交过来的表单数据
app.use(express.urlencoded({ extended: false }))// 登录的 API 接口
app.post('/api/login', (req, res) => {// 判断用户提交的登录信息是否正确if (req.body.username !== 'admin' || req.body.password !== '11') {return res.send({ status: 1, msg: '登录失败' })}// TODO_02:请将登录成功后的用户信息,保存到 Session 中//注意只有成功配置了express-session这个中间件之后,才能通过req.session这个属性req.session.user = req.body; //用户的信息req.session.islogin = true; //用户的登陆状态res.send({ status: 0, msg: '登录成功' });
})// 获取用户姓名的接口
app.get('/api/username', (req, res) => {if (!req.session.islogin) {return res.send({status: 1,msg: 'faile'})};// TODO_03:请从 Session 中获取用户的名称,响应给客户端res.send({status: 0,msg: 'success',username: req.session.user.username,})
})// 退出登录的接口
app.post('/api/logout', (req, res) => {// TODO_04:清空 Session 信息req.session.destroy()res.send({status: 0,msg: '退出登陆成功!',})
})// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function() {console.log('Express server running at http://127.0.0.1:80')
})
Session认证机制的局限性

Session认证机制需要配合Cookie才能实现。但是Cookie不支持跨域访问,所以当涉及前端跨域请求后端接口的时候,需要做很多配置才能实现跨域Session认证。

注意:当前端请求后端接口不存在跨域问题的时候,推荐使用Session身份认证机制。当前端需要跨域请求后端接口的时候,不推荐使用Session身份认证机制,推荐使用JWT认证机制。

4.JWT认证机制

JWT是目前最流行的跨域认证解决方案。

工作原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lnlr9mdm-1676954686012)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230216124111837.png)]

总结:用户通过Token字符串的形式,保存在客户端浏览器中。服务器通过还原Token字符串的形式来认证用户的身份

JWT组成部分

由三部分组成:Header(头部)、Payload(有效荷载)、Signature(签名)

格式:用.隔开他们三个

含义:

Payload:真正的用户信息,经过加密之后产生的字符串

Header和Signature是安全相关的部分,只是保证Token的安全性

JWT使用方式

客户端收到服务器返回的JWT之后,通常会将它存储在localStorage或sessionStorage中。此后,客户端每次与服务器通信,都要带上这个JWT的字符串,从而能进行验证,推荐的做法就是把JWT放在HTTP请求头的Authorization字段中。

在Express中使用JWT
安装JWT相关的包

npm install jsonwebtoken express-jwt

jsonwebtoken:用于生成JWT的字符串

express-jwt:将JWT字符串解析还原成JSON对象

导入JWT相关的包
// TODO_01:安装并导入 JWT 相关的两个包,分别是 jsonwebtoken 和 express-jwt
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
定义secret密钥

为了保证JWT字符串的安全性,防止JWT字符串在网络传输过程中被破解,要定义一个加密和解密的secret密钥:

1.当生成JWT字符串的时候,需要使用secret密钥对用户信息进行加密,最终得到加密好的JWT密钥

2.当把JWT字符串解析还原成JSON对象的时候,需要使用secret密钥进行解密

登录成功后生成JWT字符串

叫用jsonwebtoken包提供的sign()方法,将用户的信息加密称JWT字符串

// 登录接口
app.post('/api/login', function(req, res) {// 将 req.body 请求体中的数据,转存为 userinfo 常量const userinfo = req.body// 登录失败if (userinfo.username !== 'admin' || userinfo.password !== '000000') {return res.send({status: 400,message: '登录失败!',})}// 登录成功// TODO_03:在登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端// 参数1:用户的信息对象// 参数2:加密的秘钥// 参数3:配置对象,可以配置当前 token 的有效期// 记住:千万不要把密码加密到 token 字符中const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' })res.send({status: 200,message: '登录成功!',token: tokenStr, // 要发送给客户端的 token 字符串})
})
将JWT字符串还原为JSON对象

客户端每次在访问那些有权限的接口时,都要主动通过请求头中的Authorization字段,将Token字符串发送到服务器进行身份验证。

此时,服务器可以通过express-jwt这个中间件,自动将客户端发送过来的Token解析还原成JSON对象:

// TODO_04:注册将 JWT 字符串解析还原成 JSON 对象的中间件
// 注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到 req.user 属性上
//path: [/^\/api\//] :以/api开头的都不需要权限
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))
使用req.user获取用户信息
// 这是一个有权限的 API 接口
app.get('/admin/getinfo', function(req, res) {// TODO_05:使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端console.log(req.user)res.send({status: 200,message: '获取用户信息成功!',data: req.user, // 要发送给客户端的用户信息})
})

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6suoF9bW-1676954686012)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230216134846111.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m5JeH3R3-1676954686012)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230216134812040.png)]

捕获解析JWT失败后产生的错误

但是用express-jwt解析Token字符串时,如果客户端发送过来的Token字符串过期或者不合法,会产生一个解析失败的错误,影响项目正常运行。可以通过Express的错误中间件,捕获这个错误并进行相关处理。

// TODO_06:使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err, req, res, next) => {// 这次错误是由 token 解析失败导致的 token过期了!if (err.name === 'UnauthorizedError') {return res.send({status: 401,message: '无效的token',})}res.send({status: 500,message: '未知的错误',})
})

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RwjFB6Er-1676954686013)(C:\Users\t-y-x\AppData\Roaming\Typora\typora-user-images\image-20230216135834193.png)]

案例

bcryptjs:加密密码

永远不要相信前端提交过来的数据!!要用后端进行最后的验证

@hapi/joi包:为表单中携带的每个数据项,定义验证规则

// 要发送给客户端的 token 字符串
})
})


##### 将JWT字符串还原为JSON对象客户端每次在访问那些有权限的接口时,都要主动通过请求头中的Authorization字段,将Token字符串发送到服务器进行身份验证。此时,服务器可以通过express-jwt这个中间件,自动将客户端发送过来的Token解析还原成JSON对象:```js
// TODO_04:注册将 JWT 字符串解析还原成 JSON 对象的中间件
// 注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到 req.user 属性上
//path: [/^\/api\//] :以/api开头的都不需要权限
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))
使用req.user获取用户信息
// 这是一个有权限的 API 接口
app.get('/admin/getinfo', function(req, res) {// TODO_05:使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端console.log(req.user)res.send({status: 200,message: '获取用户信息成功!',data: req.user, // 要发送给客户端的用户信息})
})

[外链图片转存中…(img-6suoF9bW-1676954686012)]

[外链图片转存中…(img-m5JeH3R3-1676954686012)]

捕获解析JWT失败后产生的错误

但是用express-jwt解析Token字符串时,如果客户端发送过来的Token字符串过期或者不合法,会产生一个解析失败的错误,影响项目正常运行。可以通过Express的错误中间件,捕获这个错误并进行相关处理。

// TODO_06:使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err, req, res, next) => {// 这次错误是由 token 解析失败导致的 token过期了!if (err.name === 'UnauthorizedError') {return res.send({status: 401,message: '无效的token',})}res.send({status: 500,message: '未知的错误',})
})

[外链图片转存中…(img-RwjFB6Er-1676954686013)]

案例

bcryptjs:加密密码

永远不要相信前端提交过来的数据!!要用后端进行最后的验证

@hapi/joi包:为表单中携带的每个数据项,定义验证规则

@escook/express-joi中间件:来实现自动对表单数据进行验证功能


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部