1 作用

Express,快速的创建 Web 网站的服务器或 API 接口的服务器。
Express 是基于内置的 http 模块进一步封装出来的
等同于Web API 和 jQuery 的关系。后者是基于前者进一步封装出来的
Express 的本质:就是一个 npm 上的第三方包,提供了快速创建 Web 服务器的便捷方法。
Express 的本质:就是一个 npm 上的第三方包,提供了快速创建 Web 服务器的便捷方法。

2 创建基本的web服务器

  1. const express = require('express')
  2. // 创建web服务器
  3. const app = express();
  4. app.listen(80, () => {
  5. console.log('server is running');
  6. })

3 接收get和post请求

  1. const express = require('express')
  2. const app = express();
  3. app.get('/user', (req, res) => {
  4. res.send({name: 'zs', age: 18})
  5. })
  6. app.post('/user', (req, res) => {
  7. res.send('post请求成功')
  8. })
  9. app.listen(80, () => {
  10. console.log('server is running');
  11. })

4 获取url的参数req.query

  1. const express = require('express')
  2. const app = express();
  3. app.get('/user', (req, res) => {
  4. res.send(req.query)
  5. })
  6. app.listen(80, () => {
  7. console.log('server is running');
  8. })

image.png

5 获取动态匹配参数req.params

  1. const express = require('express')
  2. const app = express();
  3. app.get('/user/:id', (req, res) => {
  4. res.send(req.params)
  5. })
  6. app.listen(80, () => {
  7. console.log('server is running');
  8. })

image.png
注意: 前端只传入18, id是后端路径上的动态参数

  1. app.get('/user/:id/:name', (req, res) => {
  2. res.send(req.params)
  3. })

image.png

post 接收方式用req.body

6 托管静态资源

  1. const express = require('express');
  2. const app = express();
  3. app.use(express.static('clock'));
  4. app.listen(80, () => {
  5. console.log('start');
  6. })

Express 在指定的静态目录中查找文件,并对外提供资源的访问路径。
因此,存放静态文件的目录名不会出现在 URL 中。
clock是要托管静态目录
image.png
image.png

托管多个

  1. app.use(express.static('clock'));
  2. app.use(express.static('files'));

访问静态资源文件时,express.static() 函数会根据目录的添加顺序查找所需的文件

7 挂载路径前缀

  1. const express = require('express');
  2. const app = express();
  3. app.use('/prefix', express.static('clock'));
  4. app.listen(80, () => {
  5. console.log('start');
  6. })

image.png
也就是需要通过带有 /prefix 前缀地址来访问
若是不挂载前缀,即app.use(express.static(‘clock’));
则直接http://localhost/index.html 便可访问

8 Express路由

客户端的请求与服务器处理函数之间的映射关系
Express 中的路由分 3 部分组成,分别是请求的类型、请求的 URL 地址、处理函数。
每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数。
image.png

8.1 创建路由

① 创建路由模块对应的 .js 文件
② 调用 express.Router() 函数创建路由对象
③ 向路由对象上挂载具体的路由
④ 使用 module.exports 向外共享路由对象
⑤ 使用 app.use() 函数注册路由模块

路由模块文件:

  1. const express = require('express')
  2. const router = express.Router() // 创建路由对象
  3. router.get('/list', (req, res) => {
  4. res.send('in list');
  5. });
  6. module.exports = router;

关键点在于 express.Router() // 创建路由对象
以及共享出去

使用路由:

  1. const express = require('express')
  2. const app = express()
  3. const user_list = require('./3.路由')
  4. // 注册路由模块,并添加前缀
  5. app.use('/api', user_list);
  6. app.listen(80, () => {
  7. console.log('start');
  8. })

image.png

9 中间件

image.png
依然是水

对这次请求进行预处理。
image.png
Express 的中间件,本质上就是一个 function 处理函数
中间件函数的形参列表中,必须包含 next 参数。而路由处理函数中只包含 req 和 res。
image.png
image.png

9.1 全局中间件

  1. const express = require('express')
  2. const app = express()
  3. const x = (req, res, next) => {
  4. console.log('全局中间件');
  5. next();
  6. }
  7. app.use(x);
  8. app.get('/', (req, res)=> {
  9. console.log('get 请求');
  10. })
  11. app.listen(80, () => {
  12. console.log('start');
  13. })

