1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-08-01 16:28:03 +08:00

Compare commits

...

8 Commits

Author SHA1 Message Date
Guide
82aa276bd2
Merge pull request #2348 from imlee2021/doc2
[docs update] 去除多余的标点符号
2024-04-01 22:52:52 +08:00
Guide
ca6da95adf
Merge pull request #2351 from TymGitHub/main
LinkedHashMap勘误
2024-04-01 22:51:18 +08:00
TymGitHub
1aa88848a2
LinkedHashMap勘误
fix:修改了LRU部分测试代码中for循环起点,如果从0开始和下面代码对不上,同时多加一条数据,且修改了下方描述部分,改为【从输出结果来看,由于缓存容量为 3 ,因此,添加第 4 个元素时,第 1 个元素会被删除。添加第 5 个元素时,第 2 个元素会被删除。
】。
2024-04-01 22:21:25 +08:00
李懿
f8a755f7ec [docs update] 去除多余的标点符号 2024-04-01 17:19:57 +08:00
Guide
128ed38af5 [docs feat]vuepress主题升级 2024-03-31 23:47:46 +08:00
Guide
8d80bad083 [docs update]完善 MySQL索引下推 & Memcached vs Redis 2024-03-31 07:52:30 +08:00
Guide
758e3cb8db
Merge pull request #2344 from firgk/main
Update practical-project.md
2024-03-30 20:02:11 +08:00
firgk
cbccc3319c
Update practical-project.md
fixed an error link from` https://github.com/rymcu` to `https://github.com/rymcu/forest`
2024-03-27 23:30:14 +08:00
12 changed files with 830 additions and 776 deletions

BIN
dist.zip Normal file

Binary file not shown.

View File

@ -1,19 +1,5 @@
$theme-color: #2980b9; $theme-color: #2980b9;
$sidebar-width: 20rem; $sidebar-width: 20rem;
$sidebar-mobile-width: 16rem; $sidebar-mobile-width: 16rem;
$font-family: $font-family: 'Georgia, -apple-system, "Nimbus Roman No9 L", "PingFang SC", "Hiragino Sans GB", "Noto Serif SC", "Microsoft Yahei", "WenQuanYi Micro Hei", sans-serif';
-apple-system, $font-family-heading: 'Georgia, -apple-system, "Nimbus Roman No9 L", "PingFang SC", "Hiragino Sans GB", "Noto Serif SC", "Microsoft Yahei", "WenQuanYi Micro Hei", sans-serif';
Georgia,
SF Pro Text,
SF Pro Icons,
Helvetica Neue,
PingFangTC-light,
PingFang-SC-light;
$font-family-heading:
-apple-system,
Georgia,
SF Pro Text,
SF Pro Icons,
Helvetica Neue,
PingFangTC-light,
PingFang-SC-light;

View File

@ -255,7 +255,7 @@ PCB 主要包含下面几部分的内容:
- **先到先服务调度算法(FCFSFirst Come, First Served)** : 从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。 - **先到先服务调度算法(FCFSFirst Come, First Served)** : 从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
- **短作业优先的调度算法(SJFShortest Job First)** : 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。 - **短作业优先的调度算法(SJFShortest Job First)** : 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
- **时间片轮转调度算法RRRound-Robin** : 时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。 - **时间片轮转调度算法RRRound-Robin** : 时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
- **多级反馈队列调度算法MFQMulti-level Feedback Queue**:前面介绍的几种进程调度的算法都有一定的局限性。如**短进程优先的调度算法,仅照顾了短进程而忽略了长进程** 。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成,因而它是目前**被公认的一种较好的进程调度算法**UNIX 操作系统采取的便是这种调度算法。 - **多级反馈队列调度算法MFQMulti-level Feedback Queue**:前面介绍的几种进程调度的算法都有一定的局限性。如**短进程优先的调度算法,仅照顾了短进程而忽略了长进程** 。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成,因而它是目前**被公认的一种较好的进程调度算法**UNIX 操作系统采取的便是这种调度算法。
- **优先级调度算法Priority**:为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。 - **优先级调度算法Priority**:为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。
### 什么是僵尸进程和孤儿进程? ### 什么是僵尸进程和孤儿进程?

