 Node.js
Node.js
  Node.js
Node.js 是一个开源的、跨平台的 JavaScript 运行时环境。
# Node 版本管理工具
| 工具 | 适用场景 | 跨平台 | 速度 | 自动切换 | 管理其他语言 | 
|---|---|---|---|---|---|
| nvm | 通用方案 | ✅ (macOS/Linux) | 中等 | ❌ | ❌ | 
| fnm | 更快替代 nvm | ✅ (macOS/Linux) | ⚡ 快 | ✅ (支持 .nvmrc) | ❌ | 
| n | 极简版 | ❌ (仅 macOS/Linux) | 快 | ❌ | ❌ | 
| Volta | 自动切换 | ✅ | 中等 | ✅ | ❌ | 
| asdf | 多语言管理 | ✅ | 中等 | ✅ | ✅ | 
- nvm 安装
brew install nvm
- 常用命令
命令 功能 命令 功能 nvm install <version>安装指定版本 nvm uninstall <version>卸载指定版本 nvm use <version>切换到指定版本 nvm alias default <version>设置默认版本 nvm use system使用系统安装的 Node nvm ls已安装的所有版本 nvm ls-remote列出所有可安装的远程版本 nvm ls-remote --lts列出所有可安装的 LTS 版本 nvm current当前使用的 Node.js 版本 nvm which <version>显示某个版本的 Node.js 安装路径 
# 1.安装
Node.js (opens new window) LTS 版本和 Current 版本
- LTS 为长期版本,有稳定性
- Current 为新特性尝鲜版
node -v  # 安装后查看node版本
# 2.模块
# fs 文件模块
const fs = require("fs");
- 读取文件中的内容
/**
 * 参数1(必选)  文件的路径
 * 参数2(可选)  编码格式 默认指定 utf-8
 * 参数3(必选)  回调参数接受两个参数 (err,dataStr)
*/
fs.readFile(file[,options],callback)
2
3
4
5
6
- 向指定文件写入内容
/**
 * 参数1(必选)  文件的路径
 * 参数2(必选)  表示要写入的内容
 * 参数3(可选)  编码格式 默认指定 utf-8
 * 参数3(必选)  回调参数 err
*/
fs.writeFile(file,data[,options],callback)
2
3
4
5
6
7
# path 路径模块
path 模块是 Node.js 官方提供的,用来处理路径模块的处理
const path = require('path')
path.join([...paths])     // 将多个路径片段拼成一个完整的路径字符串
path.basename(path[,ext]) // 获取路径中的最后一部分
/**
 * path 路径名(必选)
 * ext  文件扩展名(可选)
 */
