前言
一個完整的后端請求由 4 部分組成:
- 介面地址(也就是 URL 地址)
- 請求方式(一般就是 get、set,當然還有 put、delete)
- 請求資料(request,有 head 跟 body)
- 回應資料(response)
Controller 主要的作業有以下幾項:
- 接收請求并決議引數
- 呼叫 Service 執行具體的業務代碼(可能包含引數校驗)
- 捕獲業務邏輯例外做出反饋
- 業務邏輯執行成功做出回應
解決以下 3 個問題:
- 回傳回應資料該如何統一的進行處理
- 接收到請求,處理業務邏輯時拋出了例外又該如何處理
- 當接收到請求時,如何優雅的校驗引數
統一回傳結構
封裝Result
package com.wanqi.result;
public interface IResult {
Integer getCode();
String getMessage();
}
定義狀態碼
package com.wanqi.result;
public enum ResultEnum implements IResult {
SUCCESS(2001, "介面呼叫成功"),
VALIDATE_FAILED(2002, "引數校驗失敗"),
COMMON_FAILED(2003, "介面呼叫失敗"),
FORBIDDEN(2004, "沒有權限訪問資源"),
DATA_CONVERSION(2005, "資料轉換例外");
private Integer code;
private String message;
ResultEnum(int code, String message) {
this.code=code;
this.message=message;
}
@Override
public Integer getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}
Result包裝類
package com.wanqi.result;
public class Result<T> {
private Integer code;
private String message;
private T data;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = https://www.cnblogs.com/wandaren/archive/2022/12/26/data;
}
public Result() {
}
public Result(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public static Result success(T data) {
return new Result<>(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMessage(), data);
}
public static Result success(String message, T data) {
return new Result<>(ResultEnum.SUCCESS.getCode(), message, data);
}
public static Result<?> failed() {
return new Result<>(ResultEnum.COMMON_FAILED.getCode(), ResultEnum.COMMON_FAILED.getMessage(), null);
}
public static Result<?> failed(String message) {
return new Result<>(ResultEnum.COMMON_FAILED.getCode(), message, null);
}
public static Result<?> failed(Integer code,String message) {
return new Result<>(code, message, null);
}
public static Result<?> failed(IResult errorResult) {
return new Result<>(errorResult.getCode(), errorResult.getMessage(), null);
}
public static Result instance(Integer code, String message, T data) {
Result result = new Result<>();
result.setCode(code);
result.setMessage(message);
result.setData(data);
return result;
}
}
統一包裝處理
package com.wanqi.advice;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wanqi.exception.APIException;
import com.wanqi.result.Result;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
// 如果引入了swagger或knife4j的檔案生成組件,這里需要僅掃描自己專案的包,否則檔案無法正常生成
@RestControllerAdvice(basePackages = "com.wanqi.controller")
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// response是Result型別不進行包裝
return !returnType.getParameterType().isAssignableFrom(Result.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if(returnType.getGenericParameterType().equals(String.class)){
ObjectMapper objectMapper = new ObjectMapper();
try {
return objectMapper.writeValueAsString(Result.success(body));
} catch (JsonProcessingException e) {
throw new APIException(e.getMessage());
}
}
return Result.success(body);
}
}
Controller層
package com.wanqi.controller;
import com.wanqi.pojo.TestDTO;
import com.wanqi.pojo.UserDTO;
import org.hibernate.validator.constraints.*;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
@RestController
public class TestController {
@PostMapping("/test")
public TestDTO test(@RequestBody TestDTO testDTO) {
return testDTO;
}
@PostMapping("/test1")
public String test1(@RequestBody TestDTO testDTO) {
return testDTO.getName();
}
}

不統一處理回傳
自定義注解
package com.wanqi.advice;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotControllerResponseAdvice {
}
在增強過濾方法上過濾包含這個注解的方法
@RestControllerAdvice(basePackages = "com.wanqi.controller")
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// response是Result型別,或者注釋了NotControllerResponseAdvice都不進行包裝
return !(returnType.getParameterType().isAssignableFrom(Result.class)
|| returnType.hasMethodAnnotation(NotControllerResponseAdvice.class));
}
.....
controller
- 在不需要統一處理的方法上使用@NotControllerResponseAdvice注解即可
package com.wanqi.controller;
import com.wanqi.advice.NotControllerResponseAdvice;
import com.wanqi.pojo.TestDTO;
import com.wanqi.pojo.UserDTO;
import com.wanqi.result.Result;
import org.hibernate.validator.constraints.*;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
@Validated
@RestController
public class TestController {
@GetMapping("/health")
@NotControllerResponseAdvice
public String health() {
return "success";
}
@GetMapping("/health1")
public Result health1() {
return Result.success("dddd");
}
}


