Express

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

可以用于开发Web服务器API接口服务器

是对Nodejs内置API的封装,类似WebAPIsJquery的关系。

使用Express

1
npm i express -S
1
const express = require('express')

创建Web服务器

1
2
3
4
5
6
7
const express = require('express');
//创建web服务器
const app=express();
//启动服务器
app.listen(80,()=>{
console.log("Server is running at http://127.0.0.1:80");
})

监听GET请求

1
2
3
app.get('/访问地址',(req,res)=>{
...
})

监听POST请求

1
2
3
app.post('/访问地址',(req,res)=>{
...
})

相应内容到客户端

end()方法可以返回普通字符串对象也可以返回json对象

1
res.end();

获取url中的查询参数

1
2
3
4
5
app.get('/user/?id=1',(req,res)=>{
const params=req.query; //返回Json对象
const id=req.query.id;
...
})

获取url中的动态参数

使用:匹配动态参数(路径参数),举例如下:

1
2
3
4
app.get('/user/:id',(req,res)=>{
const id=req.params; //返回Json对象
...
})

创建静态资源服务器

使用use()方法挂载路径前缀

1
2
app.use(express.static('文件夹路径'))
app.use('/路径',express.static('文件夹路径'))

Express路由

在Express中,路由指客户端的请求服务器处理函数之间的映射关系。

1
2
3
app.METHOD('/...',function(req,res){
...
})

匹配过程

image-20230716092611171

模块化路由

不建议直接将路由挂载app上,推荐将路由抽离为单个模块。

  1. 创建路由模块对应的js文件。
  2. 调用express.Router()函数创建路由对象。
1
2
const express=require('express');
const router=express.Router();
  1. 向路由对象上挂载具体的路由。
1
2
3
router.METHOD('/...',function(req,res){
...
})
  1. 使用module.exports向外共享路由对象。
1
module.exports=router
  1. 使用app.use()函数注册路由模块。

中间件

在接收到请求后,往往不会只进行依次处理,而要经过多个中间件。

中间件函数与路由函数

中间件函数相对于路由函数多了一个next参数和next()函数的调用。

1
2
3
4
app.get('/...',function(res.req,next){
...
next();
})

next()函数是实现多个中间件连续调用的关键,它将处理结果转交给下一个中间件或路由。

全局生效中间件

1
2
3
app.use(中间件函数1)
app.use(中间件函数1)
...

中间件会根据代码顺序对结果进行传递。

中间件的作用

中间件函数可以对所有的请求进行一些预处理。比如:挂载一些新的参数,req.新参数=参数值;将每个路由中相同的操作抽离作为中间件,这样就不需要每个路由都写一遍了。

局部生效的中间件

这里的局部生效是指在某个特定的路由中生效的中间件。

1
2
app.get('/...',中间件函数1,中间件函数2,...,路由函数) //写多个参数的形式添加中间件
app.get('/...',[中间件函数1,中间件函数2,...],路由函数) //使用数组将中间件函数做为一个参数

中间件分类

  1. 应用级别的中间件

将中间件绑定到app上,如:app.get()、app.use()等。

  1. 路由级别的中间件

将中间件绑定到router上,如:router.get()、router.use()等。

  1. 错误级别的中间件

错误级别的中间件专门用来捕获整个项目中发生的异常错误,从而防止项目崩溃的问题。

错误级别的中间件函数多了一个err参数。注意顺序。

1
2
3
4
5
const mw=function(err,req,res,next){
...
console.log(err.message)
next()
}

Express内置的中间件

  1. express.static快速托管静态资源。
  2. express.json解析JSON格式的请求数据。(express版本要求 4.16+)
  3. express.urlencoded解析URL-encoded格式的请求数据。(express版本要求 4.16+)

Express解析urlencoded格式的数据

默认情况下,如果不配置解析表单的中间件,req.body值为undefined

使用ajax提交的请求一般是urlencoded数据格式。

可以通过contentType设置发送的数据类型。

1
2
3
4
app.use(express.urlencoded({ extended: false })) //用于获取urlencoded格式的数据
app.get('/...',(req,res)=>{
console.log(req.body)
})

Express解析JSON格式的数据

