NodeJS-基础&应用

初识 Node.js

简介

Node.js 是什么

node.js 是一个基于 Chorme V8 引擎的 JavaScript 开发环境。

浏览器是 JavaSript 的前端运行环境,node.js 是 JavaScript 的后端运行环境。

node.js 中无法调用 DOM 和 BOM 等浏览器内置 API。

Node.js 可以做什么

  • 基于 Express 框架(Express),可以快速构建Web应用。

  • 基于 Electron 框架(Electron),可以构建跨平台的桌面应用。

  • 基于 restify 框架(Restify),可以快速构建API接口项目。

  • 读写和操作数据库、创建实用的命令行工具辅助前端开发、etc······。

学习路径

JavaScript基础语法 + Node.js 内置 API 模块(fs、path、http 等)+ 第三方 API 模块(express、mysql 等)。

环境安装

LTS 为长期稳定版,Current 为新特性尝鲜版。

查看已安装 Node.js 版本号:终端命令 node -v

在 Node.js 环境中执行 JavaScript 代码

终端命令:node "js文件路径"

fs 文件系统模块

什么是 fs 文件系统模块

Node.js 官方提供的、用来操作文件的模块。

它提供了操作文件的方法和属性,例如:

  • fs.readFile():读取指定文件内容。
  • fs.writeFile():向指定文件写入内容。

导入方式:

const fs = require('fs')

读取指定文件中的内容

fs.readFile() 语法格式

fs.readFile(path[, options], callback)

中括号内代表可选参数。

参数解读:

  • path:必选参数,字符串,表示文件的路径。
  • options:可选参数,编码格式。
  • callback:必选参数,读取完成后,拿到读取结果的回调函数。

fs.readFile() 示例代码

const fs = require('fs')

fs.readFile('1.json', 'utf8', function(err,dataStr){
	console.log(err)
	console.log('------')
	console.log(dataStr)
})
  • 读取成功时,err 的值为 null。
  • 读取失败时,dataStr 的值为 undefined。

判断文件是否读取成功

通过判断 err 是否为 null 来实现。

向指定文件中写入内容

fs.writeFile() 语法格式

fs.writeFile(file, data[, options], callback)

参数解读:

  • file:必选参数,文件路径。
  • data:必选参数,写入的内容。
  • options:可选参数,写入格式,默认utf8。
  • callback:回调函数。

fs.writeFile() 示例代码

const fs = require('fs')

fs.writeFile('test.json', '1234', 'utf8', function(err){
	console.log(err)
})

写入成功时,err 为 null。

判断文件是否写入成功

通过判断 err 是否为 null 来实现。

fs.writeFile() 的注意点

  1. fs.writeFile() 方法只能用来创建文件,不能用来创建路径。
  2. 重复调用 fs.writeFile() 写入同一文件,新内容会覆盖旧内容。

fs 模块路径动态拼接问题

不同的控制台目录运行同一个 js 文件时,js 中的相对路径是相对于控制台目录的。容易引起找不到文件的问题。

绝对路径不宜与维护。

解决方案:使用 __dirname 表示当前文件所处的目录。即被运行的文件所处的目录。

使用示例:

const fs = require('fs')

fs.readFile(__dirname+'/1.json', 'utf8', function(err,dataStr){
	console.log(err)
	console.log('------')
	console.log(dataStr)
})

path 路径模块

Node.js 官方提供的、用来处理路径的模块。

提供了一些 方法和属性。例如:

  • path.join(): 把多个路径片段拼接成一个路径字符串。
  • path.basename(): 从路径字符串中解析文件名。

导入方式:

const path = require('path')

path.join() 路径拼接

语法格式

path.join([...paths])

参数解读

  • ...paths: 字符串路径片段的序列。
  • 返回值: 字符串类型

示例代码

const path = require('path')
pathStr = path.join(__dirname, '../')
console.log(pathStr)

+ 的区别为:加号不能拼接 ../

获取路径中的文件名

path.basename()

语法格式

path.basename(path[, ext])

参数解读:

  • path: 字符串,必选参数,路径的字符串。
  • ext: 字符串,可选参数,表示文件拓展名。
  • 返回值: 字符串,表示路径中的最后一部分。

代码示例

const path = require('path')

pathStr = path.join(__dirname, '../assets/html/index.html')
console.log(pathStr);

var fullName = path.basename(pathStr)
console.log(fullName) //输出 index.html

var nameWithoutExt = path.basename(pathStr, '.html')
console.log(nameWithoutExt); //输出 index

获取路径中的文件拓展名

path.extname()

语法格式

path.extname(path)

