适配 springboot3

This commit is contained in:
click33
2023-01-05 19:20:21 +08:00
parent d09366602a
commit 1feca73e2a
134 changed files with 3238 additions and 974 deletions

View File

@@ -0,0 +1,12 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.factorypath
.idea/

View File

@@ -0,0 +1,118 @@
<?xml version='1.0' encoding='utf-8'?>
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-starter</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<packaging>jar</packaging>
<name>sa-token-reactor-spring-boot3-starter</name>
<artifactId>sa-token-reactor-spring-boot3-starter</artifactId>
<description>springboot3 reactor integrate sa-token</description>
<properties>
<springboot3.version>3.0.1</springboot3.version>
</properties>
<dependencies>
<!-- spring-boot-starter (optional) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<optional>true</optional>
</dependency>
<!-- spring-web (optional) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<!--<version>5.3.7</version>-->
<optional>true</optional>
</dependency>
<!-- reactor-core (optional) -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<optional>true</optional>
</dependency>
<!-- jackson-databind (optional) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<optional>true</optional>
</dependency>
<!-- config -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- sa-token-core -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-core</artifactId>
</dependency>
<!-- sa-token-spring-boot-autoconfig -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-autoconfig</artifactId>
</dependency>
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>${springboot.version}</version>
</dependency> -->
</dependencies>
<dependencyManagement>
<dependencies>
<!-- spring-boot-starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${springboot3.version}</version>
</dependency>
<!-- spring-boot-starter-web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.5.1</version>
</dependency>
<!-- jackson-databind (optional) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.1</version>
</dependency>
<!-- config (optional) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${springboot3.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@@ -0,0 +1,49 @@
package cn.dev33.satoken.reactor.context;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* Reactor上下文操作 [异步]
* @author kong
*
*/
public class SaReactorHolder {
/**
* key
*/
public static final Class<ServerWebExchange> CONTEXT_KEY = ServerWebExchange.class;
/**
* chain_key
*/
public static final String CHAIN_KEY = "WEB_FILTER_CHAIN_KEY";
/**
* 获取上下文对象
* @return see note
*/
public static Mono<ServerWebExchange> getContext() {
// 从全局 Mono<Context> 获取
return Mono.deferContextual(Mono::just).map(ctx -> ctx.get(CONTEXT_KEY));
}
/**
* 获取上下文对象, 并设置到同步上下文中
* @return see note
*/
public static Mono<ServerWebExchange> getContextAndSetSync() {
// 从全局 Mono<Context> 获取
return Mono.deferContextual(Mono::just).map(ctx -> {
// 设置到sync中
SaReactorSyncHolder.setContext(ctx.get(CONTEXT_KEY));
return ctx.get(CONTEXT_KEY);
}).doFinally(r->{
// 从sync中清除
SaReactorSyncHolder.clearContext();
});
}
}

View File

@@ -0,0 +1,63 @@
package cn.dev33.satoken.reactor.context;
import org.springframework.web.server.ServerWebExchange;
import cn.dev33.satoken.context.SaTokenContextForThreadLocalStorage;
import cn.dev33.satoken.context.SaTokenContextForThreadLocalStorage.Box;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.context.model.SaStorage;
import cn.dev33.satoken.fun.SaFunction;
import cn.dev33.satoken.reactor.model.SaRequestForReactor;
import cn.dev33.satoken.reactor.model.SaResponseForReactor;
import cn.dev33.satoken.reactor.model.SaStorageForReactor;
/**
* Reactor上下文操作 [同步]
* @author kong
*
*/
public class SaReactorSyncHolder {
/**
* 写入上下文对象
* @param exchange see note
*/
public static void setContext(ServerWebExchange exchange) {
SaRequest request = new SaRequestForReactor(exchange.getRequest());
SaResponse response = new SaResponseForReactor(exchange.getResponse());
SaStorage storage = new SaStorageForReactor(exchange);
SaTokenContextForThreadLocalStorage.setBox(request, response, storage);
}
/**
* 获取上下文对象
* @return see note
*/
public static ServerWebExchange getContext() {
Box box = SaTokenContextForThreadLocalStorage.getBoxNotNull();
return (ServerWebExchange)box.getStorage().getSource();
}
/**
* 清除上下文对象
*/
public static void clearContext() {
SaTokenContextForThreadLocalStorage.clearBox();
}
/**
* 写入上下文对象, 并在执行函数后将其清除
* @param exchange see note
* @param fun see note
*/
public static void setContext(ServerWebExchange exchange, SaFunction fun) {
try {
setContext(exchange);
fun.run();
} finally {
clearContext();
}
}
}

View File

@@ -0,0 +1,20 @@
package cn.dev33.satoken.reactor.error;
/**
* 定义 sa-token-reactor-spring-boot-starter 所有异常细分状态码
*
* @author kong
* @since: 2022-10-30
*/
public interface SaReactorSpringBootErrorCode {
/** 对象转 JSON 字符串失败 */
public static final int CODE_20203 = 20203;
/** JSON 字符串转 Map 失败 */
public static final int CODE_20204 = 20204;
/** 默认的 Filter 异常处理函数 */
public static final int CODE_20205 = 20205;
}

View File

@@ -0,0 +1,203 @@
package cn.dev33.satoken.reactor.filter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.core.annotation.Order;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import cn.dev33.satoken.exception.BackResultException;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.exception.StopMatchException;
import cn.dev33.satoken.filter.SaFilterAuthStrategy;
import cn.dev33.satoken.filter.SaFilterErrorStrategy;
import cn.dev33.satoken.reactor.context.SaReactorHolder;
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
import cn.dev33.satoken.reactor.error.SaReactorSpringBootErrorCode;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.util.SaTokenConsts;
import reactor.core.publisher.Mono;
/**
* Reactor全局过滤器
* @author kong
*
*/
@Order(SaTokenConsts.ASSEMBLY_ORDER)
public class SaReactorFilter implements WebFilter {
// ------------------------ 设置此过滤器 拦截 & 放行 的路由
/**
* 拦截路由
*/
private List<String> includeList = new ArrayList<>();
/**
* 放行路由
*/
private List<String> excludeList = new ArrayList<>();
/**
* 添加 [拦截路由]
* @param paths 路由
* @return 对象自身
*/
public SaReactorFilter addInclude(String... paths) {
includeList.addAll(Arrays.asList(paths));
return this;
}
/**
* 添加 [放行路由]
* @param paths 路由
* @return 对象自身
*/
public SaReactorFilter addExclude(String... paths) {
excludeList.addAll(Arrays.asList(paths));
return this;
}
/**
* 写入 [拦截路由] 集合
* @param pathList 路由集合
* @return 对象自身
*/
public SaReactorFilter setIncludeList(List<String> pathList) {
includeList = pathList;
return this;
}
/**
* 写入 [放行路由] 集合
* @param pathList 路由集合
* @return 对象自身
*/
public SaReactorFilter setExcludeList(List<String> pathList) {
excludeList = pathList;
return this;
}
/**
* 获取 [拦截路由] 集合
* @return see note
*/
public List<String> getIncludeList() {
return includeList;
}
/**
* 获取 [放行路由] 集合
* @return see note
*/
public List<String> getExcludeList() {
return excludeList;
}
// ------------------------ 钩子函数
/**
* 认证函数:每次请求执行
*/
public SaFilterAuthStrategy auth = r -> {};
/**
* 异常处理函数:每次[认证函数]发生异常时执行此函数
*/
public SaFilterErrorStrategy error = e -> {
throw new SaTokenException(e).setCode(SaReactorSpringBootErrorCode.CODE_20205);
};
/**
* 前置函数:在每次[认证函数]之前执行
*/
public SaFilterAuthStrategy beforeAuth = r -> {};
/**
* 写入[认证函数]: 每次请求执行
* @param auth see note
* @return 对象自身
*/
public SaReactorFilter setAuth(SaFilterAuthStrategy auth) {
this.auth = auth;
return this;
}
/**
* 写入[异常处理函数]:每次[认证函数]发生异常时执行此函数
* @param error see note
* @return 对象自身
*/
public SaReactorFilter setError(SaFilterErrorStrategy error) {
this.error = error;
return this;
}
/**
* 写入[前置函数]:在每次[认证函数]之前执行
* @param beforeAuth see note
* @return 对象自身
*/
public SaReactorFilter setBeforeAuth(SaFilterAuthStrategy beforeAuth) {
this.beforeAuth = beforeAuth;
return this;
}
// ------------------------ filter
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// 写入WebFilterChain对象
exchange.getAttributes().put(SaReactorHolder.CHAIN_KEY, chain);
// ---------- 全局认证处理
try {
// 写入全局上下文 (同步)
SaReactorSyncHolder.setContext(exchange);
// 执行全局过滤器
SaRouter.match(includeList).notMatch(excludeList).check(r -> {
beforeAuth.run(null);
auth.run(null);
});
} catch (StopMatchException e) {
} catch (Throwable e) {
// 1. 获取异常处理策略结果
String result = (e instanceof BackResultException) ? e.getMessage() : String.valueOf(error.run(e));
// 2. 写入输出流
if(exchange.getResponse().getHeaders().getFirst("Content-Type") == null) {
exchange.getResponse().getHeaders().set("Content-Type", "text/plain; charset=utf-8");
}
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(result.getBytes())));
} finally {
// 清除上下文
SaReactorSyncHolder.clearContext();
}
// ---------- 执行
// 写入全局上下文 (同步)
SaReactorSyncHolder.setContext(exchange);
// 执行
return chain.filter(exchange).contextWrite(ctx -> {
// 写入全局上下文 (异步)
ctx = ctx.put(SaReactorHolder.CONTEXT_KEY, exchange);
return ctx;
}).doFinally(r -> {
// 清除上下文
SaReactorSyncHolder.clearContext();
});
}
}

