一、什么是Express
	1、Express
	白龙网认为,Express是基于node.js平台的开发框架,它的作用和node.js的http模块类似,是专用来创建web服务器的。它的本质是一个npm上的第三方包,提供了快速创建web服务器的方法。
	有了内置的http模块,为什么要用Express?Express是基于http模块进一步封装出来的,能够极大的提高开发效率。http与Express关系,类似于web api与jQuery的关系,后者是基于前者进一步封装出来的。
	使用Express可以方便快捷的创建以下两种服务器:web网站服务器:、api接口服务器。
	2、安装Express
	npm install express
	3、创建web服务器
	创建服务器后,可以调用GET POST SEND等方法,还可以调用query params等参数,获取用户传递的相关数据。
	//1.导入express
	const express = require('express')
	//2.创建web服务器
	const app = express()
	//4.调用express提供了get post 方法,并向客户端响应相应的内容
	//4-1调用app.get()
	app.get('/user',(req,res) => {
	    //4-1-1res.send()内容可以是对象,测试时要注释响应的对象段代码
	    res.send({name:'白龙网',business: 'SEO',age: '18'})
	    //5.通过req.query参数,可以访问到客户通过查询字符串的形式,发送到服务器的参数; 默认情况下,返回的是空对象;当在URL中设置 ?age=18&name=bailong类的参数时,会被req.query获取到
	    console.log(req.query)
	    res.send(req.query)
	})
	 //6.通过req.params通过可以访问到URL中,通过:匹配的动态参数;默认是个空对象;:id是参数的名子,若是1,则打印1,若是2,则打印2……;id名称可任意写,例如ids等;动态参数可以是多个,例如,/user/:id/:name
	 app.get('/user/:id',(req,res) => {
	    console.log(req.params);
	    res.send(req.params)
	 })
	 //7.托管静态资源,express提供了一个非常好用的函数,express.static(),通过它,可以方便的创建一个静态资源服务器。例如,可以把公共目录下的图片、CSS文件、JS文件对外开放访问。存放静态文件的目录名不会出现在URL中。如果要托管多个静态资源文件内的内容,则多次调用express.static()即可,按照先后顺序加载。
	 app.use(express.static('./bailong'))
	  //8.挂载路径前缀,即规划目录结构
	  app.use('/list',express.static('./bailong'))
	//4-2调用app.post
	app.post('/user',(req,res) => {
	    //4-2-1res.send()内容可以是文本
	    res.send('请求成功')
	})
	//3.启动web服务器
	app.listen(80,() => {
	    console.log('web server running at 127.0.0.1');
	})
	4、nodemon
	在编写测试node.js项目的时候,如果修改了项目的代码,则需要频繁的手动close掉,然后再重新启动,非常管繁琐。可以使用nodemon这个工具,来监听项目文件的变动,当代码被修改后,nodemon会自动帮助我们重启项目,方便调试。
	安装了该工具之后,就不需要像之前那样,先重启服务器,再执行node app.js执行命令,直接执行nodemon app.js就可以自动重启项目并应用最新代码。期间可能会遇到服务器端口号冲突的问题,换个其它端口即可。
	命令:npm i -g nodemon