参数解读:

  • path: 字符串,必选参数,路径字符串。
  • 返回值: 字符串,拓展名字符串。

代码示例

const path = require('path')

pathStr = path.join(__dirname, '../assets/html/index.html')
console.log(pathStr);

var extname = path.extname(pathStr)
console.log(extname); //输出 .html

http 模块

什么是 http 模块

Node.js 官方提供的、用来创建 web 服务器的模块。

通过 http.createServer() 方法创建服务器。

导入方式:

const http = require('http')

进一步理解 http 模块的作用

在 Node.js 中,我们不需要使用第三方 web 服务器软件。

使用 http 模块,通过几行简单的代码,就能手写出一个服务器软件。

创建最基本的 web 服务器

基本步骤

  1. 导入 http 模块。
  2. 创建 web 服务器实例。
  3. 为服务器实例绑定 request 事件,监听客户端请求。
  4. 启动服务器。

代码实现

导入 http 模块

const http = require('http')

创建 web 服务器实例

const server = http.createServer()

为服务器绑定 request 事件

.on() 方法,为服务器绑定 request 事件。

server.on('request', (req, res) => {
    console.log('someOne visit our web server.');
})

启动服务器

.listen() 方法,启动当前 web 实例。

server.listen(80, () => {
    console.log('http server running at http://127.0.0.1')
})

回调函数

req:提供了请求的详细信息。 通过它可以访问请求头和请求的数据。

res:用于构造要返回给客户端的数据。

例如:

const http = require('http')

const port = 3000

const server = http.createServer((req, res) => {
  res.statusCode = 200
  res.setHeader('Content-Type', 'text/plain')
  res.end('你好世界\n')
})

server.listen(port, () => {
  console.log(`服务器运行在 http://${hostname}:${port}/`)
})

解决中文乱码问题

调用 res.end() 传入回写数据时可能会中文乱码。

解决方案:在请求头中设置 utf-8 编码方式。

res.setHeader('Content-Type', 'text/html; charset=utf-8')

根据不同的 url 返回不同的 html 内容

核心实现步骤

  1. 获取请求的 url 地址。
  2. 设置默认的相应内容为 404 Not found。
  3. 判断用户请求的是否为 //index.html 首页。
  4. 判断用户请求是否为 /about.html 关于页面。
  5. 设置响应头防止乱码。
  6. 使用 res.end() 把内容相应给客户端。

动态响应内容

const http = require('http')

const server = http.createServer()

server.on('request', (req, res) => {
    const url = req.url
    let content = '<h1>Not Found!</h1>'

    if (url === '/' || url === '/index.html') {
        content = '<h1>首页</h1>'
    } else if (url === '/about.html') {
        content = '<h1>关于</h1>'
    }

    res.statusCode=200
    res.setHeader('Content-Type', 'text/html; charset=utf-8')
    res.end(content)
})

server.listen(8080, () => {
    console.log('http server running at http://127.0.0.1:8080')
})

模块化

模块化的基本概念

遵循固定的规则,把一个大文件拆成独立并互相依赖的多个小模块。

Node.js 中的模块化

模块分类

根据模块来源不同,分成三类

  • 内置模块(Node.js 官方提供。例如 fs,path,http 等)。
  • 自定义模块(用户创建的每个 .js 文件,都是自定义模块)。
  • 第三方模块(第三方开发出的模块,使用前需下载)。

加载模块

require() 方法,可以加载所有模块。内置模块和第三方模块不需要加路径,但用户自定义模块需要。

当使用 require() 方法加载其它模块时,会执行被加载模块中的代码。

加载自定义模块时,可以省略 .js 的后缀。

模块作用域

和函数作用域类似,自定义模块中的变量、方法等成员,只能在模块内被访问。

好处:能够防止全局变量被污染。

向外共享模块作用域的成员

module 对象

在每个 .js 自定义模块中都有一个 module 对象,它里面存储了和当前模块有关的信息。

module.exports 对象

自定义模块中,可以使用 module.exports 对象,把模块内的成员共享出去。

外界用 require() 方法导入自定义模块时,得到的就是 module.exports 所指向的对象。

exports 对象

module.exports 写法复杂,引入了 exports 对象来替代它。二者作用一致,但最终结果以 module.exports 所指向的对象为准。

为防止混乱,尽量不要混用 module.exports 和 exports。

Node.js 中的模块化规范

Node.js 遵循了 CommonJS 模块化规范,CommonJS 规定了模块的特性和各模块之间如何相互依赖。

CommonJS 规定:

  1. 每个模块内部,module 变量代表当前模块。
  2. module 变量是一个对象,它的 exports (即 module.exports)是对外的接口。
  3. 加载某个模块,其实是加载该模块的 module.exports 属性。require() 方法用于加载模块。

