mirror of
https://gitee.com/dromara/sa-token.git
synced 2026-02-27 16:50:24 +08:00
demo: 新增 sa-token-demo-webflux-springboot4 示例
This commit is contained in:
@@ -0,0 +1,61 @@
|
|||||||
|
<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-webflux-springboot4</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<!-- SpringBoot 4 -->
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>4.0.3</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<!-- 定义 Sa-Token 版本号 -->
|
||||||
|
<properties>
|
||||||
|
<sa-token.version>1.44.0</sa-token.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- springboot依赖 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Sa-Token 权限认证(Reactor响应式集成), 在线文档:https://sa-token.cc/ -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.dev33</groupId>
|
||||||
|
<artifactId>sa-token-reactor-spring-boot4-starter</artifactId>
|
||||||
|
<version>${sa-token.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Sa-Token 整合 Redis -->
|
||||||
|
<!-- <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>
|
||||||
|
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
||||||
+22
@@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+48
@@ -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<Void> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+39
@@ -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());
|
||||||
|
})
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+44
@@ -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<String> getPermissionList(Object loginId, String loginType) {
|
||||||
|
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
|
||||||
|
List<String> list = new ArrayList<String>();
|
||||||
|
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<String> getRoleList(Object loginId, String loginType) {
|
||||||
|
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色
|
||||||
|
List<String> list = new ArrayList<String>();
|
||||||
|
list.add("admin");
|
||||||
|
list.add("super-admin");
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+32
@@ -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<ServerResponse> 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+19
@@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+115
@@ -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<SaResult> 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<SaResult> 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<SaResult> 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<SaResult> 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+25
@@ -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<Long> findUserIdByNamePwd(String name, String pwd) {
|
||||||
|
// ...
|
||||||
|
return Mono.just(10001L);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user