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