npm 与 包

包:第三方模块。由第三方个人或团队开发。免费供所有人使用。

包是由内置模块封装出来的,能够提升开发效率。

包和内置模块之间的关系,类似于 jQuery 和 浏览器内置 api 之间的关系。

全球最大的包共享平台:npm (npmjs.com)(搜索包的服务器)。

下载包的服务器: https://registry.npmjs.org/。

包管理工具 Node Package Manager(简称 npm 包管理工具),可以从下载包的服务器下载指定的包。

npm 默认和 node.js 一起被安装到了电脑上。

npm 初体验

初次装包后,会多出一个 node_modules 文件夹和 package-lock.json 配置文件。

  • node_modules:存放安装到项目里的包。
  • package-lock.json:记录下载到项目里面的每一个包的下载信息。

npm 会自动维护这些文件。

包的版本

可以通过 包名@ 指定版本。

包的版本是由 点分十进制 的形式进行定义的,总共有三位数字。

  • 第一位:大版本。
  • 第二位:功能版本。
  • 第三位:Bug修复版本。

版本号提升规则:前面版本号提升了,后面版本号归零。

包管理配置文件

npm规定,项目的根目录中,必须提供一个 package.json 的包管理配置文件。用来记录与项目有关的一些配置信息。例如:

  • 项目的名称、版本号、描述等。
  • 项目中都用到了哪些包。
  • 哪些包只在开发期间会用到。
  • 哪些包在开发和部署时都要用到。

多人协作的问题

第三方包体积过大,不方便组员共享源码。

解决方案:共享时剔除 node_modules

记录装了哪些包

项目根目录的 package.json 配置文件。方便剔除 node_modules 后,仍然可以共享源码。

开发中就可以把 node_modules 文件夹添加到 .gitignore 忽略文件中。

快速创建 package.json

快捷命令:

npm init -y

该命令只能在英文目录下运行成功。

安装包时,npm 会自动把包的名称和版本号,记录到 package.json 中。

dependencies 节点

package.json 文件中,有一个 dependencies 节点,专门用来记录使用 npm install 命令安装的包。

一次性安装所有的包

当我们拿到一个剔除了 node_modules 的项目之后,需要先把所有的包下载到项目中。

可以运行 npm install 命令(或 npm i)一次性安装所有的依赖包。

卸载包

npm unistall 包名称

devDependencies 节点

记录只在开发阶段用到的包的信息。

记录方式:

npm i 包名 -D # 简写
npm i 包名 --save-dev # 全写

解决下包速度慢的问题

切换镜像源

# 查看当前的下包镜像源
npm config get registry

# 将下包的镜像源切换为淘宝镜像源
npm config set registry = http://registry.npm.taobao.org/

# 检查镜像源是否切换成功
npm config get registry

nrm

nrm 小工具提供的命令,可以快速查看和切换下包的镜像源。

# 下载 nrm
npm i nrm -g

# 查看所有可用镜像源
nrm ls

# 将下包镜像源切换为 taobao 镜像
nrm use taobao

包的分类

项目包

被安装到 node_modules 中的包。

项目包分为两类:

  • 开发依赖包(devDependencies 节点中的包)。
  • 核心依赖包(dependencies 节点中的包)。
全局包

执行 npm install 时,提供了 -g 参数的包。

注:只有工具性质的包,才有全局安装的必要。判断某个包是否为必须全局安装才能使用,须参考官方提供的说明。

规范的包结构

一个规范的包,必须符合以下三点要求:

  • 包必须以单独的目录而存在。
  • 包的顶级目录下必须包含 package.json 这个包管理配置文件。
  • package.json 中必须包含 name, version, main 这三个属性,分别代表包的名字、版本号、包的入口。

开发属于自己的包

初始化包的基本结构

包文件夹中首先创建三个文件

  • package.json (包管理配置文件)。
  • index.js (包的入口文件)。
  • README.md (包的说明文档)。

初始化 package.json

{
	"name":"包的名称",
	"version":"版本",
	"main":"包的入口",
	"description":"包的描述",
	"keywords":"关键词(数组)",
	"license":"开源许可(默认 ISC)"
}

index.js 中编写功能

······

不同功能进行模块化拆分

······

编写包的说明文档

编写 README.md 文档。

一般包含:安装方式、导入方式、功能介绍、开源协议。

发布包

发布包之前,一定要把下包的服务器切换到 npm 的官方服务器,否则会导致发布包失败。

注册 npm 账号。

在控制台登录:npm login ,然后输入账号密码和邮箱。

