diff --git a/sa-token-demo/sa-token-demo-webflux-springboot4/pom.xml b/sa-token-demo/sa-token-demo-webflux-springboot4/pom.xml new file mode 100644 index 00000000..5baef48e --- /dev/null +++ b/sa-token-demo/sa-token-demo-webflux-springboot4/pom.xml @@ -0,0 +1,61 @@ + + 4.0.0 + cn.dev33 + sa-token-demo-webflux-springboot4 + 0.0.1-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-parent + 4.0.3 + + + + + + 1.44.0 + + + + + + + org.springframework.boot + spring-boot-starter-webflux + + + + + cn.dev33 + sa-token-reactor-spring-boot4-starter + ${sa-token.version} + + + + + + + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + + + diff --git a/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/SaTokenWebfluxSpringboot4Application.java b/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/SaTokenWebfluxSpringboot4Application.java new file mode 100644 index 00000000..1a10bab3 --- /dev/null +++ b/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/SaTokenWebfluxSpringboot4Application.java @@ -0,0 +1,22 @@ +package com.pj; + +import cn.dev33.satoken.SaManager; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Sa-Token整合webflux 示例 (springboot4) + * + * @author click33 + * @since 2023年1月3日 + * + */ +@SpringBootApplication +public class SaTokenWebfluxSpringboot4Application { + + public static void main(String[] args) { + SpringApplication.run(SaTokenWebfluxSpringboot4Application.class, args); + System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig()); + } + +} diff --git a/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/satoken/MyFilter.java b/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/satoken/MyFilter.java new file mode 100644 index 00000000..130582a3 --- /dev/null +++ b/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/satoken/MyFilter.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020-2099 sa-token.cc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.pj.satoken; + +import cn.dev33.satoken.reactor.context.SaReactorSyncHolder; +import cn.dev33.satoken.stp.StpUtil; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Mono; + +/** + * 自定义过滤器 + */ +@Component +public class MyFilter implements WebFilter { + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + System.out.println("进入自定义过滤器"); + + try { + // 先 set 上下文,再调用 Sa-Token 同步 API,并在 finally 里清除上下文 + SaReactorSyncHolder.setContext(exchange); + System.out.println(StpUtil.isLogin()); + } + finally { + SaReactorSyncHolder.clearContext(); + } + + return chain.filter(exchange); + } + +} diff --git a/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/satoken/SaTokenConfigure.java b/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/satoken/SaTokenConfigure.java new file mode 100644 index 00000000..8524f11f --- /dev/null +++ b/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/satoken/SaTokenConfigure.java @@ -0,0 +1,39 @@ +package com.pj.satoken; + +import cn.dev33.satoken.reactor.filter.SaReactorFilter; +import cn.dev33.satoken.util.SaResult; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * [Sa-Token 权限认证] 配置类 + * @author click33 + * + */ +@Configuration +public class SaTokenConfigure { + + /** + * 注册 [sa-token全局过滤器] + */ + @Bean + public SaReactorFilter getSaReactorFilter() { + return new SaReactorFilter() + // 指定 [拦截路由] + .addInclude("/**") + // 指定 [放行路由] + .addExclude("/favicon.ico") + // 指定[认证函数]: 每次请求执行 + .setAuth(r -> { + System.out.println("---------- sa全局认证"); + // SaRouter.match("/test/test", () -> StpUtil.checkLogin()); + }) + // 指定[异常处理函数]:每次[认证函数]发生异常时执行此函数 + .setError(e -> { + System.out.println("---------- sa全局异常 "); + return SaResult.error(e.getMessage()); + }) + ; + } + +} diff --git a/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/satoken/StpInterfaceImpl.java b/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/satoken/StpInterfaceImpl.java new file mode 100644 index 00000000..b6cc79f7 --- /dev/null +++ b/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/satoken/StpInterfaceImpl.java @@ -0,0 +1,44 @@ +package com.pj.satoken; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.stereotype.Component; + +import cn.dev33.satoken.stp.StpInterface; + +/** + * 自定义权限验证接口扩展 + */ +@Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展 +public class StpInterfaceImpl implements StpInterface { + + /** + * 返回一个账号所拥有的权限码集合 + */ + @Override + public List getPermissionList(Object loginId, String loginType) { + // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限 + List list = new ArrayList(); + list.add("101"); + list.add("user-add"); + list.add("user-delete"); + list.add("user-update"); + list.add("user-get"); + list.add("article-get"); + return list; + } + + /** + * 返回一个账号所拥有的角色标识集合 + */ + @Override + public List getRoleList(Object loginId, String loginType) { + // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色 + List list = new ArrayList(); + list.add("admin"); + list.add("super-admin"); + return list; + } + +} diff --git a/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/test/DefineRoutes.java b/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/test/DefineRoutes.java new file mode 100644 index 00000000..4e406f31 --- /dev/null +++ b/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/test/DefineRoutes.java @@ -0,0 +1,32 @@ +package com.pj.test; + +import cn.dev33.satoken.reactor.context.SaReactorSyncHolder; +import cn.dev33.satoken.stp.StpUtil; +import cn.dev33.satoken.util.SaResult; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.server.RequestPredicates; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.RouterFunctions; +import org.springframework.web.reactive.function.server.ServerResponse; + +@Configuration +public class DefineRoutes { + + /** + * 函数式编程,初始化路由表 + * @return 路由表 + */ + @Bean + public RouterFunction getRoutes() { + return RouterFunctions.route(RequestPredicates.GET("/fun"), req -> { + return SaReactorSyncHolder.setContext(req.exchange(), () -> { + System.out.println("是否登录:" + StpUtil.isLogin()); + SaResult res = SaResult.data(StpUtil.getTokenInfo()); + return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(res); + }); + }); + } + +} diff --git a/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/test/GlobalException.java b/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/test/GlobalException.java new file mode 100644 index 00000000..34743ae8 --- /dev/null +++ b/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/test/GlobalException.java @@ -0,0 +1,19 @@ +package com.pj.test; + +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-webflux-springboot4/src/main/java/com/pj/test/TestController.java b/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/test/TestController.java new file mode 100644 index 00000000..ac90d9cc --- /dev/null +++ b/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/test/TestController.java @@ -0,0 +1,115 @@ +package com.pj.test; + +import cn.dev33.satoken.reactor.context.SaReactorHolder; +import cn.dev33.satoken.reactor.context.SaReactorSyncHolder; +import cn.dev33.satoken.stp.StpUtil; +import cn.dev33.satoken.util.SaResult; +import org.springframework.beans.factory.annotation.Autowired; +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 org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.time.Duration; + +/** + * 测试专用Controller + * @author click33 + * + */ +@RestController +@RequestMapping("/test/") +public class TestController { + + @Autowired + UserService userService; + + // 登录测试:Controller 里调用 Sa-Token API --- http://localhost:8081/test/login + @RequestMapping("login") + public Mono login(@RequestParam(defaultValue="10001") String id) { + return SaReactorHolder.sync(() -> { + StpUtil.login(id); + return SaResult.ok("登录成功"); + }); + } + + // API测试:手动设置上下文、try-finally 形式 --- http://localhost:8081/test/isLogin + @RequestMapping("isLogin") + public SaResult isLogin(ServerWebExchange exchange) { + try { + SaReactorSyncHolder.setContext(exchange); + System.out.println("是否登录:" + StpUtil.isLogin()); + return SaResult.data(StpUtil.getTokenInfo()); + } finally { + SaReactorSyncHolder.clearContext(); + } + } + + // API测试:手动设置上下文、lambda 表达式形式 --- http://localhost:8081/test/isLogin2 + @RequestMapping("isLogin2") + public SaResult isLogin2(ServerWebExchange exchange) { + SaResult res = SaReactorSyncHolder.setContext(exchange, ()->{ + System.out.println("是否登录:" + StpUtil.isLogin()); + return SaResult.data(StpUtil.getTokenInfo()); + }); + return SaResult.data(res); + } + + // API测试:自动设置上下文、lambda 表达式形式 --- http://localhost:8081/test/isLogin3 + @RequestMapping("isLogin3") + public Mono isLogin3() { + return SaReactorHolder.sync(() -> { + System.out.println("是否登录:" + StpUtil.isLogin()); + userService.isLogin(); + return SaResult.data(StpUtil.getTokenInfo()); + }); + } + + // API测试:自动设置上下文、调用 userService Mono 方法 --- http://localhost:8081/test/isLogin4 + @RequestMapping("isLogin4") + public Mono isLogin4() { + return userService.findUserIdByNamePwd("ZhangSan", "123456").flatMap(userId -> { + return SaReactorHolder.sync(() -> { + StpUtil.login(userId); + return SaResult.data(StpUtil.getTokenInfo()); + }); + }); + } + + // API测试:切换线程、复杂嵌套调用 --- http://localhost:8081/test/isLogin5 + @RequestMapping("isLogin5") + public Mono isLogin5() { + System.out.println("线程id-----" + Thread.currentThread().getId()); + // 要点:在流里调用 Sa-Token API 之前,必须用 SaReactorHolder.sync( () -> {} ) 进行包裹 + return Mono.delay(Duration.ofSeconds(1)) + .doOnNext(r-> System.out.println("线程id-----" + Thread.currentThread().getId())) + .map(r-> SaReactorHolder.sync( () -> userService.isLogin() )) + .map(r-> userService.findUserIdByNamePwd("ZhangSan", "123456")) + .map(r-> SaReactorHolder.sync( () -> userService.isLogin() )) + .flatMap(isLogin -> { + System.out.println("是否登录 " + isLogin); + return SaReactorHolder.sync(() -> { + System.out.println("是否登录 " + StpUtil.isLogin()); + return SaResult.data(StpUtil.getTokenInfo()); + }); + }); + } + + // API测试:使用上下文无关的API --- http://localhost:8081/test/isLogin6 + @RequestMapping("isLogin6") + public SaResult isLogin6(@CookieValue("satoken") String satoken) { + System.out.println("token 为:" + satoken); + System.out.println("登录人:" + StpUtil.getLoginIdByToken(satoken)); + return SaResult.ok("登录人:" + StpUtil.getLoginIdByToken(satoken)); + } + + // 测试 浏览器访问: http://localhost:8081/test/test + @RequestMapping("test") + public SaResult test() { + System.out.println("线程id------- " + Thread.currentThread().getId()); + return SaResult.ok(); + } + +} diff --git a/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/test/UserService.java b/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/test/UserService.java new file mode 100644 index 00000000..004ef0ef --- /dev/null +++ b/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/java/com/pj/test/UserService.java @@ -0,0 +1,25 @@ +package com.pj.test; + +import cn.dev33.satoken.stp.StpUtil; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +/** + * 模拟 Service 方法 + * @author click33 + * @since 2025/4/6 + */ +@Service +public class UserService { + + public boolean isLogin() { + System.out.println("UserService 里调用 API 测试,是否登录:" + StpUtil.isLogin()); + return StpUtil.isLogin(); + } + + public Mono findUserIdByNamePwd(String name, String pwd) { + // ... + return Mono.just(10001L); + } + +} diff --git a/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/resources/application.yml new file mode 100644 index 00000000..94579ef3 --- /dev/null +++ b/sa-token-demo/sa-token-demo-webflux-springboot4/src/main/resources/application.yml @@ -0,0 +1,46 @@ +# 端口 +server: + port: 8081 + +# sa-token 配置 +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: 10000ms + lettuce: + pool: + # 连接池最大连接数 + max-active: 200 + # 连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms + # 连接池中的最大空闲连接 + max-idle: 10 + # 连接池中的最小空闲连接 + min-idle: 0 + +