重点在于next(),
以及app.use()调用

9.2 中间件作用—统一添加自定义属性

image.png
多个中间件之间,共享同一份 req 和 res

  1. const express = require('express')
  2. const app = express()
  3. const x = (req, res, next) => {
  4. req.a = 10
  5. next();
  6. }
  7. app.use(x);
  8. app.get('/', (req, res)=> {
  9. console.log('/ 下的a:' + req.a);
  10. })
  11. app.get('/list', req => {
  12. console.log('/list 下的a:' + req.a);
  13. })
  14. app.listen(80, () => {
  15. console.log('start');
  16. })

image.png

9.3 局部生效中间件

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

  1. const express = require('express')
  2. const app = express()
  3. const x = (req, res, next) => {
  4. req.a = 10
  5. next();
  6. }
  7. app.get('/', x, (req, res)=> {
  8. console.log('/ 下的a:' + req.a);
  9. })
  10. app.get('/list', req => {
  11. console.log('/list 下的a:' + req.a);
  12. })
  13. app.listen(80, () => {
  14. console.log('start');
  15. })

关键在于app.get(‘/‘, x, (req, res) => { … }

image.png

9.4 定义多个局部中间件

  1. const express = require('express')
  2. const app = express()
  3. const x = (req, res, next) => {
  4. console.log('第一个');
  5. next();
  6. }
  7. const y = (req, res, next) => {
  8. console.log('第二个');
  9. next();
  10. }
  11. app.get('/', x, y, (req, res)=> {
  12. console.log('/目录');
  13. })
  14. app.get('/list', req => {
  15. console.log('/list');
  16. })
  17. app.listen(80, () => {
  18. console.log('start');
  19. })

image.png
访问/list 不会有任何执行。

还有数组写法:

  1. app.get('/', [x, y], (req, res)=> {
  2. console.log('/目录');
  3. })

9.5 中间件注意事项

① 一定要在路由之前注册中间件
② 客户端发送过来的请求,可以连续调用多个中间件进行处理
③ 执行完中间件的业务代码之后,不要忘记调用 next() 函数
④ 为了防止代码逻辑混乱,调用 next() 函数后不要再写额外的代码
⑤ 连续调用多个中间件时,多个中间件之间,共享 req 和 res 对象

9.6 中间件分类

Express 模块导入 - 图17

  • 应用级别 :通过 app.use() 或 app.get() 或 app.post() ,绑定到 app 实例上的中间件
  • 路由级别:绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不过,应用级别中间件是绑定到 app 实例上,路由级别中间件绑定到 router 实例上


9.7 错误级别中间件

专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
注意:错误级别的中间件,必须注册在所有路由之后

  1. const express = require('express')
  2. const app = express()
  3. app.get('/', (req, res)=> {
  4. throw new Error('服务器内部错误');
  5. res.send('home');
  6. })
  7. app.use((err, req, res, next) => {
  8. res.send('Error ' + err.message)
  9. })
  10. app.listen(80, () => {
  11. console.log('start');
  12. })

app.use((err, req, res, next) => { … }
image.png

9.8 内置中间件

① express.static 快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性)
② express.json 解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
③ express.urlencoded 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)

9.9 内置中间件—解析json

若是不配置解析表单中间件app.use(express.json()),
则req.body默认是underfined

  1. const express = require('express')
  2. const app = express()
  3. app.use(express.json())
  4. app.post('/', (req, res)=> {
  5. res.send(req.body);
  6. })
  7. app.listen(80, () => {
  8. console.log('start');
  9. })

image.png

9.10 内置中间件— 解析 URL-encoded 格式

一般是表单默认提交格式
若是不配置app.use(express.urlencoded({extended: false}))
则req.body默认是{}

app.use(express.urlencoded({extended: false})) 是固定写法

  1. const express = require('express')
  2. const app = express()
  3. app.use(express.json());
  4. app.use(express.urlencoded({extended: false}))
  5. app.post('/', (req, res)=> {
  6. res.send(req.body);
  7. })
  8. app.post('/add',(req, res)=>{
  9. res.send(req.body);
  10. })
  11. app.listen(80, () => {
  12. console.log('start');
  13. })

image.png

9.11 第三方中间