2
3
4
5
6
7
- html 文件分离
const fs = require("fs");
const path = require("path");
const regStyle = /<style>[\s\S]*<\/style>/;
const regScript = /<script>[\s\S]*<\/script>/;
fs.readFile(path.join(__dirname, "/index.html"), "utf-8", (err, dataStr) => {
  if (err) return console.log("读取文件失败" + err.message);
  resolveCSS(dataStr);
  resolveJS(dataStr);
  resolveHTML(dataStr);
});
function resolveCSS(htmlStr) {
  const r1 = regStyle.exec(htmlStr);
  const newStr = r1[0].replace("<style>", " ").replace("</style>", " ");
  fs.writeFile(path.join(__dirname, "/clock/index.css"), newStr, (err) => {
    if (err) return console.log("css写入失败" + err.message);
  });
  console.log("css写入成功!!!");
}
function resolveJS(htmlStr) {
  const r1 = regScript.exec(htmlStr);
  const newStr = r1[0].replace("<script>", " ").replace("</script>", " ");
  fs.writeFile(path.join(__dirname, "/clock/index.js"), newStr, (err) => {
    if (err) return console.log("js写入失败" + err.message);
  });
  console.log("js写入成功!!!");
}
function resolveHTML(htmlStr) {
  console.log(regStyle);
  const newHtml = htmlStr
    .replace(regStyle, '<link rel="stylesheet" href="./index.css"></link>')
    .replace(regScript, '<script src="./index.js"></script>');
  console.log(newHtml);
  fs.writeFile(path.join(__dirname, "/clock/index.html"), newHtml, (err) => {
    if (err) return console.log("html写入失败" + err.message);
  });
  console.log("html写入成功!!!");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# http 模块
// 导入http模块
const http = require("http");
const path = require("path");
const fs = require("fs");
// 创建web服务器示例
const server = http.createServer();
server.on("request", (req, res) => {
  /**
   * req 请求对象
   * res 响应对象
   */
  const url = req.url;
  const fpath = path.join(__dirname + url);
  fs.readFile(fpath, "utf-8", (err, dataStr) => {
    if (err) return res.end("404 not find");
    // 向客户端发送指定内容,并结束这次请求
    res.end(dataStr);
  });
});
server.listen(8089, () => {
  console.log("server runing at http://127.0.0.1:8089");
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 模块化
模块化是指解决一个复杂问题时,自顶向下逐层把系统分成若干模块的过程。
- 内置模块(是由 httNode.js 提供的模块,如 path、fs、http 等)
- 自定义模块(用户自己写的模块)
- 第三方模块(第三方开发出来的模块)
# module 对象
每一个自定义的 js 中都有一个 module 对象,里面存储了和当前模式有关的信息。
# module.exports 对象
- 使用 require()方法导入模块的时候,就是导入 module.exports 所指向的对象。
- 使用 module.exports 对象将莫模块内的成员共享出去,供外界使用。
module.exports = {
  userName: "jack",
};
2
3
# export 对象
默认情况下,exports 和 module.exports 指向同一个对象。require()模块时,得到的永远是 module.exports 指向的对象。
exports.userName = "jack";
# 模块的加载机制
# 内置模块
内置模块是由 Node.js 提供的,他的加载优先级最高。如果 node_modules 中有 fs 模块,require('fs')引用的还是 Node.js 中内置的 fs 模块。
# 自定义模块
加载自定义模块时,必须指定以
./或../开头的路径标识符Node 加载规则
- 按照确切的文件名进行加载
- 补全.js扩展名进行加载
- 补全.json扩展名进行加载
- 补全.node扩展名进行加载
- 加载失败,终端报错 :::
# 第三方模块
从当前父目录开始,从
node_modules文件中加载第三方模块,如果没有找到对应的第三方模块,则移动到上一层父目录中,进行加载,直到文件的根目录。
# 以目录作为模块
- 被加载的目录下查找package.json文件,寻找 main 属性,作为 require()的入口。
- 如果目录中没有package.json文件或者 main 入口不存在或无法解析,则 Node.js 会加载目录下 index.js 文件。
- 若上面都查找失败,则终端报错Error: Cannot find module './test'
# 3.npm
Node.js 中第三方模块就是包
# 常用命令
npm -v                 # npm 版本
npm i 包名 -g           # 安装全局依赖包
npm uninstall 包名 -g   # 卸载全局依赖包
npm root -g            # 查看全局安装npm包的路径
npm view 包名 versions  # 查看该包所有的版本
npm i 包名@2.2.0        # 安装指定版本的包
npm install 包名        # 安装依赖
npm uninstall 包名       # 卸载依赖
npm i 包名 --save-dev   # 安装依赖到开发环境 简写 npm i 包名 -D
2
3
4
5
6
7
8
9
# 包的语义化版本规范
包的版本号以"点分十进制"形式来定义的。版本号提升规则(前面的版本号增加了,后面的版本号归零)
- 第 1 位数字 大版本
- 第 2 位数字 功能版本
- 第 3 位数字 Bug 修复版本
# 包管理配置文件
- 创建 package.json - npm init -y 
- 文件配置 
{
  "name": "init", // 名称
  "version": "1.0.0", // 版本
  "description": "", // 项目描述
  "main": "index.js", // npm包项目的入口文件
  "keywords": [], // 搜索的关键词
  "scripts": {
    // 启动脚本
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "", // 作者
  "license": "ISC", // 开源协议
  "dependencies": {}, // 项目依赖(生产)
  "devDependencies": {} // 项目依赖(开发)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# nrm 管理 npm 镜像源
npm i nrm -g    # 全局安装nrm
nrm ls          # 查看所有的镜像源
nrm use taobao  # 切换到淘宝镜像源
2
3
- 直接修改为淘宝镜像源(建议使用 nrm)
# 查看当前的下包镜像源
npm config get registry
# 将npm镜像源切换到淘宝镜像源
npm config set registry https://registry.npm.taobao.org/
2
3
4
# 实用的 npm 包
- i5ting_toc
把 md 文档转换成 html 页面的小工具 
# 构建 npm 包
- 包结构
- 包必须以单独的目录而存在
- 包的顶级目录必须包含package.json文件
- package.json中必须包含 name(包名)、version(版本号)、main(入口文件)三个属性
- 包文件
- 使用 npm init -y 创建package.json文件
- 新建入口 index.js,package.json中配置"main": "index.js"
- 新建README.md文件,用于包描述
- 发布包
- 注册 npm 账户,在终端执行npm login命令,输入用户名、密码、邮箱、邮箱验证吗运行 npm login之前,需要先把下载包的服务器切换为 npm 官方服务器,否则会导致发布包失败。
- 将终端切到根目录上,运行npm publish命令,即可将包发布到 npm 上。
- 删除包
- npm unpublish 包名 --fore删除 npm 上已发布的包
注意
- npm unpublish命令只能删除 72 小时以内发布的包
- npm unpublish删除的包,在 24 小时内不允许重复发布
# 4.Express
高度包容、快速而极简的 Node.js Web 框架 Express (opens new window)
// 导入express
const express = require("express");
const app = express();
// get请求
/**
 * req.params 匹配url的动态参数  /user/:id/:name  可以跟多个,但必须设置几个后面写几个
 * req.query  匹配url的查询参数  /user?age=18&name=jack
 * req.body   post请求的主体参数
 */
app.get("/user/:id/:name", function (req, res) {
  console.log(req.params);
  res.send("hello world");
});
// post 请求
app.post("/submit", function (req, res) {
  res.send("hello world");
});
app.listen(88, () => {
  console.log("http://127.0.0.1:88");
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 托管静态文件
app.use(express.static('public'));
app.use(express.static('files'));
// 访问位于 public 目录中的文件 (也可以托管多个静态资源)
http://localhost:3000/images/kitten.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/app.js
// 添加访问路径前缀
app.use('/public',express.static('../clock'))
2
3
4
5
6
7
8
9
# 路由
路由表示应用程序端点 (URI) 的定义以及端点响应客户机请求的方式。
- router.js
const express = require("express");
// 创建路由对象
const router = express.Router();
router.get("/user", (req, res) => {
  res.send("user");
});
module.exports = router;
2
3
4
5
6
7
- app.js
const express = require("express");
const router = require("./router");
const app = express();
// 挂载路由(添加前缀)
app.use("/api", router);
app.listen(88, () => {
  console.log("http://127.0.0.1:88");
});
2
3
4
5
6
7
8
# JSONP 请求
浏览其通过
<script>标签的 src 属性,请求服务器上的数据,服务器返回一个函数的调用
特点
- JSONP 不是一个 Ajax 请求,因为他没有 XMLHttpRequest 这个对象。
- JSONP 仅支持 GET 请求,不支持 POST、DELETE、PUT 请求。
- app.js
const express = require("express");
const cors = require("cors");
const app = express();
app.get("/api/jsonp", (req, res) => {
  // 获取客户端发送过来的回调函数名
  const funName = req.query.callback;
  const data = { age: 17, name: "jack" };
  // 拼接函数调用字符串
  const str = `${funName}(${JSON.stringify(data)})`;
  res.send(str);
});
// 如果已经配置cors了,jsonp请求必须在配置cors中间件之前声明
app.use(cors());
app.listen(8089, () => {
  console.log("server http://127.0.0.1:8089");
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 页面调用
$("#btnJSON").on("click", function () {
  $.ajax({
    type: "delete",
    url: "http://127.0.0.1:8089/api/jsonp",
    dataType: "jsonp",
    success: (res) => {
      console.log(res);
    },
  });
});
2
3
4
5
6
7
8
9
10
# 中间件
中间件函数能够访问请求对象 (req)、响应对象 (res) 以及应用程序的请求/响应循环中的下一个中间件函数。下一个中间件函数通常由名为 next 的变量来表示。
注意事项
- 在路由之前注册中间件 
- 客户端发送过来的请求,可以连续调用多个中间件进行处理 
- 最后需要调用 - next()函数
- 调用多个中间件,多个中间件共享 - req和- res:::
- 全局中间件(应用级别中间件) - 通过 - app.use()或- app.get()或- app.post()绑定到 app 实例上的中间件
// 全局中间件
app.use((req, res, next) => {
  next();
});
const vm = function (req, res, next) {
  next();
};
app.get("/user", vm, (req, res) => {});
// 调用多个中间件写法
app.get("/user", vm1, vm2, (req, res) => {});
app.get("/user", [vm1, vm2], (req, res) => {});
2
3
4
5
6
7
8
9
10
11
- 路由级别中间件 绑定到 express.Router()实例上的中间件
const express = require("express");
const router = express.Router();
router.use(function (req, res, next) {
  next();
});
app.use("/api", router);
2
3
4
5
6
- 错误级别的中间件 捕获项目中发生的异常错误 警告 错误中间件必须注册在路由之后,否则不会生效 
const express = require("express");
const app = express();
app.get("/user", (req, res) => {
  throw new Error("发生错误");
  res.send("user");
});
app.use((err, req, res, next) => {
  // 捕获token失效
  if ((err.name = "UnauthorizedError")) {
    return res.send("token失效");
  }
  res.send("错误" + err.message);
});
app.listen(88, () => {
  console.log("http://127.0.0.1:88");
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 内置中间件
- express.static()托管静态资源中间件
- express.json()4.16.0+ 解析 JSON 格式的请求体数据
- express.urlencoded()4.16.0+ 解析 URL-encdoded 格式的请求体数据
const express = require("express");
const app = express();
// 如果不配置express.json() req.body则默认undefined
app.use(express.json());
// 传urlencoded参数的话,如果已配置express.json()则 req.body 为 {}
app.use(express.urlencoded({ extended: false }));
2
3
4
5
6
- 第三方中间件 第三方开发出来的中间件如 body-parser
# 5.身份认证
身份认证是一种通过验证请求发送者身份的过程。它通常被用来确保只有授权用户可以访问受限资源或执行操作。
# Session 认证
通过在服务器端存储用户的会话信息来记录和验证用户的身份。在 Session 认证中,用户在登录时提供用户名和密码,然后服务器会验证这些凭证,如果验证成功,就为该用户创建一个唯一的 Session ID,并将其存储到服务器端的内存或数据库中。该 Session ID 通常会在每个请求中被包含在一个称为
Cookie的 HTTP 请求头中。
- Cookie 是什么呢?
Cookie是指浏览器端(客户端)存储在用户电脑上的小数据文件,通常是由网站服务器发送给浏览器,用于存储和检索用户的信息,如网站的登录状态、购物车信息等。下面是Cookie的几大特征
- 自动发送
- 域名独立
- 过期时限
- 4KB 大小限制
# 工作流程
- 在浏览器发送 HTTP 请求访问网站之前,网站服务器会在 HTTP 响应头中添加 Set-Cookie 字段,其中包含了要发送到浏览器中的 Cookie 内容。
- 浏览器收到响应头中的 Set-Cookie 字段后,将该 Cookie 保存到本地文件中(路径在浏览器的特定目录中),以备下次访问该网站时使用。
- 当浏览器下一次访问该网站时,将会在请求头的 Cookie 字段中带上保存的 Cookie 信息。
- 服务器收到 Cookie 后,会解析其中的内容并处理相关业务逻辑,然后将响应返回给浏览器。
# 缺点
Cookie 是明文传输的,因此敏感信息(如用户名和密码)不应该保存在 Cookie 中。此外,如果一个网站的 Cookie 过多或者太大,将会占用用户过多的磁盘空间和内存,因此最好定期清除过期或不必要的 Cookie
# 使用 session
- 安装
npm i express-session
- 配置
const express = require("express");
const session = require("express-session");
const app = express();
app.use(
  session({
    secret: "dong", // 任意字符串
    resave: false, // 固定写法
    saveUninitialized: true, // 固定写法
  })
);
app.listen(8089, () => {
  console.log("server http://127.0.0.1:8089");
});
2
3
4
5
6
7
8
9
10
11
12
13
说明
当配置成功后,可通过req.session来访问和使用 session 对象。
- 注册中间件
const express = require("express");
const session = require("express-session");
const app = express();
app.use(
  session({
    secret: "dongkj", // 任意字符串
    resave: false, // 固定写法
    saveUninitialized: true, // 固定写法
    cookie: {
      maxAge: 24 * 60 * 60 * 1000, // 24 hours
    },
  })
);
app.listen(8089, () => {
  console.log("server http://127.0.0.1:8089");
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 使用
// 存取数据
req.session.user = {};
// 清空session
req.session.destroy();
2
3
4
# JWT 认证
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间作为 JSON 对象安全地传输信息。JWT 通常用于身份验证和授权,是一种无状态的认证机制。
# JWT 的组成
- Header(头部):包含了 token 的元数据,例如加密算法等。
- Payload(有效荷载):包含了对于身份验证和授权所需的具体信息,例如用户 ID、角色等。
- Signature(签名):将第一部分和第二部分组合在一起并加密得到的结果,用于校验 token 的完整性以及认证 token 是否有效。
# 工作流程
- 用户提供用户名和密码进行身份验证。
- 服务器验证成功后,生成一个 JSON Web Token 并将其返回给客户端。
- 客户端存储该 token,并在每次请求中将其添加到 Authorization Header 中进行访问。
- 服务器验证 token 的有效性并返回响应或者拒绝响应。
# 优点
使用 JWT 的好处是可以实现无状态和分布式的系统,由于 token 存储在客户端,服务器端可以无需存储用户的信息,从而降低了服务器端的存储压力。此外,由于 JWT 使用了加密技术,可以保证 token 的安全性,防止 token 被篡改或伪造
# 使用 JWT
- 安装
# jsonwebtoken  用于生成JWT字符串
# express-jwt   用于将JWT字符串还原成json对象
npm i express-jwt jsonwebtoken
2
3
注意
一定要在路由之前配置解析 Token 中间件
配置成功后可以调用req.user获取到用户信息,最新版本改为 req.auth
- 使用
const express = require("express");
const session = require("express-session");
const cors = require("cors");
const app = express();
// 1.导入
const jwt = require("jsonwebtoken");
const expressJWT = require("express-jwt");
// 2.设置密钥
const secretKey = "dong";
// 3.配置哪些接口不需要访问权限 案例中以/api开头的接口为不需要权限
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }));
// 解决跨域问题
app.use(cors());
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
// 登录
app.post("/api/login", (req, res) => {
  const { username, password } = req.body;
  if (username != "张三" || password != "12345") {
    return res.send({
      success: false,
      message: "用户名或密码错误",
    });
  }
  /**
   * 4. 生成JWT字符串(3个参数)
   * 参数1:用户的信息对象
   * 参数2:加密的密钥
   * 参数3:配置对象
   */
  const token = jwt.sign({ username: username }, secretKey, {
    expiresIn: "55s",
  });
  res.send({
    success: true,
    data: {
      token,
    },
    message: "登录成功",
  });
});
app.post("/admin/getName", (req, res) => {
  // 5. req.user 可以获取到用户信息(token)## 最新版本改为req.auth
  res.send({
    success: true,
    data: req.user,
  });
});
// 错误中间件用来捕获错误
app.use((err, req, res, next) => {
  // 捕获token失效的错误
  if ((err.name = "UnauthorizedError")) {
    return res.send("token失效");
  }
  res.send("错误" + err.message);
});
app.listen(8089, () => {
  console.log("server http://127.0.0.1:8089");
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
- 接口调用
在请求头(Headers)中添加Authorization:Bearer Token
 
 