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");
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值