使用样例:
① 运行 npm install body-parser 安装中间件
② 使用 require 导入中间件
③ 调用 app.use() 注册并使用中间件

在 express@4.16.0 之前的版本中,经常使用 body-parser 这个第三方中间件来解析请求体数据。

Express 内置的 express.urlencoded 中间件,就是基于 body-parser 这个第三方中间件进一步封装出来的。

9.12 自定义中间件

custom-body-parser 文件:

  1. const qs = require('querystring')
  2. const parser = (req, res, next) => {
  3. // 存储客户端发来请求体数据
  4. let str = '';
  5. // 监听获取客户端发送到服务器的数据。
  6. req.on('data', (chunck) => {
  7. str += chunck
  8. })
  9. // 监听接收完毕事件
  10. req.on('end', ()=> {
  11. console.log('接收完成:' + str);
  12. req.body = qs.parse(str);
  13. next();
  14. })
  15. }
  16. module.exports = parser

数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器

主要是侦听 接收数据 事件 — data
以及接收完毕事件 — end
然后利用’querystring’模块的parse将字符串转换
image.png
引入:

  1. const express = require('express');
  2. const parser = require('./11.custom-body-parser')
  3. const app = express()
  4. app.use(parser);
  5. app.post('/', (req, res) => {
  6. res.send(req.body);
  7. })
  8. app.listen(80, () => {
  9. console.log('start');
  10. })

image.png

9.13 应用级别中间件

通过 app.use() 或 app.get() 或 app.post() ,绑定到 app 实例上的中间件,叫做应用级别的中间件。

9.14 路由级别中间件

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

  1. const express = require('express')
  2. const app = express()
  3. const router = express.Router() // 创建路由对象
  4. router.use((req, res, next) => {
  5. req.query.age = 18
  6. next()
  7. })
  8. router.get('/list', (req, res) => {
  9. console.log(req.query.age);
  10. res.send('in list');
  11. });
  12. app.use('/', router)
  13. app.listen(80)

10 使用Express写接口

定义服务器

  1. const express = require('express')
  2. const app = express()
  3. const router = require('./13 apiRouter')
  4. // 表单默认格式提交 post 专用
  5. app.use(express.urlencoded({extended:false}))
  6. app.use('/api', router)
  7. app.listen(80)

写接口

  1. const express = require('express')
  2. // 创建路由
  3. const router = express.Router()
  4. // 挂载路由
  5. router.get('/list', (req, res) => {
  6. const query = req.query
  7. res.send({
  8. status: 0,
  9. msg: 'get 请求成功',
  10. data: query
  11. })
  12. })
  13. router.post('/list', (req, res) => {
  14. const query = req.body
  15. res.send({
  16. status: 0,
  17. msg: 'post 请求成功',
  18. data: query
  19. })
  20. });
  21. module.exports = router

get:
image.png
post:
image.png

11 跨域

解决接口跨域问题的方案主要有两种:
① CORS(主流的解决方案,推荐使用)
② JSONP(有缺陷的解决方案:只支持 GET 请求)

同源指的是两个 URL 的协议、域名、端口一致,反之,则是跨域
image.png
image.png
image.png
一个是file协议,一个是http协议所以跨域了

11.1 cors中间件解决

安装

  1. npm i cors

使用

  1. const express = require('express');
  2. // 解决跨域中间件
  3. const cors = require('cors');
  4. const app = express();
  5. const router = require('./13 apiRouter');
  6. app.use(express.urlencoded({extended:false}));
  7. // 解决跨域
  8. app.use(cors())
  9. app.use('/api', router);
  10. app.listen(80)

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

image.png

11.2 CORS

CORS (Cross-Origin Resource Sharing,跨域资源共享)由一系列 HTTP 响应头组成,这些 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源。
image.png
CORS 主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了 CORS 的接口。

11.3 Access-Control-Allow-Origin

响应头部中可以携带一个 Access-Control-Allow-Origin 字段
其中,origin 参数的值指定了允许访问该资源的外域 URL。

  1. Access-Control-Allow-Origin: <origin> | *
  1. res.setHeader('Access-Control-Allow-Origin', 'http://baidu.com')
  2. res.setHeader('Access-Control-Allow-Origin', '*')
  • 有特定域名则表示只允许指定域名请求
  • 通配符* 表示允许来自任何域的请求

