Node.js第六篇:Express框架

第一章:快速上手Express

1.1-为什么要学习框架

在使用Node.js原始方式开发web应用时,我们发现不管是对请求的处理(如:路由的判断、参数的处理等),还是对响应的处理(如:设置响应状态、类型等),操作依然比较繁琐,大把的精力和时间消耗在这些和业务无关的操作上,降低了开发效率。而在实际开发中,我们关注的更多是业务(功能)的开发,为了简化我们的开发、提高我们的开发效率,Express框架诞生。

1.2-什么是Express

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

Express提供了一系列强大的特性,来帮助我们创建各种web应用。

  • 提供了简洁的路由定义方式
  • 获取HTTP请求参数进行了简化处理
  • 模板引擎支持程度高,便于渲染动态的HTML页面
  • 提供了中间件机制有效的控制HTTP请求
  • 拥有大量第三方中间件对功能进行扩展

原生Node.js和Express框架路由对比:

原生Node.js和Exprees框架获取请求参数对比

1.3-快速入门

安装express框架

命令:npm install express --save

构建第一个基于express框架的web应用

// 导入express包
const express = require('express')
// 创建服务器对象
const app = express()
// 监听请求(网站根路径)
app.get('/', (req, res) => {
  // 响应内容
  // send方法自动检测并设置响应内容类型以及状态码
  res.send('Hello Expreess')
})
// 设置端口
app.listen(3000, 'localhost')
console.log('服务器启动成功:http://localhost:3000')

第二章:中间件

2.1-认识中间件

什么是中间件

我们开发web应用,核心操作无非就是处理请求响应

从请求处理开始直到响应处理结束,才是一个完整的周期。

在请求和响应之间,我们可以通过express提供的一系列方法或第三方模块进行额外的操作。

而中间件就是处理请求和响应之间的一系列方法,这些方法可以接收客户端发送的请求、可以对请求作出响应、也可以将请求继续交给下一个中间件处理。

中间件由两部分组成,中间件方法以及请求处理函数

  • 中间件方法由Express提供,负责拦截请求
  • 请求处理函数由开发人员提供,负责处理请求。

比如我们用到的路由,其实就是一个中间件(我们用到的get和post方法就是中间件方法)

体验中间件的使用

我们可以针对同一个请求,设置多个中间件进行多次处理。

默认情况下,请求从上到下依次匹配中间件,一旦匹配成功,终止匹配。

可以调用next方法将请求控制权交给下要给中间件,直到遇到结束请求的中间件。

const express = require('express')
const app = express()
// 中间件1处理请求
app.get('/user', (req, res,next) => {
  req.name = 'Burce'
  // 调用next方法,将控制权交给下一个中间件
  next()
})
// 中间件2处理请求
app.get('/user', (req, res) => {
  res.send(req.name)
})
app.listen(3000, 'localhost')
console.log('服务器启动成功:http://localhost:3000/user')

2.2-中间件use方法

use方法使用介绍

app.use方法可以匹配所有的请求方式(不区分get和post),也可以直接传入请求处理函数,接收所有请求

app.use((req,res,next)=>{
	console.log('处理请求')
	...
	next()
})

app.use第一个参数也可以传入请求地址,代表不论什么请求方式,只要是这个请求地址就可以接收这个请求

app.use('请求路径',(req,res,next)=>{
	console.log('处理请求')
	...
	next()
})

代码

const express = require('express')
const app = express()
// 【中间件a-处理所有请求】
app.use((req, res, next) => {
  console.log('中间件a处理了请求')
  next()
})
// 【中间b-处理/user请求-不论是get还是post】
app.use('/user', (req, res, next) => {
  console.log('中间件b处理了/user请求')
  next()
})
// 【中间c-处理/user请求-get请求】
app.get('/user', (req, res, next) => {
  console.log('中间件c处理了/user-get请求')
  res.send('ok')
})
// 【中间d-处理/user请求-get请求】
app.get('/list', (req, res, next) => {
  console.log('中间件d处理了/list-get请求')
  res.send('ok')
})
app.listen(3000)

在浏览器中输入地址http://localhost:3000/user发送请求

请求结果