View File

@@ -0,0 +1,113 @@
package cn.dev33.satoken.reactor.model;
import org.springframework.http.HttpCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.reactor.context.SaReactorHolder;
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* Request for Reactor
* @author kong
*
*/
public class SaRequestForReactor implements SaRequest {
/**
* 底层Request对象
*/
protected ServerHttpRequest request;
/**
* 实例化
* @param request request对象
*/
public SaRequestForReactor(ServerHttpRequest request) {
this.request = request;
}
/**
* 获取底层源对象
*/
@Override
public Object getSource() {
return request;
}
/**
* 在 [请求体] 里获取一个值
*/
@Override
public String getParam(String name) {
return request.getQueryParams().getFirst(name);
}
/**
* 在 [请求头] 里获取一个值
*/
@Override
public String getHeader(String name) {
return request.getHeaders().getFirst(name);
}
/**
* 在 [Cookie作用域] 里获取一个值
*/
@Override
public String getCookieValue(String name) {
HttpCookie cookie = request.getCookies().getFirst(name);
if(cookie == null) {
return null;
}
return cookie.getValue();
}
/**
* 返回当前请求path (不包括上下文名称)
*/
@Override
public String getRequestPath() {
return request.getURI().getPath();
}
/**
* 返回当前请求的urlhttp://xxx.com/test
* @return see note
*/
public String getUrl() {
String currDomain = SaManager.getConfig().getCurrDomain();
if(SaFoxUtil.isEmpty(currDomain) == false) {
return currDomain + this.getRequestPath();
}
return request.getURI().toString();
}
/**
* 返回当前请求的类型
*/
@Override
public String getMethod() {
return request.getMethod().name();
}
/**
* 转发请求
*/
@Override
public Object forward(String path) {
ServerWebExchange exchange = SaReactorSyncHolder.getContext();
WebFilterChain chain = exchange.getAttribute(SaReactorHolder.CHAIN_KEY);
ServerHttpRequest newRequest = request.mutate().path(path).build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
return chain.filter(newExchange);
}
}