1
2
3
4
5
// app.use(express.urlencoded({ extended: false })) //用于获取urlencoded格式的数据
app.use(express.json()) //用于获取json格式的数据
app.get('/...',(req,res)=>{
console.log(req.body)
})

req的事件

data事件

在中间件中,需要监听 req 对象的 data 事件,来获取客户端发送到服务器的数据。如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器。所以 data 事件可能会触发多次,每一次触发 data 事件时,获取到数据只是完整数据的一部分,需要手动对接收到的数据进行拼接。

1
2
3
4
5
6
app.use((req,res)=>{
let str='';
req.on(data,(chunk)=>{
str+=chunk;
})
})

end事件

当请求体数据接收完毕之后,会自动触发 req 的 end 事件。
因此,我们可以在 req 的 end 事件中,拿到并处理完整的请求体数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 导入处理querystring的Node.js内置模块
const qs = require('querystring')

app.use((req,res,next)=>{
let str = ''
req.on('data',(chunk)=>{
str += chunk
})
req.on('end',()=>{
// 在str中存放的是完整的请求体数据)
console.log(str); // name=Ulrich&age=22&gender=male
const body = qs.parse(str) // { name: 'Ulrich', age: '22', gender: 'male' }
req.body = body; //将解析出的对象挂载到body属性上
next()
})
})

中间件模块化

类似路由模块化。

网络资源请求

由于请求协议端口等因素的影响导致出现跨域错误。一般使用CORS方案进行解决。

cors是Express的一个第三方中间件。

1
npm i cors
1
2
const cors = require('cors');
app.use(cors());

解决跨域资源共享

1. 设置响应头部

Access-Control-Allow-Origin可以设置哪些服务器可以访问资源。可以使用*代表所有域名。

1
res.setHeader('Access-Control-Allow-Origin','https://lptexas.top/')

Access-Control-Allow-Header可以设置需要发送的额外头信息。

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

image-20230718205052585

1
res.setHeader('Access-Control-Allow-Header','可以使用逗号分割多个')

Access-Control-Allow-Methods设置额外允许的请求方式。

默认情况CORS仅支持get、post、head请求。可以使用*代表所有请求。

1
res.setHeader('Access-Control-Allow-Methods','可以使用逗号分隔多个')

2. 使用cors模块

1
2
3
4
cors({
origins:['...','...'], //设置哪些服务器可以访问资源
methods:['...']//设置哪些方法可用访问资源
})

3. JSONP

浏览器端通过<script>标签的src属性,请求服务器上的数据,同时服务器返回一个函数调用。这种请求数据的方式叫做JSONP。

特点

JSONP不属于真正的Ajax请求,因为没有使用XMLHttpRequset对象。

JSONP仅支持GET请求。

简单请求和预检请求

简单请求:客户端和服务器端只会发生一次请求。

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

nodemon工具

实现热部署。

监听代码变化,重新执行代码。

1
npm i nodemon -g

使用方法

使用nodemon命令替代node

1
nodemon app.js

Mysql

安装mysql

1
npm i mysql

配置和连接数据库

1
2
3
4
5
6
7
8
const mysql = require('mysql');
//配置mysql
const db = mysql.createPool({
host: 'host',
user: 'user',
password: 'password',
database: 'mydb'
})

测试数据库连接

1
2
3
db.query('sql语句',function(error,results){
...
})

使用占位符

在sql语句中,可以使用?作为占位符。在使用数组一次指定占位符中的具体值。

1
db.query('sql语句',[占位符值1,占位符值2,...],callback)

快速的插入多个属性

1
2
3
const data={字段1:"值1",字段2:"值2"};
const sql='insert into 表名 set ?'
db.query(sql,data,callback)

更新操作

1
2
3
const sql='update 表名 set 属性=值,属性=值 where 属性=值'
//快速更新
const sql='update 表名 set ? where 属性=?'

删除操作

1
const sql='delete from 表名 where 属性=值'

身份认证

如扫码登录、手机验证码登录等。

Session认证机制

建议服务端渲染方式使用。

HTTP协议的无状态性

服务器不会主动保留每次HTTP请求的状态。每次请求都是独立的。

打破无状态性

服务器为某次请求发放身份标识Cookie

用户第一次请求服务器时,服务器通过响应头,向客户端发送一个身份Cookie,客户端会自动将Cookie保存在浏览器中。

