目录
【前文回顾】👉 初识Node.js Web应用开发框:Express_03
一. 中间件
中间件为路由服务的,用于拦截对路由的请求,也可以作出响应。
说到中间件,官网对它的阐述是这样的:
“Express是一个自身功能极简,完全是路由和中间件构成一个web开发框架:从本质上来说,一个Express应用就是在调用各种中间件。”
由此可见,中间件在Express web应用开发中的重要性。
❣️ 中间件概念
在nodejs中,中间件主要是指封装所有Http请求细节处理的方法,是从Http请求发起到响应结束过程中的处理方法。一次Http请求通常包含很多工作,如记录日志、ip过滤、查询字符串、请求体解析、Cookie处理、权限验证、参数验证、异常处理等,但对于Web应用而言,并不希望接触到这么多细节性的处理,因此引入中间件来简化和隔离这些基础设施与业务逻辑之间的细节,让开发者能够关注在业务的开发上,以达到提升开发效率的目的。
中间件的行为比较类似Java中过滤器的工作原理,就是在进入具体的业务处理之前,先让过滤器处理。它的工作模型下图所示。
❣️ 中间件结构
app.use([path],function)
path:是路由的url,默认参数‘/',意义是路由到这个路径时使用这个中间件
function:中间件函数
这个中间件函数可以理解为就是function(request,response,next)
❣️ 中间件分类
中间件分为5大类,应用级中间件、路由级中间件、内置中间件、第三方中间件、错误处理中间件
1. 应用级中间件
也称为自定义中间件 ------本质就是个函数
app.use(url,(req,res ,next)=>{
});
url 表示要拦截的URl,对应路由中的URl,一旦拦截会自动执行回调函数
next 是一个函数,表示往后执行下一个中间件或者路由
🌴 注:自定义中间件的参数说明
这个function总共有三个参数(req,res,next);
当每个请求到达服务器时,nodejs会为请求创建一个请求对象(request),该请求对象包含客户端提交上来的数据。同时也会创建一个响应对象(response),响应对象主要负责将服务器的数据响应到客户端。而最后一个参数next是一个方法,因为一个应用中可以使用多个中间件,而要想运行下一个中间件,那么上一个中间件必须运行next()。
练习:创建添加到购物车的路由(get /shopping),传递商品的价格price,最后响应'商品的最终价格为:xxxx'。添加中间件。实现对价格打九折
*02_middleware.js文件
const express = require('express');
//创建web服务器
const app = express();
//设置端口
app.listen(8080);
//只能按照URL拦截
//拦截对 /list的请求
//参数1:要拦截的URL
//参数2:回调函数,一旦拦截到,自动调用这个函数
app.use('/list', (req, res, next) => {
//req res和路由中的是一样的
//next 是一个函数,表示执行下一个中间件或者路由
//获取以查询字符串传递的数据
console.log(req.query);
//判断传递的用户名是否为管理员root
//如果不是
if (req.query.uname !== 'root') {
//响应
res.send('请提供管理员账户');
} else {
//往后执行(下一个中间件或者路由)
next();
}
});
//用户列表路由
//get /list
app.get('/list', (req, res) => {
res.send('这是所有用户的数据');
});
// http://127.0.0.1:8080/list?uname=abcd
//添加中间件,拦截添加购物车的请求
app.use('/shopping', (req, res, next) => {
//在中间件中获取数据,然后打折
console.log(req.query); // {}
//对价格打九折
req.query.price *= 0.9; //req.query.price={}*0.9=NaN
//往后执行
next();
});
//添加购物车路由 get /shopping
app.get('/shopping', (req, res) => {
//获取查询字符串传递的数据
//个人尝试理解:req.query格式化对象,req.query.price=NaN,即给空对象强制添加price属性,属性值为NAN,所以{ price: NaN }
console.log(req.query); // { price: NaN }
res.send('商品最终价格为:' + req.query.price);
});
我们简单分析一下:
启动服务:node 02_middleware.js
在8080后面输入/lis请求,被中间件拦截,向页面作出响应res.send('请提供管理员账户');在/list后面输入?uname=root,即利用查询字符串传参,然后经过中间件验证,通过验证后,往后执行用户列表路由,服务器作出响应res.send('这是所有用户的数据');
所以,如同以上,本例中我们来实现购物车路由,利用中间件打折功能
![]()
传参前:在8080后面输入/shopping请求,经过中间件打折功能,往后执行购物车路由,服务器作出响应,res.send('商品最终价格为:'+req.query.price);由于还没有把参数price的值传过去,所以最终价格为NaN。传参后:在/shopping后面输入?price=8000,即利用查询字符串传参,然后经过中间件打折功能,往后执行购物车路由,此时已携带price参数值,然后服务器作出响应res.send('商品最终价格为:'+req.query.price);
2. 路由级中间
路由器的使用,就是路由级中间件
app.use('/product',productRouter)
3. 内置中间件
托管静态资源(html,css,js,图像...)
当浏览器端请求(静态资源)文件时,不需要通过路由去寻找文件,而是让浏览器自动到指定的目录下去寻找。(没有托管的话,浏览器每请求一个文件,就得写一个请求路由去响应浏览器的请求。使用res.sendFile(__dirname+'/文件名.html'))
app.use(express.static('目录路径'))
🌴 注:我该如何请求托管的静态资源?
托管静态资源后,向服务器请求一个文件时,启动服务器后,在浏览器地址栏,端口后输入/带后缀的文件名,即可完成请求
🏝️ 扩展:内置中间件
从 4.x 版本开始,, Express 已经不再依赖 Connect 了。除了 express.static, Express 以前内置的中间件现在已经全部单独作为模块安装使用了。请参考中间件列表。
express.static(root, [options])
express.static是Express中唯一的内建中间件。用来处理静态资源文件。它以server-static模块为基础开发,负责托管 Express 应用内的静态资源。 参数root为静态资源的所在的根目录。 参数options是可选的,支持以下的属性:
属性 描述 类型 默认值 dotfiles 是否响应点文件(以点“.”开头的文件或目录)。供选择的值有"allow","deny"和"ignore"。请参阅下面的点文件。 String "ignore" etag 是否启用etag生成 Boolean true extensions 设置文件扩展名回退(设置文件扩展名备份选项):如果未找到文件,请搜索具有指定扩展名的文件并提供找到的第一个扩展名,例如:['html', 'htm'] Array [] index 发送目录索引文件。设置false将不发送。 Mixed "index.html" lastModified 设置文件在系统中的最后修改时间到Last-Modified头部。可能的取值有false和true。 Boolean true maxAge 在Cache-Control头部中设置max-age属性,精度为毫秒(ms)或则一段ms format的字符串 Number 0 redirect 当请求的pathname是一个目录的时候,重定向到尾随"/" Boolean true setHeaders 当响应静态文件请求时设置headers的方法 Funtion 如果你想获得更多关于使用中间件的细节,你可以查阅在 Express 中提供静态文件Serving static files in Express和使用中间件 - 内置中间件
❣️ Express 4.x API 中文文档:Express 4.x API 中文文档 | 菜鸟教程
❣️ Express 4.x API reference:Express 4.x - API Reference
下面的例子使用了 express.static 中间件,其中的 options 对象经过了精心的设计。var options = { dotfiles: 'ignore', etag: false, extensions: ['htm', 'html'], index: false, maxAge: '1d', redirect: false, setHeaders: function (res, path, stat) { res.set('x-timestamp', Date.now()) } } app.use(express.static('public', options))
练习:再次托管静态资源到file目录,然后添加文件测试。加入public和file目录下出现相同的文件名称,查看显示哪一个。
//引入express包
const express=require('express');
//创建web服务器
const app=express();
//设置端口
app.listen(8080);
//托管静态资源到public目录
//浏览器端请求文件,自动到public目录寻找
app.use( express.static('./public') );
app.use( express.static('./files') );
🌴 注:每个应用可有多个静态目录。
那么,同一个文件,多个托管目录,听谁的?如果在第一个托管的目录中找到文件,就不在寻找;反之,去第二个托管目录寻找,依次直到找到为止
练习:编写文件04_three.js,使用express创建web服务器,托管静态资源到public目录下,包含登录文件login.html,点击提交向服务器发请求(post /mylogin),创建对应的路由,获取请求的数据
//引入express包
const express = require('express');
//引入查询字符串模块
//const querystring=require('querystring');
//1.引入body-parser模块
const bodyParser = require('body-parser');
const app = express();
app.listen(8080);
//托管静态资源到public
app.use(express.static('./public')); //托管静态资源代替了通过路由找寻文件:app.get('/login',(req,res)=>{res.sendFile(__dirname+'/login.html');});详见day04中的03_express.js
//2.将post请求的数据解析为对象
app.use(bodyParser.urlencoded({
//是否使用扩展的查询字符串模块qs;false不使用,此时会使用官方提供的querystring,true使用
extended: false
}));
//根据表单的请求创建对应的路由
//post /mylogin
app.post('/mylogin', (req, res) => {
//获取post请求的数据
//3.获取数据,前提已经使用了body-parser中间件
console.log(req.body);
//以往我们是通过事件,来获取post请求的数据
/*
req.on('data',(chunk)=>{
//格式为buffer,需要转字符串,转后格式为查询字符串
let str=String(chunk);
//将查询字符串解析为对象---需要引入querystring模块
let obj=querystring.parse(str);
console.log(obj);
})
*/
res.send('登录成功');
});
4. 第三方中间件
属于第三方模块,需要提前下载安装
使用body-parser将post请求数据解析为对象
//1.引入body-parser模块
const bodyParser=require('body-parser');
//2.使用body-parser中间件,会将所有post请求数据解析为对象
app.use( bodyParser.urlencoded({
//是否使用扩展的模块qs;true表示使用,false表示不使用,此时会自动使用querystring模块
extended:false
}) );
//3. 在路由中获取对象格式数据
req.body
🌴 注:body-parser中间件要写在路由或是路由器之前!
🏝️ 扩展扩展:关于body-parser中间件
body-parser代替了客户端post请求发送(传递)的http请求体内容,通过事件以流的形式获取buffer数据、转字符串再转为方便的对象格式内容的繁琐过程,从而让后台可以使用req.body直接获取(接收)为对象格式的数据
//😅通过事件:以往我们是通过事件,来获取post请求的数据 req.on('data',(chunk)=>{ //格式为buffer,需要转字符串,转后格式为查询字符串 let str=String(chunk); //将查询字符串解析为对象 let obj=querystring.parse(str); console.log(obj); })
不用body-parser的话,post请求必须通过req.on('data',(chunk)=>{…}以数据流事件来获取数据(buffer格式),进而利用全局函数String()或是tostring()方法转字符串、引入查询字符串模块解析为对象
😇闲话:express中间件—body-parser获取post、get数据
在express中,已经封装好获取get参数的方法,即req.query,但是post请求的参数却没有被封装,需要我们借助中间件(body-parser)来获取!
也就是说在express中对get请求内置了req.query来获取请求数据,对post请求,需要配合使用body-parser中间件来获取,否则无法使用req.body获取post传递的数据(不引入body-parser,默认获取到的是undefined)。这也就是为什么我们在nodejs里面使用express框架中的req.body 获取post传值为undifined的原因
另外,body-parser是非常常用的一个express中间件,作用是对post请求的请求体进行解析。即bodyparser的作用:它用于解析客户端请求的body中的内容,内部使用JSON编码处理,url编码处理以及对于文件的上传处理。使用非常简单,以下两行代码已经覆盖了大部分的使用场景。
①app.use(bodyParser.json());
②app.use(bodyParser.urlencoded({ extended: false }));
5. 错误处理中间件
错误处理中间件有 4 个参数,定义错误处理中间件时必须使用这 4 个参数。即使不需要 next 对象,也必须在签名中声明它,否则中间件会被识别为一个常规中间件,不能处理错误。
错误处理中间件和其他中间件定义类似,只是要使用 4 个参数,而不是 3 个,其签名如下: (err, req, res, next)。
app.use((err,req,res,next)=>{
console.error(err.stack)
res.status(500).send('Something broke!')
})
二. mysql模块
nodejs操作mysql数据库的工具模块
🌴 闲话:mysql模块跟express模块没有任何关系,他们都是第三方模块,需要下载安装,安装后,在node_modules文件夹里查看已下载的包
连接数据库
mysql -uroot
mysql -h127.0.0.1 -p3306 -uroot -p #数据库服务器IP mysql端口 用户名 密码
🌴 闲话:本机既做客户端又做服务器。本机做数据库服务器 ip为127.0.0.1、3306为MySQL的默认端口;本机又是客户端,用户名是root、密码为空,就像ATM机器取钱(要访问银行数据库服务器)一样要输入用户名密码
增删改查
select * from emp where eid=1;
insert into emp values(...);
update emp set sex=1,salary=8000 where eid=3;
delete from emp where eid=2;
下载安装
npm install mysql
mysql使用
mysql包的使用:https://www.npmjs.com/package/mysql
1. 创建普通连接
创建普通连接或是创建连接对象:👇
//创建连接对象。mysql模块下的Connection()方法
const c=mysql.createConnection({ }) //需要提供mysql连接的相关内容
//测试连接。connect()为连接对象下的connect方法
c.connect() //测试连接,可以省略。
//执行SQL命令
c.query(sql命令,回调函数) // 要执行的SQL命令,通过回调函数获取结果
🌴 注:连接对象下的query方法
连接对象.query(sql命令,回调函数)
c.query(sql命令,回调函数)
其中回调函数的参数:
err 可能产生的错误 ---比如sql命令写错了等等
result 具体SQL命令的执行结果
//引入mysql模块
const mysql = require('mysql');
//创建连接对象
const c = mysql.createConnection({
host: '127.0.0.1',
port: '3306',
user: 'root',
password: '',
database: 'tedu' //连接成功后要进入的数据库
});
//测试连接:没有报错就是连接成功
//c.connect();
//接下来,我们使用nodejs来操作数据库,即使用mysql模块下的query(sql命令,回调函数)方法操作数据库
//执行SQL命令
//连接对象下的query方法
//参数1:SQL命令
//参数2:回调函数,用于获取结果
c.query('SELECT * FROM emp', (err, result) => {
//err 可能产生的错误
if (err) throw err;
//result SQL命令执行的结果
console.log(result); //查看SQL命令执行的结果
});
2. 创建连接池
连接池中保存若干个mysql的连接,如果要操作数据库,只需要从中取出一个连接。当使用完会自动归还到连接池
创建连接池对象:👇
//创建连接池对象。mysql模块下的createPool()方法
const pool=mysql.createPool({ });
//执行SQL命令
pool.query(sql命令,回调函数);//要执行的SQL命令,通过回调函数获取结果
🌴 注:连接池对象下的query方法
连接池对象.query(sql命令,回调函数)
pool.query(sql命令,回调函数)
其中回调函数的参数:
err 可能产生的错误
result 具体SQL命令的执行结果 // sql命令执行后返回的对象存放在这里
❣️ 连接池对象没有上面普通连接对象的测试方法connect(),只能通过直接执行SQL命令pool. query(sql命令,回调函数)
SQL注入:在用户提供的条件中,添加了具有攻击性条件。
delete from emp WHERE eid=5 or 1=1;
1=1 所有的数据都满足这个条件
占位符(?):会对用户提供的条件进行过滤,把具有攻击性的条件给删除
pool.query( SQL命令,要过滤的数据,回调函数 )
要过滤的数据格式为数组,过滤完会自动替换到SQL命令中的占位符
pool.query('delete from emp WHERE eid=?',['5 or 1=1'],(err,result)=>{if(err) throw err;
console.log(result);
});
🌴 注:普通连接与连接池,没有区别,平时我们使用连接池更多
练习:往员工表emp下插入1条数据,对所有的值进行过滤。
//引入mysql模块
const mysql = require('mysql');
//创建连接池对象
const pool = mysql.createPool({
host: '127.0.0.1',
port: '3306',
user: 'root',
password: '',
database: 'tedu',
connectionLimit: '20'
});
//执行SQL命令
/*
//在员工表中查询员工编号为1的员工的所有信息
pool.query('SELECT * FROM emp WHERE eid=1',(err,result)=>{
if(err) throw err;
console.log(result);
})
//删除编号为5的员工信息 ------附带过滤数据的功能,防止sql注入
pool.query('DELETE FROM emp WHERE eid=?',['5 or 1=1'],(err,result)=>{
if(err) throw err;
console.log(result);
});
//在员工表中插入一条数据:使用连接池对象query方法,用对象的形式向数据库插入数据
pool.query('INSERT INTO emp VALUES(?,?,?,?,?,?)',[null,'然哥',0,'1977-5-29',50000,10],(err,result)=>{
if(err) throw err;
console.log(result);
});
*/
let ran = {
//向emp表插入数据时,由于缺失的列会默认使用默认值
//所以,eid列如果设置为空的话,可以省略,会使用默认值
//eid:null,
ename: '然然',
salary: 40000,
deptId: 20,
sex: 0
}
pool.query('INSERT INTO emp SET ?', [ran], (err, result) => { // 插入对象的方式只能在mysql模块里使用,在操作mysql数据库时不能这样使用,只能使用values
if (err) throw err;
console.log(result);
});
❣️:一般的,服务器获取从客户端请求过来的数据,无论是使用get或post方法请求的,最后服务器接受(获取)的数据(req.query或req.body)都会解析为对象格式,所以,在mysql模块里提供了在要过滤的数组里放置对象数据,这样的便利操作,仅限在mysql模块里使用,在正常操作数据库时不能这样使用,只能使用values
练习:创建web服务器,托管静态资源到public目录,包含部门添加网页add.html,输入部门名称,点击提交,向服务器发请求(get /add),创建对应的路由,在路由中获取传递的数据,将数据插入到数据库tedu下的dept表中,响应“部门添加成功”
*add.html文件
<h2>部门添加</h2>
<form method="get" action="/add">
部门名称<input type="text" name="dname">
<input type="submit">
</form>
*app.js文件
//引入express包
const express = require('express');
//引入mysql包
const mysql = require('mysql');
//创建web服务器
const app = express();
//设置端口
app.listen(8080);
//托管静态资源到public目录
app.use(express.static('./public'));
//连接mysql数据库,创建连接池
const pool = mysql.createPool({
host: '127.0.0.1',
port: '3306',
user: 'root',
password: '',
database: 'tedu',
connectionLimit: '20'
});
//根据表单请求创建对应的路由
//get /add
// 因为托管了静态资源,所以地址栏直接输入/add.html即可。http://127.0.0.1:8080/add.html
app.get('/add', (req, res) => {
//获取查询字符串传递的数据
console.log(req.query);
//将数据插入到数据表dept
pool.query('INSERT INTO dept SET ?', [req.query], (err, result) => {
if (err) throw err;
console.log(result);
//只有数据插入后再响应
res.send('部门添加成功');
});
});
🚀 写在最后 🚀
Tips:对中间件的一个小总结👇
1、中间件就是一种功能的封装方式,就是封装在程序中处理http请求的功能,
2、中间件是在管道中执行
3、中间件有一个next()函数,如果不调用next函数,请求就在这个中间件中终止了,
4、中间件和路由处理器的参数中都有回调函数,这个函数有2,3,4个参数 如果有两个参数就是req和res;如果有三个参数就是req,res和next 如果有四个参数就是err,req,res,next
5、如果不调用next ,管道就会终止,不会再有处理器做后续响应,应该向客户端发送一个响应
6、如果调用了next,不应该发送响应到客户端,如果发送了,则后面发送的响应都会被忽略
7、中间件的第一个参数可以是路径,如果忽略则全部都匹配
【后文传送门】👉 回顾node中如何使用Express模块写接口_05
如果这篇【文章】有帮助到你,希望可以给【青春木鱼】点个赞👍,创作不易,相比官方的陈述,我更喜欢用【通俗易懂】的文笔去讲解每一个知识点,如果有对【前端技术】感兴趣的小可爱,也欢迎关注❤️❤️❤️【青春木鱼】❤️❤️❤️,我将会给你带来巨大的【收获与惊喜】💕💕!