refactor: 重构所有 starter 组件的 SaTokenContext 上下文读写策略

This commit is contained in:
click33
2025-04-06 23:22:01 +08:00
parent 36cc99a70c
commit 55f0c94aec
92 changed files with 1538 additions and 1234 deletions

View File

@@ -17,4 +17,5 @@ public class SaTokenDemoApp {
Solon.start(SaTokenDemoApp.class, args);
System.out.println("\n启动成功Sa-Token配置如下" + SaManager.getConfig());
}
}

View File

@@ -1,6 +1,7 @@
package com.pj.test;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.servlet.util.SaTokenContextUtil;
import cn.dev33.satoken.spring.SpringMVCUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -18,7 +19,9 @@ public class TestController {
// 测试 浏览器访问: http://localhost:8081/test/test
@RequestMapping("test")
public SaResult test() {
System.out.println("------------进来了");
System.out.println("------------进来了");
System.out.println(SpringMVCUtil.getRequest());
System.out.println(SaTokenContextUtil.getRequest());
return SaResult.ok();
}

View File

@@ -73,8 +73,12 @@
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</dependencies>
<!-- 构建配置 -->
<build>

View File

@@ -1,6 +1,7 @@
package com.pj.test;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.servlet.util.SaTokenContextUtil;
import cn.dev33.satoken.spring.SpringMVCUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -17,9 +18,12 @@ public class Test2Controller {
@RequestMapping("/test")
public SaResult test2() {
StpUtil.login(30003);
System.out.println(StpUtil.getSession().timeout());
System.out.println(StpUtil.getStpLogic().getTokenSession(false));
System.out.println(SpringMVCUtil.getRequest());
System.out.println(SaTokenContextUtil.getRequest());
// StpUtil.login(30003);
// System.out.println(StpUtil.getSession().timeout());
// System.out.println(StpUtil.getStpLogic().getTokenSession(false));
return SaResult.ok();
}

View File

@@ -26,10 +26,6 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Sa-Token 权限认证Reactor响应式集成, 在线文档https://sa-token.cc/ -->
<dependency>
@@ -37,21 +33,14 @@
<artifactId>sa-token-reactor-spring-boot3-starter</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- sa-token整合redis (使用jdk默认序列化方式) -->
<!-- Sa-Token 整合 Redis -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis</artifactId>
<artifactId>sa-token-redis-template</artifactId>
<version>${sa-token.version}</version>
</dependency> -->
<!-- sa-token整合redis (使用jackson序列化方式) -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>${sa-token.version}</version>
</dependency> -->
<!-- 提供redis连接池 -->
<!-- <dependency>
<groupId>org.apache.commons</groupId>

View File

@@ -1,12 +1,10 @@
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;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
/**
* [Sa-Token 权限认证] 配置类
* @author click33
@@ -33,7 +31,7 @@ public class SaTokenConfigure {
// 指定[异常处理函数]:每次[认证函数]发生异常时执行此函数
.setError(e -> {
System.out.println("---------- sa全局异常 ");
return AjaxJson.getError(e.getMessage());
return SaResult.error(e.getMessage());
})
;
}

View File

@@ -1,5 +1,8 @@
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;
@@ -8,10 +11,6 @@ import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.stp.StpUtil;
@Configuration
public class DefineRoutes {
@@ -23,12 +22,11 @@ public class DefineRoutes {
@Bean
public RouterFunction<ServerResponse> getRoutes() {
return RouterFunctions.route(RequestPredicates.GET("/fun"), req -> {
// 测试打印
System.out.println("是否登录:" + StpUtil.isLogin());
// 返回结果
AjaxJson aj = AjaxJson.getSuccessData(StpUtil.getTokenInfo());
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).syncBody(aj);
return SaReactorSyncHolder.setContext(req.exchange(), () -> {
System.out.println("是否登录:" + StpUtil.isLogin());
SaResult res = SaResult.data(StpUtil.getTokenInfo());
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).syncBody(res);
});
});
}

View File

@@ -1,52 +1,19 @@
package com.pj.test;
import org.springframework.web.bind.annotation.ControllerAdvice;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.exception.DisableServiceException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理
*/
@ControllerAdvice // 可指定包前缀,比如:(basePackages = "com.pj.admin")
@RestControllerAdvice
public class GlobalException {
// 全局异常拦截(拦截项目中的所有异常)
@ResponseBody
@ExceptionHandler
public AjaxJson handlerException(Exception e)
throws Exception {
// 打印堆栈,以供调试
System.out.println("全局异常---------------");
e.printStackTrace();
// 不同异常返回不同状态码
AjaxJson aj = null;
if (e instanceof NotLoginException) { // 如果是未登录异常
NotLoginException ee = (NotLoginException) e;
aj = AjaxJson.getNotLogin().setMsg(ee.getMessage());
} else if(e instanceof NotRoleException) { // 如果是角色异常
NotRoleException ee = (NotRoleException) e;
aj = AjaxJson.getNotJur("无此角色:" + ee.getRole());
} else if(e instanceof NotPermissionException) { // 如果是权限异常
NotPermissionException ee = (NotPermissionException) e;
aj = AjaxJson.getNotJur("无此权限:" + ee.getPermission());
} else if(e instanceof DisableServiceException) { // 如果是被封禁异常
DisableServiceException ee = (DisableServiceException) e;
aj = AjaxJson.getNotJur("账号被封禁:" + ee.getDisableTime() + "秒后解封");
} else { // 普通异常, 输出500 + 异常信息
aj = AjaxJson.getError(e.getMessage());
}
// 返回给前端
return aj;
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}

View File

@@ -1,17 +1,19 @@
package com.pj.test;
import java.time.Duration;
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 com.pj.util.AjaxJson;
import cn.dev33.satoken.reactor.context.SaReactorHolder;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.time.Duration;
/**
* 测试专用Controller
* @author click33
@@ -21,60 +23,93 @@ import reactor.core.publisher.Mono;
@RequestMapping("/test/")
public class TestController {
// 测试登录接口 [同步模式] 浏览器访问: http://localhost:8081/test/login
@Autowired
UserService userService;
// 登录测试Controller 里调用 Sa-Token API --- http://localhost:8081/test/login
@RequestMapping("login")
public AjaxJson login(@RequestParam(defaultValue="10001") String id) {
StpUtil.login(id);
return AjaxJson.getSuccess("登录成功");
}
// API测试 [同步模式] 浏览器访问: http://localhost:8081/test/isLogin
@RequestMapping("isLogin")
public AjaxJson isLogin() {
System.out.println("当前会话是否登录:" + StpUtil.isLogin());
return AjaxJson.getSuccessData(StpUtil.getTokenInfo());
}
// API测试 [异步模式] 浏览器访问: http://localhost:8081/test/isLogin2
@RequestMapping("isLogin2")
public Mono<AjaxJson> isLogin2() {
System.out.println("当前会话是否登录:" + StpUtil.isLogin());
AjaxJson aj = AjaxJson.getSuccessData(StpUtil.getTokenInfo());
return Mono.just(aj);
}
// API测试 [异步模式, 同一线程] 浏览器访问: http://localhost:8081/test/isLogin3
@RequestMapping("isLogin3")
public Mono<AjaxJson> isLogin3() {
System.out.println("当前会话是否登录:" + StpUtil.isLogin());
// 异步方式
return SaReactorHolder.getContext().map(e -> {
System.out.println("当前会话是否登录2" + StpUtil.isLogin());
return AjaxJson.getSuccessData(StpUtil.getTokenInfo());
public Mono<SaResult> login(@RequestParam(defaultValue="10001") String id) {
return SaReactorHolder.sync(() -> {
StpUtil.login(id);
return SaResult.ok("登录成功");
});
}
// API测试 [异步模式, 不同线程] 浏览器访问: http://localhost:8081/test/isLogin4
// 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<AjaxJson> isLogin4() {
System.out.println("当前会话是否登录:" + StpUtil.isLogin());
System.out.println("线程id-----" + Thread.currentThread().getId());
return Mono.delay(Duration.ofSeconds(1)).flatMap(r->{
return SaReactorHolder.getContext().map(rr->{
System.out.println("线程id---内--" + Thread.currentThread().getId());
System.out.println("当前会话是否登录2" + StpUtil.isLogin());
return AjaxJson.getSuccessData(StpUtil.getTokenInfo());
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 AjaxJson test() {
System.out.println("线程id-----------Controller--" + Thread.currentThread().getId() + "\t\t");
System.out.println("当前会话是否登录:" + StpUtil.isLogin());
return AjaxJson.getSuccessData(StpUtil.getTokenInfo());
public SaResult test() {
System.out.println("线程id------- " + Thread.currentThread().getId());
return SaResult.ok();
}
}

View File

@@ -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);
}
}

View File

@@ -1,154 +0,0 @@
package com.pj.util;
import java.io.Serializable;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* ajax请求返回Json格式数据的封装
*/
public class AjaxJson implements Serializable{
private static final long serialVersionUID = 1L; // 序列化版本号
public static final int CODE_SUCCESS = 200; // 成功状态码
public static final int CODE_ERROR = 500; // 错误状态码
public static final int CODE_WARNING = 501; // 警告状态码
public static final int CODE_NOT_JUR = 403; // 无权限状态码
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
public int code; // 状态码
public String msg; // 描述信息
public Object data; // 携带对象
public Long dataCount; // 数据总数,用于分页
/**
* 返回code
* @return
*/
public int getCode() {
return this.code;
}
/**
* 给msg赋值连缀风格
*/
public AjaxJson setMsg(String msg) {
this.msg = msg;
return this;
}
public String getMsg() {
return this.msg;
}
/**
* 给data赋值连缀风格
*/
public AjaxJson setData(Object data) {
this.data = data;
return this;
}
/**
* 将data还原为指定类型并返回
*/
@SuppressWarnings("unchecked")
public <T> T getData(Class<T> cs) {
return (T) data;
}
// ============================ 构建 ==================================
public AjaxJson(int code, String msg, Object data, Long dataCount) {
this.code = code;
this.msg = msg;
this.data = data;
this.dataCount = dataCount;
}
// 返回成功
public static AjaxJson getSuccess() {
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
}
public static AjaxJson getSuccess(String msg) {
return new AjaxJson(CODE_SUCCESS, msg, null, null);
}
public static AjaxJson getSuccess(String msg, Object data) {
return new AjaxJson(CODE_SUCCESS, msg, data, null);
}
public static AjaxJson getSuccessData(Object data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
public static AjaxJson getSuccessArray(Object... data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
// 返回失败
public static AjaxJson getError() {
return new AjaxJson(CODE_ERROR, "error", null, null);
}
public static AjaxJson getError(String msg) {
return new AjaxJson(CODE_ERROR, msg, null, null);
}
// 返回警告
public static AjaxJson getWarning() {
return new AjaxJson(CODE_ERROR, "warning", null, null);
}
public static AjaxJson getWarning(String msg) {
return new AjaxJson(CODE_WARNING, msg, null, null);
}
// 返回未登录
public static AjaxJson getNotLogin() {
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
}
// 返回没有权限的
public static AjaxJson getNotJur(String msg) {
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
}
// 返回一个自定义状态码的
public static AjaxJson get(int code, String msg){
return new AjaxJson(code, msg, null, null);
}
// 返回分页和数据的
public static AjaxJson getPageData(Long dataCount, Object data){
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
}
// 返回,根据受影响行数的(大于0=ok小于0=error)
public static AjaxJson getByLine(int line){
if(line > 0){
return getSuccess("ok", line);
}
return getError("error").setData(line);
}
// 返回,根据布尔值来确定最终结果的 (true=okfalse=error)
public static AjaxJson getByBoolean(boolean b){
return b ? getSuccess("ok") : getError("error");
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
// 转JSON格式输出
try {
return new ObjectMapper().writeValueAsString(this);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -10,7 +10,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.14</version>
<version>2.7.18</version>
<relativePath/>
</parent>
@@ -26,11 +26,7 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Sa-Token 权限认证Reactor响应式集成, 在线文档https://sa-token.cc/ -->
<dependency>
<groupId>cn.dev33</groupId>
@@ -38,20 +34,13 @@
<version>${sa-token.version}</version>
</dependency>
<!-- sa-token整合redis (使用jdk默认序列化方式) -->
<!-- Sa-Token 整合 Redis -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis</artifactId>
<artifactId>sa-token-redis-template</artifactId>
<version>${sa-token.version}</version>
</dependency> -->
<!-- sa-token整合redis (使用jackson序列化方式) -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>${sa-token.version}</version>
</dependency> -->
<!-- 提供redis连接池 -->
<!-- <dependency>
<groupId>org.apache.commons</groupId>

View File

@@ -1,10 +1,9 @@
package com.pj;
import cn.dev33.satoken.SaManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaManager;
/**
* Sa-Token整合webflux 示例
* @author click33

View File

@@ -1,7 +1,7 @@
package com.pj.satoken;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.util.SaResult;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -32,7 +32,7 @@ public class SaTokenConfigure {
.setError(e -> {
System.out.println("---------- sa全局异常 ");
e.printStackTrace();
return AjaxJson.getError(e.getMessage());
return SaResult.error(e.getMessage());
})
;
}

View File

@@ -1,5 +1,8 @@
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;
@@ -8,10 +11,6 @@ import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.stp.StpUtil;
@Configuration
public class DefineRoutes {
@@ -23,12 +22,11 @@ public class DefineRoutes {
@Bean
public RouterFunction<ServerResponse> getRoutes() {
return RouterFunctions.route(RequestPredicates.GET("/fun"), req -> {
// 测试打印
System.out.println("是否登录:" + StpUtil.isLogin());
// 返回结果
AjaxJson aj = AjaxJson.getSuccessData(StpUtil.getTokenInfo());
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).syncBody(aj);
return SaReactorSyncHolder.setContext(req.exchange(), () -> {
System.out.println("是否登录:" + StpUtil.isLogin());
SaResult res = SaResult.data(StpUtil.getTokenInfo());
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).syncBody(res);
});
});
}

View File

@@ -1,52 +1,19 @@
package com.pj.test;
import org.springframework.web.bind.annotation.ControllerAdvice;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.exception.DisableServiceException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理
*/
@ControllerAdvice // 可指定包前缀,比如:(basePackages = "com.pj.admin")
@RestControllerAdvice
public class GlobalException {
// 全局异常拦截(拦截项目中的所有异常)
@ResponseBody
@ExceptionHandler
public AjaxJson handlerException(Exception e)
throws Exception {
// 打印堆栈,以供调试
System.out.println("全局异常---------------");
e.printStackTrace();
// 不同异常返回不同状态码
AjaxJson aj = null;
if (e instanceof NotLoginException) { // 如果是未登录异常
NotLoginException ee = (NotLoginException) e;
aj = AjaxJson.getNotLogin().setMsg(ee.getMessage());
} else if(e instanceof NotRoleException) { // 如果是角色异常
NotRoleException ee = (NotRoleException) e;
aj = AjaxJson.getNotJur("无此角色:" + ee.getRole());
} else if(e instanceof NotPermissionException) { // 如果是权限异常
NotPermissionException ee = (NotPermissionException) e;
aj = AjaxJson.getNotJur("无此权限:" + ee.getPermission());
} else if(e instanceof DisableServiceException) { // 如果是被封禁异常
DisableServiceException ee = (DisableServiceException) e;
aj = AjaxJson.getNotJur("账号被封禁:" + ee.getDisableTime() + "秒后解封");
} else { // 普通异常, 输出500 + 异常信息
aj = AjaxJson.getError(e.getMessage());
}
// 返回给前端
return aj;
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}

View File

@@ -1,11 +1,15 @@
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 com.pj.util.AjaxJson;
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;
@@ -19,60 +23,93 @@ import java.time.Duration;
@RequestMapping("/test/")
public class TestController {
// 测试登录接口 [同步模式] 浏览器访问: http://localhost:8081/test/login
@Autowired
UserService userService;
// 登录测试Controller 里调用 Sa-Token API --- http://localhost:8081/test/login
@RequestMapping("login")
public AjaxJson login(@RequestParam(defaultValue="10001") String id) {
StpUtil.login(id);
return AjaxJson.getSuccess("登录成功");
}
// API测试 [同步模式] 浏览器访问: http://localhost:8081/test/isLogin
@RequestMapping("isLogin")
public AjaxJson isLogin() {
System.out.println("当前会话是否登录:" + StpUtil.isLogin());
return AjaxJson.getSuccessData(StpUtil.getTokenInfo());
}
// API测试 [异步模式] 浏览器访问: http://localhost:8081/test/isLogin2
@RequestMapping("isLogin2")
public Mono<AjaxJson> isLogin2() {
System.out.println("当前会话是否登录:" + StpUtil.isLogin());
AjaxJson aj = AjaxJson.getSuccessData(StpUtil.getTokenInfo());
return Mono.just(aj);
}
// API测试 [异步模式, 同一线程] 浏览器访问: http://localhost:8081/test/isLogin3
@RequestMapping("isLogin3")
public Mono<AjaxJson> isLogin3() {
System.out.println("当前会话是否登录:" + StpUtil.isLogin());
// 异步方式
return SaReactorHolder.getContext().map(e -> {
System.out.println("当前会话是否登录2" + StpUtil.isLogin());
return AjaxJson.getSuccessData(StpUtil.getTokenInfo());
public Mono<SaResult> login(@RequestParam(defaultValue="10001") String id) {
return SaReactorHolder.sync(() -> {
StpUtil.login(id);
return SaResult.ok("登录成功");
});
}
// API测试 [异步模式, 不同线程] 浏览器访问: http://localhost:8081/test/isLogin4
// 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<AjaxJson> isLogin4() {
System.out.println("当前会话是否登录:" + StpUtil.isLogin());
System.out.println("线程id-----" + Thread.currentThread().getId());
return Mono.delay(Duration.ofSeconds(1)).flatMap(r->{
return SaReactorHolder.getContext().map(rr->{
System.out.println("线程id---内--" + Thread.currentThread().getId());
System.out.println("当前会话是否登录2" + StpUtil.isLogin());
return AjaxJson.getSuccessData(StpUtil.getTokenInfo());
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 AjaxJson test() {
System.out.println("线程id-----------Controller--" + Thread.currentThread().getId() + "\t\t");
System.out.println("当前会话是否登录:" + StpUtil.isLogin());
return AjaxJson.getSuccessData(StpUtil.getTokenInfo());
public SaResult test() {
System.out.println("线程id------- " + Thread.currentThread().getId());
return SaResult.ok();
}
}

View File

@@ -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);
}
}

View File

@@ -1,154 +0,0 @@
package com.pj.util;
import java.io.Serializable;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* ajax请求返回Json格式数据的封装
*/
public class AjaxJson implements Serializable{
private static final long serialVersionUID = 1L; // 序列化版本号
public static final int CODE_SUCCESS = 200; // 成功状态码
public static final int CODE_ERROR = 500; // 错误状态码
public static final int CODE_WARNING = 501; // 警告状态码
public static final int CODE_NOT_JUR = 403; // 无权限状态码
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
public int code; // 状态码
public String msg; // 描述信息
public Object data; // 携带对象
public Long dataCount; // 数据总数,用于分页
/**
* 返回code
* @return
*/
public int getCode() {
return this.code;
}
/**
* 给msg赋值连缀风格
*/
public AjaxJson setMsg(String msg) {
this.msg = msg;
return this;
}
public String getMsg() {
return this.msg;
}
/**
* 给data赋值连缀风格
*/
public AjaxJson setData(Object data) {
this.data = data;
return this;
}
/**
* 将data还原为指定类型并返回
*/
@SuppressWarnings("unchecked")
public <T> T getData(Class<T> cs) {
return (T) data;
}
// ============================ 构建 ==================================
public AjaxJson(int code, String msg, Object data, Long dataCount) {
this.code = code;
this.msg = msg;
this.data = data;
this.dataCount = dataCount;
}
// 返回成功
public static AjaxJson getSuccess() {
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
}
public static AjaxJson getSuccess(String msg) {
return new AjaxJson(CODE_SUCCESS, msg, null, null);
}
public static AjaxJson getSuccess(String msg, Object data) {
return new AjaxJson(CODE_SUCCESS, msg, data, null);
}
public static AjaxJson getSuccessData(Object data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
public static AjaxJson getSuccessArray(Object... data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
// 返回失败
public static AjaxJson getError() {
return new AjaxJson(CODE_ERROR, "error", null, null);
}
public static AjaxJson getError(String msg) {
return new AjaxJson(CODE_ERROR, msg, null, null);
}
// 返回警告
public static AjaxJson getWarning() {
return new AjaxJson(CODE_ERROR, "warning", null, null);
}
public static AjaxJson getWarning(String msg) {
return new AjaxJson(CODE_WARNING, msg, null, null);
}
// 返回未登录
public static AjaxJson getNotLogin() {
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
}
// 返回没有权限的
public static AjaxJson getNotJur(String msg) {
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
}
// 返回一个自定义状态码的
public static AjaxJson get(int code, String msg){
return new AjaxJson(code, msg, null, null);
}
// 返回分页和数据的
public static AjaxJson getPageData(Long dataCount, Object data){
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
}
// 返回,根据受影响行数的(大于0=ok小于0=error)
public static AjaxJson getByLine(int line){
if(line > 0){
return getSuccess("ok", line);
}
return getError("error").setData(line);
}
// 返回,根据布尔值来确定最终结果的 (true=okfalse=error)
public static AjaxJson getByBoolean(boolean b){
return b ? getSuccess("ok") : getError("error");
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
// 转JSON格式输出
try {
return new ObjectMapper().writeValueAsString(this);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}