一、初识Node.js
1.1 什么是Node.js
Node.js是一个基于Chrome V8引擎的JavaScript运行环境
1.2 Node.js中的JavaScript运行环境
包含V8引擎和内置API(fs、path、http、js内置对象、querystring…)
- 浏览器是JavaScript的前端运行环境
- Node.js是JavaScript的后端运行环境
- Node.js中无法调用DOM和BOM等浏览器内置API
1.3 Node.js可以做什么?
Node.js作为一个JavaScript 的运行环境,仅仅提供了基础的功能和API。然而,基于Nodejs 提供的这些基础能,很多强大的工具和框架如雨后春笋,层出不穷,所以学会了Nodejs,可以让前端程序员胜任更多的工作和岗位:
- 基于Express框架(http://www.expressjs.com.cn/),可以快速构建Web应用
- 基于Electron框架(https://electronjs.org/),可以构建跨平台的桌面应用
- 基于restify框架(http://restify.com/),可以快速构建API接口项目读写和操作数据库、创建实用的命令行工具辅助前端开发、etc…
总之: Node.js是大前端时代的“大宝剑”,有了Node.js这个超级buff的加持,前端程序员的行业竞争力会越来越强!
1.4 在Node.js环境中执行JS代码
node 文件名
二、fs文件系统模块
2.1 什么是文件系统模块
fs模块是Node.js官方提供的、用来操作文件的模块,它提供了一系列的方法和属性,用来满足用户对文件的操作需求
例如:
fs.readFile()
方法,用来读取指定文件中的内容fs.writeFile()
方法,用来向指定的文件中写入内容
如果要在JavaScript代码中,使用fs模块来操作文件,则需要使用如下的方式先导入它:
const fs = require( 'fs ')
2.2 读取指定文件中的内容
使用fs.readFile()
方法,可以读取指定文件中的内容,语法格式如下:
fs.readFile(path[,options],callback)
- 参数1:必选参数,字符串,表示文件的路径
- 参数2:可选参数,表示以什么编码格式来读取文件
- 参数3:必选参数,文件读取完成后,通过回调函数拿到读取的结果。
示例代码:
// 1、导入 fs 文件系统模块
const fs = require('fs')
// 2、调用fs.readFile() 方法读取文件
fs.readFile('./files/11.txt', 'utf8', function (err, data) {
// 当文件不存在读取失败
// 如果读取失败,则err的值为错误对象,data的值为undefined
console.log(err);
// 如果读取成功,则err的值为null
// 可以判断err是否为null,为null则读取成功
if (err) {
console.log('文件读取失败' + err.message)
}
console.log(data);
})
2.3 向指定的文件中写入内容
使用fs.writeFile()
可以向指定的文件中写入内容,语法格式如下:
fs.writeFile(file, data[, options], callback)
- 参数1:必选参数,字符串,表示文件的存放路径
- 参数2:必选参数,表示要写入的内容
- 参数3:可选参数,表示以什么编码格式来读取文件,默认值是utf8
- 参数4:必选参数,文件写入完成后的回调函数。
示例代码:
// 1、导入 fs 文件系统模块
const fs = require('fs')
// 2、调用fs.writeFile() 方法,写入文件内容
fs.writeFile('./files/2.txt', '你好', 'utf8', function (err) {
// 如果文件夹不存在,则写入失败。
// 若文件夹存在,文件不存在则创建文件
// 若文件存在,则写入内容覆盖原文件
// 如果写入失败,则err的值为错误对象,data的值为undefined
console.log(err);
// 可以判断err是否为null,为null则写入成功
if (err) {
console.log('文件写入失败' + err.message)
}
})
2.4 案例
需求:将当前路径下的文件夹files下的3.txt文件中的内容写入4.txt,并实现格式转换
原始格式:
小红=100 小白=98 小绿=90
完成格式:
小红:100
小白:98
小绿:90
实现:
// 导入fs模块
const fs = require('fs')
// 2.调用fs.readFile()方法读取文件
fs.readFile('./files/3.txt', 'utf8', function (err, dataStr) {
// 3.判断文件是否读取成功
if (err) {
return console.log('读取文件失败' + err.message)
}
console.log('读取文件成功' + dataStr)
// 4.1先把成绩的数据,按照空格进行分割
const arrOld = dataStr.split(' ')
// 4.2循环分割后的数组,对每一项的数据,进行字符串的替换操作
const arrNew = []
arrOld.forEach(item => {
arrNew.push(item.replace('=', ':'))
})
// 4.3把新数组中的每一项,进行合并,得到新的字符串
const newStr = arrNew.join('\n')
// 5.调用fs.writeFile()方法,把处理完毕的成绩,写入到新文件中
fs.writeFile('./files/4.txt', newStr, function (err) {
if (err) {
return console.log('写入文件失败!' + err.message)
}
console.log('写入文件成功!')
})
})
2.5 路径动态拼接的问题
在使用fs 模块操作文件时,如果提供的操作路径是以/或…/开头的相对路径时,很容易出现路径动态拼接错误的问题。
原因:代码在运行的时候,会以执行 node 命令时所处的目录,动态拼接出被操作文件的完整路径。
解决方案:
- 在使用fs模块操作文件时,直接提供完整的路径,不要提供/或…/开头的相对路径,从而防止路径动态拼接的问题。
- 使用
__dirname
表示当前文件所在的目录
const fs = require('fs')
//__dirname表示当前文件所在的目录
fs.readFile(__dirname + '/files/1.txt', 'utf8', function (err, data) {
if (err) {
return console.log('读取文件失败' + err.message)
}
console.log('读取文件成功' + data)
})
三、path路径模块
3.1 什么是路径模块
path模块是Node.js官方提供的,用来处理路径的模块,它提供一系列的方法和属性,用来满足用户对于路径的处理需求
例如:
path.join()
方法,用来将多个路径片段拼接为较为完整的路径字符串path.basename()
,用来从路径字符串中,将文件名解析出来
如果要在JavaScript代码中,使用path模块来处理路径,则需要使用以下的方式先导入它
const path = require('path')
3.2 路径拼接
使用path.join
方法,可以把多个路径片段拼接为较为完整的路径字符串
示例代码:
const path = require('path')
const pathStr = path.join('a', '/b/c', '..', './d', 'e')
console.log(pathStr)//输出a\b\d\e
const pathStr2 = path.join(__dirname, '.files/1.txt')
console.log(pathStr2)//输出当前文件所处目录\.files\1.txt
3.3 获取路径中的文件名
使用path.basename()
可以获取路径中的最后一部分,经常通过这个方法获取路径中的文件名,语法格式如下:
path.basename(path[,ext])
- 参数1: 必选参数,表示一个路径的字符串
- 参数2: 可选参数,表示文件拓展名
- 返回:表示路径中的最后一部分
示例代码:
const path = require('path')
const fPath = 'la/b/c/index.html'//文件的存放路径
const fullName = path.basename(fPath)
console.log(fullName)//输出index.html
const nameWithoutExt = path.basename(fPath, '.html')
console.log(nameWithoutExt)//输出index
3.4 获取路径中的文件扩展名
使用path.extname()
方法可以获取路径中的拓展名部分,语法格式如下
path.extname(path)
- path:必选参数,表示一个路径的字符串
- 返回:返回得到的拓展名字符串
示例代码:
const path = require('path')
const fPath = '/a/b/c/index.html' //路径字符串
const fExt = path.extname(fPath)
console.log(fExt)//输出.html
四、http模块
4.1 什么是http模块
Http模块是Node.js官方提供的,用来创建web服务器的模块。通过http模块提供的Http.createServer()
方法,就能方便的把一台普通电脑,变成一台web服务器,从而对外提供Web资源服务。
如果要在JavaScript代码中,使用path模块来创建web服务器,则需要使用以下的方式先导入它
const http=require('http')
4.2 创建最基本的web服务器
// 1.导入Http模块
const http = require('http')
// 2.创建web服务器实例
const server = http.createServer()
// 3.为服务器实例绑定request事件,监听客户端的请求
//使用服务器实例的.on()方法,为服务器绑定一个request事件
server.on('request', (req, res) => {
//只要有客户端来请求我们自己的服务器,就会触发request事件,从而调用这个事件处理函数
console.log('someone visit our web server.')
})
// 4.启动服务器
//调用server.listen(端口号,cb回调)方法,即可启动web服务器
server.listen(80, () => {
console.log('http server running at http://127.0.0.1')
})
4.3 request请求对象
只要服务器接收到了客户端的请求,就会调用server.on()
为服务器绑定的request事件处理函数
如果想在事件处理函数中,访问与客户端相关的数据或属性,可以使用如下的方式:
server.on('request',(req,res)=>{
//req是请求对象,它包含了与客户端相关的数据和属性,例如:
//req.url是客户端请求的url地址
//req.method 是客户端的method请求类型
//req.body 是客户端发送过来的请求体数据
const str='Your request url is ${req.url},and request method is ${req.method}'
console.log(str)
})
4.4 response响应对象
server.on('request',(req,res)=>{
//res是响应对象,它包含了与服务器相关的数据和属性,例如:
//要发送到客户端的字符串
const str='Your request url is ${req.url},and request method is ${req.method}'
//res.end()方法的调用:
//向客户端发送指定内容,并结束这次请求的处理过程
res.end(str)
})
4.5 解决中文乱码问题
当调用res.end()
方法,向客户端发送中文内容的时候,会出现乱码问题,此时,需要手动设置内容的编码格式
server.on('request',(req,res)=>{
//发送的内容包含中文
conststr='您请求的url地址是${req.url},请求的method类型是${req.method}'
//为了防止中文显示乱码的问题,需要设置响应头
res.setHeader('Content-Type','text/html;charset=utf-8')
//把包含中文的内容,响应给客户端
res.end(str)
})
4.6 根据不同的url响应不同的html内容
// 1.导入Http模块
const http = require('http')
// 2.创建web服务器实例
const server = http.createServer()
server.on('request', function (req, res) {
//1.获取请求的Url地址
const url = req.url
//2.设置默认的内容
let content = '<h1>404 Not found!</h1>'
//3.判断用户请求的url
if (url === '/' || url === '/index.html') {
content = '<h1>首页</h1>'
} else if (url === '/about.html') {
content = '<h1>关于页面</h1>'
}
//为了防止中文显示乱码的问题,需要设置响应头
res.setHeader('Content-Type', 'text/html;charset=utf-8')
//把包含中文的内容,响应给客户端
res.end(content)
})
// 4.启动服务器
//调用server.listen(端口号,cb回调)方法,即可启动web服务器
server.listen(80, () => {
console.log('http server running at http://127.0.0.1')
})
五、模块化
5.1 什么是模块化
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分为若干模块的过程。对于整个系统来说,模块是可组合、分解和变换的单元。
编程领域中的模块化:就是遵守固定的规则,把一个大文件拆成独立并且互相依赖的多个小模块。
模块化拆分的好处:
- 提高了代码的复用性
- 提高了代码的可维护性
- 可以实现按需加载
5.2 Node.js中的模块化
根据模块来源的不同,将模块分为了三大类,分别是:
- 内置模块(node.js官方提供,如fs,path,http等)
- 自定义模块(用户创建的每个Js文件)
- 第三方模块(由第三方开发出来的模块)
记载模块
使用require()
方法,可以加载模块
//加载内置模块
const http = require('http')
//加载自定义模块
const aa = require('./aa.js')
//加载第三方模块
const moment = require('moment')
注意:使用require()
方法加载其他模块时,会执行被加载模块中的代码
什么是模块作用域
和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域
模块作用域有效防止了全局变量污染的问题
示例代码:
a.js
const username = '张三'
b.js
const a = require('./a')
console.log(a)//输出空对象
//无法访问到a模块的私有成员
向外共享模块作用域中的成员
在每个自定义模块中都有一个module
对象,它里面存储了和当前模块有关的信息,打印如下
Module {
id: '...',
path: '...',
exports: {...},
filename: '...',
loaded: false,
children: [...],
paths: [
...
]
}
可以使用Module.exports
对象将模块内的成员共享出去,供外界使用。外界用require()
方法导入自定义模块时,得到的就是Module.exports
所指的对象
代码示例:
a.js
// module.exports.username = '张三'
// module.exports.say = function (){
// console.log('i am 张三')
// }
// 两种方式都可
module.exports = {
nickname: '黑子',
say() {
console.log('i am 小黑子')
}
}
b.js
const a = require('./a')
console.log(a);//{ nickname: '黑子', say: [Function: say] }
由于module.exports
单词写起来比较复杂,为了简化向外共享成员的代码,Node.js提供了exports
对象。
exports = {
nickname: '黑子',
say() {
console.log('i am 小黑子')
}
}
默认情况下,exports和module.exports指向同一个对象,最终共享的结果,还是以module.exports指向的结果为准
为了防止混乱,建议不要在同一个模块中同时使用exports
和module.exports
5.3 npm与包
包的基本概念
Node.js中的第三方模块又叫做包
由于Node.js的内置模块仅提供了一些底层的API,导致在基于内置模块进行项目开发时,效率很低。
包是基于内置模块封装出来的,提供了更高级、更方便的API,极大地提高了开发效率
在项目中安装包的命令
npm install 包完整名称
# 简写
npm i 包完整名称
# 安装指定版本
npm i moment@2.22.2
# 安装的包只在开发时用到
npm i 包完整名称-D
# 全局包
npm i 包完整名称-g
初次装包完成后,在项目文件夹下多一个叫做node_modules的文件夹和package-lock.json的配置文件
- node_modules文件夹用来存放所有已安装到项目中的包。
require()
导入第三方包时,就是从这个目录中查找并加载包 - package-lock.json配置文件用来记录node_modules目录下的每一个包的下载信息,例如包的版本号,下载地址等
包管理配置文件
多人协作的问题:第三方的包的体积过大,不方便成员之间共享项目源代码
解决方案:
- 共享时剔除node_modules
- 在项目根目录中,创建一个叫做package.json的配置文件,即可用来记录在项目中安装了哪些包
- 把Node_modules文件夹,添加到.gitignore忽略文件夹中
npm包管理工具提供了一个快捷命令,可以在执行命令时所处的目录中,快速创建package.json这个包管理配置文件:
npm init -y
- 上述命令只能在英文且无空格的目录下运行成功
- 运行
npm install
命令安装包的时候,npm包管理工具,会自动把包的名称和版本号,记录到package.json中
安装所有包
npm install
- 执行
npm install
命令时,npm 包管理工具会先读取package.json中的dependencies节点 - 读取到记录的所有依赖包名称和版本号之后,包管理工具会把这些包一次性的下载到项目中去
卸载包
npm uninstall 包名
npm uninstall
命令执行成功后,会把卸载的包,自动从package.json的dependencies
中移除掉
5.4 模块的加载机制
优先从缓存中加载
模块在第一次被加载后会被缓存。这也意味着多次调用require()
不会导致模块的代码被多次执行。
注意:不论是内置模块,自定义模块,还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率。
内置模块的加载机制
内置模块加载优先级最高。例如,你想导入一个自定义的名为fs的模块,但始终返回内置的fs模块
自定义模块的加载机制
使用require()
加载自定义模块时,必须指定以./或…/开头的路径标识符。如果没有指定的话,则node模块就会把他当成内置模块或者第三方模块进行加载。
在使用require()导入自定义模块时,如果省略了文件的拓展名,则node.js就会按顺序分别尝试加载以下的文件
- 按照确切的文件名进行加载
- 补全.js拓展名进行加载
- 补全.json拓展名进行加载
- 补全.node拓展名进行加载
- 加载失败,终端报错
第三方模块的加载机制
如果传递给require()
的模块标识符不是一个内置模块,也没有./等等开头,则Node.js会从当前模块的父目录开始,尝试从node_modules文件夹中加载第三方模块。如果没有找到第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录
目录作为模块
当把目录作为模块标识符,传递给require()
进行加载的时候,有三种加载方式:
- 在被加载的目录下查找一个叫做package.json 的文件,并寻找 main属性,作为require()加载的入口
- 如果目录里没有package.json文件,或者main 入口不存在或无法解析,则Node.,s将会试图加载目录下的 indexjs文件。
- 如果以上两步都失败了,则Node.js 会在终端打印错误消息,报告模块的缺失: Error. Cannot find module ‘xoo’
六、初识express
6.1 什么是express
Express是基于Node.js平台,快速,开放,极简的Web开发框架
使用Node.js提供原生的http模块也能创建Web服务器,但是http内置模块用起来很复杂,Express是基于内置的http模块进一步封装起来的,能够极大的提高开发效率
使用Express可以方便快速地创建Web网站服务器或API接口服务器
6.2 安装express
npm i express@4.17.1
6.3 使用express创建最基本的服务器
//1.导入express
const express = require('express')
//2.创建web服务器
const app = express()
//3.启动web服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
6.4 监听GET请求
通过app.get()
方法,可以监听客户端的GET请求,具体的语法格式如下:
app.get('请求url',function(req,res){
/*处理函数*/
})
- 参数1:客户端请求的url地址
- 参数2:请求对应的处理函数
- req:请求对象(包含了与请求属性相关的属性与方法)
- res:响应对象(包含了与响应对象相关的属性与方法)
6.5 监听POST请求
通过app.post()
方法,可以监听客户端的POST请求,具体的语法格式如下:
app.post('请求url',function(req,res){
/*处理函数*/
})
- 参数1:客户端请求的url地址
- 参数2:请求对应的处理函数
- req:请求对象(包含了与请求属性相关的属性与方法)
- res:响应对象(包含了与响应对象相关的属性与方法)
6.6 把内容响应给客户端
通过res.send()
方法,可以把处理好的内容,发送给客户端
app.get('/user',(req,res)=>{
//向客户发送json请求
res.send({name:'zs',age:20,gender:'男'})
})
app.post('/user',(req,res)=>{
//向客户端发送文本内容
res.send('请求帮助')
})
6.7 获取URL中携带的查询参数
通过req.query
对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数:
app.get('/', (req, res) => {
//通过req.query可以获取到客户端发送过来的查询参数
//注意:在默认情况下,req.query是一个空对象
//客户端使用?name=zs&age=20这种查询字符串形式,发送到服务器参数
//可以通过req.query对象访问到,例如:
//req.query.name req.query.age
console.log(req.query)
res.send(req.query)
})
6.8 获取URL的动态参数
通过req.params
对象,可以访问到Url中,通过:
匹配到的动态参数
//url地址中,我们可以通过:参数名的形式,匹配动态参数值
app.get('user/:id',(req,res)=>{
//req.params默认是一个空对象
//里面存放这通过:动态匹配到的参数值
console.log(req.params)
})
6.9 托管静态资源
我们可以通过express.static()
创建一个静态资源服务器。
例如,通过如下代码就可以将public目录下的图片、CSS文件、JavaScript文件对外开放访问了:
app.use(express.static('public'))
//访问路径:http://localhost/xxx
现在,你就可以访问public目录中的所有文件了
注意:Express在指定的静态目录中查找文件,并对外提供资源的访问路径。因此,存放静态文件的目录名不会出现在URL中。
托管多个静态资源目录
如果要托管多个静态资源目录,请多次调用express.static)函数:
app.use(express.static('public'))
app.use(express.static('files'))
访问静态资源文件时,express.static()函数会根据目录的添加顺序查找所需的文件。
挂载路径前缀
如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:
app.use('/public', express.static('public'))
//访问路径:http://localhost/public/xxx
七、express 路由
7.1 什么是express路由
广义上来讲,路由就是映射关系
在Express中,路由指的是客户端请求与服务器处理函数之间的映射关系
Express中的路由分三部分组成,分别是请求的类型,请求的URL地址,处理函数。格式如下:
app.METHOD(PATH,HANDLER)
代码示例:
//匹配GET请求,且请求URL为/
app.get('/',function(req,res){
res.send('Hello World!')
})
//匹配post请求,且请求URL为/
app.post('/',function(req,res){
res.send('Got a POST request')
})
7.2 路由的匹配过程
每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数。
在匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的URL同时匹配成功,则 Express 会将这次请求,转交给对应的 function函数进行处理。
路由匹配的注意点:
- 按照定义的先后顺序进行匹配
- 请求类型和请求的URL同时匹配成功,才会调用对应的处理函数
7.3 模块化路由
为了方便对路由进行模块化的管理,Express不建议将路由直接挂载到app上,而是推荐将路由抽离为单独的模块。
代码示例:
创建路由(createRouter.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 new user.')
})
// 4. 向外导出路由对象
module.exports = router
使用路由(useRouter.js)
const express = require('express')
const app = express()
// 1. 导入路由模块
const router = require('./16创建路由')
// 2. 注册路由模块
app.use(router)
// 2. 注册路由模块,使用app.use()注册路由模块,并添加统一的访问前缀/api
//app.use('/api', router)
app.listen(80, () => {
console.log('http://127.0.0.1')
})
八、express中间件
8.1 什么是express中间件
当一个请求到达Express的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理。
Express的中间件,本质上就是一个 function处理函数,Express中间件的格式如下:
app.get('/',function(req, res, next){
next()
})
注意:中间件函数的形参列表中,必须包含next 参数。而路由处理函数中只包含req和res。
next函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由。
**多个中间件之间,共享同一份req和res。**基于这样的特性,我们可以在上游的中间件中,统一为req或res对象添加自定义的属性或方法,供下游的中间件或路由进行使用。
注意点:
- 一定要在路由之前注册中间件
- 客户端发送过来的请求,可以连续调用多个中间件进行处理
- 执行完中间件的业务代码之后,不要忘记调用next()函数
- 为了防止代码逻辑混乱,调用next()函数后不要再写额外的代码
- 连续调用多个中间件时,多个中间件之间,共享req和res 对象
8.2 定义中间件函数
//常量mv所指向的,就是一个中间价函数
const mv=funcntion(req,res,next){
console.log('这是一个最简单的中间件函数')
//注意:在当前中间价的业务处理完毕后,必须调用next()函数
//表示把流转关系交给下一个中间价或路由器
next()
}
8.3 全局生效的中间件函数
客户端发起的任何请求,到达服务器之后,都会触发的中间件,叫做全局生效的中间价。
通过调用app.use(中间件函数),
即可定义一个全局生效的中间价,示例代码如下:
//全局生效的中间件
app.use(mv)
// 这是定义全局中间件的简化形式
app.use((req, res, next) => {
console.log('这是最简单的中间件函数')
next()
})
小结
const express = require('express')
const app = express()
// 这是定义全局中间件的简化形式
app.use((req, res, next) => {
// 获取到请求到达服务器的时间
const time = Date.now()
// 为 req 对象,挂载自定义属性,从而把时间共享给后面的所有路由
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(80, () => {
console.log('http://127.0.0.1')
})
8.4 局部生效的中间件函数
const mw1 = (req, res, next) => {
console.log('调用了局部生效的中间件')
next()
}
8.5 中间件的分类
应用级别的中间件
通过app.use()或app.get()或 app.post(),绑定到 app实例上的中间件,叫做应用级别的中间件
路由级别的中间件
绑定到express.Router()实例上的中间件,叫做路由级别的中间件。
错误级别中间件
专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
格式: 错误级别中间件的function处理函数中,必须有4个形参,形参顺序从前到后,分别是(err, req, res, next)。
错误级别的中间件,放到所有路由之后
app.use((err, req, res, next) => {
console.log('发生了错误!' + err.message)
res.send('Error:' + err.message)
})
Express 内置中间件
自Express 4.16.0版本开始,Express 内置了3个常用的中间件,极大的提高了Express项目的开发效率和体验
express.static
快速托管静态资源的内置中间件,例如: HTML文件、图片、CSS样式等(无兼容性)express.json
解析JSON格式的请求体数据(有兼容性,仅在4.16.0+版本中可用)express.urlencoded
解析URL-encoded格式的请求体数据(有兼容性,仅在4.16.0+版本中可用)
// 通过 express.json() 这个中间件,解析表单中的 JSON 格式的数据
app.use(express.json())
// 通过 express.urlencoded() 这个中间件,来解析 表单中的 url-encoded 格式的数据
app.use(express.urlencoded({ extended: false }))
自定义中间件
自定义一个解析URL参数的中间件
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 导入 Node.js 内置的 querystring 模块
const qs = require('querystring')
// 这是解析表单数据的中间件
app.use((req, res, next) => {
// 定义中间件具体的业务逻辑
// 1. 定义一个 str 字符串,专门用来存储客户端发送过来的请求体数据
let str = ''
// 2. 监听 req 的 data 事件
req.on('data', (chunk) => {
str += chunk
})
// 3. 监听 req 的 end 事件
req.on('end', () => {
// 在 str 中存放的是完整的请求体数据
// console.log(str)
// TODO: 把字符串格式的请求体数据,解析成对象格式
const body = qs.parse(str)
req.body = body
next()
})
})
app.post('/user', (req, res) => {
res.send(req.body)
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
8.6 使用CORS解决跨域问题
cors 是 Express的一个第三方中间件。通过安装和配置cors 中间件,可以很方便地解决跨域问题。使用步骤分为如下3步:
- 运行
npm install cors
安装中间件 - 使用
const cors = require('cors')
导入中间件 - 在路由之前调用
app.use(cors)
配置中间件
CORS (Cross-Origin Resource Sharing,跨域资源共享)由一系列HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止前端JS代码跨域获取资源。
浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了CORS相关的HTTP响应头,就可以解除浏览器端的跨域访问限制。
CORS响应头部-Access-Control-Allow-Origin
响应头部中可以携带一个Access-Control-Allow-Origin
字段,其语法如下:
Access-Control-Allow-Origin: <origin> | *
其中,origin 参数的值指定了允许访问该资源的外域URL。
//只允许来自http://abc.cn的请求
res.setHeader('Access-Control-Allow-origin', 'http://abc.cn')
//表示允许来自任何域的请求
res.setHeader('Access-Control-Allow-origin', '*')
CORS响应头部- Access-Control-Allow-Headers
默认情况下,CORS仅支持客户端向服务器发送如下的9个请求头;
Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width ,Content-Type (值仅限于text/plain、multipart/form-data、application/x-www-form-urlencoded 三者之一)
如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过Access-Control-Allow-Headers
对额外的请求头进行声明,否则这次请求会失败!
//允许客户端额外向服务器发送 Content-Type请求头和X-Custom-Header请求头
//注意:多个请求头之间使用英文的短号进行分割
res.setHeader ('Access-Control-Allow-Headers', 'Content-Type,X-Custom-Header')
CORS 响应头部- Access-Control-Allow-Methods
默认情况下,CORS仅支持客户端发起 GET、POST、HEAD请求。
如果客户端希望通过PUT、DELETE等方式请求服务器的资源,则需要在服务器端,通过Access-Control-Alow-Methods
来指明实际请求所允许使用的 HTTP方法。
示例代码如下:
//只允许POST、GET、DELETE、HEAD 请求方法
res.setHeader('Access-Control-Allow-Methods','POST,GET,DELETE,HEAD')
//允许所有的HTTP请求方法
res.setHeader ('Access-Control-Allow-Methods', '*')
九、Node.js整合MySQL
9.1 安装并配置MySQL
安装MySQL模块
npm i mysql
npm i mysql2 # mysql8.0及以上推荐使用
配置MySQL模块
// 1.导入MySQL模块
const mysql = require('mysql2')
// 2.连接MySQL数据库
const db = mysql.createConnection({
host: '127.0.0.1', // 数据库ip地址
user: 'root', //用户名
password: 'root',//密码
database: 'book'//数据库名称
})
// 3.测试连接
db.connect(err=>{
//如果err===null,则连接成功
console.log(err)
})
9.2 执行SQL语句
db.query(sql,[参数1,参数2....] (err, results) => {
//其他操作
})
- sql 为要执行的SQL语句
- err 为出现的错误信息
- result 为执行的结果
代码示例:
执行查询,返回的是查询结果对象数组
db.query('select * from book', (err, results) => {
if (err) {
return console.log('出错' + err.message)
}
console.log(results)//返回一个对象数组
})
执行增删改,返回的执行结果信息
//?表示占位符
const sql = 'insert into user values(null,?,?,null,null)'
const username = '张三'
const password = '12345'
// 使用数组的形式,依次为?占位符指定具体的值
db.query(sql, [username, password], (err, results) => {
if (err) {
return console.log('出错' + err)
}
console.log(results.affectedRows)//返回影响行数
})
十、在Express中使用session
安装express-session中间件
npm i express-session
注册session中间件
// 1.导入session中间件
const session = require('express-session')
// 2.注册中间件
app.use(session({
secret: 'hello',//secret可以为任意字符串
resave: false,//每次请求都重新设置session
saveUninitialized//每次请求都设置个session
}))
向session中存数据
app.post('/login', (req, res) => {
if (req.body.username !== 'admin' || req.body.password !== '123456') {
return res.send({status: 1, msg: '登录失败'})
}
req.session.user = req.body.username
req.session.islogin = true
res.send({status: 0, msg: '登录成功'})
})
向session中取数据
app.post('/index', (req, res) => {
if (!req.session.islogin) {
return res.send({status: 1, msg: '失败'})
}
res.send({status: 0, data: res.session.username, msg: '登录成功'})
})
清空session信息
app.post('/logout', (req, res) => {
req.session.destroy()
res.send({status: 0, msg: '退出登录成功'})
})