如何使用Spring Boot修改HttpServletResponse以在json中包含一些标准属性?

时间:2018-08-26 23:04:31

标签: java spring rest spring-mvc spring-boot

我正在春季启动中编写REST API应用程序。我希望我的json响应的签名是这样的:

示例:1

{
  "status": "Error"
  "httpcode": 500
  "dev_message": "ServerException"
  "user_message": "Oops..something went wrong with the app. Please try again."
  "response": {
    ...
   }
}

示例:2

{
  "status": "Success"
  "httpcode": 200
  "dev_message": "APICallSuccess"
  "user_message": "Successfully called API"
  "response": {
      "userid": "test",
      "age": 31 
       ...
      "country": "India"
   }
}

其中status, httpcode, dev_messageuser_message在请求生命周期的不同阶段动态更新。例如,如果API令牌不正确,授权过滤器应将httpstatus字段更新为401,将dev_message更新为AuthException。另一方面,如果用户试图访问另一个不允许的用户数据,则控制器应更新这些字段。

我在这里有两个部分的问题:

  1. 如何在Spring MVC / Spring Boot中实现这一点?我知道这涉及RequestHandlerInterceptorFilters,但是信息太多了,我无法缩小范围。
  2. 这是构建API的正确方法吗?我希望客户真正知道出了什么问题,而不仅仅是返回http状态代码。

编辑1: 以下是针对控制器级别的许多答案。我希望使用过滤器以更高的执行级别处理此问题。假设验证失败,那么我的请求甚至不会到达控制器。在所有这些情况下,我都希望在一个地方实施成功和失败消息,以使其简洁明了。

2 个答案:

答案 0 :(得分:-1)

我相信您正在寻找:使用Spring ResponseEntity来处理HTTP响应 https://www.baeldung.com/spring-response-entity

@GetMapping("/age")
ResponseEntity<String> age(
  @RequestParam("yearOfBirth") int yearOfBirth) {

    if (isInFuture(yearOfBirth)) {
        return new ResponseEntity<>(
          "Year of birth cannot be in the future", 
          HttpStatus.BAD_REQUEST);
    }

    return new ResponseEntity<>(
      "Your age is " + calculateAge(yearOfBirth), 
      HttpStatus.OK);
}

您可以根据需要设置错误404、403。请让我知道它是否有效

答案 1 :(得分:-1)

在两种情况下,无论是错误还是响应,我都使用了非常相似的JSON响应。您可以尝试一下,它可能会对您有所帮助:)

错误JSON:

{
    "data": null,
    "message": "Content type 'text/plain;charset=UTF-8' not supported",
    "infoType": "ERROR",
    "statusCode": 415,
    "errors": [
        "text/plain;charset=UTF-8 media type is not supported. Supported media types are application/octet-stream text/plain application/xml text/xml application/x-www-form-urlencoded application/*+xml multipart/form-data application/json application/*+json */"
    ]
}

响应JSON:

{
    "data": "{
        "id": 0,
        "name": null,
        "nestedObject": {
            "id": 0,
            "name": "",
            "url": "",
            // Other Details
        },
        "address": "address",
        "city": null,
        "country": "country"
    }",
    "message": "Your success message comes here",
    "infoType": "INFO",
    "statusCode": 200
}

我创建了一个抽象类,即Message。响应和错误这两个类扩展了Message。

您可以查看以下代码:

1。信息类型使用的枚举:

public enum InfoType {
  ERROR, INFO, WARNING
}

2。消息的抽象类:

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.http.HttpStatus;

public abstract class Message {

    private String message;

    private InfoType infoType;

    @JsonIgnore
    private HttpStatus status;

    private int statusCode;

    public Message(){

    }

    public Message(String message, InfoType infoType, HttpStatus status) {
        this.message = message;
        this.infoType = infoType;
        this.status = status;
        this.statusCode = status.value();
    }

    //Getters and Setters

}

3。 POJO用于​​响应:

import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import org.springframework.http.HttpStatus;


@JsonPropertyOrder({ "data", "message", "infoType", "status", "statusCode"})
public class ResponseMessage extends Message{

    private Object data;

    private ResponseMessage() {
    }

    public ResponseMessage(Object data, String message, InfoType infoType, HttpStatus status) {
        super(message, infoType, status);
        this.data = data;
    }

    //Getters and Setters

}

4。 POJO用于​​错误:

import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import org.springframework.http.HttpStatus;

import java.util.Collections;
import java.util.List;

@JsonPropertyOrder({ "data", "message", "infoType", "status", "statusCode", "errors"})
public class ErrorMessage extends Message {

    private Object data;

    private List<String> errors;

    private ErrorMessage() {
    }

    public ErrorMessage(Object data, String message, InfoType infoType, HttpStatus status) {
        super(message, infoType, status);
        this.data = data;
    }

    public ErrorMessage(String message, InfoType infoType, HttpStatus status) {
        super(message, infoType, status);
        this.data = null;
    }