二、Express路由
	1、路由
	就是映射关系。 在express中,路由是指客户端与服务器处理函数之间的映射关系。分为请求类型、请求的URL地址、处理函数等大部分。语法格式是:app.method(path,handler)。
	2、路由的匹配过程
	当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数。
	在匹配时,会按照请求的先后顺序进行匹配,如果请求的类型和请求的URL同时匹配成功,则express会将这次的请求,转交给对应的function函数处理。
	路由挂载的越多,体积会越来越大,用的比较少。
	3、路由的使用
	//1.导入express
	const express = require('express')
	//2.创建一个服务器
	const app = express()
	app.get('/',function(req,res) {
	    res.send('get请求成功')
	})
	app.post('/',function(req,res) {
	    res.send('post请求成功')
	})
	//3.启动服务器
	app.listen(8091,() => {
	    console.log('server running at http:127.0.0.1:8080');
	})
	4、路由的模块化
	路由挂载到实例上,会导致代码越来越多,体积越来越大,建议把路由抽离为单独的模块。
	A.新建router.js文件,按照如下步骤创建路由模块
	    //1.导入express
	    const express = require('express')
	    //2.创建路由对象
	    const router = express.Router()
	    //3.挂载具体的路由
	    router.get('/user/list',(req,res) => {
	        res.send('get user list')
	    })
	    router.post('/user/add',(req,res) => {
	        res.send('add usr list')
	    })
	    //4.导出路由对象
	    module.exports = router
	B.在服务器上(index.js)导入、注册路由模块
	    const express = require('express')
	    const app = express()
	    //1.导入路由模块
	    const router = require('../router')
	    //2.注册路由模块,app.use()函数的作用,是用来注册中间件;
	    // app.use(router)
	    //2-2为跌幅添加前缀
	    app.use('/api',router)
	    app.listen(3050,() => {
	        console.log('express server running at http://127.0.0.1:3050');
	    })
	C.执行nodemon index.js,查看返回数据
