SpringBoot统⼀返回格式及参数校验
SpringBoot统⼀返回格式及参数校验
说明:以下内容摘抄⾃以下博⽂:
⼀、SpringBoot统⼀返回格式
⼀个标准的返回格式⾄少包含3部分:当然也可以按需加⼊其他扩展值,⽐如我们就在返回对象中添加了接⼝调⽤时间
1. status 状态值:由后端统⼀定义各种返回结果的状态码
2. message 描述:本次接⼝调⽤的结果描述
3. data 数据:本次返回的数据。
4. timestamp: 接⼝调⽤时间
{
"status":"100",
"message":"操作成功",
"data":"hello,javadaily"
}
1. 定义返回对象
package com.linwei.jsr.demo.base;
import com.linwei.ums.ReturnCodeEnum;
import lombok.Data;
/**
* @author: Linwei
* @date 2021/8/12
* @Description:
*/
@Data
public class ResultData<T> {
private int status;
private String message;
private T data;
private long timestamp ;
public ResultData (){
this.timestamp = System.currentTimeMillis();
}
public static <T> ResultData<T> success(T data) {
ResultData<T> resultData = new ResultData<>();
resultData.setStatus(Code());
resultData.setMessage(Message());
resultData.setData(data);
return resultData;
}
public static <T> ResultData<T> fail(int code, String message) {
ResultData<T> resultData = new ResultData<T>();
resultData.setStatus(code);
resultData.setMessage(message);
return resultData;
}
}
2. 定义状态码枚举类
package com.linwei.ums;
/**
* @author: Linwei
* @date 2021/8/12
* @Description:
*/
public enum ReturnCodeEnum {
/**操作成功**/
RC100(100,"操作成功"),
/**操作失败**/
RC999(999,"操作失败"),
/**服务限流**/
RC200(200,"服务开启限流保护,请稍后再试!"),
/**服务降级**/
RC201(201,"服务开启降级保护,请稍后再试!"),
/**热点参数限流**/
RC202(202,"热点参数限流,请稍后再试!"),
/**系统规则不满⾜**/
RC203(203,"系统规则不满⾜要求,请稍后再试!"),
/
**授权规则不通过**/
RC204(204,"授权规则不通过,请稍后再试!"),
/**access_denied**/
RC403(403,"⽆访问权限,请联系管理员授予权限"),
/**access_denied**/
RC401(401,"匿名⽤户访问⽆权限资源时的异常"),
/**服务异常**/
RC500(500,"系统异常,请稍后重试"),
INVALID_TOKEN(2001,"访问令牌不合法"),
ACCESS_DENIED(2003,"没有权限访问该资源"),
CLIENT_AUTHENTICATION_FAILED(1001,"客户端认证失败"),
USERNAME_OR_PASSWORD_ERROR(1002,"⽤户名或密码错误"),
UNSUPPORTED_GRANT_TYPE(1003, "不⽀持的认证模式");
/**⾃定义状态码**/
private final int code;
/**⾃定义描述**/
private final String message;
ReturnCodeEnum(int code, String message){
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
3. 统⼀返回格式验证
@ApiOperation("正常普通请求")
spring framework是什么系统@GetMapping("/hello")
public ResultData<String> getInfo(){
return ResultData.success("hello,java");
}
此时调⽤接⼝获取到的返回值是这样:
{
"status": 100,
"message": "操作成功",
"data": "hello,java",
"timestamp": 1628764577677
}
这样确实已经实现了我们想要的结果,我在很多项⽬中看到的都是这种写法,在Controller层通过ResultData.success()对返回结果进⾏包装后返回给前端。
看到这⾥我们不妨停下来想想,这样做有什么弊端呢?
最⼤的弊端就是我们后⾯每写⼀个接⼝都需要调⽤ResultData.success()这⾏代码对结果进⾏包装,重复劳动,浪费体⼒;⽽且还很容易被其他⽼鸟给嘲笑。
所以呢我们需要对代码进⾏优化,⽬标就是不要每个接⼝都⼿⼯制定ResultData返回值。
要优化这段代码很简单,我们只需要借助SpringBoot提供的ResponseBodyAdvice即可。我们只需要编写⼀个具体实现类即可。
package com.linwei.jsr.demo.base;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import MethodParameter;
import org.springframework.http.MediaType;
import org.verter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.hod.annotation.ResponseBodyAdvice;
/**
* @author: Linwei
* @date 2021/8/12
* @Description:
* 避免每个接⼝都⼿⼯制定ResultData返回值
* 借助SpringBoot提供的ResponseBodyAdvice, controller直接返回数据对象即可,advice⾃动封装成统⼀返回对象;
* --增加该类后,Knife4j访问报错,通过basePackages可解决,如下:
* * swagger相当于是寄宿在应⽤程序中的⼀个web服务,统⼀响应处理器拦截了应⽤所有的响应,对swagger-ui的响应产⽣了影响。
* * 解决集成Swagger出现404问题,配置统⼀响应处理器拦截的范围,只拦截本项⽬的Controller类
*/
@RestControllerAdvice(basePackages = "com.linwei.ller")
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
@Autowired
private ObjectMapper objectMapper;
// 启⽤ advice功能 ; 默认false
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) if(o instanceof String){
return objectMapper.writeValueAsString(ResultData.success(o));
}
if(o instanceof ResultData){
return o;
}
return ResultData.success(o);
}
}
@RestControllerAdvice是@RestController注解的增强,可以实现三个⽅⾯的功能:
1. 全局异常处理
2. 全局数据绑定
3. 全局数据预处理
if(o instanceof String){
return objectMapper.writeValueAsString(ResultData.success(o));
}
这段代码⼀定要加,如果Controller直接返回String的话,SpringBoot是直接返回,故我们需要⼿动转换成json。
经过上⾯的处理我们就再也不需要通过ResultData.success()来进⾏转换了,直接返回原始数据格式,SpringBoot⾃动帮我们实现包装类的封装。
@ApiOperation("正常普通请求")
@GetMapping("/hello")
public String getInfo(){
return "hello,java";
}
{
"status":100,
"message":"操作成功",
"data":"hello,java",
"timestamp":1628764866582
}
是不是感觉很完美,别急,还有个问题在等着你呢。
接⼝异常问题
此时有个问题,由于我们没对Controller的异常进⾏处理,当我们调⽤的⽅法⼀旦出现异常,就会出现问题,⽐如下⾯这个接⼝
@ApiOperation("系统异常测试")
@GetMapping("/wrong")
public int error(){
int i = 9/0;
return i;
}
{
"timestamp": "2021-08-12T10:43:05.251+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/result/wrong"
}
这显然不是我们想要的结果,没有按照我们统⼀的格式返回数据,前端看了会打⼈的。
别急,接下来我们进⼊第⼆个议题,如何优雅的处理全局异常。
这个时候,我们还是要⽤到@RestControllerAdvice 这个注解,上⾯提到,它也可⽤于全局异常处理器;
package com.linwei.jsr.demo.base;
import com.linwei.ums.ReturnCodeEnum;
slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.stream.Collectors;
/**
* @author: Linwei
* @date 2021/8/12
* @Description:
* 服务层全局响应异常处理器
*/
@Slf4j
@RestControllerAdvice
public class RestExceptionHandler {
/**
* 默认全局异常处理。
* @param e the e
* @return ResultData
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResultData<String> exception(Exception e) {
<("全局异常信息 ex={}", e.getMessage(), e);
return ResultData.fail(Code(),e.getMessage());
}
@ExceptionHandler(value = {BindException.class, ValidationException.class, MethodArgumentNotValidException.class})
public ResponseEntity<ResultData<String>> handleValidatedException(Exception e) {
ResultData<String> resp = null;
if (e instanceof MethodArgumentNotValidException) {
// BeanValidation exception
MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e;
resp = ResultData.fail(HttpStatus.BAD_REQUEST.value(),
.map(ObjectError::getDefaultMessage)
.collect(Collectors.joining("; "))
);
} else if (e instanceof ConstraintViolationException) {
/
/ BeanValidation GET simple param
ConstraintViolationException ex = (ConstraintViolationException) e;
resp = ResultData.fail(HttpStatus.BAD_REQUEST.value(),
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining("; "))
);
} else if (e instanceof BindException) {
// BeanValidation GET object param
BindException ex = (BindException) e;
resp = ResultData.fail(HttpStatus.BAD_REQUEST.value(),
.map(ObjectError::getDefaultMessage)
.collect(Collectors.joining("; "))
);
}
<("参数校验异常:{}",Message());
return new ResponseEntity<>(resp,HttpStatus.BAD_REQUEST);
}
}
全局异常接⼊返回的标准格式
要让全局异常接⼊标准格式很简单,因为全局异常处理器已经帮我们封装好了标准格式,我们只需要直接返回给客户端即可。关键代码:
ResponseAdvice.java
@SneakyThrows
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) if(o instanceof String){
return objectMapper.writeValueAsString(ResultData.success(o));
}
if(o instanceof ResultData){
return o;
}
return ResultData.success(o);
}
这时候我们再调⽤上⾯的错误⽅法,返回的结果就符合我们的要求了。
package com.linwei.ller;
import com.linwei.jsr.demo.base.ResultData;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author: Linwei
* @date 2021/8/12
* @Description:
*/
@Api("统⼀返回结果测试接⼝")
@RequestMapping("/result")
@RestController
public class ResultTestController {
@ApiOperation("正常普通请求")
@GetMapping("/hello")
public String getInfo(){
return "hello,java";
}
@ApiOperation("系统异常测试")
@GetMapping("/wrong")
public int error(){
int i = 9/0;
return i;
}
@ApiOperation("⾃定义异常测试")
@GetMapping("error1")
public void empty(){
throw new RuntimeException("⾃定义异常");
}
}
/result/wrong
{
"status": 500,
"message": "/ by zero",
"data": null,
"timestamp": 1628765343273
}
/result/error1
{
"status": 500,
"message": "⾃定义异常",
"data": null,
"timestamp": 1628765364205
}
好了,今天的⽂章就到这⾥了,希望通过这篇⽂章你能掌握如何在你项⽬中友好实现统⼀标准格式到返回并且可以优雅的处理全局异常。
⼆、SpringBoot中集成参数校验
在⽇常的接⼝开发中,为了防⽌⾮法参数对业务造成影响,经常需要对接⼝的参数做校验,例如登录的时候需要校验⽤户名密码是否为空,创建⽤户的时候需要校验邮件、⼿机号码格式是否准确。靠代码对接⼝参数⼀个个校验的话就太繁琐了,代码可读性极差。
Validator框架就是为了解决开发⼈员在开发的时候少写代码,提升开发效率;Validator专门⽤来进⾏接⼝参数校验,例如常见的必填校验,email格式校验,⽤户名必须位于6到12之间等等
Validator校验框架遵循了JSR-303验证规范(参数校验规范), JSR是Java Specification Requests的缩写。
1. 加依赖
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
</dependency>
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-validation</artifactid>
</dependency>
注:从springboot-2.3开始,校验包被独⽴成了⼀个starter组件,所以需要引⼊validation和web,⽽springboot-2.3之前的版本只需要引⼊ web 依赖就可以了。
2. 定义⼀个⽤来测试的实体
package com.linwei.ity;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import org.straints.Length;
import straints.Email;
import straints.NotBlank;
import straints.NotEmpty;
/**
* @author: Linwei
* @date 2021/8/12
* @Description:
*/
@Data
@ApiModel("测试实体")
public class TestEntityVO {
private String id;
@Length(min = 6,max = 12,message = "appId长度必须位于6到12之间")
private String appId;
@NotBlank(message = "名字为必填项")
private String name;
@Email(message = "请填写正确的邮箱地址")
private String email;
private String sex;
@NotEmpty(message = "级别不能为空")
private String level;
}
在实际开发中对于需要校验的字段都需要设置对应的业务提⽰,即message属性。
常见的约束注解如下:
注解功能
@AssertFalse可以为null,如果不为null的话必须为false
@AssertTrue可以为null,如果不为null的话必须为true
@DecimalMax设置不能超过最⼤值
@DecimalMin设置不能超过最⼩值
@Digits设置必须是数字且数字整数的位数和⼩数的位数必须在指定范围内
@Future⽇期必须在当前⽇期的未来
@Past⽇期必须在当前⽇期的过去
@Max最⼤不得超过此最⼤值
@Min最⼤不得⼩于此最⼩值
@NotNull不能为null,可以是空
@Null必须为null
@Pattern必须满⾜指定的正则表达式
@Size集合、数组、map等的size()值必须在指定范围内
@Email必须是email格式
@Length长度必须在指定范围内
@NotBlank字符串不能为null,字符串trim()后也不能等于“”
@NotEmpty不能为null,集合、数组、map等size()不能为0;字符串trim()后可以等于“”
@Range值必须在指定范围内
@URL必须是⼀个URL
注:此表格只是简单的对注解功能的说明,并没有对每⼀个注解的属性进⾏说明;可详见源码。
3. 定义⼀个controller测试
package com.linwei.ller;
import com.linwei.ity.TestEntityVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
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 straints.Email;
/**
* @author: Linwei
* @date 2021/8/12
* @Description:
*/
@Slf4j
@Validated
@Api("JSR参数校验测试接⼝")
@RequestMapping("/valid")
@RestController
public class ValidTestController {
@ApiOperation("RequestBody校验")
@PostMapping("/test1")
public String test1(@Validated @RequestBody TestEntityVO validVO){
log.info("validEntity is {}", validVO);
return "test1 valid success";
}
@ApiOperation("Form校验")
@PostMapping(value = "/test2")
public String test2(@Validated TestEntityVO validVO){
log.info("validEntity is {}", validVO);
return "test2 valid success";
}
@ApiOperation("单参数校验")
@PostMapping(value = "/test3")
public String test3(@Email String email){
log.info("email is {}", email);
return "email valid success";
}
}
虽然我们之前定义了全局异常,也看到了确实⽣效了,但是Validator校验框架返回的错误提⽰太臃肿了,不便于阅读,为了⽅便前端提⽰,我们需要将其简化⼀下。
直接修改之前定义的RestExceptionHandler,单独拦截参数校验的三个异
常:javax.validation.ConstraintViolationException,org.springframework.validation.BindException,org.springframework.web.bind.MethodArgumentNotValidException,代码如下:
@ExceptionHandler(value = {BindException.class, ValidationException.class, MethodArgumentNotValidException.class})
public ResponseEntity<resultdata<string>> handleValidatedException(Exception e) {
ResultData<string> resp = null;
if (e instanceof MethodArgumentNotValidException) {
// BeanValidation exception
MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e;
resp = ResultData.fail(HttpStatus.BAD_REQUEST.value(),
.map(ObjectError::getDefaultMessage)
.collect(Collectors.joining("; "))
);
} else if (e instanceof ConstraintViolationException) {
/
/ BeanValidation GET simple param
ConstraintViolationException ex = (ConstraintViolationException) e;
resp = ResultData.fail(HttpStatus.BAD_REQUEST.value(),
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论