    public ErrorMessage(String message, InfoType infoType, HttpStatus status, List<String> errors) {
        super(message, infoType, status);
        this.errors = errors;
        this.data = null;
    }

    public ErrorMessage(String message, InfoType infoType, HttpStatus status, String error) {
        super(message, infoType, status);
        this.errors = Collections.singletonList(error);
        this.data = null;
    }

    //Getters and Setters
}

5。用于警告/错误/响应的服务:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

@Component
public class ResponseMessageService {

    @Autowired
    private ResponseMessages responseMessages;

    @Autowired
    private ErrorMessages errorMessages;

    public ResponseMessage generateResponseMessage(Object data, String messageKey, HttpStatus status) {
        return new ResponseMessage(data,responseMessages.getProperty(messageKey), InfoType.INFO, status);
    }

    public ResponseMessage generateWarningMessage(Object data,String messageKey, HttpStatus status) {
        return new ResponseMessage(data,responseMessages.getProperty(messageKey), InfoType.WARNING , status);
    }

    public ErrorMessage generateErrorMessage(Object data,String messageKey, HttpStatus status) {
        return new ErrorMessage(data,errorMessages.getProperty(messageKey), InfoType.ERROR , status);
    }
}

6。用于从属性文件读取错误的服务:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import java.text.MessageFormat;
import java.util.List;

@Component
@PropertySource("classpath:errormessage.properties")
public class ErrorMessages {

    @Autowired
    private Environment env;

    public String getProperty(String property) {
        return env.getProperty(property);
    }
}

7。从属性文件读取消息的服务:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import java.text.MessageFormat;
import java.util.List;

@Component
@PropertySource("classpath:responsemessage.properties")
public class ResponseMessages {

    @Autowired
    private Environment env;

    public String getProperty(String property) {
        return env.getProperty(property);
    }
}

8。最后利用以上内容的资源/ API类:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("user")
public class UserResource {

    @Autowired
    ResponseMessageService responseMessageService;

    @RequestMapping(value="/register", method = RequestMethod.POST)
    public ResponseEntity register(@RequestBody User user){
        if(success){
            return ResponseEntity.ok(responseMessageService.generateResponseMessage(null,"register.success",HttpStatus.OK));
        }else{
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(responseMessageService.generateErrorMessage(null,"register.fail",HttpStatus.BAD_REQUEST));
        }
    }