View File

@ -223,7 +223,7 @@ PS: 不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,
**优点** **优点**
更新代价比聚簇索引要小 。非聚簇索引的更新代价就没有聚簇索引那么大了,非聚簇索引的叶子节点是不存放数据的 更新代价比聚簇索引要小 。非聚簇索引的更新代价就没有聚簇索引那么大了,非聚簇索引的叶子节点是不存放数据的
**缺点** **缺点**
@ -391,19 +391,29 @@ MySQL 8.0.13 版本引入了索引跳跃扫描Index Skip Scan简称 ISS
**索引下推Index Condition Pushdown简称 ICP** 是 **MySQL 5.6** 版本中提供的一项索引优化功能,它允许存储引擎在索引遍历过程中,执行部分 `WHERE`字句的判断条件,直接过滤掉不满足条件的记录,从而减少回表次数,提高查询效率。 **索引下推Index Condition Pushdown简称 ICP** 是 **MySQL 5.6** 版本中提供的一项索引优化功能,它允许存储引擎在索引遍历过程中,执行部分 `WHERE`字句的判断条件,直接过滤掉不满足条件的记录,从而减少回表次数,提高查询效率。
假设我们有一个名为 `usr` 的表,其中包含 `id`, `name`, 和 `age` 3 个字段,`name` 字段上创建了索引 假设我们有一个名为 `user` 的表,其中包含 `id`, `username`, `zipcode``birthdate` 4 个字段,创建了联合索引`(zipcode, birthdate)`
```sql ```sql
#查询名字以"Aoki"开头且年龄为30岁的用户 CREATE TABLE `user` (
EXPLAIN SELECT * FROM usr `id` int NOT NULL AUTO_INCREMENT,
WHERE name LIKE 'Aoki%' AND age = 30; `username` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`zipcode` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`birthdate` date NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_username_birthdate` (`zipcode`,`birthdate`) ) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8mb4;
# 查询 zipcode 为 431200 且生日在 3 月的用户
# birthdate 字段使用函数索引失效
SELECT * FROM user WHERE zipcode = '431200' AND MONTH(birthdate) = 3;
``` ```
- 没有索引下推之前,即使 `name` 字段建立的索引可以帮助我们快速定位到了以“张”开头的用户,但我们仍然需要对每一个找到的以“张”开头的用户进行回表操作,获取完整的用户数据,再判断 `age` 字段是否等于 30。 - 没有索引下推之前,即使 `zipcode` 字段利用索引可以帮助我们快速定位到 `zipcode = '431200'` 的用户,但我们仍然需要对每一个找到的用户进行回表操作,获取完整的用户数据,再去判断 `MONTH(birthdate) = 3`
- 有了索引下推之后,存储引擎会在使用 `name` 索引查找以"张"开头的记录时,同时检查 `age` 字段是否等于 30。这样只有同时满足 `name``age` 条件的记录才会被返回,减少了回表次数。 - 有了索引下推之后,存储引擎会在使用`zipcode` 字段索引查找`zipcode = '431200'` 的用户时,同时判断`MONTH(birthdate) = 3`。这样,只有同时满足条件的记录才会被返回,减少了回表次数。
![](https://oss.javaguide.cn/github/javaguide/database/mysql/index-condition-pushdown.png) ![](https://oss.javaguide.cn/github/javaguide/database/mysql/index-condition-pushdown.png)
![](https://oss.javaguide.cn/github/javaguide/database/mysql/index-condition-pushdown-graphic-illustration.png)
再来讲讲索引下推的具体原理,先看下面这张 MySQL 简要架构图。 再来讲讲索引下推的具体原理,先看下面这张 MySQL 简要架构图。
![](https://oss.javaguide.cn/javaguide/13526879-3037b144ed09eb88.png) ![](https://oss.javaguide.cn/javaguide/13526879-3037b144ed09eb88.png)
@ -416,13 +426,13 @@ MySQL 可以简单分为 Server 层和存储引擎层这两层。Server 层处
没有索引下推之前: 没有索引下推之前:
- 存储引擎层先根据 `name` 索引字段找到所有以“张”开头用户的主键 ID然后二次回表查询获取完整的用户数据。 - 存储引擎层先根据 `zipcode` 索引字段找到所有 `zipcode = '431200'` 的用户的主键 ID然后二次回表查询获取完整的用户数据
- 存储引擎层把所有以“张”开头的用户数据全部交给 Server 层Server 层根据`age` 字段是否等于 30 这一条件再进一步做筛选。 - 存储引擎层把所有 `zipcode = '431200'` 的用户数据全部交给 Server 层Server 层根据`MONTH(birthdate) = 3`这一条件再进一步做筛选。
有了索引下推之后: 有了索引下推之后:
- 存储引擎层先根据 `name` 索引字段找到所有以“张”开头的用户,然后直接判断`age` 字段是否等于 30筛选出符合条件的 主键 ID。 - 存储引擎层先根据 `zipcode` 索引字段找到所有 `zipcode = '431200'` 的用户,然后直接判断 `MONTH(birthdate) = 3`,筛选出符合条件的主键 ID
- 二次回表查询,根据符合条件的主键 ID 去获取完整的用户数据 - 二次回表查询,根据符合条件的主键 ID 去获取完整的用户数据
- 存储引擎层把符合条件的用户数据全部交给 Server 层。 - 存储引擎层把符合条件的用户数据全部交给 Server 层。
可以看出,**除了可以减少回表次数之外,索引下推还可以减少存储引擎层和 Server 层的数据传输量。** 可以看出,**除了可以减少回表次数之外,索引下推还可以减少存储引擎层和 Server 层的数据传输量。**

View File

@ -20,7 +20,7 @@ head:
[Redis](https://redis.io/) **RE**mote **DI**ctionary **S**erver是一个基于 C 语言开发的开源 NoSQL 数据库BSD 许可。与传统数据库不同的是Redis 的数据是保存在内存中的内存数据库支持持久化因此读写速度非常快被广泛应用于分布式缓存方向。并且Redis 存储的是 KV 键值对数据。 [Redis](https://redis.io/) **RE**mote **DI**ctionary **S**erver是一个基于 C 语言开发的开源 NoSQL 数据库BSD 许可。与传统数据库不同的是Redis 的数据是保存在内存中的内存数据库支持持久化因此读写速度非常快被广泛应用于分布式缓存方向。并且Redis 存储的是 KV 键值对数据。
为了满足不同的业务场景Redis 内置了多种数据类型实现(比如 String、Hash、Sorted Set、Bitmap、HyperLogLog、GEO。并且Redis 还支持事务、持久化、Lua 脚本、多种开箱即用的集群方案Redis Sentinel、Redis Cluster 为了满足不同的业务场景Redis 内置了多种数据类型实现(比如 String、Hash、Sorted Set、Bitmap、HyperLogLog、GEO。并且Redis 还支持事务、持久化、Lua 脚本、发布订阅模型、多种开箱即用的集群方案Redis Sentinel、Redis Cluster
![Redis 数据类型概览](https://oss.javaguide.cn/github/javaguide/database/redis/redis-overview-of-data-types-2023-09-28.jpg) ![Redis 数据类型概览](https://oss.javaguide.cn/github/javaguide/database/redis/redis-overview-of-data-types-2023-09-28.jpg)
@ -36,14 +36,17 @@ Redis 没有外部依赖Linux 和 OS X 是 Redis 开发和测试最多的两
Redis 内部做了非常多的性能优化,比较重要的有下面 3 点: Redis 内部做了非常多的性能优化,比较重要的有下面 3 点:
1. Redis 基于内存,内存的访问速度是磁盘的上千倍 1. Redis 基于内存,内存的访问速度比磁盘快很多
2. Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用Redis 线程模式后面会详细介绍到); 2. Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用Redis 线程模式后面会详细介绍到);
3. Redis 内置了多种优化过后的数据类型/结构实现,性能非常高。 3. Redis 内置了多种优化过后的数据类型/结构实现,性能非常高。
4. Redis 通信协议实现简单且解析高效。
> 下面这张图片总结的挺不错的,分享一下,出自 [Why is Redis so fast?](https://twitter.com/alexxubyte/status/1498703822528544770) 。 > 下面这张图片总结的挺不错的,分享一下,出自 [Why is Redis so fast?](https://twitter.com/alexxubyte/status/1498703822528544770) 。
![why-redis-so-fast](./images/why-redis-so-fast.png) ![why-redis-so-fast](./images/why-redis-so-fast.png)
那既然都这么快了,为什么不直接用 Redis 当主数据库呢?主要是因为内存成本太高且 Redis 提供的数据持久化仍然有数据丢失的风险。
### 分布式缓存常见的技术选型方案有哪些? ### 分布式缓存常见的技术选型方案有哪些?
分布式缓存的话,比较老牌同时也是使用的比较多的还是 **Memcached****Redis**。不过,现在基本没有看过还有项目使用 **Memcached** 来做缓存,都是直接用 **Redis** 分布式缓存的话,比较老牌同时也是使用的比较多的还是 **Memcached****Redis**。不过,现在基本没有看过还有项目使用 **Memcached** 来做缓存,都是直接用 **Redis**
@ -73,14 +76,12 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来
**区别** **区别**
1. **Redis 支持更丰富的数据类型(支持更复杂的应用场景)**。Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 listsetzsethash 等数据结构的存储。Memcached 只支持最简单的 k/v 数据类型。 1. **数据类型**Redis 支持更丰富的数据类型支持更复杂的应用场景。Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 listsetzsethash 等数据结构的存储。Memcached 只支持最简单的 k/v 数据类型。
2. **Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memcached 把数据全部存在内存之中。** 2. **数据持久化**Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memcached 把数据全部存在内存之中。也就是说Redis 有灾难恢复机制而 Memcached 没有。
3. **Redis 有灾难恢复机制。** 因为可以把缓存中的数据持久化到磁盘上。 3. **集群模式支持**Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 自 3.0 版本起是原生支持集群模式的。
4. **Redis 在服务器内存使用完之后可以将不用的数据放到磁盘上。但是Memcached 在服务器内存使用完之后,就会直接报异常。** 4. **线程模型**Memcached 是多线程,非阻塞 IO 复用的网络模型Redis 使用单线程的多路 IO 复用模型。 Redis 6.0 针对网络数据的读写引入了多线程)
5. **Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 cluster 模式的。** 5. **特性支持**Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持。并且Redis 支持更多的编程语言。
6. **Memcached 是多线程,非阻塞 IO 复用的网络模型Redis 使用单线程的多路 IO 复用模型。** Redis 6.0 针对网络数据的读写引入了多线程) 6. **过期数据删除**Memcached 过期数据的删除策略只用了惰性删除,而 Redis 同时使用了惰性删除与定期删除。
7. **Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持。并且Redis 支持更多的编程语言。**
8. **Memcached 过期数据的删除策略只用了惰性删除,而 Redis 同时使用了惰性删除与定期删除。**
相信看了上面的对比之后,我们已经没有什么理由可以选择使用 Memcached 来作为自己项目的分布式缓存了。 相信看了上面的对比之后,我们已经没有什么理由可以选择使用 Memcached 来作为自己项目的分布式缓存了。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

View File

@ -119,7 +119,8 @@ cache.put(1, "one");
cache.put(2, "two"); cache.put(2, "two");
cache.put(3, "three"); cache.put(3, "three");
cache.put(4, "four"); cache.put(4, "four");
for (int i = 0; i <= 4; i++) { cache.put(5, "five");
for (int i = 1; i <= 5; i++) {
System.out.println(cache.get(i)); System.out.println(cache.get(i));
} }
``` ```
@ -128,12 +129,13 @@ for (int i = 0; i <= 4; i++) {
```java ```java
null null
two null
three three
four four
five
``` ```
从输出结果来看,由于缓存容量为 2 ,因此,添加第 3 个元素时,第 1 个元素会被删除。添加第 4 个元素时,第 2 个元素会被删除。 从输出结果来看,由于缓存容量为 3 ,因此,添加第 4 个元素时,第 1 个元素会被删除。添加第 5 个元素时,第 2 个元素会被删除。
## LinkedHashMap 源码解析 ## LinkedHashMap 源码解析

View File

@ -171,7 +171,7 @@ Java 堆是垃圾收集器管理的主要区域,因此也被称作 **GC 堆(
![](https://oss.javaguide.cn/github/javaguide/java/jvm/20210425134508117.png) ![](https://oss.javaguide.cn/github/javaguide/java/jvm/20210425134508117.png)
1、整个永久代有一个 JVM 本身设置的固定大小上限,无法进行调整,而元空间使用的是本地内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。 1、整个永久代有一个 JVM 本身设置的固定大小上限,无法进行调整(也就是受到 JVM 内存的限制),而元空间使用的是本地内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。
> 当元空间溢出时会得到如下错误:`java.lang.OutOfMemoryError: MetaSpace` > 当元空间溢出时会得到如下错误:`java.lang.OutOfMemoryError: MetaSpace`
@ -181,6 +181,8 @@ Java 堆是垃圾收集器管理的主要区域,因此也被称作 **GC 堆(
3、在 JDK8合并 HotSpot 和 JRockit 的代码时, JRockit 从来没有一个叫永久代的东西, 合并之后就没有必要额外的设置这么一个永久代的地方了。 3、在 JDK8合并 HotSpot 和 JRockit 的代码时, JRockit 从来没有一个叫永久代的东西, 合并之后就没有必要额外的设置这么一个永久代的地方了。
4、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
**方法区常用参数有哪些?** **方法区常用参数有哪些?**
JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小。 JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小。

View File

@ -26,7 +26,7 @@ icon: project
下面这几个项目都是非常适合 Spring Boot 初学者学习的,下面的大部分项目的总体代码架构我都看过,个人觉得还算不错,不会误导没有实际做过项目的朋友。 下面这几个项目都是非常适合 Spring Boot 初学者学习的,下面的大部分项目的总体代码架构我都看过,个人觉得还算不错,不会误导没有实际做过项目的朋友。
- [paicoding](https://github.com/itwanger/paicoding):一款好用又强大的开源社区,基于 Spring Boot 系列主流技术栈,附详细的教程。 - [paicoding](https://github.com/itwanger/paicoding):一款好用又强大的开源社区,基于 Spring Boot 系列主流技术栈,附详细的教程。
- [forest](https://github.com/rymcu):下一代的知识社区系统,可以自定义专题和作品集。后端基于 SpringBoot + Shrio + MyBatis + JWT + Redis前端基于 Vue + NuxtJS + Element-UI。 - [forest](https://github.com/rymcu/forest):下一代的知识社区系统,可以自定义专题和作品集。后端基于 SpringBoot + Shrio + MyBatis + JWT + Redis前端基于 Vue + NuxtJS + Element-UI。
- [vhr](https://github.com/lenve/vhr "vhr"):微人事是一个前后端分离的人力资源管理系统,项目采用 SpringBoot+Vue 开发。 - [vhr](https://github.com/lenve/vhr "vhr"):微人事是一个前后端分离的人力资源管理系统,项目采用 SpringBoot+Vue 开发。
- [community](https://github.com/codedrinker/community):开源论坛、问答系统,现有功能提问、回复、通知、最新、最热、消除零回复功能。功能持续更新中…… 技术栈 Spring、Spring Boot、MyBatis、MySQL/H2、Bootstrap。 - [community](https://github.com/codedrinker/community):开源论坛、问答系统,现有功能提问、回复、通知、最新、最热、消除零回复功能。功能持续更新中…… 技术栈 Spring、Spring Boot、MyBatis、MySQL/H2、Bootstrap。
- [VBlog](https://github.com/lenve/VBlog)V 部落Vue+SpringBoot 实现的多用户博客管理平台! - [VBlog](https://github.com/lenve/VBlog)V 部落Vue+SpringBoot 实现的多用户博客管理平台!

View File

@ -83,7 +83,9 @@ public class XSSFilter implements Filter {
使用 Session 进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到 Cookie需要 Cookie 保存 `SessionId`),所以不适合移动端。 使用 Session 进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到 Cookie需要 Cookie 保存 `SessionId`),所以不适合移动端。
但是,使用 JWT 进行身份认证就不会存在这种问题,因为只要 JWT 可以被客户端存储就能够使用,而且 JWT 还可以跨语言使用。 但是,使用 JWT 进行身份认证就不会存在这种问题,因为只要 JWT 可以被客户端存储就能够使用,而且 JWT 还可以跨语言使用。
> 为什么使用 Session 进行身份认证的话不适合移动端 > 为什么使用 Session 进行身份认证的话不适合移动端
>
> 1. 状态管理: Session 基于服务器端的状态管理,而移动端应用通常是无状态的。移动设备的连接可能不稳定或中断,因此难以维护长期的会话状态。如果使用 Session 进行身份认证,移动应用需要频繁地与服务器进行会话维护,增加了网络开销和复杂性; > 1. 状态管理: Session 基于服务器端的状态管理,而移动端应用通常是无状态的。移动设备的连接可能不稳定或中断,因此难以维护长期的会话状态。如果使用 Session 进行身份认证,移动应用需要频繁地与服务器进行会话维护,增加了网络开销和复杂性;
> 2. 兼容性: 移动端应用通常会面向多个平台,如 iOS、Android 和 Web。每个平台对于 Session 的管理和存储方式可能不同,可能导致跨平台兼容性的问题; > 2. 兼容性: 移动端应用通常会面向多个平台,如 iOS、Android 和 Web。每个平台对于 Session 的管理和存储方式可能不同,可能导致跨平台兼容性的问题;
> 3. 安全性: 移动设备通常处于不受信任的网络环境,存在数据泄露和攻击的风险。将敏感的会话信息存储在移动设备上增加了被攻击的潜在风险。 > 3. 安全性: 移动设备通常处于不受信任的网络环境,存在数据泄露和攻击的风险。将敏感的会话信息存储在移动设备上增加了被攻击的潜在风险。
@ -110,9 +112,9 @@ public class XSSFilter implements Filter {
那我们如何解决这个问题呢?查阅了很多资料,我简单总结了下面 4 种方案: 那我们如何解决这个问题呢?查阅了很多资料,我简单总结了下面 4 种方案:
**1、将 JWT 存入内存数据库** **1、将 JWT 存入数据库**
JWT 存入 DB 中Redis 内存数据库在这里是不错的选择。如果需要让某个 JWT 失效就直接从 Redis 中删除这个 JWT 即可。但是,这样会导致每次使用 JWT 发送请求都要先从 DB 中查询 JWT 是否存在的步骤,而且违背了 JWT 的无状态原则。 有效的 JWT 存入数据库中,更建议使用内存数据库比如 Redis。如果需要让某个 JWT 失效就直接从 Redis 中删除这个 JWT 即可。但是,这样会导致每次使用 JWT 都要先从 Redis 中查询 JWT 是否存在的步骤,而且违背了 JWT 的无状态原则。
**2、黑名单机制** **2、黑名单机制**
@ -143,28 +145,30 @@ JWT 有效期一般都建议设置的不太长,那么 JWT 过期后如何认
JWT 认证的话,我们应该如何解决续签问题呢?查阅了很多资料,我简单总结了下面 4 种方案: JWT 认证的话,我们应该如何解决续签问题呢?查阅了很多资料,我简单总结了下面 4 种方案:
**1、类似于 Session 认证中的做法** **1、类似于 Session 认证中的做法(不推荐)**
这种方案满足于大部分场景。假设服务端给的 JWT 有效期设置为 30 分钟,服务端每次进行校验时,如果发现 JWT 的有效期马上快过期了,服务端就重新生成 JWT 给客户端。客户端每次请求都检查新旧 JWT如果不一致则更新本地的 JWT。这种做法的问题是仅仅在快过期的时候请求才会更新 JWT ,对客户端不是很友好。 这种方案满足于大部分场景。假设服务端给的 JWT 有效期设置为 30 分钟,服务端每次进行校验时,如果发现 JWT 的有效期马上快过期了,服务端就重新生成 JWT 给客户端。客户端每次请求都检查新旧 JWT如果不一致则更新本地的 JWT。这种做法的问题是仅仅在快过期的时候请求才会更新 JWT 对客户端不是很友好。
**2、每次请求都返回新 JWT** **2、每次请求都返回新 JWT(不推荐)**
这种方案的的思路很简单,但是,开销会比较大,尤其是在服务端要存储维护 JWT 的情况下。 这种方案的的思路很简单,但是,开销会比较大,尤其是在服务端要存储维护 JWT 的情况下。
**3、JWT 有效期设置到半夜** **3、JWT 有效期设置到半夜(不推荐)**
这种方案是一种折衷的方案,保证了大部分用户白天可以正常登录,适用于对安全性要求不高的系统。 这种方案是一种折衷的方案,保证了大部分用户白天可以正常登录,适用于对安全性要求不高的系统。
**4、用户登录返回两个 JWT** **4、用户登录返回两个 JWT(推荐)**
第一个是 accessJWT ,它的过期时间 JWT 本身的过期时间比如半个小时,另外一个是 refreshJWT 它的过期时间更长一点比如为 1 天。客户端登录后,将 accessJWT 和 refreshJWT 保存在本地,每次访问将 accessJWT 传给服务端。服务端校验 accessJWT 的有效性,如果过期的话,就将 refreshJWT 传给服务端。如果有效,服务端就生成新的 accessJWT 给客户端。否则,客户端就重新登录即可。 第一个是 accessJWT ,它的过期时间 JWT 本身的过期时间比如半个小时,另外一个是 refreshJWT 它的过期时间更长一点比如为 1 天。refreshJWT 只用来获取 accessJWT不容易被泄露。
客户端登录后,将 accessJWT 和 refreshJWT 保存在本地,每次访问将 accessJWT 传给服务端。服务端校验 accessJWT 的有效性,如果过期的话,就将 refreshJWT 传给服务端。如果有效,服务端就生成新的 accessJWT 给客户端。否则,客户端就重新登录即可。
这种方案的不足是: 这种方案的不足是:
- 需要客户端来配合; - 需要客户端来配合;
- 用户注销的时候需要同时保证两个 JWT 都无效; - 用户注销的时候需要同时保证两个 JWT 都无效;
- 重新请求获取 JWT 的过程中会有短暂 JWT 不可用的情况(可以通过在客户端设置定时器,当 accessJWT 快过期的时候,提前去通过 refreshJWT 获取新的 accessJWT; - 重新请求获取 JWT 的过程中会有短暂 JWT 不可用的情况(可以通过在客户端设置定时器,当 accessJWT 快过期的时候,提前去通过 refreshJWT 获取新的 accessJWT;
- 存在安全问题,只要拿到了未过期的 refreshJWT 就一直可以获取到 accessJWT。 - 存在安全问题,只要拿到了未过期的 refreshJWT 就一直可以获取到 accessJWT。不过,由于 refreshJWT 只用来获取 accessJWT不容易被泄露。
## 总结 ## 总结

View File

@ -21,18 +21,18 @@
}, },
"packageManager": "pnpm@8.15.1", "packageManager": "pnpm@8.15.1",
"dependencies": { "dependencies": {
"@vuepress/bundler-vite": "2.0.0-rc.6", "@vuepress/bundler-vite": "2.0.0-rc.9",
"@vuepress/plugin-copyright": "2.0.0-rc.11", "@vuepress/plugin-copyright": "2.0.0-rc.21",
"@vuepress/plugin-feed": "2.0.0-rc.11", "@vuepress/plugin-feed": "2.0.0-rc.21",
"@vuepress/plugin-search": "2.0.0-rc.11", "@vuepress/plugin-search": "2.0.0-rc.21",
"husky": "9.0.10", "husky": "9.0.10",
"markdownlint-cli2": "0.12.1", "markdownlint-cli2": "0.12.1",
"mathjax-full": "3.2.2", "mathjax-full": "3.2.2",
"nano-staged": "0.8.0", "nano-staged": "0.8.0",
"nodejs-jieba": "0.1.2", "nodejs-jieba": "0.1.2",
"prettier": "3.2.5", "prettier": "3.2.5",
"vue": "3.4.15", "vue": "^3.4.21",
"vuepress": "2.0.0-rc.6", "vuepress": "2.0.0-rc.9",
"vuepress-theme-hope": "2.0.0-rc.22" "vuepress-theme-hope": "2.0.0-rc.32"
} }
} }

1483
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff