GraphQL Directive(指令)是GraphQL中的一種特殊類型,它允許開發者在GraphQL schema中添加元數據,以控制查詢和解析操作的行為
Directive的詳細說明及使用可見GraphQL(五)指令[Directive]詳解
本文將介紹通過自定義Directive實現的GraphQL登錄態校驗,步驟依次為:
Schema
中定義directive
- 實現
DgsReactiveCustomContextBuilderWithRequest
接口,構建請求內全局使用的上下文對象 - 實現
SchemaDirectiveWiring
,對Field
進行攔截校驗 Directive
注入
Schema Directive定義
"必須要登錄"
directive @needLogin on FIELD_DEFINITIONtype 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);@Autowiredprivate ReactiveLoginService reactiveLoginService;@Autowiredprivate HttpHeaderAuthorization httpHeaderAuthorization;@NotNull@Overridepublic 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() {}@Overridepublic 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 {@DgsComponentpublic class SecuredDirectiveRegistration {private NeedLoginDirective needLoginDirective;public SecuredDirectiveRegistration(NeedLoginDirective needLoginDirective) {this.needLoginDirective = needLoginDirective;}@DgsRuntimeWiringpublic RuntimeWiring.Builder addSecuredDirective(RuntimeWiring.Builder builder) {return builder.directive(NeedLoginDirective.NEED_LOGIN_DIRECTIVE,needLoginDirective);}}
}
參考資料:
- GraphQL(五)指令[Directive]詳解
- Derectives 原理