把终端切换到 包的根目录下,运行 npm publish 命令,即可成功发布。(包名不能和已存在的包名重复)。

删除已发布的包

npm unpublish 包名 --force

注:

  • 只能删除发布 72 小时以内的包。
  • 删除的包,24 小时内不允许重复发布。
  • 发布包要慎重,尽量不发无意义的包。

模块的加载机制

优先从缓存中加载

模块在第一次加载后会被缓存。即多次通过 require() 调用不会导致模块的代码被执行多次。能够提升模块的加载效率。

内置模块加载机制

内置模块的加载优先级最高。

自定义模块加载机制

使用 require() 加载自定义模块时,必须指定以 ./, ../ 开头的路径标识符。如果没有指定,node 会把它当作内置模块或第三方模块进行加载。

在使用 require() 导入自定义模块时,如果省略了文件的拓展名,则 node.js 会按顺序分别尝试加载以下的文件:

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

第三方模块的加载机制

传递给 require() 的模块标识符不是一个内置模块,也没有以 ./../ 开头,则 Node.js 会从当前模块的父目录开始,尝试从 /node_modules 文件夹中加载第三方模块。

如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。

目录作为模块

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

  • 在被加载的目录下查找 package.json,并寻找 main 属性,作为 require() 的入口。
  • 如果没有 package.json,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的 index.js 文件。
  • 如果以上两步都失败,Node.js 会在终端报错。

Express

初识 Express

官网:Express - 基于 Node.js 平台的 web 应用开发框架 - Express 中文文档 | Express 中文网 (expressjs.com.cn)

一个 npm 上的第三方包,能够快速创建 web 服务器。

http 模块也能创建服务器,但开发效率低。express 就是对 http 模块的封装,极大的提高了开发效率。

Express 基本使用

安装

在项目所处的目录,运行如下终端命令即可。

npm i express@4.17.1
创建基本 web 服务器
const express = require('express')

const app = express()

app.listen(80, () => {
    console.log("运行成功");
})
监听 GET 请求

通过 app.get() 方法。

app.get('请求url', function (req, res) {
    /* 处理函数 */
})
监听 POST 请求

通过 app.post() 方法。

app.post('请求url', function (req, res) {
    /* 处理函数 */
})
把内容响应给客户端

通过 res.send() 方法。

app.get('/user', (req, res) => {
    // 向客户端发送 json 对象
    res.send({ name:'zs', age:'18', gender:'男' })
})

app.post('/user', (req, res) => {
    // 向客户端发送文本内容
    res.send("请求成功")
})
获取 URL 中的查询参数

通过 req.query 对象获得

app.get('/user', (req, res) => {
    // 向客户端发送 json 对象
    console.log(req.query);
    res.send({ name:'zs', age:'18', gender:'男' })
})
获取 URL 中的动态参数

通过 req.params 可以访问到 URL 中,通过 : 匹配到的动态参数。

app.get('/user/:id', (req, res) => {
    // 向客户端发送 json 对象
    console.log(req.params);
    res.send({ name:'zs', age:'18', gender:'男' })
})

托管静态资源

express.static() 能够非常方便地创建静态资源服务器

例如:通过如下代码可以将 public 目录下的图片、css文件、JavaScript文件对外开放访问。

app.use(express.static('public'))

注:public 文件夹的名称不会出现在 url 中。

例如:127.0.0.1:80/html/index.html 访问的是 public 文件夹下的 html 文件夹下的 index.html 文件。

托管多个静态资源目录,只需要多次调用 express.static()

多次调用的情况下,访问的优先级为调用顺序。

express.static() 可以添加路径参数。如:app.use('/user', express.static('public')) 代表的是访问 public 文件夹内的内容,必须要加 /user 前缀。

nodemon

作用:监听项目文件变动,自动重启项目。

安装方式:全局安装。

npm install -g nodemon

使用 nodemon

运行项目时,将 node 命令替换为 nodemon 命令。

例:nodemon index.js

Express 路由

路由的概念

广义上讲,路由就是映射关系。

Express 中的路由

Express 中,路由指的是客户端的请求与服务器处理函数之间的映射关系。

Express 中的路由由三部分组成,分别是请求的类型、请求的 URL 地址、处理函数,格式如下:

app.METHOD(PATH, HANDLER)

路由的使用

路由的基本使用

app.get('/router', (req, res) => { 
    res.send("hello router!")
})

app.post('/router', (req, res) => { 
    res.send("hello router!")
})

模块化路由

为了方便对路由进行模块化的管理,Express 不建议将路由直接挂载到 app 上,而是推荐将路由抽离为单独的模块。

