SpringClould微服務架構搭建--統一響應、入參校驗、異常處理(四)


學而時習之,不亦說乎

前言

對於一個由后端提供的接口來說,有一個統一的響應格式,方便入參校驗,統一的異常處理,是必不可少的,今天我們將這三個基礎功能集成到項目中,使項目更貼近實際的開發場景。

統一響應

在項目開發中,一般返回給前端的都會是一個統一的返回響應對象,因此后端需要封裝一個泛型類來作為響應對象,這樣做的好處是前后端能統一接口返回,可以做規范的響應處理。
實現步驟:

  1. 創建mingx-common微服務,用於處理公共業務,包括統一響應,統一異常攔截,工具類等。
  2. 在src/mian/java下新建com.mingx.common包,在此包下面創建
    AppResult.java,AppResultBuilder.java,ResultCode.java文件,作用分別如下:
  • AppResult.java:規范返回結果的格式的類
package com.mingx.common;

public class AppResult<T> {

    private int code;
    private String msg;
    private T data;// 數據

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
  • AppResultBuilder.java:方便返回格式組裝的類
package com.mingx.common;

public class AppResultBuilder {

    private static Integer successCode = ResultCode.SUCCESS.getCode();
    private static String successMsg = ResultCode.SUCCESS.getMsg();

    //成功,不返回具體數據
    public static AppResult<String> success(){
        AppResult<String> result = new AppResult<String>();
        result.setCode(successCode);
        result.setMsg(successMsg);
        result.setData("");
        return result;
    }
    //成功,返回數據
    public static <T> AppResult<T> success(T t){
        AppResult<T> result = new AppResult<T>();
        result.setCode(successCode);
        result.setMsg(successMsg);
        result.setData(t);
        return result;
    }

    //失敗,返回失敗信息
    public static <T> AppResult<T> error(ResultCode code){
        AppResult<T> result = new AppResult<T>();
        result.setCode(code.getCode());
        result.setMsg(code.getMsg());
        return result;
    }

    //失敗,返回失敗信息
    public static <T> AppResult<T> error(ResultCode code,String extraMsg){
        AppResult<T> result = new AppResult<T>();
        result.setCode(code.getCode());
        result.setMsg(code.getMsg() + "," + extraMsg);
        return result;
    }

    //失敗,返回失敗信息
    public static <T> AppResult<T> error(Integer code,String extraMsg){
        AppResult<T> result = new AppResult<T>();
        result.setCode(code);
        result.setMsg(extraMsg);
        return result;
    }

}
  • ResultCode.java:枚舉類,定義返回信息
package com.mingx.common;

public enum ResultCode {
    /* 成功狀態碼 */
    SUCCESS(10000"success"),
    /* 系統錯誤: */
    SYSTEM_ERROR(10001"系統繁忙,請稍后重試"),
    /* 參數錯誤: */
    PARAM_ERROR(10002"參數有誤"),
    /* 非法登錄:*/
    ILLEGAL_ERROR(10003"用戶非法登錄"),

    /* 用戶模塊:20001-29999*/
    USER_NOT_LOGGED_IN(20001"用戶未登錄"),
    USER_LOGIN_ERROR(20002"用戶名或者密碼錯誤,請檢查重試"),
    USER_ACCOUNT_FORBIDDEN(20003"賬號已被禁用"),
    USER_HAS_EXISTED(20004"用戶已存在");

    private Integer code;
    private String msg;

