java 如何使用Springboot WebFlux返回验证错误消息

3z6pesqy  于 2023-02-28  发布在  Java
关注(0)|答案(1)|浏览(162)

我如何使用WebFlux返回Springboot 3.0的自定义验证错误?
我已经接通了下面的控制器

import jakarta.validation.Valid;
//...

@Validated
@RestController
@RequestMapping("/organizations")
@RequiredArgsConstructor
public class OrganizationController {

    private final OrganizationService organizationService;

    @PostMapping("/create")
    public Mono<ResponseEntity<Organization>> create(@Valid @RequestBody final OrganizationDto organizationDto) {

        return organizationService.create(organizationDto).map(ResponseEntity::ok);
    }
}

OrganizationDto已设置为:

import jakarta.validation.constraints.NotNull;
//...

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public final class OrganizationDto {

    @NotNull    
    private String name;

    //...
}

最后我得到了我认为正确的ValidationHandler/ErrorController

@Slf4j
@ControllerAdvice
@RequiredArgsConstructor
public class ErrorController {

    @ResponseBody
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map<String, String> handleValidationExceptions(final MethodArgumentNotValidException ex) {

        final BindingResult bindingResult = ex.getBindingResult();
        final List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        final Map<String, String> errors = new HashMap<>();
        fieldErrors.forEach(error -> errors.put(error.getField(), error.getDefaultMessage()));
        
        return errors;
    }

}

但是,如果我将有效负载发送到的控制器中的端点

{
  "name": null
}

我回来了

{
    "timestamp": 1677430410704,
    "path": "/organizations/create",
    "status": 400,
    "error": "Bad Request",
    "requestId": "2050221b-2"
}

这几乎就是我想要的,但是我尝试将验证失败的原因放入响应中,但是没有任何运气
我已经在handleValidationExceptions上设置了断点,但看起来我永远不会进入它,而且我也没有在服务器端日志中看到任何指向正在发生的事情的东西。
我的类路径上确实有org.springframework.boot:spring-boot-starter-validation,而且我使用的是最新的Springboot 3.0.3
我是否遗漏了某个步骤或注解?

q3aa0525

q3aa05251#

我能够通过删除ErrorController并返回到我以前尝试过的方法来解决这个问题,即编写WebExceptionHandler的自定义实现
这里需要注意的重要一点是,必须设置@Order值,否则将跳过此实现,并且永远不会调用它(这正是我最初忽略此解决方案的原因)。
我的WebExceptionHandler版本

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.bind.support.WebExchangeBindException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;

import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
@RestControllerAdvice
@RequiredArgsConstructor
public class ValidationHandler implements WebExceptionHandler {

    private final ObjectMapper objectMapper;

    @Override
    @SneakyThrows
    public Mono<Void> handle(final ServerWebExchange exchange, final Throwable throwable) {

        if (throwable instanceof WebExchangeBindException validationEx) {
            final Map<String, String> errors = getValidationErrors(validationEx);

            exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
            exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);

            return writeResponse(exchange, objectMapper.writeValueAsBytes(errors));
        } else {
            return Mono.error(throwable);
        }
    }

    private Map<String, String> getValidationErrors(final WebExchangeBindException validationEx) {

        return validationEx.getBindingResult().getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField,
                error -> Optional.ofNullable(error.getDefaultMessage()).orElse("")));
    }

    private Mono<Void> writeResponse(final ServerWebExchange exchange, final byte[] responseBytes) {

        return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(responseBytes)));
    }

}

这将正确返回以下响应:

{
    "name": "must not be null"
}

在传递问题中使用的请求时。

注意:如果有人使用此选项,请小心使用@SneakyThrows

相关问题