步骤如下:

  1. 创建路由模块对应的 .js 文件。
  2. 调用 express.Router() 函数创建路由实例。
  3. 向路由实例上挂载具体的路由。
  4. 使用 module.exports 向外共享路由文件。
  5. 使用 app.use() 函数注册路由模块。

为路由模块添加前缀

// 导入路由模块
const userRouter = require('./router/user.js')

// 使用 app.use() 注册路由模块,并添加统一的访问前缀 /api
app.use('/api', userRouter)

Express 中间件

中间件(Middleware),特指业务流程的中间处理环节。

调用流程

当一个请求到达 Express 的服务器后,可以连续调用多个中间件,从而对这次请求进行预处理。

Express 中间件的格式

app.get('/get', function(req, res,next){
    next()
})

中间件函数的形参列表中,必须包含 next 函数,而路由处理函数只包含 reqres

next 函数的作用

next 函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由。

Express 中间件初体验

定义中间件函数
const mw = function (req, res, next) {
    console.log('一个最简单的中间件函数')
    next()
}
全局生效的中间件

任何请求都会触发的中间件。

通过 app.use(中间件函数),即可定义一个全局生效的中间件。示例代码如下:

const mw = function (req, res, next) {
    console.log('一个最简单的中间件函数')
    next()
}
app.use(mw)
定义中间件函数的简化形式
app.use(function (req, res, next) {
    console.log('一个最简单的中间件函数')
    next()
})
中间件的作用

多个中间件共享一份 reqres。因此,我们可以在上游的中间件中统一为 reqres 对象添加自定义的属性或方法,供下游的中间件或路由使用。

定义多个全局中间件

使用 app.use() 连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行调用。

例如:

app.use(function (req, res, next) {
    console.log('第一个中间件')
    next()
})
app.use(function (req, res, next) {
    console.log('第二个中间件')
    next()
})
app.use(function (req, res, next) {
    console.log('第三个中间件')
    next()
})
局部生效的中间件

不使用 app.use() 定义的中间件,为局部生效的中间件。示例代码如下:

// 定义一个中间件函数 mw
const mw = function (req, res, next) {
    console.log('一个最简单的中间件函数')
    next()
}

// mw 这个中间件只在 当前路由 生效
app.get('/user/:id', mw, (req, res) => {
    // 向客户端发送 json 对象
    console.log(req.params);
    res.send({ name:'zhangsan', age:'18', gender:'男' })
})
定义多个局部中间件

在路由中,通过如下两种等价的方式,使用多个局部中间件。

app.get('/', mw1, mw2, (req, res) => { res.send('Page Home') })
app.get('/', [mw1, mw2], (req, res) => { res.send('Page Home') })
中间件的 5 个使用注意事项
  1. 一定要在路由之前注册中间件。
  2. 客户端发送过来的请求,可以连续调用多个中间件进行处理。
  3. 执行完中间件的业务代码后,不要忘记调用 next() 函数
  4. 为了防止代码逻辑混乱,调用 next() 函数后不要再写额外的代码。
  5. 连续调用多个中间件时,它们共享 reqres 对象。

中间件的分类

为方便理解和记忆中间件的使用,Express 官方把中间件分为五大类。分别是:

  1. 应用级别的中间件。
  2. 路由级别的中间件。
  3. 错误级别的中间件。
  4. Express 内置的中间件。
  5. 第三方的中间件。
应用级别的中间件

通过绑定到 app 实例上的中间件。初体验中的示例均为应用级别。

路由级别的中间件

绑定到 express.Router() 实例上的中间件。示例代码如下:

const express = require('express')

const router = express.Router()

router.use(function (res, req, next) {
    console.log('路由级别的中间件')
    next()
})


router.get('/query', (req, res) => {
    res.send('查询用户')
})

module.exports = router
错误级别的中间件

作用:专门用来捕获整个项目中发生的错误,从而防止项目异常崩溃的问题。

格式:错误级别中间件的 function 处理函数中,必须有四个形参,形参顺序从前到后,分别是 (error, req, res, next)

app.get('/', (req, res)=>{
	throw new Error('服务器内部发生了错误!')
    res.send('Home Page')
})

app.use(function(error, req, res, next){
    console.log('发生了错误:' + err.message)
    res.send('Error !' + err.message)
})

注:错误级别的中间件,必须注册在所有路由之后。

Express 内置的中间件

Express 4.16.0 版本开始,Express 内置了 3 个常用的中间件:

  • express.static:快速托管静态资源的内置中间件。例如:html 文件、图片、CSS 样式等(无兼容性)。
  • express.json:解析 JSON 格式的请求数据(有兼容性,仅在 4.16.0+ 版本中可用)。通过 req.body 获取数据。不配置该中间件,req.bodyundefine
  • express.urlencoded:解析 URL-encoded 格式的请求体数据(有兼容性,同上)。获取数据方式同上。
