mirror of
https://gitee.com/dromara/sa-token.git
synced 2025-10-07 15:14:23 +08:00
demo: 新增异步场景使用 demo。
This commit is contained in:
137
sa-token-demo/sa-token-demo-async/pom.xml
Normal file
137
sa-token-demo/sa-token-demo-async/pom.xml
Normal file
@@ -0,0 +1,137 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-demo-async</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
|
||||
<!-- SpringBoot -->
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.14</version>
|
||||
<!--<version>2.3.0.RELEASE</version>-->
|
||||
<!-- <version>1.5.9.RELEASE</version> -->
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.41.0</sa-token.version>
|
||||
<java.run.main.class>com.pj.SaTokenAsyncApplication</java.run.main.class>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- SpringBoot依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc/ -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.36</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 整合 Redis (使用jdk默认序列化方式) -->
|
||||
<!-- <dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency> -->
|
||||
|
||||
<!-- Sa-Token 整合 RedisTemplate -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-template</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 提供Redis连接池 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- @ConfigurationProperties -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<!-- 构建配置 -->
|
||||
<build>
|
||||
<!-- 配置资源目录 -->
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/java</directory>
|
||||
<includes>
|
||||
<include>**/*.xml</include>
|
||||
</includes>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<includes>
|
||||
<include>**/*.*</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<!-- 打包jar文件时,配置manifest文件,加入lib包的jar依赖 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addClasspath>true</addClasspath>
|
||||
<classpathPrefix>lib/</classpathPrefix>
|
||||
<mainClass>${java.run.main.class}</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- 拷贝依赖的jar包到lib目录 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>
|
||||
${project.build.directory}/lib
|
||||
</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@@ -0,0 +1,25 @@
|
||||
package com.pj;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
|
||||
/**
|
||||
* Sa-Token 异步方案 测试
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@EnableAsync // 启用异步
|
||||
@EnableScheduling // 启动定时任务
|
||||
@SpringBootApplication
|
||||
public class SaTokenAsyncApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SaTokenAsyncApplication.class, args);
|
||||
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
package com.pj.current;
|
||||
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalException {
|
||||
|
||||
// 全局异常拦截(拦截项目中的所有异常)
|
||||
@ExceptionHandler
|
||||
public SaResult handlerException(Exception e) {
|
||||
e.printStackTrace();
|
||||
return SaResult.error(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
package com.pj.satoken;
|
||||
|
||||
import cn.dev33.satoken.filter.SaServletFilter;
|
||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
|
||||
/**
|
||||
* [Sa-Token 权限认证] 配置类
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* 注册 Sa-Token 拦截器打开注解鉴权功能
|
||||
*/
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 注册 Sa-Token 拦截器打开注解鉴权功能
|
||||
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册 [Sa-Token 全局过滤器]
|
||||
*/
|
||||
@Bean
|
||||
public SaServletFilter getSaServletFilter() {
|
||||
return new SaServletFilter()
|
||||
|
||||
// 指定 [拦截路由] 与 [放行路由]
|
||||
.addInclude("/**")// .addExclude("/favicon.ico")
|
||||
|
||||
// 认证函数: 每次请求执行
|
||||
.setAuth(obj -> {
|
||||
// 输出 API 请求日志,方便调试代码
|
||||
// SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
|
||||
|
||||
})
|
||||
|
||||
// 异常处理函数:每次认证函数发生异常时执行此函数
|
||||
.setError(e -> {
|
||||
System.out.println("---------- sa全局异常 ");
|
||||
e.printStackTrace();
|
||||
return SaResult.error(e.getMessage());
|
||||
})
|
||||
|
||||
;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,139 @@
|
||||
package com.pj.test;
|
||||
|
||||
import cn.dev33.satoken.context.mock.SaTokenContextMockUtil;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.web.bind.annotation.CookieValue;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 测试几种场景的异步场景 Controller
|
||||
*
|
||||
* @author click33
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/test/")
|
||||
public class TestController {
|
||||
|
||||
@Autowired
|
||||
public ThreadPoolTaskExecutor taskExecutor;
|
||||
|
||||
// 【同步】登录 ---- http://localhost:8081/test/login?id=10001
|
||||
@RequestMapping("login")
|
||||
public SaResult login(@RequestParam(defaultValue = "10001") long id) {
|
||||
StpUtil.login(id);
|
||||
return SaResult.ok("登录成功");
|
||||
}
|
||||
|
||||
// 【同步】判断是否登录 --- http://localhost:8081/test/isLogin
|
||||
@RequestMapping("isLogin")
|
||||
public SaResult isLogin() {
|
||||
System.out.println("是否登录:" + StpUtil.isLogin());
|
||||
return SaResult.data(StpUtil.getTokenValue());
|
||||
}
|
||||
|
||||
// 【同步】注销 浏览器访问: http://localhost:8081/test/logout
|
||||
@RequestMapping("logout")
|
||||
public SaResult logout() {
|
||||
StpUtil.logout();
|
||||
return SaResult.data(null);
|
||||
}
|
||||
|
||||
// 【异步】new Thread --- http://localhost:8081/test/isLogin2
|
||||
@RequestMapping("isLogin2")
|
||||
public SaResult isLogin2() {
|
||||
System.out.println("是否登录:" + StpUtil.isLogin());
|
||||
String tokenValue = StpUtil.getTokenValue();
|
||||
new Thread(() -> {
|
||||
SaTokenContextMockUtil.setMockContext(()->{
|
||||
StpUtil.setTokenValueToStorage(tokenValue);
|
||||
System.out.println("是否登录:" + StpUtil.isLogin());
|
||||
});
|
||||
}).start();
|
||||
return SaResult.data(StpUtil.getTokenValue());
|
||||
}
|
||||
|
||||
// 【异步】线程池 ThreadPoolTaskExecutor --- http://localhost:8081/test/isLogin3
|
||||
@RequestMapping("isLogin3")
|
||||
public SaResult isLogin3(HttpServletRequest request, HttpServletResponse response) {
|
||||
System.out.println("是否登录:" + StpUtil.isLogin());
|
||||
String tokenValue = StpUtil.getTokenValue();
|
||||
taskExecutor.execute(() -> {
|
||||
SaTokenContextMockUtil.setMockContext(()->{
|
||||
StpUtil.setTokenValueToStorage(tokenValue);
|
||||
System.out.println("是否登录:" + StpUtil.isLogin());
|
||||
});
|
||||
});
|
||||
return SaResult.data(StpUtil.getTokenValue());
|
||||
}
|
||||
|
||||
// 【异步】@Async --- http://localhost:8081/test/isLogin4
|
||||
@Async
|
||||
@RequestMapping("isLogin4")
|
||||
public SaResult isLogin4(@CookieValue("satoken") String satoken) {
|
||||
SaTokenContextMockUtil.setMockContext(()->{
|
||||
StpUtil.setTokenValueToStorage(satoken);
|
||||
System.out.println("是否登录:" + StpUtil.isLogin());
|
||||
});
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 【异步】定时任务
|
||||
@Scheduled(cron = "0 * * * * ?") // 一分钟执行一次
|
||||
// @Scheduled(cron = "0/10 * * * * ?") // 十秒执行一次
|
||||
public void scheduledMethod(){
|
||||
// 错误写法:直接调用 Sa-Token API 会报错
|
||||
// System.out.println("定时任务,Mock 范围外:是否登录:" + StpUtil.isLogin());
|
||||
System.out.println(SaFoxUtil.formatDate(new Date()));
|
||||
|
||||
// 需要先设置模拟上下文
|
||||
SaTokenContextMockUtil.setMockContext(() -> {
|
||||
// StpUtil.setTokenValueToStorage("f452571f-bfdb-413d-aba9-e26992cf07be"); // 模拟 Token
|
||||
System.out.println("定时任务,Mock 范围内:是否登录:" + StpUtil.isLogin());
|
||||
// 模拟登录
|
||||
// StpUtil.login(10066); // 模拟 登录
|
||||
// System.out.println("定时任务,Mock 范围内:登录账号:" + StpUtil.getLoginId());
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
使用 InheritableThreadLocal 存储上下文带来的坑:
|
||||
|
||||
@RequestMapping("isLogin2")
|
||||
public SaResult isLogin2() {
|
||||
System.out.println("是否登录:" + StpUtil.isLogin());
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
System.out.println("是否登录:" + StpUtil.isLogin());
|
||||
}).start();
|
||||
|
||||
return SaResult.data(null);
|
||||
}
|
||||
|
||||
如果不 Thread.sleep(1000),外面 true,里面 true
|
||||
如果 Thread.sleep(1000),则外面 true,里面false
|
||||
|
||||
因为 SpringBoot 会在请求结束后清除 request 里的数据,
|
||||
此时子线程内部可以读取到 request,但是 request 无值,导致代码既能成功运行,又逻辑错误,是一种难以排查的隐形 bug
|
||||
应该避免使用 InheritableThreadLocal 来存储上下文数据
|
||||
|
||||
*/
|
@@ -0,0 +1,49 @@
|
||||
# 端口
|
||||
server:
|
||||
port: 8081
|
||||
|
||||
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
|
||||
sa-token:
|
||||
# token 名称 (同时也是 cookie 名称)
|
||||
token-name: satoken
|
||||
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
|
||||
timeout: 2592000
|
||||
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
|
||||
active-timeout: -1
|
||||
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
|
||||
is-concurrent: true
|
||||
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
|
||||
is-share: false
|
||||
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
|
||||
token-style: uuid
|
||||
# 是否输出操作日志
|
||||
is-log: true
|
||||
|
||||
spring:
|
||||
# redis配置
|
||||
redis:
|
||||
# Redis数据库索引(默认为0)
|
||||
database: 0
|
||||
# Redis服务器地址
|
||||
host: 127.0.0.1
|
||||
# Redis服务器连接端口
|
||||
port: 6379
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
max-active: 200
|
||||
# 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 10
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user