三、Express中间件
	middleware,业务流程的中间处理环节。
	当一个请求到达express服务器后,可以连续调用多个中间件,从而对这次请求进行预处理。上一个中间件的输出,作为下一个中间件的输入。
	express中间件,本质是一个function处理函数。例如,app.get('/',function(req,res,next) {next()})。中间件的形参中,必须包含一个next参数,而路由的处理函数只有req,res。
	next()函数的作用,是实现多个中间件连续调用的关键,它表示流转关系转交给下一个中间件的或者路由。
	1、定义中间件函数
	  const express = require('express')
	  const app = express()
	  //定义一个中间件函数
	  const mw = function(req,res,next) {
	      console.log('这是一个中间件函数');
	      //把流转关系提交给一下函数或者路由
	      next()
	  }
	  app.listen(8070,() => {
	      console.log('server running at http://127.0.0.1');
	  })
	2、全局生效中间件
	客户端发起的任何请求,到达服务器后,都会触发中间件,这样的中间件叫全局生效中间件。通过调用app.use(中间件函数),即可定义一个全局生效的中间件。
	const express = require('express')
	const app = express()
	//定义一个中间件函数
	const mw = function(req,res,next) {
	    console.log('这是一个中间件函数');
	    //把流转关系提交给一下函数或者路由
	    next()
	}
	//把MW注册为全局生效中间件
	  app.use(mw)
	  //请求到达服务器后,先触发全局生效中间件,然后触发定义的路由
	  app.get ('/',(req,res) => {
	      console.log('调用了/这个路由');
	      res.send('home page')
	  })
	  app.get('/user',(req,res) => {
	      console.log('调用了/USER路由');
	      res.send('user page')
	  })
	  app.listen(8070,() => {
	      console.log('server running at http://127.0.0.1');
	  })
	3、定义全局中间件的简化形式
	就是把定义与注册全局生效中间件函数合并为一步完成。
	  const express = require('express')
	  const app = express()
	  //定义并注册全局有效中间件函数
	  app.use((req,res,next) => {
	      console.log('定义并注册了一个全局生效中间件函数');
	      next()
	  })
	  //请求到达服务器后,先触发全局生效中间件,然后触发定义的路由
	  app.get ('/',(req,res) => {
	      console.log('调用了/这个路由');
	      res.send('home page')
	  })
	  app.get('/user',(req,res) => {
	      console.log('调用了/USER路由');
	      res.send('user page')
	  })
	  app.listen(8070,() => {
	      console.log('server running at http://127.0.0.1');
	  })
	4、中间件的作用
	多个中间件之间,共享同一份req,res,基于这样的特性,可以在上游中间件中,统一为对象添加自定义的属性和方法,供下游的中间件或者路由使用。
	  const express = require('express')
	  const app = express()
	  app.use((req,res,next) => {
	      const time = new Date()
	      //在中间件定义一个属性,那么下级中间件或者路由都可以使用
	      req.startTime = time
	      next()
	  })
	  app.get ('/',(req,res) => {
	      res.send('home page' + req.startTime)
	  })
	  app.get('/user',(req,res) => {
	      res.send('user page' + req.startTime)
	  })
	  app.listen(8070,() => {
	      console.log('server running at http://127.0.0.1');
	  })
	5、定义多个全局中间件
	使用app.use()连续定义多个全局足件,客户端请求到达之后,按照中间件定义的先后顺序去执行。
	  const express = require('express')
	  const app = express()
	  //定义第一个中间件
	  app.use((req,res,next) => {
	      console.log('这是第一个中间件');
	      next()
	  })
	  //定义第二个中间件
	  app.use((req,res,next) => {
	      console.log('这是第二个中间件');
	      next()
	  })
	  //定义一个路由
	  app.get('/user',(req,res) => {
	      res.send('访问了/user页面')
	  })
	  app.listen(80,() => {
	      console.log('server running http://127.0.0.1');
	  })
	6、定义局部中间件
	不使用app.use()定义的中间件,叫做局部生效中间件。
	  const express = require('express')
	  const app = express()
	  //定义局部中间件函数
	  const mw = (req,res,next) => {
	      console.log('调用了局部中间件函数');
	      next()
	  }
	  //定义第一个路由,局部中间件只能在第一个路由生效,而在第二个路由中无法生效
	  app.get('/',mw,(req,res) => {
	      res.send('home page')
	  })
	  //定义第二个个路由
	  app.get('/user',(req,res) => {
	      res.send('访问了/user页面')
	  })
	  app.listen(80,() => {
	      console.log('server running http://127.0.0.1');
	  })
	7、定义多个局部中间件
	可以用逗号分隔多个局部中间件,也可以用数组的形式调用多个局部中间件。
	  const express = require('express')
	  const app = express()
	  //定义第一个局部中间件函数
	  const mw1 = (req,res,next) => {
	      console.log('定义第一个局部中间件函数');
	      next()
	  }
	  //定义第二个局部中间件函数
	  const mw2 = (req,res,next) => {
	      console.log('定义第二个局部中间件函数');
	      next()
	  }
	  //调用多个路由的方法:mw1,mw2;或者[mw1,mw2]
	  app.get('/',mw1,mw2,(req,res) => {
	      res.send('home page')
	  })
	  //定义第二个个路由
	  app.get('/user',(req,res) => {
	      res.send('访问了/user页面')
	  })
	  app.listen(80,() => {
	      console.log('server running http://127.0.0.1');
	  })
	注意事项
	一定要在路由之间注册中间件;正确的方式,声明、注册中间件,添加路由;
	客户端发送过来的请问,可以连续调用多个中间件;
	执行业务代码之后,必须要有next()函数;
	为了防止代码的逻辑混乱,调用next()函数之后,不再写额外的代码;
	连续调用多个中间件的时候,多个中间件之间是共享req,res的。
	8、中间件的分类
	A.应用级别的中间件
	app.use() app.get() app.post()等,绑定到app实例上的中间件,都叫应用级别中间件。涉及全局或者局部中间件。
	B.路由级别中间件
	绑定到express.Router()实例上的中间件,叫路由级别的中间件。它的用法与应用级别的中间件没有区别,只不过,应用级别中间件是绑定到app实例上;路由级别的中间件是绑定到router上。
	C.错误级别中间件
	作用是专门用来捕获项目中发生异常的错误,从而防止项目异常导致崩溃的问题。错误中间件的错误处理函数中必须包含4个形参,形参顺序从前到后分别是:err,req,res,next。
	  const express = require('express')
	  const app = express()
	  app.get('/',(req,res) => {
	      //抛出异常
	    throw new Error('服务器发生了错误')
	      res.send('home page')
	  })
	  //定义一个错误中间件,捕获错误并给出提示,错误中间件必须注册在所有路由之后;否则无法正常工作
	  app.use((err,req,res,next) => {
	      console.log('发生了错误' + err.message);
	      res.send('Error:' + err.message)
	  })
	  app.listen(80,() => {
	      console.log('server running http://127.0.0.1');
	  })
	D.express内置中间件
	express4.16版本开始,express内置了3个常用的中间件:
	【express.static】快速托管静态资源的中间件,例如,HTML/CSS/JS文件;
	【express.json】解析json格式的请求数据,有兼容性要求,仅在4.16+版本中可用;例如,app.use(express.json())
	    const express = require('express')
	    const app = express()
	    //express.json()解析客户端提交的JSON数据,测试时,可以在postman工具下使用post请求服务器,在body->raw->json下输入JSON格式的数据对象{"name":"白龙网","age":18}
	    app.use(express.json())
	    app.post('/user',(req,res) => {
	        //req.body用来获取客户端提交的数据;默认情况下,如果没有express.json()的加持,req.body获取的数据是undefined
	        console.log(req.body);
	        res.send('ok')
	    })
	    app.listen(80,() => {
	        console.log('server running http://127.0.0.1');
	    })
	【express.urlencoded】解析url-encoded格式的请求数据,有兼容性要求,仅仅在4.16+版本中可用;例如,app.use(express.urlencoded(extended: false))
	    const express = require('express')
	    const app = express()
	    app.use(express.urlencoded({extended: false}))
	    app.post('/book',(req,res) => {
	        console.log(req.body);
	        res.send('book ok')
	    })
	    app.listen(80,() => {
	        console.log('server running http://127.0.0.1');
	    })
	E.第三方中间件
	非内置,第三方开发的中间件,叫做第三方中间件。按需下载即可。
	要使用第三方中间件,先安装body-parser,再导入,配置
	    const express = require('express')
	    const app = express()
	    //1.导入模块
	    const parser = require('body-parser')
	    //2.配置客户端请求的表单数据格式,类似于中间件
	    app.use(parser.urlencoded({extended: false}))
	    app.post('/user',(req,res) => {
	        console.log(req.body);
	        res.send('books ok')
	    })
	    app.listen(80,() => {
	        console.log('server running http://127.0.0.1');
	    })
	9、自定义中间件
	  (1)完整功能
	    const express = require('express')
	    //导入内置的模块,使用其中的方法parse()把字符串解析为对象
	    const qs = require('querystring')
	    const app = express()
	    //1.定义一个解析表单数据的中间件
	    //2.定义一个字符串,用来存储客户端发来的请求数据
	    let str = ''
	    app.use((req,res,next) => {
	    //3.监听req的data事件
	    req.on('data',(chunk) => {
	        str += chunk
	    })
	    //4.监听req的end事件,在str中存放的是完整的请求数据
	    req.on('end',() => {
	        console.log(str);
	        //5.把字符串解析成对象
	      const body = qs.parse(str)
	      console.log(body);
	      //6.挂载body数据,供下游路由使用
	      req.body = body
	      next()
	    })
	    })
	    //5.向服务器请求数据
	    app.post('/user',(req,res) => {
	        //7.直接使用上游挂载的req.body数据
	        res.send(req.body)
	    })
	    app.listen(80,() => {
	        console.log('server running http://127.0.0.1');
	    })
	(2)封装成模块需要2步
	  A.新建custom-body-parse.js文件,主体功能如下:
	  //导入内置的模块,使用其中的方法parse()把字符串解析为对象
	  const qs = require('querystring')
	  let str = ''
	  const customBodyParse =  (req,res,next) => {
	  //3.监听req的data事件
	  req.on('data',(chunk) => {
	      str += chunk
	  })
	  //4.监听req的end事件,在str中存放的是完整的请求数据
	  req.on('end',() => {
	      console.log(str);
	      //5.把字符串解析成对象
	    const body = qs.parse(str)
	    console.log(body);
	    //6.挂载body数据,供下游路由使用
	    req.body = body
	    next()
	  })
	  }
	  //导出函数
	  module.exports = customBodyParse
	B.在服务器上导入自定义模块并注册
	  const express = require('express')
	  const app = express()
	  //1.导入自定义的中间件
	  const customBodyParse = require('./custom-body-parse.js')
	  //2.注册自定义中间件
	  app.use(customBodyParse)
	  //5.向服务器请求数据
	  app.post('/user',(req,res) => {
	      //7.直接使用上游挂载的req.body数据
	      res.send(req.body)
	  })
	  app.listen(80,() => {
	      console.log('server running http://127.0.0.1');
	  })
