【禁止血壓飆升】阿里大佬寫的 Controller 太優雅了!

Java技术栈發表於2024-08-08

作者:小塵
連結:https://juejin.cn/post/7357172505961578511

前言

見過幾千行程式碼的 controller嗎?我見過。

見過全是 try catch 的 controller 嗎,我見過。

見過全是欄位校驗的 controller 嗎,我見過。

見過全是業務程式碼的 controller 嗎?不好意思,我們公司很多業務寫在 controller 的。

看見這些我真的血壓高。

正文

不優雅的 controller

@RestController
@RequestMapping("/user/test")
public class UserController {

    private static Logger logger = LoggerFactory.getLogger(UserController.class);

    @Autowired
    private UserService userService;

    @Autowired
    private AuthService authService;

    @PostMapping
    public CommonResult userRegistration(@RequestBody UserVo userVo) {
        if (StringUtils.isBlank(userVo.getUsername())){
            return CommonResult.error("使用者名稱不能為空");
        }
        if (StringUtils.isBlank(userVo.getPassword())){
            return CommonResult.error("密碼不能為空");
        }
        logger.info("註冊使用者:{}" , userVo.getUsername());
        try {
            userService.registerUser(userVo.getUsername());
            return CommonResult.ok();
        }catch (Exception e){
            logger.error("註冊使用者失敗:{}", userVo.getUsername(), e);
            return CommonResult.error("註冊失敗");
        }
    }

    @PostMapping("/login")
    @PermitAll
    @ApiOperation("使用賬號密碼登入")
    public CommonResult<AuthLoginRespVO> login(@RequestBody AuthLoginReqVO reqVO) {
        if (StringUtils.isBlank(reqVO.getUsername())){
            return CommonResult.error("使用者名稱不能為空");
        }
        if (StringUtils.isBlank(reqVO.getPassword())){
            return CommonResult.error("密碼不能為空");
        }
        try {
            return success(authService.login(reqVO));
        }catch (Exception e){
            logger.error("註冊使用者失敗:{}", reqVO.getUsername(), e);
            return CommonResult.error("註冊失敗");
        }
    }

}

Spring Boot 基礎就不介紹了,推薦看這個實戰專案:

https://github.com/javastacks/spring-boot-best-practice

優雅的controller

@RestController
@RequestMapping("/user/test")
public class UserController1 {

    private static Logger logger = LoggerFactory.getLogger(UserController1.class);

    @Autowired
    private UserService userService;

    @Autowired
    private AuthService authService;

    @PostMapping("/userRegistration")
    public CommonResult userRegistration(@RequestBody @Valid UserVo userVo) {
        userService.registerUser(userVo.getUsername());
        return CommonResult.ok();
    }

    @PostMapping("/login")
    @PermitAll
    @ApiOperation("使用賬號密碼登入")
    public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
        return success(authService.login(reqVO));
    }

}

程式碼量直接減一半呀,這還不算上有些直接把業務邏輯寫在 controller 的,看到這些我真的直接吐血

改造流程

校驗方式

這個 if 校驗看得我哪哪都不爽。好歹給我寫一個斷言吧。Assert.notNull(userVo.getUsername(), "使用者名稱不能為空");

這不香嗎?確實不香。

使用 spring 提供的@Valid

在入參時使用@Valid註解,並且在 vo 中使用校驗註解,如AuthLoginReqVO

@ApiModel(value = "管理後臺 - 賬號密碼登入 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AuthLoginReqVO {

    @ApiModelProperty(value = "賬號", required = true, example = "user")
    @NotEmpty(message = "登入賬號不能為空")
    @Length(min = 4, max = 16, message = "賬號長度為 4-16 位")
    @Pattern(regexp = "^[A-Za-z0-9]+$", message = "賬號格式為數字以及字母")
    private String username;

    @ApiModelProperty(value = "密碼", required = true, example = "password")
    @NotEmpty(message = "密碼不能為空")
    @Length(min = 4, max = 16, message = "密碼長度為 4-16 位")
    private String password;

}

@Valid

