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");
}