    ResultCode(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

入參校驗

平時項目中,難免需要對參數 進行一些參數正確性的校驗,這些校驗出現在業務代碼中,讓我們的業務代碼顯得臃腫,而且,頻繁的編寫這類參數校驗代碼很無聊。鑒於此,覺得 Hibernate Validator 框架剛好解決了這些問題,可以很優雅的方式實現參數的校驗,讓業務代碼 和 校驗邏輯 分開,不再編寫重復的校驗邏輯。

Hibernate Validator 是 Bean Validation 的參考實現 . Hibernate Validator 提供了 JSR 303 規范中所有內置 constraint 的實現,除此之外還有一些附加的 constraint。

Bean Validation 為 JavaBean 驗證定義了相應的元數據模型和API。缺省的元數據是 Java Annotations,通過使用 XML 可以對原有的元數據信息進行覆蓋和擴展。Bean Validation 是一個運行時的數據驗證框架,在驗證之后驗證的錯誤信息會被馬上返回。

准備步驟:

  1. 在父pom中增加hibernate validator主版本管理
<hibernate-validator.version>6.0.14.Final</hibernate-validator.version>

<!-- hibernate validator主版本管理 -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>${hibernate-validator.version}</version>
</dependency>
  1. 在src/mian/java下新建com.mingx.common包,在此包下面創建RegexpContants接口,用於常用正則表達式管理,根據項目中的實際情況添加。
package com.mingx.common;

/**
 * 通用正則表達式
 *
 * @author Admin
 */
public interface RegexpContants {

    /**
     * 可空標記
     */
    String NULLFLAG = "^$|";

    /**
     * 手機正則表達式
     */
    String MOBIL_EREGEXP = "^(13|14|15|16|17|18|19)\\d{9}$";

    /**
     * 郵箱正則表達式
     */
    String EMAIL_EREGEXP = "^\\s*\\w+(?:\\.{0,1}[\\w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*\\.[a-zA-Z]+\\s*$";

    /**
     * 身份證正則表達式
     */
    String ID_CARD_EREGEXP = "(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x)$)";

    /**
     * 固話正則表達式
     */
    String TELEPHONE_EREGEXP = "^(0\\d{2,3}-)?\\d{7,8}(-\\d{3,4})?$";

    /**
     * 網站正則表達式
     */
    String URL_EREGEXP = "(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]";

    /**
     * 6位短信驗證碼
     */
    String SIXNUMBER_EREGEXP = "^\\d{6}$";

    /**
     * 4位短信驗證碼
     */
    String FOURNUMBER_EREGEXP = "^\\d{4}$";

    /**
     * 驗證數字
     */
    String NUMBER_EREGEXP = "^\\d{1,}$";

    /**
     * 年齡
     */
    String AGE_EREGEXP = "^[0-9]{1,2}$";

    /**
     * 密碼(6-12位字母或數字)正則表達式
     */
    String PASSWORD_OR_EREGEXP = "^[0-9A-Za-z]{6,12}$";

    /**
     * 密碼(6-12位字母和數字)正則表達式
     */
    String PASSWORD_AND_EREGEXP = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,12}$";

    /**
     * 中文姓名正則表達式
     */
    String CHINESE_NAME_EREGEXP = "^[\u4e00-\u9fa5]+(\\·[\u4e00-\u9fa5]+)*$";

    /**
     * 金額正則表達式 正整數,不能為小數或者負數
     */
    String MONEY_EREGEXP = "^([1-9]\\d*)*$";

    /**
     * 只能輸入數字 0 或 1
     **/
    String ZERO_OR_ONE_EREGEXP = "^[0-1]{1}$";

    /**
     * 正整數
     */
    String POSITIVE_NUMBER = "^[0-9]*[1-9][0-9]*$";

    /**
     * 年月日日期模式
     */
    String SIMPLE_DATE_PATTERN = "^[1-9]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$";

    /**
     * 年月日日期模式無-
     */
    String SIMPLE_DATE_PATTERN_SIMPLE = "^[1-9]\\d{3}(0[1-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])$";

    /**
     * 日期格式驗證
     */
    String DATE_FORMATE_TEMPLATE1_EREGEXP = "^[1-2][0-9][0-9][0-9]-([1][0-2]|0?[1-9])-([12][0-9]|3[01]|0?[1-9]) ([01][0-9]|[2][0-3]):[0-5][0-9]$";

    /**
     * 郵編格式驗證
     */
    String POSTCODE_EREGEXP = "^[0-9]{6}$";
}

在后續的實際運用中在具體看如何使用。

統一異常響應

  1. 在mingx-common的pom中引入spring-boot-starter-web依賴
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.mingx</groupId>
    <artifactId>mingx-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>

  <groupId>com.mingx.common</groupId>
  <artifactId>mingx-common</artifactId>
  <packaging>jar</packaging>


  <dependencies>

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
      </dependency>

   </dependencies>

</project>
  1. 在src/mian/java下新建com.mingx.common,hander包,在此包下面創建SysExceptionHandler.java

利用ControllerAdvice注解實現全局的異常處理,首先是針對入參校驗的異常處理,會拋出MethodArgumentNotValidException的異常,首先被捕獲,返回統一的參數有誤響應。接下來捕獲所有Exception的異常,此處異常其實可以再細化,現在為了模擬,統一捕獲Exception異常,返回統一的【系統繁忙,請稍后重試】響應。

package com.mingx.common.hander;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import com.mingx.common.AppResult;
import com.mingx.common.AppResultBuilder;
import com.mingx.common.ResultCode;

@ControllerAdvice
@Component
public class SysExceptionHandler {


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

