Node.js
Buffer 缓冲区
- Buffer 的结构与数组类似,操作方法也与数组类似
- 数组不能存储二进制文件,Buffer 是专门存储二进制数据的
- Buffer 存储的是二进制数据,显示时以 16 进制的形式显示
- Buffer 每一个元素范围是 00 ~ ff,即 0 ~ 255,00000000 ~ 11111111
- 每一个元素占用一个字节内存
- Buffer 是对底层内存的直接操作,因此大小一旦确定就不能修改
Buffer 常用方法:
Buffer.from(str[, encoding])
:将一个字符串转换为 BufferBuffer.alloc(size)
:创建指定大小的 BufferBuffer.alloUnsafe(size)
:创建指定大小的 Buffer,可能包含敏感数据(分配内存时不会清除内存残留的数据)buf.toString()
:将 Buffer 数据转为字符串
fs 文件系统模块
- fs 模块中所有的操作都有两种形式可供选择:同步和异步
- 同步文件系统会阻塞程序的执行,也就是除非操作完毕,否则不会向下执行代码
- 异步文件系统不会阻塞程序的执行,而是在操作完成时,通过回调函数将结果返回
- 实际开发很少用同步方式,因此只介绍异步方式
读取文件
简单文件读取语法格式:
1 | fs.readFile(path[, options], callback) |
path
:文件路径options
:配置选项,若是字符串则指定编码格式encoding
:编码格式flag
:打开方式
callback
:回调函数err
:错误信息data
:读取的数据,如果未指定编码格式则返回一个 Buffer
1 | const fs = require('fs') |
流式文件读取
简单文件读取的方式会一次性读取文件内容到内存中,若文件较大,会占用过多内存影响系统性能,且读取速度慢。大文件适合用流式文件读取,它会分多次将文件读取到内存中
1 | var fs = require('fs') |
简便方式:
1 | var fs = require('fs') |
写入文件
简单文件写入语法格式:
1 | fs.writeFile(file, data[, options], callback) |
file
:文件路径data
:写入内容options
:配置选项,包含encoding
,mode
,flag
;若是字符串则指定编码格式callback
:回调函数
1 | const fs = require('fs') |
流式文件写入
1 | // 同步、异步、简单文件的写入都不适合大文件的写入,性能较差,容易导致内存溢出 |
路径动态拼接问题 __dirname
在使用 fs 模块操作文件时,如果提供的操作路径是以 ./
或 ../
开头的相对路径时,容易出现路径动态拼接错误的问题。
- 原因:代码在运行的时候,会以执行 node 命令时所处的目录,动态拼接出被操作文件的完整路径
- 解决方案:在使用 fs 模块操作文件时,直接提供完整的路径,从而防止路径动态拼接的问题
__dirname
获取文件所处的绝对路径
1 | fs.readFile(__dirname + '/files/1.txt', 'utf8', function(err, data) { |
其它操作
验证路径是否存在:
fs.exists(path, callback)
fs.existsSync(path)
获取文件信息:
fs.stat(path, callback)
fs.stat(path)
删除文件:
fs.unlink(path, callback)
fs.unlinkSync(path)
列出文件:
fs.readdir(path[,options], callback)
fs.readdirSync(path[, options])
截断文件:
fs.truncate(path, len, callback)
fs.truncateSync(path, len)
建立目录:
fs.mkdir(path[, mode], callback)
fs.mkdirSync(path[, mode])
删除目录:
fs.rmdir(path, callback)
fs.rmdirSync(path)
重命名文件和目录:
fs.rename(oldPath, newPath, callback)
fs.renameSync(oldPath, newPath)
监视文件更改:
fs.watchFile(filename[, options], listener)
path 路径模块
path 模块是 Node.js 官方提供的用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径的处理需求
路径拼接
1 | const path = require('path') |
获取路径中文件名
使用 path.basename()
方法,可以获取路径中的最后一部分,常通过该方法获取路径中的文件名
1 | path.basename(path[, ext]) |
path
: 文件路径ext
: 文件扩展名
1 | const path = require('path') |
获取路径中文件扩展名
1 | const path = require('path') |
http 模块
http 模块是 Node.js 官方提供的、用来创建 web 服务器的模块
创建基本 Web 服务器
1 | const http = require('http') |
实现简陋路由效果
1 | const http = require('http') |
模块化
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分为若干模块的过程,模块是可组合,分解和更换的单元。模块化可提高代码的复用性和可维护性,实现按需加载。模块化规范是对代码进行模块化拆分和组合时需要遵守的规则,如使用何种语法格式引用模块和向外暴露成员
模块分类
- 内置模块:Node.js 官方提供的模块,如 http、fs、path 等。内置模块加载优先级最高
- 自定义模块
加载自定义模块时,路径要以 ./
或 ../
开头,否则会作为内置模块或第三方模块加载。导入自定义模块时,若省略文件扩展名,则 Node.js 会按顺序尝试加载文件:
- 按确切的文件名加载
- 补全
.js
扩展名加载 - 补全
.json
扩展名加载 - 补全
.node
扩展名加载 - 报错
- 第三方模块
若导入第三方模块, Node.js 会从当前模块的父目录开始,尝试从 /node_modules
文件夹中加载第三方模块。如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。例如,假设在 C:\Users\WXZ\project\foo.js
文件里调用了 require('tools')
,则 Node.js 会按以下顺序查找:
C:\Users\WXZ\project\node_modules\tools
C:\Users\WXZ\node_modules\tools
C:\Users\node_modules\tools
C:\node_modules\tools
模块作用域
自定义模块中都有一个 module
对象,存储了和当前模块有关的信息。在自定义模块中,可以使用 module.exports
对象,将模块内的成员共享出去,供外界使用。导入自定义模块时,得到的就是 module.exports
指向的对象。默认情况下,exports
和 module.exports
指向同一个对象。最终共享的结果,以 module.exports
指向的对象为准。
每个模块内部,module
变量代表当前模块,module
变量是一个对象,module.exports
是对外的接口,加载某个模块即加载该模块的 module.exports
属性。模块第一次加载后会被缓存,即多次调用 require()
不会导致模块的代码被执行多次,提高模块加载效率
Express
Express 是用于快速创建服务器的第三方模块。基于 Node.js 平台,快速、开放、极简的 Web 开发框架
基本使用
安装 Express:
1 | npm install express |
创建服务器,监听客户端请求,并返回内容:
1 | const express = require('express') |
托管静态资源
- 通过
express.static()
方法可创建静态资源服务器,向外开放访问静态资源。 - Express 在指定的静态目录中查找文件,并对外提供资源的访问路径,存放静态文件的目录名不会出现在 URL 中
- 访问静态资源时,会根据托管顺序查找文件
- 可为静态资源访问路径添加前缀
1 | app.use(express.static('public')) |
路由
创建路由模块:
1 | const express = require('express') |
注册路由模块:
1 | const express = require('express') |
中间件
中间件是指流程的中间处理环节。服务器收到请求后,可先调用中间件进行预处理。中间件是一个函数,包含 req
, res
, next
三个参数,next()
参数把流转关系交给下一个中间件或路由
中间件注意事项:
- 在注册路由之前注册中间件(错误级别中间件除外)
- 中间件可连续调用多个
- 别忘记调用
next()
函数 next()
函数后别写代码- 多个中间件共享
req
,res
对象
全局中间件
过 app.use()
定义的中间件为全局中间件
1 | const express = require('express') |
局部中间件
1 | const express = require('express') |
中间件分类
- 应用级别的中间件
通过 app.use()
或 app.get()
或 app.post()
,绑定到 app
实例上的中间件
- 路由级别的中间件
绑定到 express.Router()
实例上的中间件,叫做路由级别的中间件。用法和应用级别中间件没有区别。应用级别中间件是绑定到 app
实例上,路由级别中间件绑定到 router
实例上
1 | const app = express() |
- 错误级别的中间件
用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。错误级别中间件的处理函数中,必须有 4 个形参,形参顺序从前到后分别是 (err, req, res, next)
。错误级别的中间件必须注册在所有路由之后
1 | const express = require('express') |
- 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+ 版本中可用)
1 | app.use(express.json()) |
- 第三方中间件
CORS 跨域资源共享
CORS(Cross-Origin Resource Sharing,跨域资源共享)解决跨域,是通过 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源。浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了 CORS 相关的 HTTP 响应头,就可解除浏览器端的跨域访问限制。CORS 主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了 CORS 的接口。CORS 在浏览器中有兼容性。只有支持 XMLHttpRequest Level2 的浏览器,才能正常访问开启了 CORS 的服务端接口(例如:IE10+,Chrome4+,FireFox3.5+)。
cors 中间件解决跨域
- 安装中间件:
npm install cors
- 导入中间件:
const cors = require('cors')
- 配置中间件:
app.use(cors())
CORS 常见响应头
Access-Control-Allow-Origin
:制定了允许访问资源的外域 URL
1 | res.setHeader('Access-Control-Allow-Origin', 'https://wuxingzzz.top') |
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
对额外的请求头进行声明,否则这次请求会失败!
1 | res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header') |
Access-Control-Allow-Methods
默认情况下,CORS 仅支持客户端发起 GET,POST,HEAD 请求。如果客户端希望通过 PUT,DELETE 等方式请求服务器的资源,则需要在服务器端,通过 Access-Control-Alow-Methods
来指明实际请求所允许使用的 HTTP 方法
1 | res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, HEAD') |
数据库和身份认证
Node 操作 mysql
- 安装 mysql 模块:
npm install mysql
- 建立连接
1 | const mysql = require('mysql') |
- 测试是否正常工作
1 | db.query('select 1', (err, results) => { |
- 查询数据
1 | db.query('select * from users', (err, results) => { |
- 插入数据
1 | // ? 表示占位符 |
向表中新增数据时,如果数据对象的每个属性和数据表的字段一一对应,则可以通过如下方式快速插入数据:
1 | const user = {username: 'WXZ', password: '123456'} |
- 更新数据
1 | const sql = 'update users set username=?, password=? where id=?' |
快捷方式:
1 | const user = {id: 7, username: 'WXZ', password: '123456'} |
- 删除数据
1 | const sql = 'delete from users where id=?' |
使用 delete 语句会真正删除数据,保险起见,使用标记删除的形式,模拟删除的动作。即在表中设置状态字段,标记当前的数据是否被删除
1 | db.query('update users set status=1 where id=?', 7, (err, results) => { |
Web 开发模式
服务端渲染
服务器发送给客户端的 HTML 页面,是在服务器通过字符串的拼接动态生成的。因此客户端不需要使用 Ajax 额外请求页面的数据
1 | app.get('/index.html', (req, res) => { |
优点:
- 前端耗时短。浏览器只需直接渲染页面,无需额外请求数据
- 有利于 SEO。服务器响应的是完整的 HTML 页面内容,有利于爬虫爬取信息
缺点:
- 占用服务器资源。服务器需要完成页面内容的拼接,若请求比较多,会对服务器造成一定访问压力
- 不利于前后端分离,开发效率低
前后端分离
前后端分离的开发模式,依赖于 Ajax 技术的广泛应用。后端只负责提供 API 接口,前端使用 Ajax 调用接口
优点:
- 开发体验好。前端专业页面开发,后端专注接口开发
- 用户体验好。页面局部刷新,无需重新请求页面
- 减轻服务器的渲染压力。页面最终在浏览器里生成
缺点:
- 不利于 SEO。完整的 HTML 页面在浏览器拼接完成,因此爬虫无法爬取页面的有效信息。Vue、React 等框架的 SSR(server side render)技术能解决 SEO 问题
身份认证
Session 认证机制
服务端渲染推荐使用 Session 认证机制
- 安装 express-session 中间件
npm install express-session
- 配置中间件
1 | const session = require('express-session') |
- 向 session 中存数据。中间件配置成功后,可通过
req.session
访问 session 对象,存储用户信息
1 | app.post('/api/login', (req, res) => { |
- 从 session 取数据
1 | app.get('/api/username', (req, res) => { |
- 清空 session
1 | app.post('/api/logout', (req, res) => { |
JWT 认证机制
前后端分离推荐使用 JWT(JSON Web Token)认证机制,是目前最流行的跨域认证解决方案。Session 认证机制需要配合 Cookie 才能实现。由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域 Session 认证。当前端请求后端接口不存在跨域问题的时候,推荐使用 Session 身份认证机制。当前端需要跨域请求后端接口的时候,不推荐使用 Session 身份认证机制,推荐使用 JWT 认证机制。JWT将用户的信息通过 Token 字符串的形式,保存在客户端浏览器中。服务器通过还原 Token 字符串的形式来认证用户的身份。
JWT 组成部分:
- Header,Payload,Signature
- Payload 是真正的用户信息,加密后的字符串
- Header 和 Signature 是安全性相关部分,保证 Token 安全性
- 三者使用
.
分隔:Header.Payload.Signature
JWT 使用方式:
- 客户端会把 JWT 存储在 localStorage 或 sessionStorage 中
- 此后客户端与服务端通信需要携带 JWT 进行身份认证,将 JWT 存在 HTTP 请求头
Authorization
字段中 - 加上 Bearer 前缀:
Authorization: Bearer <token>
Express 使用 JWT:
- 安装:
jsonwebtoken
用于生成 JWT 字符串;express-jwt
用于将 JWT 字符串解析还原成 JSON 对象
1 | npm install jsonwebtoken express-jwt |
- 定义 secret 密钥
为保证 JWT 字符串的安全性,防止其在网络传输过程中被破解,需定义用于加密和解密的 secret 密钥。生成 JWT 字符串时,使用密钥加密信息,得到加密好的 JWT 字符串。把 JWT 字符串解析还原成 JSON 对象时,使用密钥解密
1 | const jwt = require('jsonwebtoken') |
- 生成 JWT 字符串
1 | app.post('/api/login', (req, res) => { |
- JWT 字符串还原为 JSON 对象
客户端访问有权限的接口时,需通过请求头的 Authorization
字段,将 Token 字符串发送到服务器进行身份认证。服务器可以通过 express-jwt
中间件将客户端发送过来的 Token 解析还原成 JSON 对象
1 | // unless({ path: [/^\/api\//] }) 指定哪些接口无需访问权限 |
- 获取用户信息
当 express-jwt
中间件配置成功后,即可在那些有权限的接口中,使用 req.user
对象,来访问从 JWT 字符串中解析出来的用户信息
1 | app.get('/admin/getinfo', (req, res) => { |
- 捕获解析 JWT 失败后产生的错误
当使用 express-jwt
解析 Token 字符串时,如果客户端发送过来的 Token 字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行。通过 Express 的错误中间件,捕获这个错误并进行相关的处理
1 | app.use((err, req, res, next) => { |