app.use(express.json())

app.use(express.unlencoded({extended: false}))
第三方的中间件

按需下载并配置的中间件。

例如:在 Express 4.16.0 之前的版本中,经常用 body-parser 这个第三方中间件,来解析请求体数据,使用步骤如下:

  1. 运行 npm install body-parser 安装中间件。
  2. 使用 require 导入中间件。
  3. 调用 app.use() 注册并使用中间件。

使用 Express 写接口

创建基本的服务器

app.js

const express = require('express')
const app = express()

app.listen(80, () => {
    console.log('Express server running at http://127.0.0.1');
})

创建 API 路由模块

router/index.js

const express = require('express')

const router = express.Router()

module.exports = router

app.js

const router = require(path.join(__dirname, './router/index'))

app.use('/api', router)

编写 get 接口

router.get('/query', (req, res) => {
    let id = req.query.id
    console.log(id);
    res.send('查询用户')
})

编写 post 请求

//配置中间件
router.use(express.json())
router.use(express.urlencoded({ extended: false }))

router.post('/add', (req, res) => {
    let body = req.body
    console.log(body)
    res.send({
        status: 200,
        msg: '添加成功',
        data: body
    })
})

CROS 跨域资源共享

上文的 getpost 请求是不支持跨域请求的。

解决跨域问题的解决方案主要有两种:

  • CORS(主流的解决方案)。
  • JSONP(有缺陷的解决方案,只支持 GET 请求)。
使用 cors 中间件解决跨域问题

corsexpress 的一个第三方中间件。通过安装和配置 cors 中间件,可以很方便的解决跨域问题。

使用步骤如下:

  1. 运行 npm install cors 安装中间件。
  2. 使用 const cors = require('cors') 导入中间件。
  3. 路由之前调用 app.use(cors()) 配置中间件。
什么是 CORS

CORS 由一系列 HTTP 响应头组成,这些 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源。

浏览器的同源安全策略默认会阻止网页跨域获取资源。但如果接口服务器配置了 CORS 相关的 HTTP 响应头,就可以解除浏览器端的跨域访问限制。

CORS 的注意事项

cors 主要在服务端进行配置。客户端无需做任何额外的配置。

cors 在浏览器有兼容性。只有支持 XMLHttpRequest Level2 的浏览器,才能正常访问开启了 cors 的服务端接口。

CORS 响应头部

Access-Control-Allow-Origin

响应头部中可以携带一个 Access-Control-Allow-Origin 字段,其语法如下:

Access-Control-Allow-Origin: <origin> | *

其中,origin 的值制定了允许访问该资源的外域 URL。

例如,下面的字段值将只允许来自 http://huangguosheng.com 的请求

res.setHeader('Access-Control-Allow-Origin', 'http://huangguosheng.com')

若果指定了通配符 * ,则代表允许来自任何域的请求,示例代码如下:

res.setHeader('Access-Control-Allow-Origin', '*')

Access-Control-Allow-Headers

默认情况下,CORS 仅支持客户端向服务器发送如下的 9 个请求头:

Accept, Accept-language, Content-Language, DPR, Downlink, Save-Data, Viewport-WidthWidth, Content-Type(值仅限于:text/plain, multipart/form-data, application/x-www-form-urlencoded 三者之一))

如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过Access-Control-Allow-Headers对额外的请求头进行声明,否则这次请求会失败!

res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Customer-Header')

Access-Control-Allow-Methods

默认情况下,CORS 只支持 GET, POST, HEAD 请求。

如果客户端希望通过 PUT, DELETE 等方式请求服务器资源,则需要设置该请求头:

// 只允许 POST, GET, PUT, DELETE 方法
res.set('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE')

// 允许所有的 HTTP 方法
res.set('Access-Control-Allow-Methods', '*')
CORS 请求的分类

客户端在请求 CORS 接口时,根据请求方式和请求头的不同,可以将 CORS 的请求分为两大类,分别是:

  1. 简单请求。
  2. 预检请求。
简单请求

满足以下两个条件的请求:

  • 请求方式:GETPOSTHEAD 三者之一。
  • HTTP头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、ViewportWidth、Width、Content-Type(只有三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)。
预检请求

只要符合以下任何一个条件的请求,都需要进行预检请求:

  • 请求方式为:GET、POST、HEAD 之外的 Method 类型。
  • 请求头中包含自定义头部字段。
  • 向服务器发送了 application/json 格式的数据。

