如何使用spring-boot 寫出簡潔而優雅的restful 接口

寫一個Restful接口很簡單,但是要寫出一個健壯而優雅的接口並不容易,通常一個接口包含輸入請求參數、輸出響應消息及接口中的異常輸出。通過對輸入請求參數在入口處統一校驗,可以及早發現數據的問題,規範統一的響應輸出和異常信息使你的Restful接口變得更優雅。
一、使用validation對輸入參數進行校驗
如果接口的輸入信息不在入口處進行校驗,我們就需要在業務層寫上很多的判斷邏輯,比如下面這種寫法:

@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和spring 的validation可以讓我們只關注業務邏輯二不用去擔心數據是否規範的問題。

  1. 在maven的pom文件中引入validation
<dependency>
   <groupId>javax.validation</groupId>
   <artifactId>validation-api</artifactId>
   <version>${validation-api.version}</version>
</dependency>
  1. 編寫 entity類
    使用@NotEmpty和@NotNull等註解標註需要進行校驗的字段
@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;
}

3.編寫controller
controller上需要加上@Validated註解標註,在接口中需要校驗的參數前面加上@Valid 註解,Movie前面的@Valid表示實體Movie會使用validation進行校驗

@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();
	}
}
4.編寫業務代碼

```java
@Override
	public void addMovie(Movie movie) {
		//業務代碼
		movieDao.save(movie);
	}

二、使用統一的輸出格式
1.定義響應消息體ResponseResult
code響應狀態碼,message 狀態碼描述,data響應的數據

```java
@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);
	}

}

2.定義狀態碼枚舉

@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;
	}
}

三、使用@ControllerAdvice攔截異常,統一輸出異常信息
1.自定義異常類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);
	}
}

2.定義異常攔截類
程序中拋出的所有異常都會被攔截然後統一輸出,避免輸出不友好的異常提示信息。

@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);
	}
	}

五、使用postman測試,查看validation是否生效
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
源碼
https://github.com/tangyajun/spring-validation-demo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章