如何使用spring-boot 写出简洁而优雅的restful 接口

前言

  • 写一个Restful接口很简单,但是要写出一个健壮而优雅的接口并不容易,通常一个接口包含输入参数、输出响应消息及接口中异常信息输出。通过对请求输入参数在入口处进行统一校验,可以提前发现数据的问题而减少业务层数据校验模板代码,规范统一的响应格式和异常信息使你的Restful接口变得更优雅。

统一进行输入参数校验

如果接口的输入参数不在controller层进行校验,我们就需要在业务层写很多的判断逻辑对数据进行验证。比如下面这种写法:

@Override
	public void addMovie(Movie movie) {
		if (StringUtils.isEmpty(movie.getName())) {
			throw new BusinessException("电影名称不能为空");
		}
		if (movie.getDuration() == null) {
			throw new BusinessException("电影时长不能为空");
		}
		if (StringUtils.isEmpty(movie.getDescription())){
			throw new BusinessException("电影描述不能为空");
		}
		if (CollectionUtils.isEmpty(movie.getActors())) {
			throw new BusinessException("演员不能为空");
		}
		//业务代码
		
	}

从上面的代码可以看出业务代码还没有开始写,已经写了一堆的逻辑判断,看起来很不优雅。使用java的validation-api和spring 的validation可以让我们只需关注业务逻辑而不用担心数据是否规范的问题。

  • 在maven的pom文件中引入validation
<dependency>
   <groupId>javax.validation</groupId>
   <artifactId>validation-api</artifactId>
   <version>${validation-api.version}</version>
</dependency>
  • 使用@NotEmpty和@NotNull注解对需要校验的实体属性进行标注,在注解的message属性加上提示信息。
@Getter
@Setter
public class Movie {
	private String id;
	@NotEmpty(message = "Movie name cannot be empty")
	private String name;
	@NotNull(message = "电影时长不能为空")
	private Integer duration;
	@NotNull(message = "演员不能为空")
	@NotEmpty(message = "演员不能为空")
	private List<@Valid Actor> actors;
	@NotEmpty(message = "电影描述不能为空")
	private String description;
}
  • controller需要加上@Validated注解,接口中需要校验的参数前面加上@Valid 注解。如下面的addMovie方法中的实体Movie前面使用了@Valid直接,表示该restful接口收到请求后会对实体Movie中的属性进行校验(使用了@NotEmpty和@NotNull等注解标注的属性)
@Validated
@RestController
@RequestMapping(value = "/movies")
public class MovieController {
	@PostMapping
	public ResponseResult addMovie(@RequestBody  @Valid Movie movie) {
		movieService.addMovie(movie);
		System.out.println("test");
		System.out.println(movie);
		return ResponseResult.success();
	}
}
  • 编写业务代码
    业务代码中不再需要对实体中的数据进行校验,代码看起来更简洁。
@Override
	public void addMovie(Movie movie) {
		//业务代码
		movieDao.save(movie);
	}

统一接口数据输出格式

  • 接口输出参数包含:code(响应状态码),message(描述信息),data(响应的数据) 三部分。
  • 定义响应消息体ResponseResult
@Getter
@Setter
public class ResponseResult<T> {
	/**
	 * 状态码
	 */
	int code;
	/**
	 * 描述信息
	 */
	String message;
	/**
	 * 接口返回的数据
	 */
	T data;

	private ResponseResult() {
		this(200,"success");
	}

	private ResponseResult(int code,String message) {
		this.code=code;
		this.message=message;
	}

	private ResponseResult(ResponseMessage responseMessage) {
		this.code= responseMessage.getCode();
		this.message= responseMessage.getMessage();
	}

	private ResponseResult(int code,String message,T data) {
		this.code=code;
		this.message=message;
		this.data=data;
	}

	private ResponseResult (ResponseMessage responseMessage, T data) {
		this.code= responseMessage.getCode();
		this.message= responseMessage.getMessage();
		this.data=data;
	}

	public static ResponseResult success() {
		return new ResponseResult();
	}

	public static <T> ResponseResult success(T data) {
		return success(ResponseMessage.SUCCESS.getCode(),"success",data);
	}

	public static ResponseResult success(int code,String message) {
		return success(code,message,null);
	}
	
	public static ResponseResult success(ResponseMessage responseMessage) {
		return success(responseMessage.getCode(),responseMessage.getMessage(),null);
	}

	public static <T> ResponseResult success(ResponseMessage responseMessage,T data) {
		return success(responseMessage.getCode(),responseMessage.getMessage(),data);
	}

	public static <T> ResponseResult success(int code,String message,T data) {
		return new ResponseResult(code,message,data);
	}

	public static ResponseResult fail() {
		return fail(ResponseMessage.FAIL.getCode(),ResponseMessage.FAIL.getMessage());
	}

	public static ResponseResult fail(int code,String message) {
		return fail(code,message,null);
	}

	public static ResponseResult fail(ResponseMessage responseMessage) {
		return fail(responseMessage.getCode(),responseMessage.getMessage(),null);
	}

	public static <T> ResponseResult fail(ResponseMessage responseMessage, T data) {
		return fail(responseMessage.getCode(),responseMessage.getMessage(),data);
	}

	public static <T> ResponseResult fail(int code,String message,T data) {
		return new ResponseResult(code,message,data);
	}

}
  • 定义状态码枚举
@Getter
public enum StatusCode {
	/**
	 * 操作成功
	 */
	SUCCESS(200,"success"),
	/**
	 * 新增成功
	 */
	ADD_SUCCESS(204,"success"),
	/**
	 * 操作失败
	 */
	FAIL(-1,"fail"),
	/**
	 * 资源不存在
	 */
	NOT_FOUND(404,"resource not found"),
	/**
	 * 没有权限访问
	 */
	NOT_AUTH(401,"没有权限访问"),
	/**
	 * 未知错误
	 */
	ERROR(500,"未知错误");
	private int code;
	private String message;

	private StatusCode(int code, String message) {
	    this.code=code;
	    this.message=message;
	}
}

统一输出异常信息的格式

  • 自定义异常类BusinessException
@Getter
public class BusinessException extends RuntimeException {
	private String message;
	private Throwable throwable;
	public BusinessException(String message) {
		this(message,null);
	}

	public BusinessException(String message,Throwable throwable) {
		super(message,throwable);
	}
}
  • 定义异常拦截类
    程序中抛出的所有异常都会被拦截然后统一输出,避免输出不友好的异常提示信息。可以根据业务需要定义不同异常,在此统一处理后输出。
@RestControllerAdvice
@ControllerAdvice
public class GlobalExceptionHandler {
	/**
	* 
	*/
	@ExceptionHandler(Exception.class)
	public ResponseResult handleException(Exception e) {
		ResponseResult result=ResponseResult.fail(500,e.getMessage());
		return result;
	}
	/**
	* 拦截业务异常
	*/
	@ExceptionHandler(BusinessException.class)
	public ResponseResult handleBusinessException(BusinessException e) {
		return ResponseResult.fail(500,e.getMessage());
	}
	
	/**
	* 拦截参数校验异常
	*/
	@ExceptionHandler(MethodArgumentNotValidException.class)
	public ResponseResult handleMethodArgumentNotValidException(MethodArgumentNotValidException methodArgumentNotValidException) {
		StringBuilder errorMessage=new StringBuilder();
		List<ObjectError> objectErrors=methodArgumentNotValidException.getBindingResult().getAllErrors();
		if (!CollectionUtils.isEmpty(objectErrors)) {
			for (int i = 0; i < objectErrors.size(); i++) {
				if (i == 0) {
					errorMessage.append(objectErrors.get(i).getDefaultMessage());
				} else {
					errorMessage.append(",");
					errorMessage.append(objectErrors.get(i).getDefaultMessage());
				}
			}
		}else {
			errorMessage.append("MethodArgumentNotValidException occured.");
		}
		return ResponseResult.fail(400,errorMessage.toString());
	}
    
    /**
    * 拦截自定义约束异常
    */
	@ExceptionHandler(ConstraintViolationException.class)
	public ResponseResult handle(ConstraintViolationException constraintViolationException) {
		Set<ConstraintViolation<?>> violations = constraintViolationException.getConstraintViolations();
		String errorMessage = "";
		if (!violations.isEmpty()) {
			StringBuilder builder = new StringBuilder();
			violations.forEach(violation -> builder.append(" " + violation.getMessage()));
			errorMessage = builder.toString();
		} else {
			errorMessage = "ConstraintViolationException occured.";
		}
		return ResponseResult.fail(400,errorMessage);
	}
	}

测试

restful接口的输入、输出及异常信息的处理都写完了,然后使用postman进行测试,验证接口的可用性和健壮性。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
源码地址
https://github.com/tangyajun/spring-validation-demo

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值