11.4 Access-Control-Allow-Headers

默认情况下,CORS 仅支持客户端向服务器发送如下的 9 个请求头:

  1. Accept
  2. Accept-Language、
  3. Content-Language、
  4. DPR、
  5. Downlink、
  6. Save-Data、
  7. Viewport-Width、
  8. Width
  9. Content-Type (值仅限于 text/plain、multipart/form-data、application/x-www-form-urlencoded 三者之一)

如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过 Access-Control-Allow-Headers 对额外的请求头进行声明,否则这次请求会失败!

  1. res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header')

X-Custom-Header是自定义头信息。
多个请求头可以用英文逗号分割。
image.png

11.5 Access-Control-Allow-Methods

默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求。

希望通过 PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,
通过 Access-Control-Alow-Methods 来指明实际请求所允许使用的 HTTP 方法

  1. res.setHeader('Access-Control-Allow-Methods', '*')
  2. res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, HEAD')

11.6 CORS请求分类

Express 模块导入 - 图31简单请求:同时满足以下两大条件的请求。
① 请求方式:GET、POST、HEAD 三者之一
② HTTP 头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、 Downlink、Save-Data、Viewport-Width、Width 、Content-Type(只有三个值application/x-www-formurlencoded、multipart/form-data、text/plain)

预检请求:只要符合以下任何一个条件的请求
① 请求方式为 GET、POST、HEAD 之外的请求 Method 类型
② 请求头中包含自定义头部字段
③ 向服务器发送了 application/json 格式的数据

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

两者区别:
简单请求的特点:客户端与服务器之间只会发生一次请求。
预检请求的特点:客户端与服务器之间会发生两次请求,OPTION 预检请求成功之后,才会发起真正的请求。

11.7 JSONP接口

  1. // 必须在配置 cors 中间件之前,配置 JSONP 的接口
  2. app.get('/api/jsonp', (req, res) => {
  3. // 1. 得到函数的名称
  4. const funcName = req.query.callback
  5. // 2. 定义要发送到客户端的数据对象
  6. const data = { name: 'zs', age: 22 }
  7. // 3. 拼接出一个函数的调用
  8. const scriptStr = `${funcName}(${JSON.stringify(data)})`
  9. // 4. 把拼接的字符串,响应给客户端
  10. res.send(scriptStr)
  11. })
  12. // 一定要在路由之前,配置 cors 这个中间件,从而解决接口跨域的问题
  13. const cors = require('cors')
  14. app.use(cors())
  15. // 导入路由模块
  16. const router = require('./16.apiRouter')
  17. // 把路由模块,注册到 app 上
  18. app.use('/api', router)

使用jQuery发起

  1. // 4. 为 JSONP 按钮绑定点击事件处理函数
  2. $('#btnJSONP').on('click', function () {
  3. $.ajax({
  4. type: 'GET',
  5. url: 'http://127.0.0.1/api/jsonp',
  6. dataType: 'jsonp',
  7. success: function (res) {
  8. console.log(res)
  9. },
  10. })
  11. })

其中req.query.callback 是一个jQuery前缀+随机数: jQuery34105356366114972404_1624436483824

其中模版字符串得出
jQuery34105356366114972404_1624436483824({“name”:”zs”,”age”:22})
响应给客户端

12 链接数据库

安装

  1. npm i mysql

配置

  1. const mysql = require('mysql');
  2. const db = mysql.createPool({
  3. host: 'localhost',
  4. user: 'root',
  5. password: 'Aa111111',
  6. database: 'my_db_01'
  7. })
  8. db.query('select * from users', (err, results) => {
  9. if (err) return console.log(err.message);
  10. console.log(results);
  11. })

image.png

12.1 插入数据

  1. const user = {
  2. username: 'nimaleb',
  3. password: '123456'
  4. }
  5. const sql = 'insert into users (username, password) values (?, ?)';
  6. db.query(sql, [user.username, user.password], (err, results) => {
  7. if(err) return console.log(err.message);
  8. console.log(results);
  9. if (results.affectedRows === 1) {
  10. console.log('success');
  11. }
  12. })

