1. 程式人生 > 其它 >SpringBoot實戰 - 秒殺系統構建(1)

SpringBoot實戰 - 秒殺系統構建(1)

技術標籤:專案實戰spring

SpringBoot實戰 - 秒殺系統構建(1)

在完成了SpringBoot的學習後,本人開始學習一個電商秒殺系統的原始碼,嘗試通過原始碼學習和動手搭建的方式來加強對SpringBoot框架的實踐使用。不可否認的是,在學習原始碼的過程中,領悟到了許多在看書過程中學不到的開發方式,體會到了許多不同的開發需求,遂有此博文系列,分享學習過程中的筆記。

更多技術文章:個人部落格

1. Model 與 ViewObject

1.1 Model

在過去的SpringMVC與SpringBoot學習中,程式碼示例通常是三層架構,也就是Web+Service+Dao。但在學習電商秒殺系統的原始碼過程中,發現在Service層中的各個Service服務不會簡單地把資料庫的物件關係對映(即封裝了資料表中各個欄位的物件)直接透傳給上一層(也就是Web層);
一般地,會在service包下新建一個model包,在其中建立一個XxxModel類,用於封裝Web層服務業務邏輯所需要的欄位。這個Model才是真正意義上SpringMVC業務邏輯互動的模型概念

@Service
public class UserServiceImpl implements UserService{ @Autowired private UserDOMapper userDOMapper; @Autowired private UserPasswordDOMapper userPasswordDOMapper; /** * 返回UserModel的原因: * service層不可以簡單把資料庫的對映(即資料庫對映的UserDO等類)透傳返回給想要service的服務, * 必須使用Model,這個Model才是真正意義上MVC業務邏輯互動的模型概念 */
@Override public UserModel getUserById(Integer id) { //呼叫UserDOMapper獲取對應的UserDO UserDO userDO = userDOMapper.selectByPrimaryKey(id); if(userDO == null){ return null; } //通過使用者id獲取對應的使用者加密密碼資訊 UserPasswordDO userPasswordDO = userPasswordDOMapper.selectByUserId(id); return convertFromDataObject(userDO, userPasswordDO); } private UserModel convertFromDataObject(UserDO userDO, UserPasswordDO userPasswordDO){ if(userDO == null){ return null; } UserModel userModel = new UserModel(); BeanUtils.copyProperties(userDO, userModel); if(userPasswordDO != null){ userModel.setEncryptPassword(userPasswordDO.getEncryptPassword()); } return userModel; } }
@Data
public class UserModel {

    private Integer id;
    private String name;
    private Byte gender;
    private Integer age;
    private String telphone;
    private String registerMode;
    private String thirdPartyId;
    private String encryptPassword;

}

1.2 ViewObject

與回傳Model而非資料庫對映物件的原因類似,Web層的Controller一般也不會直接回傳整個Model給前端,因為Model是一個全欄位的模型,包含了相應角色的所有資料庫對映資訊,若直接返回一個Model到前端使用者,則會透傳給很多不必要的資訊,如加密的密碼…所以一般又會在controller包下新建一個viewobject包,在這裡面將所需資訊封裝建立為一個VO類,如UserVO

/**
 * 返回UserVO的原因:Model是一個全欄位的模型,包含了相應角色的所有資料庫對映資訊,
 * 若直接返回一個Model到前端使用者,則會透傳給很多不必要的資訊,如加密的密碼...
 * 所以一般在controller包下新建一個viewobject包,在這裡面將所需資訊封裝為一個VO類,如UserVO
*/
@RequestMapping("/get")
@ResponseBody
public UserVO getUser(@RequestParam(name="id")Integer id){
    //呼叫service服務獲取對應id的使用者物件並返回前端
    UserModel userModel = userService.getUserById(id);
    //將核心領域模型使用者物件轉化為可供UI使用的viewobject
    return convertFromModel(userModel);
}
    
private UserVO convertFromModel(UserModel userModel){
    if(userModel == null){
        return null;
    }
    UserVO userVO = new UserVO();
    BeanUtils.copyProperties(userModel, userVO);
    return userVO;
}
@Data
public class UserVO {

    private Integer id;
    private String name;
    private Byte gender;
    private Integer age;
    private String telphone;

}

2. 通用返回型別 - CommonReturnType

對於請求而言,成功的請求應該以狀態碼200響應,錯誤的要以4xx或5響應。對於Web服務層來說,一般成功的請求都應該使用狀態碼200響應,如果層次是服務中的某個程式碼發生了錯誤,SpringBoot框架底層就會以500等狀態碼響應客戶端/瀏覽器。但按理說,請求是成功傳送並接收的,所以不應該以500等響應,而且這種響應方式也無法讓前端開發人員對此異常進行處理。對此,在開發過程中,我們常常會構建一個CommonReturnType類,其中有status欄位和data欄位,其中status欄位表明對應請求的服務處理結果是"success"或"fail",而data欄位則在status="success"時儲存前端需要的json資料,在status="fail"時,儲存通用的錯誤碼格式。這樣無論Web服務成功或失敗與否,都會返回一個CommonReturnType物件給前端,而前端人員就可以根據這個物件中的status和data做出相應的處理
這個時候Controller程式碼變更為

@RequestMapping("/get")
@ResponseBody
public CommonReturnType getUser(@RequestParam(name="id")Integer id) throws BusinessException {
    //呼叫service服務獲取對應id的使用者物件並返回前端
    UserModel userModel = userService.getUserById(id);
    //將核心領域模型使用者物件轉化為可供UI使用的viewobject
    UserVO userVO = convertFromModel(userModel);
    //返回通用物件
    return CommonReturnType.create(userVO);
}
@Data
public class CommonReturnType {
    // 表明對應請求的服務處理結果"success"或"fail"
    private String status;
    // 若status="success",則data內返回前端需要的json資料
    // 若status="fail",則data內使用通用的錯誤碼格式
    private Object data;

    // 定義一個通用的建立方法
    public static CommonReturnType create(Object result){
        return CommonReturnType.create(result, "success");
    }

    public static CommonReturnType create(Object result, String status){
        CommonReturnType type = new CommonReturnType();
        type.setStatus(status);
        type.setData(result);
        return type;
    }

}

2.1 在通用返回型別中提供錯誤碼 - CommonError

上面說到data欄位在status="fail"時,要儲存通用的錯誤碼格式,所以在這需要建立並定義各種錯誤型別。一般在開發過程中會使用包裝模式,通過介面+列舉類+異常類構建錯誤資訊

public interface CommonError {
    int getErrCode();
    String getErrMsg();
    CommonError setErrMsg(String errMsg);
}
public enum EmBusinessError implements CommonError{
    // 1000X為通用錯誤型別
    PARAMETER_VALIDATION_ERROR(10001, "非法引數"),
    UNKNOWN_ERROR(10002, "未知錯誤"),
    // 2000X開頭為使用者資訊相關錯誤
    USER_NOT_EXIST(20001, "使用者不存在");

    private int errCode;
    private String errMsg;

    private EmBusinessError(int errCode, String errMsg){
        this.errCode = errCode;
        this.errMsg = errMsg;
    }

    @Override
    public int getErrCode() {
        return this.errCode;
    }

    @Override
    public String getErrMsg() {
        return this.errMsg;
    }

    // 用於通用錯誤碼,修改其中的errMsg
    @Override
    public CommonError setErrMsg(String errMsg) {
        this.errMsg = errMsg;
        return this;
    }
}

/**
 * @project: MiaoshaProject
 * @program: BusinessException
 * @description:
 * @author: JIAJUN LIANG
 * @create: 2021-01-21 22:24
 **/
//包裝器:業務異常實現
public class BusinessException extends Exception implements CommonError{

    private CommonError commonError;

    // 直接接收EmBusinessError的傳參用於構造業務異常
    public BusinessException(CommonError commonError){
        super(); // 父類Exception初始化
        this.commonError = commonError;
    }

    // 接收自定義errMsg的方式構造業務異常
    public BusinessException(CommonError commonError, String errMsg){
        super();
        this.commonError = commonError;
        this.commonError.setErrMsg(errMsg);
    }

    @Override
    public int getErrCode() {
        return this.commonError.getErrCode();
    }

    @Override
    public String getErrMsg() {
        return this.commonError.getErrMsg();
    }

    @Override
    public CommonError setErrMsg(String errMsg) {
        this.commonError.setErrMsg(errMsg);
        return this;
    }
}

相應地,在對錯誤資訊構建好後,controller需要做出更改,比如:在查詢不到使用者資訊時,丟擲一個內含USER_NOT_EXIST的BusinessException給SpringBoot的異常處理機制處理

@RequestMapping("/get")
@ResponseBody
public CommonReturnType getUser(@RequestParam(name="id")Integer id) throws BusinessException {
    //呼叫service服務獲取對應id的使用者物件並返回前端
    UserModel userModel = userService.getUserById(id);

    //若獲取的對應使用者資訊不存在
    if(userModel == null){
        throw new BusinessException(EmBusinessError.USER_NOT_EXIST);
    }

    //將核心領域模型使用者物件轉化為可供UI使用的viewobject
    UserVO userVO = convertFromModel(userModel);
    //返回通用物件
    return CommonReturnType.create(userVO);
}

//定義ExceptionHandler解決未被controller層吸收的exception
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Object handlerException(HttpServletRequest request, Exception exception){
    Map<String, Object> responseData = new HashMap<>();
    if(exception instanceof BusinessException){
        BusinessException businessException = (BusinessException) exception;
        responseData.put("errCode", businessException.getErrCode());
        responseData.put("errMsg", businessException.getErrMsg());
    } else {
        responseData.put("errCode", EmBusinessError.UNKNOWN_ERROR.getErrCode());
        responseData.put("errMsg", EmBusinessError.UNKNOWN_ERROR.getErrMsg());
    }
    return CommonReturnType.create(responseData, "fail");
}