Spring @Valid 消息未通过响应

Spring @Valid Message Not Coming Through in Response

我有一个使用 Spring 和 Axon 的 CQRS Rest API。使用 javax.validation 库为输入设置验证。验证工作正常,发现 'username' 需要至少 2 个字符长。但是,与验证失败相关的消息未显示在对 Post 请求的响应中。

回复最终缺少 'errors' 和 'message' 信息,我不确定为什么。 MethodArgumentNotValidException 不会被推送到 try-catch 块并得到处理吗?如果验证工作正常,为什么消息没有以“400”状态响应发出?

我错过了什么?

这是我想要得到的回应:

{
    "timestamp": "2022-04-24T01:23:27.809+00:00",
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "Size.registerUserCommand.user.account.username",
                "Size.user.account.username",
                "Size.username",
                "Size.java.lang.String",
                "Size"
            ],
            "arguments": [
               {
                    "codes": [
                        "registerUserCommand.user.account.username",
                        "user.account.username"
                    ],
                    "arguments": null,
                    "defaultMessage": "user.account.username",
                    "code": "user.account.username"
               },
               2134838498,
               2
           ],
           "defaultMessage": "username must have a minimum of 2 characters",
           "objectName": "registerUserCommand",
           "field": "user.account.username",
           "rejectedValue": "m",
           "bindingFailure": false,
           "code": "Size
        }
    ],
    "message": "Validation failed for object='registerUserCommand'. Error count: 1",
    "path": "/api/v1/registerUser"
}

这是我实际得到的回复:

{
    "timestamp": "2022-04-24T01:23:27.809+00:00",
    "status": 400,
    "error": "Bad Request",
    "message": "",
    "path": "/api/v1/registerUser"
}

Post 正在发送到控制器的请求:

{
    "user": {
        "firstname": "Mike",
        "lastname": "Jacobs",
        "emailAddress": "mike@spripgbank.com",
        "account": {
            "username": "m",
            "password": "mik959593e0",
            "roles": [
                "READ_PRIVILEGE"
            ]
        }
    }
}

Post 请求的控制台输出:

2022-04-24 20:32:21.339  INFO 11040 --- [nio-8081-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-04-24 20:32:21.340  INFO 11040 --- [nio-8081-exec-2] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-04-24 20:32:21.343  INFO 11040 --- [nio-8081-exec-2] o.s.web.servlet.DispatcherServlet        : Completed initialization in 3 ms
2022-04-24 20:32:22.120  WARN 11040 --- [nio-8081-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity<com.springbank.user.cmd.api.dto.RegisterUserResponse> com.springbank.user.cmd.api.controllers.RegisterUserController.registerUser(com.springbank.user.cmd.api.commands.RegisterUserCommand): [Field error in object 'registerUserCommand' on field 'user.account.username': rejected value [m]; codes [Size.registerUserCommand.user.account.username,Size.user.account.username,Size.username,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [registerUserCommand.user.account.username,user.account.username]; arguments []; default message [user.account.username],2147483647,2]; default message [username must have a min of 2 characters]] ]

接受请求的控制器:

    package com.springbank.user.cmd.api.controllers;

    import com.springbank.user.cmd.api.commands.RegisterUserCommand;
    import com.springbank.user.cmd.api.dto.RegisterUserResponse;
    import org.axonframework.commandhandling.gateway.CommandGateway;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import javax.validation.Valid;
    import java.util.UUID;
    
    @RestController
    @RequestMapping(path = "/api/v1/registerUser")
    public class RegisterUserController {
         private final CommandGateway commandGateway;

         @Autowired
         public RegisterUserController(CommandGateway commandGateway) {
             this.commandGateway = commandGateway;
         }
    
         @PostMapping
         public ResponseEntity<RegisterUserResponse> registerUser(@Valid @RequestBody RegisterUserCommand command) {
             var id = UUID.randomUUID().toString();
             command.setId(id);
    
             try{
                commandGateway.send(command);
                return new ResponseEntity<>(new RegisterUserResponse( id, "User Successfully Registered"), HttpStatus.CREATED);
    
             }catch (Exception e) {
                 var safeErrorMessage = "Error while processing register user request for id - " + id;
                 System.out.println(e.toString());
    
                 return new ResponseEntity<>(new RegisterUserResponse( id, safeErrorMessage), HttpStatus.INTERNAL_SERVER_ERROR);
             }
         }
    }

命令:

package com.springbank.user.cmd.api.commands;

import com.springbank.user.core.models.User;
import lombok.Builder;
import lombok.Data;
import org.axonframework.modelling.command.TargetAggregateIdentifier;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;

@Data
@Builder
public class RegisterUserCommand {

    @TargetAggregateIdentifier
    private String id;
    @NotNull(message = "Need to supply User info")
    @Valid
    private User user;
}

用户模型:

package com.springbank.user.core.models;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import javax.validation.Valid;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Document(collection = "users")
public class User {
    @Id
    private String id;
    @NotEmpty(message = "firstname is mandatory")
    private String firstname;
    @NotEmpty(message = "lastname is mandatory")
    private String lastname;
    @Email(message = "please provide a valid email")
    private String emailAddress;
    @NotNull(message = "Please provide account Credentials")
    @Valid
    private Account account;
}

进行验证的帐户模型:

package com.springbank.user.core.models;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Account {
    @Size(min = 2, message = "username must have a min of 2 characters")
    private String username;
    @Size(min = 7, message = "password must have a min of 7 characters")
    private String password;
    @NotNull(message = "specify at least one user role")
    private List<Role> roles;
}

RegisterUserResponse:

package com.springbank.user.cmd.api.dto;


public class RegisterUserResponse extends BaseResponse{
    private String id;

    public RegisterUserResponse(String id, String message) {
        super(message);
        this.id = id;
    }
}

    

基本响应:

package com.springbank.user.cmd.api.dto;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class BaseResponse {
    private String Message;
}

由于您使用 @Valid RegisterUserCommand 注释控制器方法参数,因此将在执行进入您的 try/catch 块之前对对象进行验证。

如果您查看位于 ResponseEntityExceptionHandler 中的以下方法,您会发现默认实现如下所示。如您所见,默认是将null作为body发送,这导致细节被遗漏了。

protected ResponseEntity<Object> handleMethodArgumentNotValid(
        MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {

    return handleExceptionInternal(ex, null, headers, status, request);
}

因此,如果您想以不同方式处理该错误,则需要为该异常类型添加自定义异常处理并覆盖默认处理程序。