image.png
如果数据对象的每个属性和数据表的字段一一对应,则可以通过如下方式快速插入数据:

  1. const user = {
  2. username: 'nima',
  3. password: '123456'
  4. }
  5. const sql = 'insert into users set ?';
  6. db.query(sql, user, (err, results) => {
  7. if(err) return console.log(err.message);
  8. console.log(results);
  9. if (results.affectedRows === 1) {
  10. console.log('success');
  11. }
  12. })

image.png

12.2 更新数据

  1. const user = {
  2. id: 1,
  3. username: 'nim',
  4. password: '123456'
  5. }
  6. const sql = 'update users set username=?, password=? where id=?';
  7. db.query(sql, [user.username, user.password, user.id], (err, results) => {
  8. if(err) return console.log(err.message);
  9. console.log(results);
  10. if (results.affectedRows === 1) {
  11. console.log('success');
  12. }
  13. })

image.png
数据对象的每个属性和数据表的字段一一对应,则可以通过如下方式快速更新表数据:

  1. const user = {
  2. id: 1,
  3. username: 'nim',
  4. password: '123456'
  5. }
  6. const sql = 'update users set ? where id=?';
  7. db.query(sql, [user, user.id], (err, results) => {
  8. if(err) return console.log(err.message);
  9. console.log(results);
  10. if (results.affectedRows === 1) {
  11. console.log('success');
  12. }
  13. })

image.png

12.3 删除数据

  1. const sql = 'delete from users where id=?';
  2. db.query(sql, 10, (err, results) => {
  3. if(err) return console.log(err.message);
  4. console.log(results);
  5. if (results.affectedRows === 1) {
  6. console.log('success');
  7. }
  8. })

image.png

12.4 标记删除

  1. const sql = 'update users set status=1 where id=?';
  2. db.query(sql, 1, (err, results) => {
  3. if(err) return console.log(err.message);
  4. console.log(results);
  5. if (results.affectedRows === 1) {
  6. console.log('success');
  7. }
  8. })

所谓的标记删除,就是在表中设置类似于 status 这样的状态字段,来标记当前这条数据是否被删除
image.png

13 前后端身份认证

13.1 web开发模式

目前主流的 Web 开发模式有两种,分别是:
① 基于服务端渲染的传统 Web 开发模式
② 基于前后端分离的新型 Web 开发模式

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

13.2 身份认证方式

① 服务端渲染推荐使用 Session 认证机制
② 前后端分离推荐使用 JWT 认证机制

13.3 Session认证机制

重点词:HTTP协议无状态性、 Cookie
HTTP 协议的无状态性,指的是客户端的每次 HTTP 请求都是独立的,连续多个请求之间没有直接的关系,服务器不会主动保留每次 HTTP 请求的状态。

http无状态,所以用到cookie进行身份区别

Cookie 是存储在用户浏览器中的一段不超过 4 KB 的字符串。
image.png
它由一个名称(Name)、一个值(Value)和其它几个用 于控制 Cookie 有效期、安全性、使用范围的可选属性组成。

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

Cookie的几大特性: ① 自动发送 ② 域名独立 ③ 过期时限 ④ 4KB 限制

Cookie来源:
客户端第一次请求服务器的时候,服务器通过响应头的形式向客户端发送一个身份认证的 Cookie,客户端会自动将 Cookie 保存在浏览器中。

随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie,通过请求头的形式发送给 服务器,服务器即可验明客户端的身份。
image.png
由于 Cookie 是存储在浏览器中的,而且浏览器也提供了读写 Cookie 的 API,因此 Cookie 很容易被伪造,不具有安全性。因此不建议服务器将重要的隐私数据,通过 Cookie 的形式发送给浏览器。

image.png

Session认证机制
image.png
关键在于用户信息存储在了服务器内存中,通过原先发送给客户端,现在接收到返回的cookie的来查找对应信息是否存在。而不像以前直接对用户信息进行交互。

13.4 安装配置express-session

  1. npm i express-session

配置session中间件

  1. const express = require('express')
  2. const session = require('express-session')
  3. const app = express()
  4. app.use(session({
  5. secret:'keyboard', // 可以为任意字符串
  6. resave: false, // 固定写法
  7. saveUninitialized: true // 固定写法
  8. }))

