未能获取有效的上下文
使用版本:
1.38.0
涉及的功能模块:
gateway
测试步骤:
- 我经过以下步骤测试:
- 使用Apache HttpClient5构造了一个WebClient.Builder
@Bean("ticketFluxBuilder")
public WebClient.Builder ticketBuilder(HttpComponentsClientHttpConnector httpComponentsClientHttpConnector){
return WebClient.builder()
.baseUrl(iscProperties.getServer())
.clientConnector(httpComponentsClientHttpConnector)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.CONNECTION, "Keep-Alive")
.defaultHeader(HttpHeaders.ACCEPT_ENCODING, "br,deflate.gzip,x-gzip");
}
- 通过WebClient.Builder构造了一个请求
public Mono<String> checkTicket(WebClient.Builder builder, String ticket, String service, String pgtUrl, boolean renew) {
return builder.build()
.post()
.uri(uriBuilder -> {
URI build = uriBuilder
.path("/serviceValidate")
.queryParam("ticket", ticket)
.queryParam("service", service)
.queryParam("pgtUrl", pgtUrl)
.queryParam("renew", renew)
.build();
log.info("url: {}", build);
return build;
})
.retrieve()
.bodyToMono(String.class)
.publishOn(Schedulers.parallel())
.retry(3)
.doOnError(throwable -> {
ExceptionUtil.printStackTrace(throwable);
});
}
- 获取到相关信息之后,经过一系列转换,使用StpUtil.login
fluxUser.flatMap((Function<User, Mono<Resp<User>>>)
user -> {
StpUtil.login(user.name);
return Mono.just(checkTicket(user));
}
- 得出以下结果:
cn.dev33.satoken.exception.SaTokenContextException: 未能获取有效的上下文
at cn.dev33.satoken.context.SaTokenContextForThreadLocalStorage.getBoxNotNull(SaTokenContextForThreadLocalStorage.java:72)
- 其中第 xx 行的代码输出表现 和文档上描述的不一致:
SaTokenContextForThreadLocalStorage的getBoxNotNull()的box为空
public static Box getBoxNotNull() {
Box box = (Box)boxThreadLocal.get();
if (box == null) {
throw (new SaTokenContextException("未能获取有效的上下文")).setCode(10002);
} else {
return box;
}
}
- 我的理解是: WebClient.Builder请求后切换了线程,导致SaTokenContextForThreadLocalStorage的InheritableThreadLocal拿不到数据,但是WebClient是webflux的最佳请求方案
builder.build()
.post()
.uri(uriBuilder -> {
URI build = uriBuilder
.path("/serviceValidate")
.queryParam("ticket", ticket)
.queryParam("service", service)
.queryParam("pgtUrl", pgtUrl)
.queryParam("renew", renew)
.build();
log.info("url: {}", build);
return build;
})
.retrieve()
.bodyToMono(String.class)
.subscribeOn(Schedulers.fromExecutorService(createExecutorService))
.publishOn(Schedulers.parallel())
.retry(3)
.doOnError(throwable -> {
ExceptionUtil.printStackTrace(throwable);
});
有没有办法在不添加ExecutorService的情况下解决这个问题?
可以参考这个文章解决:https://blog.csdn.net/weixin_40116478/article/details/141557676?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22141557676%22%2C%22source%22%3A%22weixin_40116478%22%7D
可以参考这个文章解决:https://blog.csdn.net/weixin_40116478/article/details/141557676?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22141557676%22%2C%22source%22%3A%22weixin_40116478%22%7D
不是这个问题,是webclient和框架冲突了
builder.build()
.post()
.uri(uriBuilder -> {
URI build = uriBuilder
.path("/serviceValidate")
.queryParam("ticket", ticket)
.queryParam("service", service)
.queryParam("pgtUrl", pgtUrl)
.queryParam("renew", renew)
.build();
log.info("url: {}", build);
return build;
})
.retrieve()
.bodyToMono(String.class)
.retry(3)
.doOnError(throwable -> {
ExceptionUtil.printStackTrace(throwable);
});
添加了也不行
我也遇到了同样的问题,使用的是spring-boot-starter-webflux 和 sa-token-reactor-spring-boot3-starter 1.39.0
/**
* 在当前线程的 SaRequest 包装对象
*
* @return /
*/
public static SaRequest getRequest() {
return getBoxNotNull().getRequest();
}
/**
* 在当前线程的 SaResponse 包装对象
*
* @return /
*/
public static SaResponse getResponse() {
return getBoxNotNull().getResponse();
}
/**
* 在当前线程的 SaStorage 存储器包装对象
*
* @return /
*/
public static SaStorage getStorage() {
return getBoxNotNull().getStorage();
}
我发现http请求中进行StpUtil.login()时,会调用两次getRequest,调用一次getStorage,其中在getStorage()时,报错:未能获取有效的上下文
分析源码发现box的初始化发生在此处:
SaReactorSyncHolder:
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);
}
SaTokenContextForThreadLocalStorage中:
/**
* 基于 ThreadLocal 的 [ Box 存储器 ]
*/
public static ThreadLocal<Box> boxThreadLocal = new InheritableThreadLocal<>();
/**
* 初始化当前线程的 [ Box 存储器 ]
* @param request {@link SaRequest}
* @param response {@link SaResponse}
* @param storage {@link SaStorage}
*/
public static void setBox(SaRequest request, SaResponse response, SaStorage storage) {
Box bok = new Box(request, response, storage);
boxThreadLocal.set(bok);
}
两次getRequest使用的是同一个box,而getStorage重新初始化了一个box,也就是重新调用了setContext方法(setContext时,box是初始化成功的了,但是在getBoxNotNull时,拿到的为null)
执行过程中其实SaSession已经创建好,并且在debug时也可以通过打印的token拿出来,但是在SaStorage storage = SaHolder.getStorage();时发生错误,我发获取storage
我不知道是否是因为getStorage前调用了setContext重新初始化box导致的
以下方法执行失败报错:未能获取有效的上下文
public Mono<Result> login(AccountDTO accountDTO) {
return Mono.just(accountDTO)
.flatMap(dto -> {
if (StrUtil.isNotBlank(dto.getMail())) {
return getByMail(dto.getMail());
} else if (StrUtil.isNotBlank(dto.getUsername())) {
return getByUsername(dto.getUsername());
} else {
return Mono.error(new ServiceException(CommonResultType.PARAMETER_ERROR));
}
})
.map(account -> {
StpUtil.login(account.getAccountId());
return Result.success(StpUtil.getSessionByLoginId(account.getAccountId()));
});
}
强制转化为阻塞式之后执行成功
public Mono<Result> login(AccountDTO accountDTO) {
return Mono.fromCallable(() -> {
if (StrUtil.isBlank(accountDTO.getPassword()) || StrUtil.isBlank(accountDTO.getMail())) {
return Result.fail(CommonResultType.PARAMETER_ERROR);
}
Account loginAccount = getByMail(accountDTO.getMail()).block();
StpUtil.login(loginAccount.getAccountId());
//登陆成功,返回token
return Result.success(StpUtil.getTokenInfo());
})
.subscribeOn(Schedulers.boundedElastic()) // 切换到 boundedElastic 线程池
;
}
两种写法,在执行getRequest和getStorage时都是不同的线程,但是不太清除为什么强制阻塞后可以执行成功
同样问题
spring-boot-starter-webflux,sa-token-reactor-spring-boot3-starter +1
https://github.com/dromara/Sa-Token/issues/445
https://github.com/dromara/Sa-Token/issues/506 参考着2个应该可以解决
遇到同样问题...
同样的问题,使用webclient返回Mono后,报获取不到上下文,研究了一下。是因为异步的原因。自己还解决不了。 最后全换成restTemplate方式了。就没问题, 有没有大佬帮忙看看这个问题怎么解决。