Skip to content

Commit 6cf3e96

Browse files
committed
feat: 单点登录
1 parent 6c4c9e2 commit 6cf3e96

File tree

2 files changed

+275
-0
lines changed

2 files changed

+275
-0
lines changed
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
---
2+
title: CommonJS 和 ES Modules
3+
author: 高红翔
4+
date: 2023-03-20 11:00:58
5+
categories:
6+
tags:
7+
---
8+
9+
### **在使用上的差别主要有:**
10+
11+
- `CommonJS` 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
12+
13+
- `CommonJS` 模块是运行时加载,ES6 模块是编译时输出接口(静态编译)。
14+
- `CommonJs` 是单个值导出,`ES6 Module` 可以导出多个
15+
- `CommonJs` 是动态语法可以写在判断里,`ES6 Module` 静态语法只能写在顶层
16+
- `CommonJs``this` 是当前模块,`ES6 Module``this``undefined`
17+
- `CommonJS`是服务器端模块的规范,`CommonJS`规范加载模块是同步的
18+
19+
**CommonJS****ES Modules (ESM)** 是两种 JavaScript 中的模块化系统,它们的主要区别在于模块的定义方式、加载机制、语法和使用场景。下面从多个维度来对比这两者的区别:
20+
21+
### 1. **模块定义方式和语法**
22+
23+
- **CommonJS**:使用 `require` 语法引入模块,用 `module.exports``exports` 导出模块。CommonJS 是模块系统的早期实现,主要用于 Node.js 服务器端开发。
24+
25+
```js
26+
// 导出(CommonJS)
27+
module.exports = {
28+
greet: function () {
29+
console.log("Hello World")
30+
},
31+
}
32+
// 导入(CommonJS)
33+
const myModule = require("./myModule")
34+
myModule.greet() // 输出:Hello World
35+
```
36+
37+
- **ES Modules (ESM)**:使用 `import``export` 语法导入和导出模块。ESM 是 JavaScript 官方标准化的模块系统,原生支持浏览器和 Node.js。
38+
39+
```js
40+
// 导出(ES Modules)
41+
export function greet() {
42+
console.log("Hello World")
43+
}
44+
45+
// 导入(ES Modules)
46+
import { greet } from "./myModule.js"
47+
greet() // 输出:Hello World
48+
```
49+
50+
### 2. **加载机制**
51+
52+
- **CommonJS****同步加载**模块。这意味着在 `require` 调用时,Node.js 会立即执行并返回模块的内容,这种同步特性使得它非常适合在服务器端使用,因为服务器端代码不会受到网络延迟等因素的影响。
53+
- CommonJS 的模块在首次加载时会被执行,结果会被缓存,后续的 `require` 调用会直接返回缓存的结果,而不会重复执行模块代码。
54+
- **ESM****异步加载**模块。ESM 的 `import` 语句是基于 Promise 的,这使得模块加载可以是异步的,尤其适用于浏览器端,避免阻塞页面的加载。
55+
- ESM 在加载时会进行静态分析,因此在编译阶段就能确定模块依赖关系,这样可以实现更好的优化和提前报错。
56+
57+
### 3. **运行时与编译时**
58+
59+
- **CommonJS**:是**运行时**模块解析系统。`require` 语句会在代码运行时被解释执行,依赖可以根据运行时的条件动态加载。
60+
61+
```js
62+
if (someCondition) {
63+
const moduleA = require("./moduleA")
64+
} else {
65+
const moduleB = require("./moduleB")
66+
}
67+
```
68+
69+
- **ESM**:是**编译时**模块系统,导入模块的声明必须位于代码的顶层,不能在条件语句或函数中动态引入。这是为了让编译器能在编译时确定模块的依赖关系。
70+
71+
```js
72+
// 以下是非法的,在 ESM 中不能使用动态导入
73+
if (someCondition) {
74+
import { funcA } from "./moduleA.js" // 错误
75+
}
76+
```
77+
78+
### 4. **导出机制**
79+
80+
- **CommonJS**:通过 `module.exports``exports` 导出对象、函数或变量。它可以导出整个对象或模块的不同部分。
81+
82+
```js
83+
// 导出单个对象或函数
84+
module.exports = function greet() {
85+
console.log("Hello")
86+
}
87+
88+
// 导出多个值
89+
exports.greet = function () {
90+
console.log("Hello")
91+
}
92+
```
93+
94+
- **ESM**:使用 `export` 关键字导出,可以进行**命名导出****默认导出**。命名导出允许导出多个变量或函数,而默认导出则只能导出一个默认值。
95+
96+
```js
97+
// 命名导出
98+
export const name = "John"
99+
export function greet() {
100+
console.log("Hello")
101+
}
102+
103+
// 默认导出
104+
export default function () {
105+
console.log("Default Export")
106+
}
107+
```
108+
109+
### 5. **模块的缓存**
110+
111+
- **CommonJS**:模块在第一次加载后会被缓存,后续对该模块的 `require` 调用会返回缓存的结果,而不是重新执行模块代码。
112+
113+
```js
114+
// 第一次 require 模块时,模块代码会执行
115+
const moduleA = require("./moduleA")
116+
117+
// 第二次 require 相同模块时,返回的是缓存的结果
118+
const moduleA_again = require("./moduleA")
119+
```
120+
121+
- **ESM**:同样会对模块进行缓存,首次加载后,模块会被缓存并复用。和 CommonJS 一样,模块只会在第一次加载时执行代码。
122+
123+
### 6. **顶层作用域**
124+
125+
- **CommonJS**:每个模块都有自己的**独立作用域**,而不是全局作用域。模块内部的变量和函数默认是私有的,只有通过 `module.exports``exports` 公开的部分才对外可见。
126+
- **ESM**:同样具有模块作用域,默认情况下,模块内部的变量、函数等都是私有的,只有通过 `export` 导出的部分才对外可见。
127+
128+
### 7. **模块循环依赖**
129+
130+
- **CommonJS**:允许并且能够处理循环依赖问题,但在循环依赖的情况下,只有模块的部分代码会被导入(即已执行的部分)。
131+
132+
```js
133+
// moduleA.js
134+
const moduleB = require("./moduleB")
135+
console.log("Module A")
136+
137+
// moduleB.js
138+
const moduleA = require("./moduleA")
139+
console.log("Module B")
140+
```
141+
142+
- **ESM**:也允许循环依赖,但 ESM 会确保模块的所有依赖关系都被初始化,在加载时会执行一部分已解析的模块代码。因此 ESM 更适合处理循环依赖。
143+
144+
### 8. **浏览器支持**
145+
146+
- **CommonJS**:主要用于 Node.js 环境,浏览器原生不支持 CommonJS 模块化系统。在浏览器中使用 CommonJS 通常需要通过打包工具(如 Webpack 或 Browserify)将模块转换成浏览器可执行的代码。
147+
148+
- **ESM**:浏览器原生支持 ES Modules,尤其是在现代浏览器中,使用 `<script type="module">` 标签可以直接加载 ES 模块,无需额外的打包工具。
149+
150+
```html
151+
<script type="module" src="app.js"></script>
152+
```
153+
154+
### 9. **使用场景**
155+
156+
- **CommonJS**:由于它是 Node.js 的标准模块系统,主要用于服务器端开发。
157+
- **ESM**:逐渐成为 JavaScript 的标准模块系统,被广泛应用于浏览器和 Node.js 环境中。
158+
159+
### 10. **默认导出与命名导出**
160+
161+
- **CommonJS**:没有默认导出的概念,模块可以导出单个对象或多个属性,但没有专门的语法区分默认导出和命名导出。
162+
- **ESM**:支持**默认导出****命名导出**。一个模块可以有多个命名导出,但只能有一个默认导出。
163+
164+
### 11 值得引用和拷贝
165+
166+
- **CommonJS 模块输出的是值的拷贝**:当使用 CommonJS `require` 引入一个模块时,模块中的值会被**拷贝**到引入模块的地方。也就是说,模块的导出值是**在模块首次加载时计算并返回**的,而后续对该模块的 `require` 调用都会返回相同的拷贝。**如果模块内部的值发生变化,这种变化不会影响到已经引入的拷贝**
167+
168+
- **ES6 模块输出的是值的引用**:当使用 ES Modules (`import/export`) 引入一个模块时,模块中的导出值是**引用**,即导出的是一个“绑定”,不会被拷贝。如果模块内部的值发生变化,**在其他模块中通过 `import` 导入的值也会随之更新**,因为它们引用的是同一个内存地址。
169+
170+
#### 1. **CommonJS 模块输出的是值的拷贝**
171+
172+
CommonJS 的模块机制是运行时加载,模块在首次被 `require` 时会执行整个模块代码,并缓存导出的结果。后续的 `require` 操作不会重新执行模块代码,而是直接使用缓存的拷贝。
173+
174+
```js
175+
// counter.js (CommonJS 模块)
176+
let count = 0
177+
178+
function increment() {
179+
count++
180+
}
181+
182+
module.exports = {
183+
count,
184+
increment,
185+
}
186+
```
187+
188+
```js
189+
// main.js (CommonJS 导入)
190+
const counter = require("./counter")
191+
192+
console.log(counter.count) // 输出 0
193+
counter.increment()
194+
console.log(counter.count) // 输出 0 (模块导出的 count 是拷贝,值不会更新)
195+
```
196+
197+
在这个例子中,`counter.js` 模块导出的 `count` 值在 `require` 时就被固定下来。即使 `counter.increment()` 修改了模块内部的 `count`,但由于 `require` 返回的是**初始值的拷贝**,所以 `main.js` 中的 `count` 不会更新。
198+
199+
#### 2. **ES Modules 输出的是值的引用**
200+
201+
ES6 的模块机制是编译时加载,`import` 导入的是一个对原模块的**引用**,即使原模块中的变量发生变化,导入模块中的值也会实时更新。
202+
203+
```js
204+
// counter.js (ES Modules)
205+
let count = 0
206+
207+
export function increment() {
208+
count++
209+
}
210+
211+
export { count }
212+
```
213+
214+
```js
215+
// main.js (ES Modules 导入)
216+
import { count, increment } from "./counter.js"
217+
218+
console.log(count) // 输出 0
219+
increment()
220+
console.log(count) // 输出 1 (模块导出的 count 是引用,值会更新)
221+
```
222+
223+
在这个例子中,`count` 是通过 `export` 导出的,它是一个引用。当 `increment()` 被调用时,`count` 的值更新了,而 `main.js` 中的 `count` 也随之更新,因为它引用的是同一个内存地址。
224+
225+
#### 总结
226+
227+
- **CommonJS** 导出的是**值的拷贝**,模块首次加载时会执行并缓存,后续使用的都是这个缓存的结果,模块内部变量的变化不会影响到其他文件。
228+
- **ES Modules** 导出的是**值的引用**,模块导入时会动态引用模块的内部值,如果模块内部的值发生变化,导入的地方也会感知到这些变化。
229+
230+
所以,ES Modules 的 `import` 具有动态性,而 CommonJS 的 `require` 更像是静态的快照。
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
title: 单点登录
3+
author: 高红翔
4+
date: 2024-10-09 16:14:56
5+
categories:
6+
tags:
7+
---
8+
9+
### 单点登录(SSO)
10+
11+
参考文档:https://juejin.cn/post/7044328327762411534?searchId=20240918172034A3B355DF0CFBF66410FA
12+
13+
### 1 登录
14+
15+
1. 用户访问系统 1 的受保护资源,系统 1 发现用户未登录,跳转至 sso 认证中心,并将自己的地址作为参数
16+
2. sso 认证中心发现用户未登录,将用户引导至登录页面(带系统 1 地址)
17+
3. 用户输入用户名密码提交登录申请
18+
4. `sso认证中心校验用户信息,创建用户与sso认证中心之间的会话,称为全局会话(这时该会话信息保存到cookie中),同时创建授权令牌` 设置 PToken
19+
20+
5. sso 认证中心带着令牌(sTokenBackendValidate)跳转到最初的请求地址(系统 1) 302 跳转
21+
6. 系统 1 拿到令牌,掉取后端服务,后端服务, 去 sso 认证中心校验令牌是否有效(置换 SToken)
22+
7. sso 认证中心校验令牌,返回有效,注册系统 1,并给当前域名下写入 stoken
23+
8. `系统1使用该令牌创建与用户的会话,称为局部会话(seesion),返回受保护资源`
24+
9. 重新获取用户信息携带 token,返回用户信息
25+
10. 用户访问系统 2 的受保护资源
26+
11. 系统 2 发现用户未登录,跳转至 sso 认证中心,并将自己的`地址`和之前和`sso认证中心的会话cookie信`息作为参数
27+
12. sso 认证中心发现用户已登录,跳转回系统 2 的地址,并附上令牌
28+
13. 系统 2 拿到令牌,去 sso 认证中心校验令牌是否有效,并给当前系统二域名下写入 stoken
29+
14. 系统 2 使用该令牌创建与用户的局部会话,返回受保护资源
30+
31+
> 用户登录成功之后,会与 sso 认证中心及访问的子系统建立会话,用户与 sso 认证中心建立的会话称为全局会话,用户与各个子系统建立的会话称为局部会话,局部会话建立之后,用户访问子系统受保护资源将不再通过 sso 认证中心,全局会话与局部会话有如下约束关系
32+
33+
### 2. 注销
34+
35+
1. 用户向系统 1 发起注销请求
36+
37+
2. 系统 1 根据用户与系统 1 建立的会话 id 拿到令牌,向 sso 认证中心发起注销请求
38+
39+
3. sso 认证中心校验令牌有效,销毁全局会话,同时取出所有用此令牌注册的系统地址
40+
41+
4. sso 认证中心向所有注册系统发起注销请求
42+
43+
5. 各注册系统接收 sso 认证中心的注销请求,销毁局部会话
44+
45+
6. sso 认证中心引导用户至登录页面

0 commit comments

Comments
 (0)