四、使用Express写接口
	1、创建服务器、api路由模块、GET/POST接口
	(1)API路由模块
	如下代码放入apiRouter文件即可
	    //1.定义一个api路由模块并公开出去
	    const express = require('express')
	    const router = express.Router()
	    //2.定义一个get接口,需要注意的是,网站地址后面的目录是:/api/get
	    router.get('/get',(req,res) => {
	        //2-1通过req.query获取客户端通过查询字符串,发送到服务器的数据
	        const query = req.query
	        //2-2通过res.send()方法,向客户端响应处理的结果
	        res.send({
	            status: 0,
	            msg: 'GET请求成功',
	            data: query
	        })
	    })
	    //3.定义一个post接口
	    router.post('/post',(req,res) => {
	        //3-1接收客户端提交的数据,使用postman工具测试提交数据时,要在url-encoded格式下发送数据
	      const body = req.body
	      //3-2向客户端响应数据
	      res.send({
	        status: 0,
	        msg: 'POST请求成功',
	        data: body
	      })
	    })
	    //4.定义一个delete接口
	    router.delete('/delete',(req,res) => {
	        res.send({
	            staus: 0,
	            msg: 'DELETE请求成功'
	        })
	    })
	    module.exports = router
	(2)在服务器中导入并注册API路由模块
	如下代码放入index.js执行即可
	    //1.创建服务器
	    const express = require('express')
	    const app = express()
	    //3.获取encodeed格式的数据,必须配置中间件
	    app.use(express.urlencoded({extended: false}))
	    //4-1导入中间件cors,解决跨域问题
	    const cors = require('cors')
	    //4-2注册cors中间件
	    app.use(cors())
	    //2-1.导入apiRouter
	    const router = require('./apiRouter')
	    //2-2注册
	    app.use('/api',router)
	    app.listen(80,() => {
	        console.log('server running http://127.0.0.1');
	    })
	2、解决接口跨域问题
	像本地服务器引用在线JQ库的接口跨域问题,推荐使用cors中间件解决,不推荐JSONP,因为JSONP存在缺陷,不只支持GET,不支持POST。