自定義例外與統一攔截例外
自定義例外
package com.wanqi.exception;
import com.wanqi.result.ResultEnum;
//自定義例外
public class APIException extends RuntimeException {
private int code;
private String msg;
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
public APIException(String message) {
super(message);
this.code = ResultEnum.DATA_CONVERSION.getCode();
this.msg = ResultEnum.DATA_CONVERSION.getMessage();
}
}
統一攔截例外
package com.wanqi.advice;
import com.wanqi.exception.APIException;
import com.wanqi.result.IResult;
import com.wanqi.result.Result;
import com.wanqi.result.ResultEnum;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolationException;
//統一攔截例外
@RestControllerAdvice(basePackages = "com.wanqi")
public class ControllerExceptionAdvice {
/**
* 捕獲 {@code APIException} 例外
*/
@ExceptionHandler({APIException.class})
public Result<?> handleBusinessException(APIException ex) {
return Result.failed(ex.getMessage());
}
/**
* {@code @RequestBody} 引數校驗不通過時拋出的例外處理
*/
@ExceptionHandler({MethodArgumentNotValidException.class})
public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
BindingResult bindingResult = ex.getBindingResult();
StringBuilder sb = new StringBuilder("校驗失敗:");
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
}
String msg = sb.toString();
if (StringUtils.hasText(msg)) {
return Result.failed(new IResult() {
@Override
public Integer getCode() {
return ResultEnum.VALIDATE_FAILED.getCode();
}
@Override
public String getMessage() {
return msg;
}
});
}
return Result.failed(ResultEnum.VALIDATE_FAILED);
}
/**
* {@code @PathVariable} 和 {@code @RequestParam} 引數校驗不通過時拋出的例外處理
*/
@ExceptionHandler({ConstraintViolationException.class})
public Result<?> handleConstraintViolationException(ConstraintViolationException ex) {
if (StringUtils.hasText(ex.getMessage())) {
return Result.failed(ResultEnum.VALIDATE_FAILED.getCode(), ex.getMessage());
}
return Result.failed(ResultEnum.VALIDATE_FAILED);
}
/**
* 頂級例外捕獲并統一處理,當其他例外無法處理時候選擇使用
*/
@ExceptionHandler({Exception.class})
public Result<?> handle(Exception ex) {
return Result.failed(ex.getMessage());
}
}
引數校驗
- 使用hibernate-validator,@Validated注解引數校驗
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.5.Final</version>
</dependency>
@RequestBody 引數校驗
- Post、Put 請求的引數推薦使用 @RequestBody 請求體引數,
- 對 @RequestBody 引數進行校驗需要在物件中加入校驗條件后,再搭配 @Validated 即可完成自動校驗,
UserDTO
package com.wanqi.pojo;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotNull;
public class UserDTO {
private Long userId;
@NotNull
@Length(min = 2,max = 10)
private String userName;
@NotNull
@Length(min = 6,max = 20)
private String account;
@NotNull
@Length(min = 8,max = 20)
private String password;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "UserDTO{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", account='" + account + '\'' +
", password='" + password + '\'' +
'}';
}
}
controller
package com.wanqi.controller;
import com.wanqi.pojo.TestDTO;
import com.wanqi.pojo.UserDTO;
import org.hibernate.validator.constraints.*;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
@Validated
@RestController
public class TestController {
// 使用@Valid或者@Validated
@PostMapping("/user")
public UserDTO user(@RequestBody @Validated UserDTO userDTO) {
return userDTO;
}
}

@PathVariable 和 @RequestParam 引數校驗
- Get 請求的引數接收一般依賴這兩個注解,但是處于 url 有長度限制和代碼的可維護性,超過 5 個引數盡量用物體來傳參,
- 對 @PathVariable 和 @RequestParam 引數進行校驗需要在入參宣告約束的注解,
controller
package com.wanqi.controller;
import com.wanqi.pojo.TestDTO;
import com.wanqi.pojo.UserDTO;
import org.hibernate.validator.constraints.*;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
@Validated
@RestController
public class TestController {
@GetMapping("/{num}")
public Integer detail(@PathVariable("num") @Min(value = https://www.cnblogs.com/wandaren/archive/2022/12/26/1 ,message ="不能小于1") @Max(20) Integer num) {
return num * num;
}
@GetMapping("/getByEmail")
public String getByAccount(@RequestParam @NotBlank @Email String email) {
return email;
}
}


自定義校驗規則
自定義校驗規則需要做兩件事情:
- 自定義注解類,定義錯誤資訊和一些其他需要的內容
- 注解校驗器,定義判定規則
自定義注解類
package com.wanqi.validator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = MobileValidator.class)
public @interface Mobile {
/**
* 是否允許為空
*/
boolean required() default true;
/**
* 校驗不通過回傳的提示資訊
*/
String message() default "不是一個手機號碼格式";
/**
* Constraint要求的屬性,用于分組校驗和擴展,留空就好
*/
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
注解校驗器,實作ConstraintValidator介面
package com.wanqi.validator;
import org.springframework.util.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MobileValidator implements ConstraintValidator<Mobile, CharSequence> {
private boolean required = false;
private final Pattern pattern = Pattern.compile("^1[34578][0-9]{9}$"); // 驗證手機號
/**
* 在驗證開始前呼叫注解里的方法,從而獲取到一些注解里的引數
*
* @param constraintAnnotation annotation instance for a given constraint declaration
*/
@Override
public void initialize(Mobile constraintAnnotation) {
this.required = constraintAnnotation.required();
}
/**
* 判斷引數是否合法
*
* @param value object to validate
* @param context context in which the constraint is evaluated
*/
@Override
public boolean isValid(CharSequence value, ConstraintValidatorContext context) {
if (this.required) {
// 驗證
return isMobile(value);
}
if (StringUtils.hasText(value)) {
// 驗證
return isMobile(value);
}
return true;
}
private boolean isMobile(final CharSequence str) {
Matcher m = pattern.matcher(str);
return m.matches();
}
}
使用
package com.wanqi.controller;
import com.wanqi.validator.Mobile;
import org.hibernate.validator.constraints.*;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@Validated
@RestController
public class TestController {
@GetMapping("/phone")
public String phone(@RequestParam @Mobile String phone) {
return phone;
}
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/540726.html
標籤:其他
上一篇:Maven多模塊管理

