Node.js学习笔记

一、初识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指向的结果为准

为了防止混乱,建议不要在同一个模块中同时使用exportsmodule.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就会按顺序分别尝试加载以下的文件

  1. 按照确切的文件名进行加载
  2. 补全.js拓展名进行加载
  3. 补全.json拓展名进行加载
  4. 补全.node拓展名进行加载
  5. 加载失败,终端报错

第三方模块的加载机制

如果传递给require()的模块标识符不是一个内置模块,也没有./等等开头,则Node.js会从当前模块的父目录开始,尝试从node_modules文件夹中加载第三方模块。如果没有找到第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录

目录作为模块

当把目录作为模块标识符,传递给require()进行加载的时候,有三种加载方式:

  1. 在被加载的目录下查找一个叫做package.json 的文件,并寻找 main属性,作为require()加载的入口
  2. 如果目录里没有package.json文件,或者main 入口不存在或无法解析,则Node.,s将会试图加载目录下的 indexjs文件。
  3. 如果以上两步都失败了,则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: '退出登录成功'})
})
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值