    @RequestMapping(value="/login", method = RequestMethod.POST)
    public ResponseEntity login(@RequestBody User user){
        if(success){
            return ResponseEntity.ok().body(responseMessageService.generateResponseMessage(data,"login.success",HttpStatus.OK));
        }else{
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(responseMessageService.generateResponseMessage(null, "login.fail",HttpStatus.BAD_REQUEST));
        }
    }

}
  1. 对于处理异常,我正在使用CustomExceptionHandler。同样在此处理程序中,我使用的是相同的错误格式。

    import org.springframework.beans.TypeMismatchException;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.validation.BindException;
    import org.springframework.validation.FieldError;
    import org.springframework.validation.ObjectError;
    import org.springframework.web.HttpMediaTypeNotSupportedException;
    import org.springframework.web.HttpRequestMethodNotSupportedException;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.MissingServletRequestParameterException;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.context.request.WebRequest;
    import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
    import org.springframework.web.multipart.support.MissingServletRequestPartException;
    import org.springframework.web.servlet.NoHandlerFoundException;
    import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
    
    import javax.validation.ConstraintViolation;
    import javax.validation.ConstraintViolationException;
    import java.util.ArrayList;
    import java.util.List;
    
    
    @ControllerAdvice
    public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
    
        // 400
    
        @Override
        protected ResponseEntity<Object> handleMethodArgumentNotValid(final MethodArgumentNotValidException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
            logger.info(ex.getClass().getName());
            //
            final List<String> errors = new ArrayList<String>();
            for (final FieldError error : ex.getBindingResult().getFieldErrors()) {
                errors.add(error.getField() + ": " + error.getDefaultMessage());
            }
            for (final ObjectError error : ex.getBindingResult().getGlobalErrors()) {
                errors.add(error.getObjectName() + ": " + error.getDefaultMessage());
            }
    
            final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.BAD_REQUEST, errors);
            return handleExceptionInternal(ex, errorMessage, headers, errorMessage.getStatus(), request);
        }
    
        @Override
        protected ResponseEntity<Object> handleBindException(final BindException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
            logger.info(ex.getClass().getName());
            //
            final List<String> errors = new ArrayList<String>();
            for (final FieldError error : ex.getBindingResult().getFieldErrors()) {
                errors.add(error.getField() + ": " + error.getDefaultMessage());
            }
            for (final ObjectError error : ex.getBindingResult().getGlobalErrors()) {
                errors.add(error.getObjectName() + ": " + error.getDefaultMessage());
            }
    
            final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.BAD_REQUEST, errors);
            return handleExceptionInternal(ex, errorMessage, headers, errorMessage.getStatus(), request);
        }
    
        @Override
        protected ResponseEntity<Object> handleTypeMismatch(final TypeMismatchException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
            logger.info(ex.getClass().getName());
            //
            final String error = ex.getValue() + " value for " + ex.getPropertyName() + " should be of type " + ex.getRequiredType();
    
            final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.BAD_REQUEST, error);
            return new ResponseEntity<Object>(errorMessage, new HttpHeaders(), errorMessage.getStatus());
        }
    
        @Override
        protected ResponseEntity<Object> handleMissingServletRequestPart(final MissingServletRequestPartException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
            logger.info(ex.getClass().getName());
            //
            final String error = ex.getRequestPartName() + " part is missing";
            final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.BAD_REQUEST, error);
            return new ResponseEntity<Object>(errorMessage, new HttpHeaders(), errorMessage.getStatus());
        }
    
        @Override
        protected ResponseEntity<Object> handleMissingServletRequestParameter(final MissingServletRequestParameterException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
            logger.info(ex.getClass().getName());
            //
            final String error = ex.getParameterName() + " parameter is missing";
            final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.BAD_REQUEST, error);
            return new ResponseEntity<Object>(errorMessage, new HttpHeaders(), errorMessage.getStatus());
        }
    
        //
    
        @ExceptionHandler({ MethodArgumentTypeMismatchException.class })
        public ResponseEntity<Object> handleMethodArgumentTypeMismatch(final MethodArgumentTypeMismatchException ex, final WebRequest request) {
            logger.info(ex.getClass().getName());
            //
            final String error = ex.getName() + " should be of type " + ex.getRequiredType().getName();
            final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.BAD_REQUEST, error);
            return new ResponseEntity<Object>(errorMessage, new HttpHeaders(), errorMessage.getStatus());
        }
    
        @ExceptionHandler({ ConstraintViolationException.class })
        public ResponseEntity<Object> handleConstraintViolation(final ConstraintViolationException ex, final WebRequest request) {
            logger.info(ex.getClass().getName());
            //
            final List<String> errors = new ArrayList<String>();
            for (final ConstraintViolation<?> violation : ex.getConstraintViolations()) {
                errors.add(violation.getRootBeanClass().getName() + " " + violation.getPropertyPath() + ": " + violation.getMessage());
            }
    
            final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.BAD_REQUEST, errors);
            return new ResponseEntity<Object>(errorMessage, new HttpHeaders(), errorMessage.getStatus());
        }
    
        // 404
    
        @Override
        protected ResponseEntity<Object> handleNoHandlerFoundException(final NoHandlerFoundException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
            logger.info(ex.getClass().getName());
            //
            final String error = "No handler found for " + ex.getHttpMethod() + " " + ex.getRequestURL();
    
            final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.NOT_FOUND, error);
            return new ResponseEntity<Object>(errorMessage, new HttpHeaders(), errorMessage.getStatus());
        }
    
        // 405
    
        @Override
        protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(final HttpRequestMethodNotSupportedException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
            logger.info(ex.getClass().getName());
            //
            final StringBuilder builder = new StringBuilder();
            builder.append(ex.getMethod());
            builder.append(" method is not supported for this request. Supported methods are ");
            ex.getSupportedHttpMethods().forEach(t -> builder.append(t + " "));
    
            final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.METHOD_NOT_ALLOWED, builder.toString());
            return new ResponseEntity<Object>(errorMessage, new HttpHeaders(), errorMessage.getStatus());
        }
    
        // 415
    
        @Override
        protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(final HttpMediaTypeNotSupportedException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
            logger.info(ex.getClass().getName());
            //
            final StringBuilder builder = new StringBuilder();
            builder.append(ex.getContentType());
            builder.append(" media type is not supported. Supported media types are ");
            ex.getSupportedMediaTypes().forEach(t -> builder.append(t + " "));
    
            final ErrorMessage errorMessage = new ErrorMessage(ex.getLocalizedMessage(), InfoType.ERROR, HttpStatus.UNSUPPORTED_MEDIA_TYPE, builder.substring(0, builder.length() - 2));
            return new ResponseEntity<Object>(errorMessage, new HttpHeaders(), errorMessage.getStatus());
        }
    
        // 500
    
        @ExceptionHandler({ Exception.class })
        public ResponseEntity<Object> handleAll(final Exception ex, final WebRequest request) {
            logger.error("Error Occurred in Class " + ex.getClass().getName() + " ", ex);
            String message = ex.getMessage();
            String stackTrace = ex.fillInStackTrace().toString();
    
            final ErrorMessage errorMessage = new ErrorMessage(message, InfoType.ERROR, HttpStatus.INTERNAL_SERVER_ERROR, stackTrace);
            return new ResponseEntity<Object>(errorMessage, new HttpHeaders(), errorMessage.getStatus());
        }
    
    }  
    

以下是我使用过的参考文献:

1。https://www.baeldung.com/global-error-handler-in-a-spring-rest-api
2。 https://github.com/eugenp/tutorials/blob/master/spring-security-rest/src/main/java/org/baeldung/web/CustomRestExceptionHandler.java