Spring Webflux拦截请求体指定键值对返回400及架构设计疑问
问题原因分析
- 你写的
request.bodyToMono(Comment.class)链式调用只是构造了响应式流,没有被订阅也没有作为返回值返回,Webflux不会主动执行这部分没有被加入到处理链路中的流,所以逻辑完全不生效 - 同步
try-catch无法捕获响应式流内部发射的异常信号,NoSuchElementException是流在空数据时发出的错误信号,不会以同步异常的形式抛出,所以这部分捕获逻辑也无效 - 当前逻辑没有在检测到commentId存在时中断请求流程,最后还是返回了
next.handle(request)将请求转发给后续handler处理
修复后的路由过滤器代码
@Slf4j @Configuration public class CommentsRouter { @Bean public RouterFunction<ServerResponse> commentsRoute(CommentsHandler commentsHandler) { return RouterFunctions .route(GET("/v1/comments/{parentId}"), commentsHandler::getComments) .andRoute(GET("/v1/comments/{commentId}"), commentsHandler::getComment) .andRoute(POST("/v1/comments").and(accept(APPLICATION_JSON)), commentsHandler::addComment) .filter(((request, next) -> { // 只拦截指定POST请求 if (request.path().equalsIgnoreCase("/v1/comments") && request.method() == HttpMethod.POST) { Map<String, String> commentIdError = Collections.singletonMap("error", "Cannot assign commentId from client"); Map<String, String> emptyBodyError = Collections.singletonMap("error", "No content found while creating comment"); // 读取请求体并做校验,将校验逻辑加入响应式链路 return request.bodyToMono(Comment.class) // 处理空请求体的情况 .switchIfEmpty(Mono.defer(() -> { log.error("Empty body found for request {}", request.path()); return ServerResponse.badRequest() .contentType(MediaType.APPLICATION_JSON) .bodyValue(emptyBodyError); })) // 校验commentId是否存在非空 .flatMap(comment -> { if (Objects.nonNull(comment.getCommentId()) && !comment.getCommentId().isBlank()) { log.error("Pre-Assigned commentId found in payload {} for {} request {}", comment, request.methodName(), request.path()); return ServerResponse.badRequest() .contentType(MediaType.APPLICATION_JSON) .bodyValue(commentIdError); } // 校验通过,将请求体重新包装后传给后续handler,否则后续handler读取不到body return next.handle(ServerRequest.from(request) .body(comment) .build()); }); } // 非目标请求直接放行 return next.handle(request); })); } }
注意必须把校验后的请求体重新包装到ServerRequest里传给后续handler,因为请求体的流默认只能被读取一次,如果直接读了不重写,后续handler会拿不到请求体内容
校验逻辑存放位置的设计建议
- 如果这个commentId的校验规则是仅针对当前
/v1/comments的POST接口的业务规则,建议迁移到Service层,更符合单一职责原则:路由层只负责请求转发,Handler层负责参数解析、响应封装,Service层负责业务校验和逻辑处理,后续维护业务规则时只需要修改Service层代码即可 - 如果有多个不同接口都需要做"禁止客户端上传commentId"的通用校验,才适合放在Router的全局/局部过滤器中作为横切逻辑处理
- 也可以选择把校验逻辑放在Handler层,和其他接口参数校验逻辑放在一起,适合接口层统一做参数合法性校验的架构规范
内容的提问来源于stack exchange,提问作者Anadi Misra
相关产品推荐
相关产品推荐