View File

@@ -0,0 +1,77 @@
package cn.dev33.satoken.reactor.model;
import java.net.URI;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import cn.dev33.satoken.context.model.SaResponse;
/**
* Response for Reactor
* @author kong
*
*/
public class SaResponseForReactor implements SaResponse {
/**
* 底层Response对象
*/
protected ServerHttpResponse response;
/**
* 实例化
* @param response response对象
*/
public SaResponseForReactor(ServerHttpResponse response) {
this.response = response;
}
/**
* 获取底层源对象
*/
@Override
public Object getSource() {
return response;
}
/**
* 设置响应状态码
*/
@Override
public SaResponse setStatus(int sc) {
response.setStatusCode(HttpStatus.valueOf(sc));
return this;
}
/**
* 在响应头里写入一个值
*/
@Override
public SaResponse setHeader(String name, String value) {
response.getHeaders().set(name, value);
return this;
}
/**
* 在响应头里添加一个值
* @param name 名字
* @param value 值
* @return 对象自身
*/
public SaResponse addHeader(String name, String value) {
response.getHeaders().add(name, value);
return this;
}
/**
* 重定向
*/
@Override
public Object redirect(String url) {
response.setStatusCode(HttpStatus.FOUND);
response.getHeaders().setLocation(URI.create(url));
return null;
}
}