在浏览器与服务器正式通讯之前,浏览器会先发送 OPTION 请求进行预检验,以获知服务器是否允许该实际请求,所以这一次的 OPTION 请求称为”预检请求“。服务器成功响应后,才会发出真正的请求,并且携带真实的数据。

简单请求和预检请求的区别

简单请求的特点:客户端与服务器之间只会发生一次请求。

预检请求的特点:客户端与服务器之间会发生两次请求,OPTION 预检请求成功之后,才会发起真正的请求。

JSONP 接口

浏览器端通过 <script> 标签的 src 属性, 请求服务器上的数据,同时,服务器返回一个函数的调用。

特点:

  1. JSONP 不属于真正的 Ajax 请求,因为它没有使用 XMLHttpRequest 对象。
  2. JSONP 仅支持 GET 请求。

注意事项

如果项目中已经配置了CORS 跨域资源共享,为了防止冲突,必须在配置CORS 中间件之前声明 JSONP的接口。否则
JSONP 接口会被处理成开启了CORS 的接口。示例代码如下:

app.get('/api/jsonp', (req, res)=>{})

app.use(cors())

app.get('/api/get', (req, res)=>{})

实现 jsonp 接口的步骤

  1. 获得客户端发送过来的回调函数的名字。
  2. 得到通过 jsonp 形式发送给客户端的数据。
  3. 根据前两步得到的数据,拼接出一个函数调用的字符串。
  4. 把上一步拼接得到的字符串,响应给客户端的 <script> 客户端进行解析。

实现具体代码

app.get('/api/jsonp', (req, res)=>{
    const funcName = req.query.callback
    const data = { name: 'zs', age: 18 }
    const scriptStr = `${funcName}(${JSON.stringify(data)})`
    res.send(scriptStr)
})

网页中发起 jsonp 请求

$.ajax({
    method: 'GET',
    url: 'http://127.0.0.1/api/jsonp'
    dataType: 'jsonp',
    success: function(res){
        console.log(res)
    }
})

数据库与身份认证

在项目中操作数据库

步骤

  1. 安装 MySQL 数据库的第三方模块(mysql)。
  2. 通过 mysql 模块连接到 MySQL 数据库。
  3. 通过 mysql 模块执行 sql 语句。

安装模块

npm install mysql

配置 mysql 模块

const mysql = require('mysql')

const db = mysql.createPool({
    host: '127.0.0.1',
    user: 'root',
    password: '1234',
    database: 'db1'
})

使用 mysql

查询

db.query('select * from stu', (err, results) => {
    if (err) {
        return console.log(err)
    }
    console.log(results)
})

插入

可以使用 ? 作为占位符。

const user = { username: 'lisi', password: '1234' }
const sqlStr = 'insert into users (username, password) values (?, ?)'
db.query(sqlStr, [user.username, user.password], (err, results)=>{
	if (err) {
        return console.log(err)
    }
    if(results.affectedRows === 1){
        console.log('插入成功')
    }
})

插入数据的便捷方式

当数据对象每个属性和数据表的字段对应时,则可以直接把对象作为参数。

const user = { username: 'lisi', password: '1234' }
const sqlStr = 'insert into users set ?'
db.query(sqlStr, user, (err, results)=>{
	if (err) {
        return console.log(err)
    }
    if(results.affectedRows === 1){
        console.log('插入成功')
    }
})

更新数据及其快捷方式

······

删除数据及其快捷方式

······

Web 开发模式

目前主流的 web 开发模式有 2 种,分别是:

  1. 基于服务端渲染的传统 web 开发模式。(前后端不分离)
  2. 基于前后端分离的新型 web 开发模式。(前后端分离)

前后端的身份认证

身份认证

通过一定的手段,完成对用户身份的确认。

不同开发模式下的身份认证

  1. 服务端渲染:Session 验证机制。
  2. 前后端分离: JWT 认证机制。

在 Express 中使用 Session 认证

安装 express-session 中间件

npm install express-session

配置 express-session 中间件

let session = require('express-session')

app.use(session({
    secret: 'zjut666',	//secret 的值可以为任意字符串
    resave: false,	//固定写法
    saveUninitialized: true	//固定写法
}))

向 session 中存数据

中间件配置成功后,即可通过 req.session 来访问和使用 session 对象,从而存储用户的关键信息:

req.session.user = req.body //将用户的信息,存储到 Session 中
req.session.islogin = true

清空 session 信息

req.session.destroy()

JWT 认证机制

session 的局限性

Session 认证机制需要配合 Cookie才能实现。由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域 Session 认证。

