第九节:Express框架详解(中间件、参数解析、响应返回、日志、文件上传、路由、静态资源、异常处理)

一. express入门

1. 简介

      Express是一个路由和中间件的Web框架,它本身的功能非常少,本质上是一系列中间件函数的调用。

 (官网:https://www.expressjs.com.cn/

2. 快速搭建

(1). 方案1-使用脚手架 【不推荐】

      A. 安装脚手架:npm install -g express-generator

      B. 创建项目:express express-demo

      C. 安装依赖:npm install

      D. 启动项目:node bin/www

(2). 方案2-全部收搭建

      A. 初始化项目【npm init -y】

      B. 安装express 【npm install express】

3. 什么是中间件?

(1). 中间件的本质是传递给express的一个回调函数; 这个回调函数接受三个参数:

        请求对象(request对象)、 响应对象(response对象)、next函数(在express中定义的用于执行下一个中间件的函数)

(2). 中间件可以执行以下任务:

       A. 执行任何代码

       B. 更改请求(request)和响应(response)对象;

       C. 结束请求-响应周期(返回数据);

       D. 调用栈中的下一个中间件;

注:如果当前中间件功能没有结束请求-响应周期(res.end),则必须调用next()将控制权传递给下一个中间件功能,否则,请求将被挂起.

const express = require("express");
const app = express();

// 第一个中间件
// 给app传入的的回调函数就是一个中间件
app.post("/checkLogin", (request, response, next) => {
	console.log("我是匹配到的第一个中间件哦");
	// 1. 可以执行相关业务
	// 获取请求参数
	// 执行DB业务
	// 直接返回给客户端

	// 2. 执行下一个中间件
	next();
});

// 下面也是一个中间件
app.use((request, response, next) => {
	console.log("我是第二个中间件");
	response.end("登录成功了");
});

app.listen(9090, () => {
	console.log("------------express服务器启动成功了------------");
});

 

二. 中间件详解

1. 中间件提供两种使用方式

   (1). app.use()  可以匹配任何请求

   (2). app.method 不同的方法匹配不同的请求 。 比如:app.post()、app.get()

2. 中间件的匹配规则如下

    (1).当express接收到客户端发送的网络请求时, 从上往下在所有中间中依次进行匹配,当匹配到第一个符合要求的中间件时, 那么就会执行这个中间件

    (2).如果当前中间件功能没有结束请求-响应周期(res.end),则必须调用next()将控制权传递给下一个中间件功能,否则,请求将被挂起

3. 普通的中间件

   使用app.use,默认可以匹配任何路径和任何请求。(ps:可以再回调中通过 request.method 进一步区分请求类型,但通常不这么做,代码臃肿)

   如下案例1: 两个中间件都会被请求到,且按顺序执行

const express = require("express");
const app = express();
app.listen(9090, () => {
	console.log("------------express服务器启动成功了------------");
});

// 案例1. 普通中间件
{
	app.use((request, response, next) => {
		console.log("first middleware");
		next();
	});
	app.use((request, response, next) => {
		console.log("second middleware");
		response.end("请求成功了");
	});
} 

4. 路径匹配

     app.use中的第一个参数可以传入路径,这样就限制了该路径下的任何请求。

     如下案例2:只要是路径 http://localhost:9090/checkLogin, 任何请求请求都可以访问   

{
	app.use("/checkLogin", (request, response, next) => {
		console.log("checkLogin middleware");
		response.end("登录成功");
	});
}

5. 方法匹配+路径匹配

     app.post 和 app.get ,第一个参数传入路径,则分别匹配改路径下的post请求和get请求

     如下案例3:

       Post请求 + 路径/checkLogin:可以匹配到第一个中间件

       Get请求 + 路径/getUserInfo:可以匹配到第二个中间件 

{
	// 第一个中间件
	app.post("/checkLogin", (request, response, next) => {
		console.log("checkLogin middleware");
		response.end("登录成功");
	});
	// 第二个中间件
	app.get("/getUserInfo", (request, response, next) => {
		console.log("getUserInfo middleware");
		response.end("获取用户信息成功");
	});
}

6. 多个中间件

   可以在一个方法中传入多个回调函数,配合next方法,组成多个中间件

   如下案例4

{
	app.get(
		"/getUserInfo",
		(req, res, next) => {
			console.log("match /home get middleware01");
			next();
		},
		(req, res, next) => {
			console.log("match /home get middleware02");
			next();
		},
		(req, res, next) => {
			console.log("match /home get middleware03");
			next();
		},
		(req, res, next) => {
			console.log("match /home get middleware04");
			res.end("------结束了--------");
		}
	);
}

 

三.  参数解析

1. 通过post请求发送json格式

 【对应postman中的 raw--json】 如图1

  通过 app.use(express.json()) 中间件解析客户端发送过来的json请求,在回调函数中通过 res.body 直接可以获取这个对象。

const express = require("express");
const app = express();
app.listen(9090, () => {
	console.log("------------express服务器启动成功了------------");
});

// 1. 通过post请求发送json格式
{
	// 使用中间件
	app.use(express.json());

	app.use("/checkLogin", (req, res, next) => {
		// 直接可以获取提交过来的json格式
		console.log(req.body); //{ userAccount: 'admin', userPwd: '123456' }

		// 返回结果
		res.end(`请求成功`);
	});
} 

 

2. 通过post请求发送x-www-form-urlencoded格式

 【对应postman中的x-www-form-urlencoded】 如图2

   通过 app.use(express.urlencoded({ extended: true }))解析客户端传递来的urlencoded格式,默认使用的node内置querystring模块, { extended: true }: 不再使用内置的querystring, 而是使用qs第三方库 

{
	// 使用中间件
	app.use(express.urlencoded({ extended: true }));

	app.use("/checkLogin", (req, res, next) => {
		// 直接可以获取提交过来的json格式
		console.log(req.body); //{ userAccount: 'admin', userPwd: '123456' }

		// 返回结果
		res.end(`请求成功`);
	});
}

 

3. 通过post请求发送form-data格式

  【对应postman中的form-data】 

(1). 通过使用multer中间件,需要先【npm install multer】

(2). 然后导入并使用中间件,就可以接受到客户端传递过来的form-data中的数据了。

 {
	// 编写中间件
	const multer = require("multer");
	const formdata = multer();

	// 使用中间件
	app.use("/checkLogin", formdata.any(), (req, res, next) => {
		// 直接可以获取提交过来的json格式
		console.log(req.body); //{ userAccount: 'admin', userPwd: '123456' }

		// 返回结果
		res.end(`请求成功`);
	});
}

 

4. 通过get请求中的URL的query

    形如:http://localhost:9090/checkLogin?userAccount=admin&userPwd=123456

    在node中通过 request.query 直接就可以获取这个对象 

{
	app.get("/checkLogin", (request, response, next) => {
		// 获取请求参数
		console.log(request.query); //{ userAccount: 'admin', userPwd: '123456' }

		// 返回结果
		response.end(`请求成功`);
	});
}

 

5. 通过get请求中的URL的params

  形如:http://localhost:9090/checkLogin/admin/123456

  在node中的匹配路径为:"/checkLogin/:userAccount/:userPwd",最终通过request.params获取

{
	app.get("/checkLogin/:userAccount/:userPwd", (request, response, next) => {
		// 获取请求参数
		console.log(request.params); //{ userAccount: 'admin', userPwd: '123456' }

		// 返回结果
		response.end(`请求成功`);
	});
}

 

四. 响应返回

1. end方法

     类似于http中的response.end方法,用法是一致的

2. json方法 --(最常用)

     json方法中可以传入很多的类型:object、array、string、boolean、number、null等,它们会被转换成json格式返回;

3. status方法

  用于设置状态码;可以和json方法同时使用

  注:这里是一个函数,而不是属性赋值;

const express = require("express");
const app = express();
app.listen(9090, () => {
	console.log("------------express服务器启动成功了------------");
});

app.post("/checkLogin", (request, response, next) => {
	// 1. end方法
	// response.end("登录成功");

	// 2. json方法
	// response.json({
	// 	status: "ok",
	// 	msg: "登录成功",
	// 	data: {
	// 		userName: "ypf",
	// 		useAge: 18,
	// 	},
	// });

	// 3. status方法 (配合json同时使用)
	response.status(401);
	response.json({
		status: "ok",
		msg: "登录成功",
		data: {
			userName: "ypf",
			useAge: 18,
		},
	});
});

 

五. 日志记录

1. 通过指令安装包  【npm install morgan】

2. 通过下面代码使用中间件

   注:这里的日志路径文件夹必须事先存在,否则启动的时候报错

const express = require("express");
const fs = require("fs");
const app = express();
app.listen(9090, () => {
	console.log("------------express服务器启动成功了------------");
});

// 使用中间件
const morgan = require("morgan");
const writeStream = fs.createWriteStream("./logs/myLog.txt");
app.use(morgan("combined", { stream: writeStream }));

app.post("/checkLogin", (request, response, next) => {
	response.end("请求成功");
});

日志截图:

六. 文件上传 

1. 说明

 (1). 需要通过指令【npm install multer】 安装中间件multer

 (2). 在multer中可以配置文件存放路径 和 文件命名规则

2. 单文件上传

  通过 myUpload.single("myFile1")方法来配置,这里的myFile1为参数名

代码

const express = require("express");
const app = express();
app.listen(9090, () => {
	console.log("------------express服务器启动成功了------------");
});
// 引入multer中间件
const multer = require("multer");
{
	let myUpload = multer({
		// dest: "./uploads", //这种写法没有后缀名
		storage: multer.diskStorage({
			// 文件存放路径
			destination(req, file, callback) {
				callback(null, "./uploads");
			},
			// 文件命名规则
			filename(req, file, callback) {
				// 时间戳_文件名.后缀名   (file.originalname:  文件名.后缀名)
				callback(null, Date.now() + "_" + file.originalname);
			},
		}),
	});

	app.post("/singleUpload", myUpload.single("myFile1"), (req, res, next) => {
		console.log(req.file);
		res.end("文件上传成功~");
	});
}

调用 

 

3. 多文件上传

  通过 myUpload.array("myManyFile1")方法来配置,这里的myManyFile1为参数名

代码

const express = require("express");
const app = express();
app.listen(9090, () => {
	console.log("------------express服务器启动成功了------------");
});
// 引入multer中间件
const multer = require("multer");
// 2. 多文件上传
{
	let myUpload = multer({
		// dest: "./uploads", //这种写法没有后缀名
		storage: multer.diskStorage({
			destination(req, file, callback) {
				callback(null, "./uploads");
			},
			filename(req, file, callback) {
				// 时间戳_文件名.后缀名   (file.originalname:  文件名.后缀名)
				callback(null, Date.now() + "_" + file.originalname);
			},
		}),
	});

	app.post("/manyUpload", myUpload.array("myManyFile1"), (req, res, next) => {
		console.log(req.files);
		res.end("文件上传成功~");
	});
}

调用 

 

七. 路由的使用 

1.背景

  如果我们将所有的代码逻辑都写在app中,那么app会变得越来越复杂:

 (1). 一方面完整的Web服务器包含非常多的处理逻辑;

 (2). 另一方面有些处理逻辑其实是一个整体,我们应该将它们放在一起:比如对users相关的处理

      获取用户列表;获取某一个用户信息; 创建一个新的用户;删除一个用户; 更新一个用户

查看代码
 const express = require("express");
const app = express();
app.listen(9090, () => {
	console.log("------------express服务器启动成功了------------");
});

// 1. 原始写法--都写在这个app中
{
	app.get("/users", (req, res, next) => {
		res.json("用户列表数据");
	});
	app.get("/users/:id", (req, res, next) => {
		const id = req.params.id;
		res.json("某一个用户的数据:" + id);
	});
	app.post("/users", (req, res, next) => {
		res.json("创建用户成功");
	});
	app.delete("/users/:id", (req, res, next) => {
		const id = req.params.id;
		res.json("删除某一个用户的数据:" + id);
	});
	app.patch("/users/:id", (req, res, next) => {
		const id = req.params.id;
		res.json("修改某一个用户的数据:" + id);
	});
} 

2. 什么是路由?

    使用 express.Router来创建一个路由处理程序: 一个Router实例拥有完整的中间件和路由系统; 因此,它也被称为 迷你应用程序(mini-app)

3. 如何使用

   (1). 创建路由 const router = express.Router();

   (2). 使用路由构建各种请求匹配器 get post put等等

   (3). 使用中间件 app.use("/users", router);

   注:上述的每个请求前面都会拼上 /users

使用路由的写法(不抽文件)

查看代码
 // 2. 使用路由写法
{
	// 2.1 创建路由
	const router = express.Router();
	// 2.2 各种请求
	router.get("/", (req, res, next) => {
		res.json("用户列表数据");
	});
	router.get("/:id", (req, res, next) => {
		const id = req.params.id;
		res.json("某一个用户的数据:" + id);
	});
	router.post("/", (req, res, next) => {
		res.json("创建用户成功");
	});
	router.delete("/:id", (req, res, next) => {
		const id = req.params.id;
		res.json("删除某一个用户的数据:" + id);
	});
	router.patch("/:id", (req, res, next) => {
		const id = req.params.id;
		res.json("修改某一个用户的数据:" + id);
	});
	// 2.3 使用路由 (上述的每个请求前面都会拼上 /users)
	app.use("/users", router);
}

通常将路由相关的单独抽离到一个文件中

{
	const myRouter = require("./router/myRouter");
	//  使用路由 (上述的每个请求前面都会拼上 /users)
	app.use("/users", myRouter);
}
 查看代码
const express = require("express");

// 2.1 创建路由
const router = express.Router();
// 2.2 各种请求
router.get("/", (req, res, next) => {
	res.json("用户列表数据");
});
router.get("/:id", (req, res, next) => {
	const id = req.params.id;
	res.json("某一个用户的数据:" + id);
});
router.post("/", (req, res, next) => {
	res.json("创建用户成功");
});
router.delete("/:id", (req, res, next) => {
	const id = req.params.id;
	res.json("删除某一个用户的数据:" + id);
});
router.patch("/:id", (req, res, next) => {
	const id = req.params.id;
	res.json("修改某一个用户的数据:" + id);
});

// 3.将路由导出
module.exports = router;

 

八. 静态资源服务器 

Node也可以作为静态资源服务器,并且express给我们提供了方便部署静态资源的方法;

比如可以直接部署一个静态项目;或者把一些图片放到静态资源里,便于访问

使用 express.static() 这个内置中间件来执行。

如下案例:

     1. 把picture文件夹作为静态资源   如:   http://localhost:9090/test.jpg

     2. 在builder文件夹里部署一个静态资源项目  http://localhost:9090/index.html

    特别注意:上面两个案例都是相当于直接根目录了,所以不要拼接 /picture  和 /build

const express = require("express");
const app = express();
app.listen(9090, () => {
	console.log("------------express服务器启动成功了------------");
});

// 1. 直接将一个文件夹作为静态资源(才可以通过浏览器地址来访问这个资源)
app.use(express.static("./picture"));

// 2. 可以将一个完整的静态项目部署在这里
app.use(express.static("./build"));

// 编写中间件
app.post("/checkLogin", (req, res, next) => {
	res.end("登录成功");
});

 

九. 异常处理方案 

 
   通俗的来说,就是单独抽离出来一个中间,通过next方法进入,统一来出来各种异常,自己定义异常码。
 
代码分享
const express = require("express");
const app = express();
app.listen(9090, () => {
	console.log("------------express服务器启动成功了------------");
});

app.use(express.json());
app.post("/checkLogin", (request, response, next) => {
	let { userAccount, userPwd } = request.body;
	if (!userAccount || !userPwd) {
		next("error1");
	} else if (userAccount !== "admin" || userPwd !== "123456") {
		next("error2");
	} else {
		response.json({ status: "ok", msg: "登录成功" });
	}
});

// 统一处理异常中间件
// 第一个参数为接收next传递过来的异常码

app.use((errCode, request, response, next) => {
	let msg = "";
	switch (errCode) {
		case "error1":
			msg = "用户名或密码为空";
			break;
		case "error2":
			msg = "用户名或密码不正确";
			break;
		default:
			msg = "登录失败";
			break;
	}
	response.json({ status: "error", msg });
});

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2023-02-13 08:00  Yaopengfei  阅读(499)  评论(0编辑  收藏  举报