随后,每次客户端访问浏览器,浏览器会自动将身份认证相关的Cookie,通过请求头的形式,发送给服务器,服务器会鉴别客户端身份。

Cookie很容易伪造,不具有安全性。

提高身份认证安全性

客户端Cookie+服务器Cookie认证Session认证机制的精髓。

Session工作原理

image-20230720074015101

在Express中使用Session认证

1
npm i express-session
1
2
3
4
5
6
7
8
9
 const express=require('express');
const session=require('express-session');

const app=express();
app.use(session({
secret:'', //可以为任意字符串
resave:false, //固定写法
saveUninitinalized:true //固定写法
}));

向session中存数据

配置完毕后,可以使用req.session,来访问和使用session对象。

1
2
3
4
app.post('/...',(req,res)=>{
req.session.xxx=xxx;
...
})

取session中的数据

1
2
3
4
app.get('/...',(req,res)=>{
console.log(req.session.xxx);
...
})

清空session

1
req.session.destroy();

JWT认证机制

推荐前后端分离方式使用。

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

当前端需要跨域请求后端接口时,推荐使用JWT认证机制

JWT是什么?

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

工作原理

image-20230723154056241

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

JWT组成部分

Header头部、Payload有效负载、Signature签名。

xxxxxxxxxx25 1// 引入 events 模块2var events = require(‘events’);3// 创建 eventEmitter 对象4var eventEmitter = new events.EventEmitter();5 6// 创建事件处理程序7var connectHandler = function connected() {8   console.log(‘连接成功。’);9  10   // 触发 data_received 事件 11   eventEmitter.emit(‘data_received’);12}13 14// 绑定 connection 事件处理程序15eventEmitter.on(‘connection’, connectHandler);16 17// 使用匿名函数绑定 data_received 事件18eventEmitter.on(‘data_received’, function(){19   console.log(‘数据接收成功。’);20});21 22// 触发 connection 事件 23eventEmitter.emit(‘connection’);24 25console.log(“程序执行完毕。”);shell

1
Header.Payload.Signature
  1. Header头部和Signature签名,属于安全性相关部分,用于保证Token的安全性。
  2. Payload有效负载,真正的信息,它由用户信息加密后生成。

JWT使用方式

客户端收到的JWT保存在localStoragesessionStorage中。

客户端与服务器通信一般将JWT字符放在头部Authorization字段中。

1
2
3
npm i jsonwebtoken express-jwt
#jsonwebtoken用于生成jwt字符串
#express-jwt用于解析jwt还原成json对象
1
2
const jwt = require('jsonwebtoken');
const expressJWT = require('express-jwt');

登录成功后生成jwt字符串,响应给客户端

调用jsonwebtoken中的sign()方法。

1
jsonwebtoken.sign(用户的信息对象,加密的密钥,配置对象如可以配置当前token的时间)
1
2
const secretKey='LPtexas'
const jwtStr=jwt.sign({userName:req.body.userName,password:req.body.password},secretKey,{expiresIn:'30m'});

将jwt字符串还原为json对象

使用express-jwt中间件,调用unless({{path:["/..."]}})方法可以指定哪些接口不需要访问权限。

1
2
3
4
5
//5.3.3之前的版本使用:
app.use(expressJWT({secret:secretKey}).unless({path:[/正则表达式/]}));
//使用正则进行匹配path,两个斜线中间写正则表达式
//5.3.3之后的版本使用:
app.use(expressJWT.expressjwt({ secret: secretKey ,algorithms:["HS256"]}).unless({ path: [/正则表达式/] }));

将jwt字符串放入头部Authorization的字段中

注意需要再值前添加Bearer

image-20230723171206339

使用req.user获取用户信息

使用了express-jwt后,jwt解析的结果被挂载到了req.user属性中。

1
2
3
4
//5.3.3版本以前使用:
req.user
//5.3.3版本以后使用:
req.auth

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

使用一个中间件来解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
app.use((err, req, res, next) => {
//捕获身份认证错误
if (err.name == 'UnauthorizedError') {
return res.send({
status: 401,
msg: err.message,
})
}
res.send({
status: 500,
msg: err.message
})
next();
})

定义secret密钥

用于保证JWT字符串的安全性。

用于加密和解密JWT。

越复杂越好。

1
const secretKey = '我是一个复杂的字符串'