中间件a处理了请求
中间件b处理了/user请求
中间件c处理了/user-get请求

2.3-中间件的实际应用

登录验证

比如登录验证,客户端在访问需要登录的页面时,可以先使用中间件判断用户的登录状态,如果未登录则拦截请求,直接响应,禁止用户进入需要登录的页面。

app.use('/user', (req, res, next) => {
  // 模拟是否登陆状态
  let isLogin = false
  if (isLogin) {
    next()
  } else {
    res.send('请先登录')
  }
})
app.get('/user', (req, res, next) => {
  res.send('欢迎来到user页面')
})

网站维护公告

在所有的路由最上面定义所有请求的中间件,直接为客户端做出响应,网站正在维护中

app.use((req, res, next) => {
  res.send('服务器正在维护中,截止到2030年')
})
app.use('/user', (req, res, next) => {
  let isLogin = true
  if (isLogin) {
    next()
  } else {
    res.send('请先登录')
  }
})
app.get('/user', (req, res, next) => {
  res.send('欢迎来到user页面')
})

自定义404页面

可以把接收所有请求的中间接件定义在路由最后面,当上面的路由不满足时,说明没有匹配的路由,此时可以响应页面不存在。

app.use('/user', (req, res, next) => {
  let isLogin = true
  if (isLogin) {
    next()
  } else {
    res.send('请先登录')
  }
})
app.get('/user', (req, res, next) => {
  res.send('欢迎来到user页面')
})
app.use((req, res, next) => {
  res.status(404).send('你访问的页面被外星人带走了!')
})

2.4-错误处理中间件

使用方式

在程序执行的过程中,不可避免可能会发生一些无法预料的错误,比如数据库连接失败、文件读取失败等。

而错误处理中间件是一个集中处理错误的地方。

app.use((err,req,res,next)=>{
	res.status(500).send('服务器内部错误')
})

对于异步错误,调用next方法,可以将错误信息通过参数的方式传递给next()方法,即可触发错误处理中间件。

app.get('/file',(req,res,next)=>{
	fs.readFile('01.txt','utf-8',(err,data)=>{
		if(err) {
			next(err)
		}else {
			res.send(data)
		}
	})
})

代码演示

const express = require('express')
const fs = require('fs')
const app = express()
app.use('/user', (req, res, next) => {
  throw new Error('未知错误')
})
app.get('/file', (req, res, next) => {
  fs.readFile('01.txt', 'utf-8', (err, data) => {
    if (err) {
      next(err)
    } else {
      res.send(data)
    }
  })
})
app.use((err, req, res, next) => {
  if (err) {
    res.status(500).send(err.message)
  }
})
app.listen(3000)

2.5-捕获错误

try catch介绍

在node.js中,异步ApI的错误信息都是通过回调函数获取的,支持Promise对象的异步API发送错误可以通过catch方法捕获。

异步函数执行如果发生错误要如何捕获错误呢?

try catch 可以捕获异步函数以及其他同步代码在执行过程中发生的错误,但是不能捕获其他类型的API发送的错误。

代码

const express = require('express')
const fs = require('fs')
/*默认情况下,fs.readFile方法返回的不是promise对象,所以无法使用await关键字,可以通过util模块下的promisify方法改造其他方法,使其可以使用await关键字 */
const promisify = require('util').promisify
fs.readFile = promisify(fs.readFile)
const app = express()
app.get('/file', async (req, res, next) => {
  try {
    const result = await fs.readFile('01.txt', 'utf-8')
    res.send(result)
  } catch (ex) { // 捕获错误
    next(ex)
  }
})
app.use((err, req, res, next) => {
  if (err) {
    res.status(500).send(err.message)
  }
})
app.listen(3000)

第三章: 模块化路由

3.1-为什么要构建模块化路由

在项目实际开发当中,我们的项目是要根据业务划分模块并且多人协同开发的,若把所有的路由都集中在一个程序文件中处理,则难以实现多人协同开发且难以维护。

3.2-如何实现模块化路由

实现方式

首先根据模块的划分,为每个模块创建独立的路由程序文件;

其次在模块中通过express的Router方法创建路由对象;

然后在模块中使用路由对象处理指定的请求;

