前端第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中间件:来实现自动对表单数据进行验证功能
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