注意:

  • 当前端请求后端接口不存在跨域问题的时候,推荐使用Session 身份认证机制。
  • 当前端需要跨域请求后端接口的时候,不推荐使用Session身份认证机制,推荐使用 JWT 认证机制。
JWT 的使用方式

客户端收到 JWT 后,通常把它存到 localStorage 或 sessionStorage 中。

需要验证身份的请求就把 jwt 放在 HTTP 请求头的 Authorization 字段中,格式如下:

Authorization: Bearer <token>

在 Express 中使用 JWT

安装 jwt 包
npm install jsonwebtoken express-jwt

其中:

  • jsonwebtoken 用于生成 JWT 字符串。
  • express-jwt 用于将 JWT 字符串解析还原成 JSON 对象。
导入 jwt 包
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
定义 secret 密钥

为保证 jwt 的安全性,需要定义密钥来对其进行加密和解密。

const secretKey = 'zjut666'
生成 jwt 字符串
jwt.sign({
        username: 'zhangsan', 
        password: '1234'
	}, 
    secretKey, 
    {
    	expiresIn:'30s'
	}
)
解析 jwt 成 json

通过 express-jwt 中间件来实现。

// unless 用来排除不需要验证的访问
app.use(expressJWT({secretKey}).unless({path: [/^\/api\//]}))

解析成功后,即可通过 req.user 或 req.auth 访问用户信息。

捕获接卸 jwt 失败后的错误

若 Token 过期或不合法影响项目的运行,可以注册一个全局的错误中间件,捕获相关错误进行处理。

app.use((err, req, res, next) =>{
    if(err.name === 'UnauthorizedError'){
        return res.send({status: 401, message: '无效的 token'})
    }
    res.send({ status: 500, message: '未知错误' })
})

项目实战

初始化

创建项目

初始化包管理配置文件

npm init -y

安装指定版本的 express

npm i express@4.17.1

根目录下新建项目入口文件 app.js

const express = require('express')
const app = express()

app.listen(80, () => {
    console.log('server is running at http://127.0.0.1')
})

配置 cors 跨域

安装中间件

npm i cors@2.8.5

在 app.js 中导入并配置 cors 中间件

const cors = require('cors')
app.use(cors())

配置解析表单数据的中间件

app.use(express.urlencoded({ extended: false }))

初始化路由相关的文件夹

在项目根目录中,新建 router 文件夹,用来存放所有的路由模块。路由模块中,只存放客户端的请求与处理函数之间的映射关系。

在项目根目录中,新建 router_handler 文件夹,用来存放所有的路由函数处理模块。专门负责存放每个路由对应的处理函数。

初始化用户路由模块

router 文件夹中新建 user.js 文件,作为用户的路由模块,并初始化如下代码:

const express = require('express')
const router = express.Router()

// 注册新用户
router.post('/register', (req, res) => {
    res.send('register OK')
})

// 登录
router.post('/login', (req, res) => {
    res.send('login OK')
})

// 将路由对象共享出去
module.exports = router

在 app.js 中,导入并使用用户路由模块

const userRouter = require('./router/user')
app.use('/api/user', userRouter)

抽离用户路由模块中的处理函数

目的:为了保证路由模块的纯粹性,所有的路由处理函数,必须抽离到路由处理函数模块中

/router_handler/user.js 中,使用 exports 对象,分别向外共享如下两个函数:

exports.regUser = (req, res) => {
    res.send('register OK')
}

exports.login = (req, res) => {
    res.send('login OK')
}

修改 /router/user.js

const express = require('express')
const router = express.Router()

const userHandler = require('../router_handler/user')

// 注册新用户
router.post('/register', userHandler.regUser)

// 登录
router.post('/login', userHandler.login)

// 将路由对象共享出去
module.exports = router

登录注册

安装并配置 mysql 模块

安装

npm i mysql@2.18.1

在项目根目录中新建 /db/index.js 文件,在此自定义模块中创建数据库的连接对象

const mysql = require('mysql')

const db = mysql.createPool({
    host: '127.0.0.1',
    user: 'root',
    password: '1234',
    database: 'sqltest'
})

module.exports = db

注册

实现步骤
  1. 检测表单数据是否合法。
  2. 检测用户名是否被占用。
  3. 对密码进行加密处理。
  4. 插入新用户。
密码加密

使用 bcrypt 包

数据库异步问题解决

使用 callback 回调:

const db = require('../db/index')

const sqlStr = {
    getAll: 'select * from user'
}

function getAll(callback) {
    db.query(sqlStr.getAll, (err, results) => {
        if (err) {
            return callback({code: 500, msg: '未知错误'})
        }
        callback(results)
    })
}

//调用
getAll(results=>{
    console.log(results)
})
  • 26
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

临安剑客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值