最后,在程序入口文件中导入各个路由模块,并使用use方法挂载到web应用中。

app.use('路由名称',路由对象)

发送请求:/路由名称/请求地址

代码演示

路由模块home.js

const express = require('express')
const home = express.Router();
home.get('/index', (req, res) => {
  res.send('欢迎来到home首页')
})
home.get('/list', (req, res) => {
  res.send('欢迎来到home列表页')
})
module.exports = home

路由模块admin.js

const express = require('express')
const admin = express.Router();
admin.get('/index', (req, res) => {
  res.send('欢迎来到admin首页')
})
admin.get('/list', (req, res) => {
  res.send('欢迎来到admin列表页')
})
module.exports = admin

web应用程序入口app.js

const express = require('express')
// 导入home路由模块
const home = require('./router/home')
// 导入admin路由模块
const admin = require('./router/admin')

const app = express()
// 把home路由模块挂载到web应用中,监听/home路径下的请求
app.use('/home', home)
// 把admin路由模块挂载到web应用中,监听/admin路径下的请求
app.use('/admin', admin)

app.listen(80)

访问:例如http://localhost/admin/list

结果:欢迎来到admin列表页

第四章:请求参数

4.1-获取get请求参数

获取方式

req.query,返回值是一个对象。

express框架内部将请求参数转换为对象。

代码演示

const express = require('express')
const app = express()

app.get('/index', (req, res) => {
  res.send(req.query)
})

app.listen(80)

请求:http://localhost/index?word=程序媛&gender=女

结果:{"word":"程序媛","gender":"女"}

4.2-获取post请求参数

获取方式

安装第三方模块:npm install body-parser

导入并配置body-parser模块:

const bodyParser = require('body-parser')
// 拦截所有请求
// extend: false 表示方法内部使用querystring模块处理请求参数格式
// extend: true 表示方法内部使用第三方模块qs处理请求参数格式
app.use(bodyParser.urlencoded({extend: false}))

获取参数:req.body 返回一个对象

代码演示

form表单

  <form action="http://localhost/add" method="post">
    <p>账号:<input type="text" name="account"></p>
    <p>密码:<input type="password" name="pwd"></p>
    <p><input type="submit" value="提交"></p>
  </form

服务端程序

const express = require('express')
// 导入body-parser模块
const bodyParser = require('body-parser')
const app = express()
// 配置bodyParser
app.use(bodyParser.urlencoded({extended:false}))

app.post('/add', (req, res) => {
  // 获取post请求参数
  res.send(req.body)
})

app.listen(80)

返回结果:{"account":"admin","pwd":"123456"}

4.3-Express中的路由参数

路由参数的定义和使用

请求路径格式: /路径名称/:参数1名称/:参数2名

调用方式:http://localhost/find/值1/值2

代码演示

const express = require('express')
const app = express()

app.get('/find/:id', (req, res) => {
  res.send(req.params)
})
// 测试:http://localhost/find/1
app.get('/find/:id/:name', (req, res) => {
  res.send(req.params)
})
// 测试:http://localhost/find/1/admin
app.get('/find', (req, res) => {
  res.send('find')
})
// 测试:http://localhost/find

app.listen(80)

第五章:静态资源的处理

通过Express内置的express.static可以方便地托管静态文件,例如img、css、javascript文件等。

配置方式

app.use('虚拟路径',express.static('静态资源真实路径'))

虚拟路径可选

代码演示

const express = require('express')
const path = require('path')
const app = express()
const statiPath = path.join(__dirname,'./public')
// app.use(express.static(statiPath))
// 测试:http://localhost/index.html
app.use('/static', express.static(statiPath))
// 测试:http://localhost/static/index.html

app.listen(80)

第六章:Express中使用模板引擎

为了使art-template模板引擎能够更好的和Express框架结合,模板引擎官方在原art-template模板引擎的基础上封装了express-art-template

安装模板引擎

安装命令: npm install art-template express-art-template

代码演示

app.js

const express = require('express')
const path = require('path')
const app = express()

// 多个模板使用公共数据
// app.locals.key = value
app.locals.users = [
  {name:'Bruce',age: 10},
  {name:'Andy',age: 20}
]

