Commit on 2025/04/09 周三 17:23:21.27

This commit is contained in:
zhangsan 2025-04-09 17:23:21 +08:00
parent 426cec0a76
commit 7dcaebcd75
6 changed files with 1122 additions and 526 deletions

File diff suppressed because it is too large Load Diff

View File

@ -79,128 +79,90 @@ IDEA快捷键
1. 二进制0b 八进制0 十六进制0x
2. 在 `System.out.println()` 方法中,"ln" 代表 "line",表示换行。因此,`println` 实际上是 "print line" 的缩写。这个方法会在输出文本后自动换行.
```java
System.out.println("nihao "+1.3331) #Java 会自动将数值转换为字符串
```
```java
System.out.println("nihao "+1.3331) #Java 会自动将数值转换为字符串
```
当直接打印一个没有重写 `toString()` 方法的对象时Java 默认会调用 `Object` 类的 `toString()` 方法,其输出格式通常为:
```java
java.lang.Object@15db9742
```
当打印重写`toString()` 方法的对象时:
```java
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person("Alice", 30);
System.out.println(person); //会自动调用对象的 toString() 方法
}
}
```
```java
Person{name='Alice', age=30}
```
3. 一维数组创建:
```java
// 方式1先声明再指定长度默认值为0、null等
int[] arr1 = new int[10]; // 创建一个长度为10的int数组
// 方式2使用初始化列表直接创建数组
int[] arr2 = {1, 2, 3, 4, 5}; // 创建并初始化一个包含5个元素的int数组
String[] strs = {"eat", "tea", "tan", "ate", "nat", "bat"};
// 方式3结合new关键字和初始化列表创建数组常用于明确指定类型时
int[] arr3 = new int[]{1, 2, 3, 4, 5}; // 与方式2效果相同
```
```java
// 方式1先声明再指定长度默认值为0、null等
int[] arr1 = new int[10]; // 创建一个长度为10的int数组
// 方式2使用初始化列表直接创建数组
int[] arr2 = {1, 2, 3, 4, 5}; // 创建并初始化一个包含5个元素的int数组
String[] strs = {"eat", "tea", "tan", "ate", "nat", "bat"};
// 方式3结合new关键字和初始化列表创建数组常用于明确指定类型时
int[] arr3 = new int[]{1, 2, 3, 4, 5}; // 与方式2效果相同
```
4. 字符串创建
```java
String str = "Hello, World!"; //1直接赋值
String str = new String("Hello, World!"); //使用 new 关键字
char[] charArray = {'H', 'e', 'l', 'l', 'o'};
String str = new String(charArray); //通过字符数组创建
```
```java
String str = "Hello, World!"; //1直接赋值
String str = new String("Hello, World!"); //使用 new 关键字
char[] charArray = {'H', 'e', 'l', 'l', 'o'};
String str = new String(charArray); //通过字符数组创建
```
5. switch-case
```java
public class SwitchCaseExample {
public static void main(String[] args) {
// 定义一个 int 类型变量,作为 switch 的表达式
int day = 3;
String dayName;
// 根据 day 的值执行相应的分支
switch(day) {
case 1:
dayName = "Monday"; // 当 day 为 1 时
break; // 结束当前 case
case 2:
dayName = "Tuesday"; // 当 day 为 2 时
break;
case 3:
dayName = "Wednesday"; // 当 day 为 3 时
break;
case 4:
dayName = "Thursday"; // 当 day 为 4 时
break;
case 5:
dayName = "Friday"; // 当 day 为 5 时
break;
case 6:
dayName = "Saturday"; // 当 day 为 6 时
break;
case 7:
dayName = "Sunday"; // 当 day 为 7 时
break;
default:
// 如果 day 不在 1 到 7 之间
dayName = "Invalid day";
}
// 输出最终结果
System.out.println("The day is: " + dayName);
}
}
```
```java
public class SwitchCaseExample {
public static void main(String[] args) {
// 定义一个 int 类型变量,作为 switch 的表达式
int day = 3;
String dayName;
// 根据 day 的值执行相应的分支
switch(day) {
case 1:
dayName = "Monday"; // 当 day 为 1 时
break; // 结束当前 case
case 2:
dayName = "Tuesday"; // 当 day 为 2 时
break;
case 3:
dayName = "Wednesday"; // 当 day 为 3 时
break;
case 4:
dayName = "Thursday"; // 当 day 为 4 时
break;
case 5:
dayName = "Friday"; // 当 day 为 5 时
break;
case 6:
dayName = "Saturday"; // 当 day 为 6 时
break;
case 7:
dayName = "Sunday"; // 当 day 为 7 时
break;
default:
// 如果 day 不在 1 到 7 之间
dayName = "Invalid day";
}
// 输出最终结果
System.out.println("The day is: " + dayName);
}
}
```
#### Java传参方式
@ -897,6 +859,23 @@ protected static volatile int counter; #定义成员变量
#### 全限定名
全限定名Fully Qualified Name简称 FQN指的是一个类或接口在 Java 中的完整名称,包括它所在的包名。例如:
- 对于类 `Integer`,其全限定名是 `java.lang.Integer`
- 对于自定义的类 `DeptServiceImpl`,如果它位于包 `edu.zju.zy123.service.impl` 中,那么它的全限定名就是 `edu.zju.zy123.service.impl.DeptServiceImpl`
使用全限定名可以消除歧义,确保指定的类型在整个项目中唯一无误。
使用场景:
Spring AOP 的 Pointcut 表达式
MyBatis的XML映射文件的**namespace属性**
### JAVA面向对象
#### **JAVA**三大特性
@ -1046,7 +1025,8 @@ class Dog extends Animal {
```
#### 接口
@ -1291,13 +1271,15 @@ public class SomeException extends Exception {
```
## 好用的方法
### toString()
Arrays.toString()
**Arrays.toString()**转一维数组
作用:方便地输出数组。
**Arrays.deepToString()**转二维数组
这个方法是是用来将数组转换成String类型输出的入参可以是longfloatdoubleintbooleanbyteobject 型的数组。
```java
@ -1319,6 +1301,45 @@ public class Main {
System.out.println("二维数组输出: " + Arrays.deepToString(twoD));
}
}
```
**自定义对象的`toString()` 方法**
每个 Java 对象默认都有 `toString()` 方法(可以根据需要覆盖)
当直接打印一个没有重写 `toString()` 方法的对象时,其输出格式通常为:
```java
java.lang.Object@15db9742
```
当打印重写`toString()` 方法的对象时:
```java
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person("Alice", 30);
System.out.println(person); //会自动调用对象的 toString() 方法
//Person{name='Alice', age=30}
}
}
```
@ -1330,7 +1351,7 @@ public class Main {
**类路径**是JVM在运行时用来查找类文件和资源文件的一组目录或JAR包。在许多项目例如Maven或Gradle项目`src/main/resources`目录下的内容在编译时会被复制到输出目录(如`target/classes``src/main/java` 下编译后的 class 文件也会放到这里。
```java
```text
MyProject/
├── src/
│ └── main/
@ -1338,16 +1359,26 @@ MyProject/
│ └── com/
│ └── example/
│ └── Main.java
└── resources/
├── emp.xml
└── static/
└── tt.img
├── resources/
│ ├── emp.xml
│ └── static/
│ └── tt.img
└── target/
└── classes/
├── com/
│ └── example/
│ └── Main.class
├── emp.xml
└── static/
└── tt.img
```
------------
```java
// 获取 resources 根目录下的 emp.xml 文件路径
String empFile = this.getClass().getClassLoader().getResource("emp.xml").getFile();
String empFileUrl = this.getClass().getClassLoader().getResource("emp.xml").getFile();
// 获取 resources/static 目录下的 tt.img 文件路径
URL resourceUrl = getClass().getClassLoader().getResource("static/tt.img");
@ -1401,6 +1432,8 @@ public class Test1Class{
**2.获取类的构造器**
- 定义类
```java
public class Cat{
private String name;
@ -1593,21 +1626,188 @@ public class FieldReflectionTest {
### Junit 单元测试
### 注解
![image-20240307172717512](https://pic.bitday.top/i/2025/03/19/u75u7l-2.png)
在 Java 中,注解用于给程序元素(类、方法、字段等)**添加元数据**,这些元数据可被编译器、工具或运行时**反射读取**以实现配置、检查、代码生成以及框架支持如依赖注入、AOP 等)功能,而不直接影响代码的业务逻辑。
比如Junit框架的@Test注解可以用在方法上,用来标记这个方法是测试方法,被`@Test`标记的方法能够被Junit框架执行。
再比如:@Override注解可以用在方法上,用来标记这个方法是重写方法,被`@Override`注解标记的方法能够被IDEA识别进行语法检查。
#### 使用注解
**定义注解**
使用 `@interface` 定义注解
```java
@Test
public void testListUser(){
List<User>list=userMapper.list();
for(User user:list){
System.out.println(user);
}
}
// 定义注解
@Retention(RetentionPolicy.RUNTIME) // 定义注解的生命周期
@Target(ElementType.METHOD) // 定义注解可以应用的Java元素类型
public @interface MyAnnotation {
// 定义注解的元素(属性)
String description() default "This is a default description";
int value() default 0;
}
```
写了@Test注解,那么该测试函数就可以直接运行!若一个测试类中写了多个测试方法,可以全部执行!
**元注解**
**是修饰注解的注解**。
@Retention(RetentionPolicy.RUNTIME) //指定注解的生命周期,即在运行时有效,可用于反射等用途。
@Target(ElementType.METHOD) //方法上的注解
@Target(ElementType.TYPE) //类上的注解(包含类、接口、枚举等类型)
**简化使用**:如果注解中只有一个属性需要设置,而且该属性名为 `value`,则在使用时可以省略属性名
```java
@MyAnnotation(5) // 等同于 @MyAnnotation(value = 5)
public void someMethod() {
// 方法实现
}
```
当需要为注解的多个属性赋值时,传参必须指明属性名称:
```java
@MyAnnotation(value = 5, description = "Specific description")
public void anotherMethod() {
// 方法实现
}
```
如果所有属性都使用默认值,可以直接使用注解而不传入任何参数:
```java
@MyAnnotation
public void anotherMethod() {
// 方法实现
}
```
#### 解析注解
```java
public class MyClass {
@MyAnnotation(value = "specific value")
public void myMethod() {
// 方法实现
}
}
```
```java
import java.lang.reflect.Method;
public class AnnotationReader {
public static void main(String[] args) throws NoSuchMethodException {
// 获取MyClass的Class对象
Class<MyClass> obj = MyClass.class;
// 获取myMethod方法的Method对象
Method method = obj.getMethod("myMethod");
// 获取方法上的MyAnnotation注解实例
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
if (annotation != null) {
// 输出注解的value值
System.out.println("注解的value: " + annotation.value());
}
}
}
```
快速检查某个注解是否存在于`method`
```java
if (method.isAnnotationPresent(MyAnnotation.class)) {
// 如果存在MyAnnotation注解则执行相应逻辑
}
```
检查方法 `method` 上是否存在 `MyAnnotation` 注解。如果存在,就返回该注解的实例,否则返回 `null`
```java
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
```
### Junit 单元测试
**步骤**
**1.导入依赖**
将 JUnit 框架的 jar 包添加到项目中注意IntelliJ IDEA 默认集成了 JUnit无需手动导入
**2.编写测试类**
- 为待测业务方法创建对应的测试类。
- 测试类中定义测试方法,要求方法必须为 `public` 且返回类型为 `void`
**3.添加测试注解**
在测试方法上添加 `@Test` 注解,确保 JUnit 能自动识别并执行该方法。
**4.运行测试**
在测试方法上右键选择“JUnit运行”。
- 测试通过显示绿色标志;
- 测试失败显示红色标志。
```java
public class UserMapperTest {
@Test
public void testListUser() {
UserMapper userMapper = new UserMapper();
List<User> list = userMapper.list();
Assert.assertNotNull("User list should not be null", list);
list.forEach(System.out::println);
}
}
```
**注意**,如果需要使用**依赖注入**,需要在测试类上加`@SpringBootTest`注解
它会启动 Spring 应用程序上下文,并在测试期间模拟运行整个 Spring Boot 应用程序。这意味着你可以在集成测试中使用 Spring 的各种功能,例如**自动装配、依赖注入、配置加载**等
```java
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void testListUser() {
List<User> list = userMapper.list();
Assert.assertNotNull("User list should not be null", list);
list.forEach(System.out::println);
}
}
```
写了`@Test`注解,那么该测试函数就可以直接运行!若一个测试类中写了多个测试方法,可以全部执行!
![image-20240307173454288](https://pic.bitday.top/i/2025/03/19/u6um98-2.png)
@ -1654,98 +1854,6 @@ public class AnnotationTest4 {
### 注解
定义:
```java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 定义注解
@Retention(RetentionPolicy.RUNTIME) // 定义注解的生命周期
@Target(ElementType.METHOD) // 定义注解可以应用的Java元素类型
public @interface MyAnnotation {
// 定义注解的元素(属性)
String description() default "This is a default description";
int value() default 0;
}
```
@Target(ElementType.METHOD) //方法上的注解
@Target(ElementType.CLASS) //类上的注解
**简化使用**:当注解只有一个元素需要设置时,且该元素的名字是`value`,在使用该注解时可以不用显式地指定元素名
```java
@MyAnnotation(5) // 等同于 @MyAnnotation(value = 5)
public void someMethod() {
// 方法实现
}
```
如果要同时设置`description`,则不能省略元素名:
```java
@MyAnnotation(value = 5, description = "Specific description")
public void anotherMethod() {
// 方法实现
}
```
**获得注解上的value**反射
```java
public class MyClass {
@MyAnnotation(value = "specific value")
public void myMethod() {
// 方法实现
}
}
```
```java
import java.lang.reflect.Method;
public class AnnotationReader {
public static void main(String[] args) throws NoSuchMethodException {
// 获取MyClass的Class对象
Class<MyClass> obj = MyClass.class;
// 获取myMethod方法的Method对象
Method method = obj.getMethod("myMethod");
// 获取方法上的MyAnnotation注解实例
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
if (annotation != null) {
// 输出注解的value值
System.out.println("注解的value: " + annotation.value());
}
}
}
```
### 对象拷贝属性
```java

View File

@ -995,7 +995,7 @@ Joe主题https://github.com/HaoOuBa/Joe
[Joe再续前缘主题 - 搭建本站同款网站 - 易航博客](https://blog.yihang.info/archives/18.html)
修改文章详情页的上方信息
自定义文章详情页的上方信息(如更新日期/文章字数第)
`typecho/usr/themes/Joe/module/single/batten.php`
```php
@ -1044,6 +1044,32 @@ if (!defined('__TYPECHO_ROOT_DIR__')) {
</div>
```
修改代码块背景色:
`typecho/usr/themes/Joe/assets/css/joe.global.css`
```css
.joe_detail__article code:not([class]) {
border-radius: var(--radius-inner, 4px); /* 可以设置一个默认值 */
background: #f5f5f5; /* 稍微偏灰的背景色 */
color: #000000; /* 黑色字体 */
padding: 2px 6px; /* 内边距可以适当增大 */
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
word-break: break-word;
font-weight: normal;
-webkit-text-size-adjust: 100%;
-webkit-font-smoothing: antialiased;
white-space: pre-wrap; /* 保持代码换行 */
font-size: 0.875em;
margin-inline-start: 0.25em;
margin-inline-end: 0.25em;
}
```
大坑:{x}会显示为勾选框无法正常进行latex公式解析因为`typecho/usr/themes/Joe/public/short.php`中设置了短代码替换,**在文章输出前**对 `$content` 中的特定标记或短代码进行搜索和替换,从而实现一系列自定义功能。现已全部注释。

View File

@ -68,11 +68,21 @@ if (flag == false) { //更常用!
`Integer.toString(int i)`:将 `int` 转换为字符串。
`Integer.compare(int a,int b)` 比较a和b的大小内部实现
```
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
```
避免了 **整数溢出** 的风险,在排序中建议使用`Integer.compare(int a,int b)`代替 `a-b`
### 常用数据结构
#### `String`
#### String
子串:字符串中**连续的一段字符**。
@ -107,7 +117,7 @@ String sortedStr = new String(charArray);
#### `StringBuffer`
#### StringBuffer
`StringBuffer` 是 Java 中用于操作可变字符串的类
@ -162,7 +172,7 @@ sb.setLength(0);
#### `HashMap`
#### HashMap
- 基于哈希表实现,查找、插入和删除的平均时间复杂度为 O(1)。
- 不保证元素的顺序。
@ -220,7 +230,7 @@ visited[i][j] = true;
#### `HashSet`
#### HashSet
- 基于哈希表实现,查找、插入和删除的平均时间复杂度为 O(1)。
@ -263,7 +273,7 @@ visited[i][j] = true;
#### `PriorityQueue`
#### PriorityQueue
- 基于优先堆(最小堆或最大堆)实现,元素按优先级排序。
- **默认是最小堆**,即队首元素是最小的。 `new PriorityQueue<>(Comparator.reverseOrder());`定义最大堆
@ -501,7 +511,7 @@ class MinHeap {
#### **`ArrayList`**
#### **ArrayList**
- 基于数组实现,支持动态扩展。
- 访问元素的时间复杂度为 O(1),在末尾插入和删除的时间复杂度为 O(1)。
@ -597,7 +607,7 @@ for (int i = 0; i < list.size(); i++) {
#### **`数组Array`**
#### **数组Array**
数组是一种固定长度的数据结构,用于存储相同类型的元素。数组的特点包括:
@ -688,7 +698,7 @@ Arrays.fill(memo, -1);
#### `二维数组`
#### 二维数组
```java
int rows = 3;
@ -724,7 +734,7 @@ public void setZeroes(int[][] matrix) {
#### `Queue`
#### Queue
队尾插入,队头取!
@ -761,7 +771,7 @@ public class QueueExample {
```
#### `Deque`(双端队列+栈)
#### Deque(双端队列+栈)
支持在队列的两端(头和尾)进行元素的插入和删除。这使得 Deque 既能作为队列FIFO又能作为栈LIFO使用。
@ -847,7 +857,7 @@ public class DequeExample {
#### `Iterator`
#### Iterator
- **`HashMap``HashSet``ArrayList``PriorityQueue`** 都实现了 `Iterable` 接口,支持 `iterator()` 方法。
@ -1135,9 +1145,9 @@ public class IntervalSort {
// 自定义比较器,先比较第一个元素,如果相等再比较第二个元素
Arrays.sort(intervals, (a, b) -> {
if (a[0] != b[0]) {
return a[0] - b[0];
return Integer.compare(a[0], b[0]);
} else {
return a[1] - b[1];
return Integer.compare(a[1], b[1]);
}
});

View File

@ -1123,8 +1123,6 @@ public class OrderTask {
## Websocket
WebSocket 是基于 TCP 的一种新的**网络协议**。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建**持久性**的连接, 并进行**双向**数据传输。

View File

@ -1,53 +1,144 @@
- 当然可以!下面是你可以直接记到笔记里的内容:
下面从架构、扩展性、安全性、管理成本等几个维度,对 **Session****JWT** 进行对比,帮助你根据场景选择合适的方案。
------
------
### 🧠 题型:**Top K 高频元素**LeetCode 347
## 一、基本原理
**题目描述**:给定一个整数数组 `nums` 和一个整数 `k`,返回出现频率最高的前 `k` 个元素,返回顺序可以任意。
| 特性 | Session | JWTJSON Web Token |
| -------- | ------------------------------------ | ----------------------------------------------- |
| 存储方式 | 服务端存储会话数据如内存、Redis | 客户端存储完整的令牌(通常在 Header 或 Cookie |
| 标识方式 | 客户端持有一个 Session ID | 客户端持有一个自包含的 Token |
| 状态管理 | 有状态Stateful服务器要维护会话 | 无状态Stateless服务器不存会话 |
------
------
### 📌 解法一:大根堆(最大堆)
## 二、对比分析
**思路**
### 1. 架构与扩展性
1. 使用 `HashMap` 统计每个元素的出现频率。
2. 构建一个**大根堆**`PriorityQueue` + 自定义比较器),根据频率降序排列。
3. 将所有元素加入堆中,**弹出前 `k` 个元素**即为答案。
- **Session**
- 单体应用:内存中维护 Map<sessionId, userData>,简单易用。
- 分布式/集群:需要共享 Session如 Redis、数据库、Sticky Session增加运维成本。
- **JWT**
- 无状态:令牌自带用户信息及签名,服务器只需校验签名即可,无需存储。
- 分布式友好:各节点只要共享签名密钥(或公钥),即可校验,无需集中存储。
**适合场景**
### 2. 性能
- 实现简单,适用于对全部元素排序后取前 `k` 个。
- 时间复杂度:**O(n log n)**,因为需要将所有 `n` 个元素都加入堆。
- **Session**
- 每次请求都要从存储(内存/Redis/DB读取会话数据IO 成本或网络开销。
- 并发高时,集中式 Session 存储可能成为瓶颈。
- **JWT**
- 校验签名HMAC 或 RSA为 CPU 操作,无网络开销,性能开销较小。
- 但 Token 通常更大(包含多段 Base64每次请求都要传输带宽略增。
------
### 3. 安全性
### 📌 解法二:小根堆(最小堆)
- **Session**
- 会话数据保存在服务器端,客户端只能拿到 Session ID敏感数据不暴露。
- 可在服务器端随时销毁或更新 Session强制登出、权限变更即时生效
- **JWT**
- 令牌自包含所有声明claims如果存敏感数据需加密JWE否则仅签名JWS也可能泄露信息。
- 无法主动撤销(除非做黑名单),需要控制有效期并结合“刷新令牌”机制。
**思路**
### 4. 可控性与管理
1. 使用 `HashMap` 统计频率。
2. 构建一个**小根堆**,堆中仅保存前 `k` 个高频元素。
3. 遍历每个元素:
- 如果堆未满,直接加入。
- 如果当前元素频率大于堆顶(最小频率),则弹出堆顶,加入当前元素
4. 最终堆中保存的就是前 `k` 个高频元素
- **Session**
- **可控性强**:服务器可随时作废 Session适合需要即时注销、权限动态调整的场景
- 过期策略灵活:可按用户、按应用统一配置。
- **JWT**
- **可控性弱**Token 一旦签发,在到期前无法从服务器强制失效(除非额外维护黑名单)
- 需要设计“短生命周期 + 刷新令牌”模式,增加实现复杂度
**适合场景**
### 5. 跨域与移动端
- 当 `k ≪ n` 时效率更高。
- 时间复杂度:**O(n log k)**,因为堆中最多维护 `k` 个元素。
- **Session**
- 依赖 Cookie同源策略跨域或移动端原生 App使用受限。
- 跨域时需配合 CORS + `withCredentials`,且浏览器必须支持并开启 Cookie。
- **JWT**
- 与 HTTP 协议无关,既可放在 Authorization 头,也可放在 URL、LocalStorage移动端/第三方客户端更友好。
- 只要客户端能发送 HTTP Header就能携带 Token。
------
### 6. 实现复杂度
### ✅ 总结对比:
- **Session**
- 框架通常开箱即用(如 Spring Session开发者只需开启即可。
- 自动处理过期、失效,管理简单。
- **JWT**
- 需要设计签名算法、密钥管理、过期策略、刷新机制、黑名单等。
- 容易因配置不当造成安全漏洞算法降级、密钥泄露、Token 劫持等)。
| 方法 | 适合场景 | 时间复杂度 | 空间复杂度 |
| ------ | --------------- | ---------- | ---------- |
| 大根堆 | k ≈ n简单易写 | O(n log n) | O(n) |
| 小根堆 | k ≪ n更高效 | O(n log k) | O(n) |
------
------
## 三、何时选用
需要我再写成代码模板笔记也可以,随时说!
| 场景类型 | 推荐方案 | 原因 |
| --------------------------- | -------- | ---------------------------------------------------------- |
| 单体 Web 应用、后台管理系统 | Session | 简单、可控、安全性高,框架支持完善。 |
| 分布式微服务、无状态 API | JWT | 无需集中存储,易扩展;移动端/第三方客户端友好。 |
| 高度安全、需即时失效场景 | Session | 可随时在服务器端销毁会话,确保强制登出或权限变更即时生效。 |
| 跨域或多端Web + App | JWT | Token 可在多种客户端轻松传递,无需依赖浏览器 Cookie。 |
------
## 四、示例代码
### Session 示例Spring Boot
```java
// 登录时创建 Session
@PostMapping("/login")
public String login(HttpServletRequest req, @RequestParam String user, @RequestParam String pass) {
if (authService.verify(user, pass)) {
req.getSession().setAttribute("userId", user);
return "登录成功";
}
return "登录失败";
}
// 受保护资源
@GetMapping("/profile")
public User profile(HttpServletRequest req) {
String userId = (String) req.getSession().getAttribute("userId");
return userService.findById(userId);
}
```
### JWT 示例Spring Boot + jjwt
```java
// 生成 Token
@PostMapping("/login")
public String login(@RequestParam String user, @RequestParam String pass) {
if (authService.verify(user, pass)) {
return Jwts.builder()
.setSubject(user)
.setExpiration(new Date(System.currentTimeMillis() + 3600_000))
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
throw new UnauthorizedException();
}
// 过滤器中校验
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) {
String token = req.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
String user = Jwts.parser().setSigningKey(secretKey)
.parseClaimsJws(token.substring(7))
.getBody().getSubject();
// 将 user 存入 SecurityContext …
}
chain.doFilter(req, res);
}
```
------
## 五、结论
- **Session**:上手简单、安全可控,适合绝大多数传统 Web 应用。
- **JWT**:更灵活、易扩展,适合分布式架构、多端场景,但需要更复杂的设计与安全防护。
根据你的项目架构、团队经验和安全需求,选择最合适的方案即可。