    /**
     *  入參校驗
     * @param exception
     * @return
     */

    @ResponseBody
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public AppResult<String> handle(MethodArgumentNotValidException exception) {
        String message = exception.getBindingResult().getAllErrors().get(0).getDefaultMessage();        
        return AppResultBuilder.error(ResultCode.PARAM_ERROR,message);
    }

    /**
     *     全局異常捕捉處理
     * @param ex
     * @return
     */

    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public AppResult<String> errorHandler(Exception ex) {
        logger.error(ex.getMessage(),ex);
        return AppResultBuilder.error(ResultCode.SYSTEM_ERROR);
    }

}

綜合實踐

做好前面的三個基本功能的實現准備后,現在模擬實現保存用戶信息的一個功能,步驟如下:

  1. 在項目pom中引入mingx-common、hibernate-validator依賴
 <dependency>
     <groupId>com.mingx.common</groupId>
     <artifactId>mingx-common</artifactId>
     <version>0.0.1-SNAPSHOT</version>
   </dependency>

   <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
   </dependency>
  1. 在mingx-user-pojo項目中創建com.mingx.user.bo包,該包專門放校驗入參的pojo類,創建SaveUserBO.java類,建議BO類的取名方式為:對應方法名 + BO,具體代碼如下:
package com.mingx.user.bo;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.Length;
import com.mingx.common.RegexpContants;
import lombok.Data;

@Data
public class SaveUserBO {

    @NotBlank(message = "用戶名稱不能為空")
    @Length(max = 10,message = "長度不能超過10個字符")
    private String name;

    @NotBlank(message = "性別不能為空")
    @Pattern(regexp = RegexpContants.ZERO_OR_ONE_EREGEXP,message = "性別格式不正確")
    private String sex;

    @NotBlank(message = "出生日期不能為空")
    @Pattern(regexp = RegexpContants.SIMPLE_DATE_PATTERN_SIMPLE,message = "出生日期格式不正確")
    private String birthday;

}

@NotBank注解即是非空注解,標識該字段不能為空

@Length注解可設置其字段長度

@Pattern注解可進行自定義正則表達式校驗,可以將相關正則表達式單獨抽取出來,利用之前創建的RegexpContants.java接口,其中定義常用正則表達式即可

其他更多校驗方面的注解可參考hibernate Validator官方給出的參考文檔,這里僅是最簡單的使用。

  1. 在mingx-user項目的SysController.java中,增加保存用戶信息接口:

注意:接口入參需要添加 @Validated 注解,才會對入參進行參數校驗

@PostMapping("/saveUser")
public AppResult<String> saveUser(@Validated @RequestBody SaveUserBO bo){

    Integer count = sysUserService.count(new QueryWrapper<SysUser>().eq("name", bo.getName()));
    if(count > 0) {
        return AppResultBuilder.error(ResultCode.USER_HAS_EXISTED);
    }

    SysUser  user = new SysUser();
    user.setName(bo.getName()).setSex(Integer.valueOf(bo.getSex()))
        .setBirthday(LocalDate.parse(bo.getBirthday(),DateTimeFormatter.ofPattern("yyyy-MM-dd")))
        .setCreateTime(LocalDateTime.now());
    sysUserService.save(user);
    return AppResultBuilder.success(user.getId());

}
  1. 啟動nacos,啟動mingx-user,打開postman,發送post請求,訪問
    http://localhost:7001/user/saveUser
    在body中加入json格式的參數,可如下:

保存成功,則如下顯示:

如果某字段為空,或者格式不正確,則會如下顯示:

由於增加了重復的邏輯判斷,再次保存,會返回:

結束語

本章未涉及到SpringClould相關的知識點,但是只要是一個合格的后端接口,都必須要這最基礎的三點功能,簡單在項目中實踐,做優化提煉,有助於技術層次的提高,不能一頭就扎到業務開發中,忽略了這些基礎功能實現的過程。

下一章我們學習使用SpringcClould Feign,一個棒棒噠聲明式偽RPC的REST客戶端。

點關注,不迷路

微信搜索【尋的足跡】關注公眾號,第一時間收到最新文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM