GraphQL Directive(指令)是GraphQL中的一种特殊类型,它允许开发者在GraphQL schema中添加元数据,以控制查询和解析操作的行为
Directive的详细说明及使用可见GraphQL(五)指令[Directive]详解
本文将介绍通过自定义Directive实现的GraphQL登录态校验,步骤依次为:
Schema
中定义directive
- 实现
DgsReactiveCustomContextBuilderWithRequest
接口,构建请求内全局使用的上下文对象 - 实现
SchemaDirectiveWiring
,对Field
进行拦截校验 Directive
注入
Schema Directive定义
"必须要登录"
directive @needLogin on FIELD_DEFINITION
type Employee {
"雇员名称"
employees(month: Date : [String] @needLogin
}
LoginContextBuilder
实现DgsReactiveCustomContextBuilderWithRequest
接口,可以使用当前的请求信息,例如 HTTP 请求头、请求参数等构建自定义的上下文对象
在查询执行过程中,GraphQL 会将该上下文对象传递给所有的数据解析器(DataFetcher),使得数据解析器能够访问和修改上下文对象中的数据
使用DgsReactiveCustomContextBuilderWithRequest
接口可以实现许多有用的功能,例如:
- 存储用户身份验证信息,以便在数据解析器中进行鉴权
- 将请求相关的信息(例如请求参数、请求头等)传递给数据解析器,以便数据解析器根据这些信息返回正确的数据
- 存储请求相关的上下文信息,例如请求开始时间、请求结束时间等,以便进行性能分析和监控
@Component
public class LoginContextBuilder implements DgsReactiveCustomContextBuilderWithRequest<YyContext> {
private static final Logger LOGGER = LoggerFactory.getLogger(LoginContextBuilder.class);
@Autowired
private ReactiveLoginService reactiveLoginService;
@Autowired
private HttpHeaderAuthorization httpHeaderAuthorization;
@NotNull
@Override
public Mono<LoginContext> build(@Nullable Map<String, ?> map, @Nullable HttpHeaders httpHeaders, @Nullable ServerRequest serverRequest) {
boolean interiorAuth = httpHeaderAuthorization.auth(serverRequest);
if(interiorAuth){
LoginContext loginContext = new LoginContext(true, IpUtil.getClientIpAddress(serverRequest)).setInteriorAuth(true);
LOGGER.info("interior request loginContext:{}",loginContext);
return Mono.just(loginContext);
}else{
return reactiveLoginService.getUid(serverRequest)
.doOnSuccess(user-> LOGGER.info("login user:{}",user))
.onErrorResume(e-> Mono.just(LoginUser.NOT_LOGIN_USER))
.map(user -> new LoginContext(user, IpUtil.getClientIpAddress(serverRequest)));
}
}
}
ReactiveLoginService
subscribeOn(Schedulers.boundedElastic())
将该Mono
订阅到一个boundedElastic
调度器的线程中,这样Mono
中的操作就会在该线程上执行,而不会阻塞当前的线程
Schedulers.boundedElastic()
调度器是一个弹性线程池,它根据需要动态地创建和销毁线程,以适应不同的负载情况
public class ReactiveLoginService {
private LoginService loginService;
public ReactiveLoginService(LoginService loginService){
this.loginService = loginService;
}
/**
* 登录校验
* @param httpServerRequest
* @return
*/
public Mono<LoginUser> getUid(ServerRequest httpServerRequest){
return Mono.fromCallable(()->{
long uid = loginService.login(new WebFluxHttpServletRequest(httpServerRequest),null);
return uid > 0 ? new LoginUser(uid) : LoginUser.NOT_LOGIN_USER;
}).subscribeOn(Schedulers.boundedElastic());
}
public void close(){
loginService.close();
}
}
Directive 实现
@Component
public class NeedLoginDirective implements SchemaDirectiveWiring {
private static final Logger LOGGER = LoggerFactory.getLogger(NeedLoginDirective.class);
public static final String NEED_LOGIN_DIRECTIVE = "needLogin";
public NeedLoginDirective() {
}
@Override
public GraphQLFieldDefinition onField(SchemaDirectiveWiringEnvironment<GraphQLFieldDefinition> environment) {
GraphQLFieldDefinition field = environment.getElement();
GraphQLFieldsContainer parentType = environment.getFieldsContainer();
// 原始DataFetcher,无需修改参数值时,最后需返回原始DataFetcher的值
DataFetcher originalDataFetcher = environment.getCodeRegistry().getDataFetcher(parentType, field);
if (field.getDirective(NEED_LOGIN_DIRECTIVE) == null) {
return field;
}
LOGGER.info("onField field:{} needLogin.", field);
DataFetcher needLoginDataFetcher = dfe -> {
LoginContext loginContext = DgsContext.getCustomContext(dfe);
if (loginContext.getLoginUser().hasLogin()) {
return originalDataFetcher.get(dfe);
} else {
LOGGER.info("not login return null");
throw new NeedLoginRuntimeException();
}
};
environment.getCodeRegistry().dataFetcher(parentType, field, needLoginDataFetcher);
return field;
}
}
Directive 注入
GraphQLSchemaConfiguration
@Configuration
public class GraphQLSchemaConfiguration {
@DgsComponent
public class SecuredDirectiveRegistration {
private NeedLoginDirective needLoginDirective;
public SecuredDirectiveRegistration(NeedLoginDirective needLoginDirective) {
this.needLoginDirective = needLoginDirective;
}
@DgsRuntimeWiring
public RuntimeWiring.Builder addSecuredDirective(RuntimeWiring.Builder builder) {
return builder.directive(NeedLoginDirective.NEED_LOGIN_DIRECTIVE,needLoginDirective);
}
}
}
参考资料: