目录
一. 中间件
❣️ 中间件概念
❣️中间件结构
❣️中间件分类
1. 应用级中间件
2.路由级中间
3. 内置中间件
4. 第三方中间件
5. 错误处理中间件
二. mysql模块
1. 创建普通连接
2. 创建连接池
🚀 写在最后🚀
【前文回顾】👉初识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 /listapp.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 /shoppingapp.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是可选的,支持以下的属性:
如果你想获得更多关于使用中间件的细节,你可以查阅在 Express 中提供静态文件Serving static files in Express和使用中间件 - 内置中间件
❣️ Express 4.x API 中文文档:Express 4.x API 中文文档 | 菜鸟教程
❣️ Express 4.xAPI 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);//托管静态资源到publicapp.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 /myloginapp.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 -urootmysql -h127.0.0.1-p3306 -uroot -p #数据库服务器IPmysql端口 用户名 密码
🌴闲话:本机既做客户端又做服务器。本机做数据库服务器 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包的使用:/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数据库时不能这样使用,只能使用valuesif (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.htmlapp.get('/add', (req, res) => {//获取查询字符串传递的数据console.log(req.query);//将数据插入到数据表deptpool.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
如果这篇【文章】有帮助到你,希望可以给【青春木鱼】点个赞👍,创作不易,相比官方的陈述,我更喜欢用【通俗易懂】的文笔去讲解每一个知识点,如果有对【前端技术】感兴趣的小可爱,也欢迎关注❤️❤️❤️【青春木鱼】❤️❤️❤️,我将会给你带来巨大的【收获与惊喜】💕💕!