SpringBoot實戰 - 秒殺系統構建(1)
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");
}