diff --git a/sa-token-demo/sa-token-demo-async/pom.xml b/sa-token-demo/sa-token-demo-async/pom.xml new file mode 100644 index 00000000..4f512b56 --- /dev/null +++ b/sa-token-demo/sa-token-demo-async/pom.xml @@ -0,0 +1,137 @@ + + 4.0.0 + cn.dev33 + sa-token-demo-async + 0.0.1-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-parent + 2.5.14 + + + + + + + + 1.41.0 + com.pj.SaTokenAsyncApplication + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-aop + + + + + cn.dev33 + sa-token-spring-boot-starter + ${sa-token.version} + + + + cn.hutool + hutool-all + 5.8.36 + + + + + + + + cn.dev33 + sa-token-redis-template + ${sa-token.version} + + + + + org.apache.commons + commons-pool2 + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + + + + src/main/java + + **/*.xml + + + + src/main/resources + + **/*.* + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + lib/ + ${java.run.main.class} + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy + package + + copy-dependencies + + + + ${project.build.directory}/lib + + + + + + + + + \ No newline at end of file diff --git a/sa-token-demo/sa-token-demo-async/src/main/java/com/pj/SaTokenAsyncApplication.java b/sa-token-demo/sa-token-demo-async/src/main/java/com/pj/SaTokenAsyncApplication.java new file mode 100644 index 00000000..d0b6e1b8 --- /dev/null +++ b/sa-token-demo/sa-token-demo-async/src/main/java/com/pj/SaTokenAsyncApplication.java @@ -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()); + } + +} diff --git a/sa-token-demo/sa-token-demo-async/src/main/java/com/pj/current/GlobalException.java b/sa-token-demo/sa-token-demo-async/src/main/java/com/pj/current/GlobalException.java new file mode 100644 index 00000000..750fc780 --- /dev/null +++ b/sa-token-demo/sa-token-demo-async/src/main/java/com/pj/current/GlobalException.java @@ -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()); + } + +} diff --git a/sa-token-demo/sa-token-demo-async/src/main/java/com/pj/satoken/SaTokenConfigure.java b/sa-token-demo/sa-token-demo-async/src/main/java/com/pj/satoken/SaTokenConfigure.java new file mode 100644 index 00000000..17b2f8cd --- /dev/null +++ b/sa-token-demo/sa-token-demo-async/src/main/java/com/pj/satoken/SaTokenConfigure.java @@ -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()); + }) + + ; + } + +} diff --git a/sa-token-demo/sa-token-demo-async/src/main/java/com/pj/test/TestController.java b/sa-token-demo/sa-token-demo-async/src/main/java/com/pj/test/TestController.java new file mode 100644 index 00000000..2b1cf173 --- /dev/null +++ b/sa-token-demo/sa-token-demo-async/src/main/java/com/pj/test/TestController.java @@ -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 来存储上下文数据 + + */ diff --git a/sa-token-demo/sa-token-demo-async/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-async/src/main/resources/application.yml new file mode 100644 index 00000000..ac474574 --- /dev/null +++ b/sa-token-demo/sa-token-demo-async/src/main/resources/application.yml @@ -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 + + + + + \ No newline at end of file diff --git a/sa-token-doc/start/download.md b/sa-token-doc/start/download.md index 677fdea2..5ef8bdfc 100644 --- a/sa-token-doc/start/download.md +++ b/sa-token-doc/start/download.md @@ -215,6 +215,7 @@ Maven依赖一直无法加载成功?[参考解决方案](https://sa-token.cc/d ├── sa-token-demo-alone-redis // [示例] Sa-Token 集成 alone-redis 模块 ├── sa-token-demo-alone-redis-cluster // [示例] Sa-Token 集成 alone-redis 模块、集群模式 ├── sa-token-demo-apikey // [示例] Sa-Token API Key 模块示例 + ├── sa-token-demo-async // [示例] Sa-Token 异步场景示例 ├── sa-token-demo-beetl // [示例] Sa-Token 集成 beetl 示例 ├── sa-token-demo-bom-import // [示例] Sa-Token bom 包导入示例 ├── sa-token-demo-case // [示例] Sa-Token 各模块示例