(1)CORS中间件
(1)CORS中间件
	通过安装和配置cors中间件,方便的解决跨域问题。一定要在路由之前配置CORS中间件,实施步骤:
	安装:npm i cors
	导入:const cors = require('cors')
	注册:app.use(cors())
	例如,下面代码,本地测试,导入在线JQ,就是跨域问题,利用cors中间件解决数据互通的问题。
	    <!DOCTYPE html>
	    <html lang="en">
	    <head>
	        <meta charset="UTF-8">
	        <meta http-equiv="X-UA-Compatible" content="IE=edge">
	        <meta name="viewport" content="width=device-width, initial-scale=1.0">
	        <title>Document</title>
	        <script src="https://cdn.jsdelivr.cn/npm/jquery@1.12.4/dist/jquery.min.js"></script>
	    </head>
	    <body>
	        <button id="btnGet">GET请求</button>
	        <button id="btnPost">POST请求</button>
	        <button id="btnDelete">删除</button>
	        <script>
	            $(function() {
	            //测试gety请求
	                $('#btnGet').on('click',function() {
	                    $.ajax({
	                        type: 'get',
	                        url: 'http://127.0.0.1/api/get',
	                        data: {name: '白龙网',age: 16},
	                        success: function(res) {
	                            console.log(res);
	                        }
	                    })
	                })
	            //测试POST请求
	                $('#btnPost').on('click',function() {
	                    $.ajax({
	                        type: 'post',
	                        url: 'http://127.0.0.1/api/post',
	                        data: {author: '白龙',gender: '男'},
	                        success: function(res) {
	                            console.log(res);
	                        }
	                    })
	                })
	            //为删除按钮绑定事件,测试
	                $('#btnDelete').on('click',function() {
	                    $.ajax({
	                        type: 'delete',
	                        url: 'http://127.0.0.1/api/delete',
	                        success: function(res) {
	                            console.log(res);
	                        }
	                    })
	                })
	            })
	        </script>
	    </body>
	    </html>
	CORS是跨域资源共享,由一系列的HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止JS代码跨域捕获资源。
	浏览器的同源安全策略默认会阻止网页跨域获取资源,但是,如果接口服务器配置了CORS相关的HTTP响应头,就可以解决浏览器的跨域访问限制。
	(2)注意事项
	只在服务器端配置,客户端无须任何额外的配置,即可请求开启了CORS的接口;
	有兼容性,只有支持XHR 2的浏览器,才支持
	(3)响应头部
	  A.允许所有网站请求/指定网站请求
	   res.setHeader('Access-Control-Allow-Origin','*')
	   res.setHeader('Access-Control-Allow-Origin','bailong.org.cn')
	  B.默认情况下,CORS仅支持客户端向服务器端发送的如下9个请求头:Accept、Accept-Language、Content-Language、DPR、DownLink、Save-Data、Viewport-Width、Width、Content-Type。
	   res.setHeader('Access-Control-Allow-Headers','Content-Type,x-custom-Header')
	  C.服务器支持所有的请求方式/或者指定的请求方式,默认只支持下述4个
	   res.setHeader('Access-Control-Allow-methods','*')
	   res.setHeader('Access-Control-Allow-Origin','GET,POST,PUT,DELETE')
	(4)请求的类型
	A.简单请求
	请求方式:GET/POST/HEAD三者之一
	HTTP头部信息不超过常用的9个,无自定义头部字段,Accept、Accept-Language、Content-Language、DPR、DownLink、Save-Data、Viewport-Width、Width、Content-Type。
	B.预检请求
	简单请求的对立面,只要符合以下任何一个条件:
	请求方式为get ,post,head之外的请求类型
	请求头中包含自定义头部字段
	向服务器发送了application/json格式的数据
	在浏览器与服务器正式通信之前,浏览器先发送OPTION请求进行预检,以获知服务器是否允许该实际请求,所以这一次的OPTION请求称为“预检请求”,服务器成功响应预检请求后,才会发送真正的请求,并且拾真实的数据。
	C.简单请求和预检请求的区别
	简单请求的特点:客户端与服务器之间只会发生一次请求;
	预检请求的特点:客户端与服务器之间会发生两次请求,OPTION预检请求之后,才会发起真正的请求;
	3、JSONP接口
	(1)JSONP及其特点