向session中存数据

  1. // 登录
  2. app.post('/api/login', (req, res) => {
  3. if (req.body.username !== 'admin' || req.body.password !== '000000') {
  4. return res.send({ status: 1, msg: '登录失败' })
  5. }
  6. // 配置了express-session中间件才有req.session
  7. req.session.user = req.body // 用户信息
  8. req.session.isLogin = true // 登录状态
  9. res.send({ status: 0, msg: '登录成功' })
  10. })

req.session 的属性随自己设置,如req.session.name;
主要用于存取数据

从 session 中取数据:

  1. // 获取用户姓名的接口
  2. app.get('/api/username', (req, res) => {
  3. if(!req.session.isLogin) {
  4. return res.send({status:1, msg: 'fail'})
  5. }
  6. res.send({
  7. status: 0,
  8. msg: 'success',
  9. username: req.session.user.username
  10. })
  11. })

清空session信息

  1. // 退出登录的接口
  2. app.post('/api/logout', (req, res) => {
  3. req.session.destroy();
  4. req.send({
  5. status: 0,
  6. msg: '退出登录成功'
  7. })
  8. })

req.session.destroy();

13.5 JWT

英文全称:JSON Web Token 是目前最流行的跨域认证解决方案。

由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域 Session 认证。

原理
image.png
用户的信息通过 Token 字符串的形式,保存在客户端浏览器中。服务器通过还原 Token 字符串的形式来认证用户的身份

13.6 JWT组成部分

分别是 Header(头部)、Payload(有效荷载)、Signature(签名)。
三者之间使用英文的“.”分隔,格式如下:
image.png
Payload 部分才是真正的用户信息,它是用户信息经过加密之后生成的字符串。

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

image.png

13.7 JWT使用方式

客户端收到服务器返回的 JWT 之后,通常会将它储存在 localStorage 或 sessionStorage 中。
此后,客户端每次与服务器通信,都要带上这个 JWT 的字符串,从而进行身份认证。

推荐的做法是把 JWT 放在 HTTP 请求头的 Authorization 字段中,格式如下:
image.png

13.8 express安装使用JWT

安装

  1. npm install jsonwebtoken express-jwt

jsonwebtoken 用于生成 JWT 字符串
express-jwt 用于将 JWT 字符串解析还原成 JSON 对象
引入:

  1. // 导入JWT相关两个包
  2. const jwt = require('jsonwebtoken');
  3. const express_jwt = require('express-jwt');

**
① 当生成 JWT 字符串的时候,需要使用 secret 密钥对用户的信息进行加密,最终得到加密好的 JWT 字符串
② 当把 JWT 字符串解析还原成 JSON 对象的时候,需要使用 secret 密钥进行解密

生成 JWT 字符串:

  1. // 定义 secret 密钥:
  2. const secret_key = 'xdeimnslig';
  3. // 登录接口
  4. app.post('/api/login', function (req, res) {
  5. const userinfo = req.body
  6. if (userinfo.username !== 'admin' || userinfo.password !== '000000') {
  7. return res.send({ status: 400, message: '登录失败!' })
  8. }
  9. const token = jwt.sign({ username: userinfo.username }, secret_key, { expiresIn: '30s' })
  10. res.send({
  11. status: 200,
  12. message: '登录成功!',
  13. token: token // 要发送给客户端的 token 字符串
  14. })
  15. })

调用 jsonwebtoken 包提供的 sign() 方法,将用户的信息加密成 JWT 字符串,响应给客户端.
{ expiresIn: ‘30s’ } 设置有效生存时间30s
注意:不要把密码加密到token,否则有泄密风险