// 配置框架使用哪个模板引擎,以及处理后缀为什么的模板
app.engine('art', require('express-art-template'))
// 设置模板存放的目录
app.set('views', path.join(__dirname, 'views'))
// 设置模板的默认后缀
app.set('view engine', 'art')

app.get('/index', (req, res) => {
  // index 模板名称  data模板中需要的数据
  res.render('index', { data: '首页的数据' })
  // render方法会自动匹配并响应
})
app.get('/list', (req, res) => {
  res.render('list',{data:'列表页的数据'})
})
app.listen(80)

list.art

{{data}}
<ol>
{{each users}}
  <li>
    第{{$index+1}}个用户
    <ul>
      <li>name:{{$value.name}}</li>
      <li>age:{{$value.age}}</li>
    </ul>
  </li>
{{/each}}
</ol>

index.art

{{data}}
<ol>
{{each users}}
  <li>
    第{{$index+1}}个用户
    <ul>
      <li>name:{{$value.name}}</li>
      <li>age:{{$value.age}}</li>
    </ul>
  </li>
{{/each}}
</ol>

第七章:cookie和Session

7.1-概述

​ 众所周知,Http协议是无状态的,也就意味着,针对浏览器与服务器之间的请求和响应(也叫会话),当两者之间的会话结束时,服务器端并不会记忆客户端(浏览器)曾经访问过。

​ 但是,在实际应用程序开发中,有些业务需要浏览器和服务器之间能够保持会话。比如常见的登录业务,在同一个浏览器下,当用户第一次登录成功并进入首页时,下次再使用同一个浏览器访问首页时,则不需要再登录。而要实现下次访问不再登录时,需要让服务端能够识别曾经访问过它的浏览器,这就需要会话跟踪技术来实现。分别是cookiesession

cookie:浏览器在电脑硬盘中开辟的一块空间,主要提供服务端存储数据。

  • cookie中的数据是以域名的形式进行区分的。
  • cookie中的数据是有过期时间的,超过时间数据会被浏览器自动删除。
  • cookie中的数据会随着请求被自动发送到服务器端。

7.3-session

实际上就是一个对象,存储在服务器端的内存中,在session对象中也可以存储多条数据,每一条数据都有一个sessionid做为唯一标识。

session依赖于cookie,比如我们要实现一个邮箱和密码登录会话跟踪的功能。其实现过程如下图:

7.4-express-session

在express框架中,可以使用第三方包express-session操作session

具体代码如下:实现一个记录用户使用某个终端访问该网站的次数

// 导入express模块
const express = require('express')
// 导入express-session模块
const session = require('express-session')
// 创建服务对象
const app = express();

// session的名称
let identityKey = 'skey';
 
//使用session
app.use(session({
  name: identityKey,
  secret: 'appkey', // 用来对session id相关的cookie进行签名
  // store: new FileStore(), // 本地存储session(文本文件,也可以选择其他store,比如redis的)
  saveUninitialized: false, // 是否自动保存未初始化的会话,建议false
  resave: false, // 是否每次都重新保存会话,建议false
  cookie: {
    maxAge: 10 * 1000 // 有效期,单位是毫秒
  }
}));

// session存储数据
app.get('/', (req, res) => {
  // 判断是否是第一次访问
  if (req.session.count) {
    req.session.count += 1;
  } else {
    req.session.count = 1
  }
  res.send('欢迎访问,您是第' + req.session.count + '此访问我们')
})

//清除session
app.get('/logout', function(req, res, next){
  // 备注:这里用的 session-file-store 在destroy 方法里,并没有销毁cookie
  // 所以客户端的 cookie 还是存在,导致的问题 --> 退出登陆后,服务端检测到cookie
  // 然后去查找对应的 session 文件,报错
  // session-file-store 本身的bug  
 
  req.session.destroy(function(err) {
    if(err){
      res.json({ret_code: 2, ret_msg: '退出登录失败'});
      return;
    }
     
    // req.session.loginUser = null;
    res.clearCookie(identityKey);
    res.send('ok')
  });
});

app.listen(80)
posted @ 2020-05-11 23:17  雷哒哒  阅读(518)  评论(0编辑  收藏  举报