View File

@@ -0,0 +1,61 @@
package cn.dev33.satoken.reactor.model;
import org.springframework.web.server.ServerWebExchange;
import cn.dev33.satoken.context.model.SaStorage;
/**
* Storage for Reactor
* @author kong
*
*/
public class SaStorageForReactor implements SaStorage {
/**
* 底层Request对象
*/
protected ServerWebExchange exchange;
/**
* 实例化
* @param exchange exchange对象
*/
public SaStorageForReactor(ServerWebExchange exchange) {
this.exchange = exchange;
}
/**
* 获取底层源对象
*/
@Override
public Object getSource() {
return exchange;
}
/**
* 在 [Request作用域] 里写入一个值
*/
@Override
public SaStorageForReactor set(String key, Object value) {
exchange.getAttributes().put(key, value);
return this;
}
/**
* 在 [Request作用域] 里获取一个值
*/
@Override
public Object get(String key) {
return exchange.getAttributes().get(key);
}
/**
* 在 [Request作用域] 里删除一个值
*/
@Override
public SaStorageForReactor delete(String key) {
exchange.getAttributes().remove(key);
return this;
}
}

View File

@@ -0,0 +1,4 @@
/**
* sa-token集成Reactor响应式编程的各个组件
*/
package cn.dev33.satoken.reactor;

View File

@@ -0,0 +1,22 @@
package cn.dev33.satoken.reactor.spring;
import cn.dev33.satoken.context.SaTokenContextForThreadLocal;
import cn.dev33.satoken.spring.SaPathMatcherHolder;
/**
* Sa-Token 上下文处理器 [ Spring Reactor 版本实现 ]
*
* @author kong
*
*/
public class SaTokenContextForSpringReactor extends SaTokenContextForThreadLocal {
/**
* 重写路由匹配方法
*/
@Override
public boolean matchPath(String pattern, String path) {
return SaPathMatcherHolder.getPathMatcher().match(pattern, path);
}
}

View File

@@ -0,0 +1,25 @@
package cn.dev33.satoken.reactor.spring;
import org.springframework.context.annotation.Bean;
import cn.dev33.satoken.context.SaTokenContext;
/**
* 注册Sa-Token所需要的Bean
* <p> Bean 的注册与注入应该分开在两个文件中,否则在某些场景下会造成循环依赖
* @author kong
*
*/
public class SaTokenContextRegister {
/**
* 获取容器交互Bean (ThreadLocal版)
*
* @return 容器交互Bean (ThreadLocal版)
*/
@Bean
public SaTokenContext getSaTokenContextForSpringReactor() {
return new SaTokenContextForSpringReactor();
}
}