13.9 将 JWT 字符串还原为 JSON 对象:

  1. app.use(expressJWT({
  2. secret: secret_key,
  3. algorithms:['HS256']
  4. }).unless({ path: [/^\/api\//] }));

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

  1. app.use(expressJWT({
  2. secret: secret_key,
  3. algorithms:['HS256']
  4. })

其中algorithms 设置加密算法

.unless({path: [/^\/api\//]}) 指定哪些接口不用访问权限

这解析出来的信息会挂载到req.user 属性上。
image.png
获取用户信息:

  1. // 这是一个有权限的 API 接口
  2. app.get('/admin/getinfo', function (req, res) {
  3. res.send({
  4. status: 200,
  5. message: '获取用户信息成功!',
  6. data: req.user
  7. })
  8. })

当 express-jwt 这个中间件配置成功之后,即可在那些有权限的接口中,使用 req.user 对象,来访问从 JWT 字符串 中解析出来的用户信息了,
image.png
image.png
token值需要添加 Bearer ,固定写法

13.10 捕获错误信息:

  1. app.use((err, req, res, next) => {
  2. if (err.name === 'UnauthorizedError') {
  3. return res.send({
  4. status: 401,
  5. message: '无效token'
  6. })
  7. }
  8. res.send({
  9. status: 500,
  10. message: '未知内部错误'
  11. })
  12. })

image.png

14 模块化规范

传统开发模式的主要问题:
① 命名冲突
② 文件依赖

通过模块化解决上述问题。
模块化就是把单独的一个功能封装到一个模块(文件)中,模块之间相互隔离,但是可以通过特定的接口公开内部成 员,也可以依赖别的模块。

ES6之前的浏览器模块化规范:

  1. AMD
  2. CMD

服务端的有:CommonJS

  • 模块分为 单文件模块 与 包
  • 模块成员导出:module.exports 和 exports
  • 模块成员导入:require(‘模块标识符’)

大一统模块化规范:ES6模块化
在 ES6 模块化规范诞生之前,Javascript 社区已经尝试并提出了 AMD、CMD、CommonJS 等模块化规范。

ES6模块化规范中定义: 

  • 每个 js 文件都是一个独立的模块 
  • 导入模块成员使用 import 关键字 
  • 暴露模块成员使用 export 关键字

15 nodejs es6模块化

15.1 安装配置

由于nodejs对es6支持不是很好。所以需要第三方在编译时转换。

  1. npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/node
  2. npm install --save @babel/polyfill

项目跟目录创建文件 babel.config.js

  1. const presets = [
  2. ["@babel/env", {
  3. targets: {
  4. edge: "17",
  5. firefox: "60",
  6. chrome: "67",
  7. safari: "11.1"
  8. }
  9. }]
  10. ];
  11. module.exports = { presets };

执行:

  1. npx babel-node index.js

15.2 导出导入

  • 默认导出语法 export default 默认导出的成员 
  • 默认导入语法 import 接收名称 from ‘模块标识符’

导出:

  1. // 当前文件模块为 m1.js
  2. // 定义私有成员 a 和 c
  3. let a = 10
  4. let c = 20
  5. // 外界访问不到变量 d ,因为它没有被暴露出去
  6. let d = 30
  7. function show() {}
  8. // 将本模块中的私有成员暴露出去,供其它模块使用
  9. export default {
  10. a,
  11. c,
  12. show
  13. }

导入:

  1. // 导入模块成员
  2. import m1 from './m1.js'
  3. console.log(m1) // { a: 10, c: 20, show: [Function: show] }

每个模块中,只允许使用唯一的一次 export default,否则会报错!

15.3 按需导入导出

  • 按需导出语法 export let s1 = 10 
  • 按需导入语法 import { s1 } from ‘模块标识符’
    1. // 当前文件模块为 m1.js
    2. // 向外按需导出变量 s1
    3. export let s1 = 'aaa'
    4. // 向外按需导出变量 s2
    5. export let s2 = 'ccc'
    6. // 向外按需导出方法 say
    7. export function say() {}
    1. // 导入模块成员
    2. import { s1, s2 as ss2, say } from './m1.js'
    3. console.log(s1) // 打印输出 aaa
    4. console.log(ss2) // 打印输出 ccc
    5. console.log(say) // 打印输出 [Function: say]
    注意:每个模块中,可以使用多次按需导出

15.4 按需和默认同时使用

导出

  1. let a = 10
  2. let c = 20
  3. function show() { }
  4. export default {
  5. a,
  6. c,
  7. show
  8. }
  9. export let s1 = 'aaa'
  10. export let s2 = 'ccc'
  11. export function say() { }

导入

  1. import m1, {s1,s2, say} from './m1.js'
  2. console.log(m1);
  3. console.log(s1);
  4. console.log(s2);
  5. console.log(say);

image.png

2.5 直接导入并执行模块代码

m2.js

  1. for(let i = 0; i < 3; i++) {
  2. console.log(i)
  3. }
  1. import './m2.js'