浏览器通过<script>标签的src属性,请求服务器上的数据,服务器返回一个函数的调用,这种请求数据的方式叫做JSONP。其特点如下:
浏览器通过<script>标签的src属性,请求服务器上的数据,服务器返回一个函数的调用,这种请求数据的方式叫做JSONP。其特点如下:
	JSONP不属于ajax请求,因为它没有使用XHR这个对象;
	JSONP仅仅支持GET请求,不支持POST PUT DELETE等请求。
	(2)创建JSONP注意事项
	如果项目中已经配置好了CORS跨域资源共享,为了防止冲突,必须在配置CORS中间件之前声明JSONP的接口,否则JSONP接口会被处理成开启了CORSR的接口。如下代码:
	A.JSONP接口代码
	  //JSONP必须在CORS中间件之前配置
	  app.get('/api/jsonp',(req,res) => {
	      //todo
	      //得到函数的名称
	      const funName =req.query.callback;
	      //定义要发送到客户端的数据对象
	      const data = {sitename: '白龙网',age: 18}
	      //拼接出一个函数的调用
	      const scriptStr = '${funName}(${JSON.stringify(data)})'
	      //把拼接的字符串响应到客户端
	      res.send(scriptStr)
	  })
	B.通过页面发起请求,测试JSONP接口
	        $('#btnJson').on('click',function() {
	            $.ajax({
	                type: 'get',
	                url: 'http://127.0.0.1/api/jsonp',
	                dataType: 'jsonp',
	                success: function(res) {
	                    console.log(res);
	                }
	            })
	        })