在SpringBoot中,@Valid是一個非常有用的註解,主要用於資料校驗。以下是關於@Valid的一些詳細資訊:

  1. 為什麼使用 @Valid 來驗證引數:在編寫介面時,我們經常需要驗證請求引數。通常,我們可能會寫大量的 if 和 if else 程式碼來進行判斷。但這樣的程式碼不僅不優雅,而且如果存在大量的驗證邏輯,這會使程式碼看起來混亂,大大降低程式碼可讀性。為了簡化這個過程,我們可以使用 @Valid 註解來幫助我們簡化驗證邏輯。
  2. @Valid 註解的作用:@Valid 的主要作用是用於資料效驗,可以在定義的實體中的屬性上,新增不同的註解來完成不同的校驗規則,而在介面類中的接收資料引數中新增 @valid 註解,這時你的實體將會開啟一個校驗的功能。
  3. @Valid 的相關注解:在實體類中不同的屬性上新增不同的註解,就能實現不同資料的效驗功能。
  4. 使用 @Valid 進行引數效驗步驟:整個過程如下,使用者訪問介面,然後進行引數效驗,因為 @Valid 不支援平面的引數效驗(直接寫在引數中欄位的效驗)所以基於 GET 請求的引數還是按照原先方式進行效驗,而 POST 則可以以實體物件為引數,可以使用 @Valid 方式進行效驗。如果效驗透過,則進入業務邏輯,否則丟擲異常,交由全域性異常處理器進行處理。
  5. @Validated與@Valid的區別@Validated@Valid 的���體。透過宣告實體中屬性的 groups ,再搭配使用 @Validated ,就能決定哪些屬性需要校驗,哪些不需要校驗。

全域性異常處理

這個全域性異常處理,可以根據自己的異常,自定義異常處理,並設定一個兜底的異常處理

@ResponseBody
@RestControllerAdvice
public class ExceptionHandlerAdvice {
    protected Logger logger = LoggerFactory.getLogger(getClass());

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public CommonResult<Object> handleValidationExceptions(MethodArgumentNotValidException ex) {
        logger.error("[handleValidationExceptions]", ex);
        StringBuilder sb = new StringBuilder();
        ex.getBindingResult().getAllErrors().forEach(error -> {
            String fieldName = ((org.springframework.validation.FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            sb.append(fieldName).append(":").append(errorMessage).append(";");
        });
        return CommonResult.error(sb.toString());
    }

    /**
     * 處理系統異常,兜底處理所有的一切
     */
    @ExceptionHandler(value = Exception.class)
    public CommonResult<?> defaultExceptionHandler(Throwable ex) {
        logger.error("[defaultExceptionHandler]", ex);
        // 返回 ERROR CommonResult
        return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
    }

}

就這麼多,搞定,這樣就擁有了漂流優雅的 controller 了

在日常開發中,還有那些血壓飆升瞬間

我拿出下圖閣下如何面對

這個閣下又如何面對,我不說,你能知道這個什麼嗎【狗頭】

總結

  • 不是很明白為什麼有些喜歡在 controller 寫業務邏輯的,曾經有個同事問我(就是喜歡在 controller 寫業務的),你這個介面寫在那裡,我需要調一下你這個介面。我滿臉問號??不是隔壁的模組嗎,為什麼要調我的介面?直接引用的我的 service 去調方法就好了。
  • 這個就是痛點,各寫各的,冗餘程式碼一堆。
  • 曾經看到一個同事寫一個儲存的方法,雖然邏輯挺多,我滑動了好久都還沒有方法還沒有結束。一個方法整整幾百行……
  • 看過 spring 原始碼都知道,spring 原始碼難啃,就是因為 spring 無限往下套娃,基本每個方法幹每個方法的事情。比如我儲存使用者時,就只是儲存使用者,至於什麼校驗丟給校驗的方法處理,什麼傳送訊息丟給傳送訊息處理,這些就不能耦合在一起。
  • 對於看到一些 if 下面一丟邏輯,然後 if 再一丟邏輯,看程式碼時很多情況不需要知道這個邏輯怎麼實現的,知道入參出參就大概這裡做什麼了。即使想知道詳細情況點進去就知道了。突出這個當前方法要做的事情就好了。
  • 阿里的開發手冊就推薦一個方法不能超過 80 行,超過可以根據業務具體調整一下。在公眾號Java核心技術回覆手冊可以獲取最新完整高畫質版。

更多文章推薦:

1.Spring Boot 3.x 教程,太全了!

2.2,000+ 道 Java面試題及答案整理(2024最新版)

3.免費獲取 IDEA 啟用碼的 7 種方式(2024最新版)

覺得不錯,別忘了隨手點贊+轉發哦!

相關文章