电商秒杀项目-用户模块开发

一、springMVC的数据模型,DO,MODEL,VO
在springMVC中,每一层都有每一层的设计思想,在mvc中model的定义分为三层。
第一层是dataobject(do),在dao层,它与数据库完完全全一一映射,它的字段和数据库里面的字段完全一样,不含有逻辑。但是在service层不可以简简单单地把对应数据库的映射返回给想要这个service的服务,所以在service层必须有model的概念,model中要把do类再过一遍,还可以加上属于这个用户对象,但是由于用户模型的关系设置在不同的表里面的字段(在java领域模型的概念里面,虽然不在同一个表但是也可能属于某个model),所以这个model才是真正意义上的mvc中的model,所以在service中应该返回这个model。操作数据库时使用的是DO,在service中转换成model再返回。但是前端只需要拿到需要展示的对象即可,而并非领域模型本身,因此在controller层需要,viewobject模型对象(vo),仅仅包含前端用户需要的信息就够了,在controller中处理完成model之后,转换成vo再返回。
在许多企业级应用里面 viewobject的定义和model的定义是完全不一样的,而且许多都会用到聚合操作。

二、返回正确信息
1 归一化responsbody的返回参数
创建一个response的package,用来处理http返回的,新建一个叫CommonReturnType的类,包含一个string类型的status和一个object类型的data。可以通过status来让前端判定这个请求有没有受理,data返回前端需要的json或者错误码格式。然后在定义一个通用的创建方法CommonReturnType;让控制器中的接口返回的都是ConmonRetrnType。

三、返回错误信息
当status时,把data定义成固定的错误码格式,这样前端就可以简单判断当status为fail时如何展示。
1 定义通用错误形式
在项目创建一个叫error的package,声明一个CommonError的interface,定义几个方法,getErrCode,getErrMsg,setErrMsg。然后定义一个EmBusinessError实现CommonError,定义一个int errCode和string errMsg,实现它的get set方法,EmBusinessError构造函数,并且定义需要的错误码。错误码定义出来之后可以直接通过对应的构造方法EmBusinessError构造出来一个实现了CommonError的EmBusinessError类型的子类,
2 创建一个统一的exception
新建一个继承Exception并实现CommonError的BusinessException
内部需要强关联一个CommonError,并且需要构造函数。

BusinessException和setErrMsg都要实现setErrMsg方法,这样就可以将原本定义的errMsg覆盖掉。

ConmonRetrnType

package com.miaosha3.response;

public class CommonReturnType {
    //表明对应请求的返回处理结果"success"或者"fail"
    private String status;

    //if status = success,data返回前端需要的json
    //if status = fail,data使用通用错误码格式
    private Object data;


    /*
    * 当控制器完成处理,调用create,如果不带 success,那默认就是success,
    * 然后创建对应的CommonReturnType,把对应的值返回
    */
    //定义一个通用的创建方法
    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;
    }

    public String getStatus() {
        return status;
    }

    public Object getData() {
        return data;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

CommonError

package com.miaosha3.error;

public interface CommonError {
    public int getErrCode();
    public String getErrMsg();
    public CommonError setErrMsg(String errMsg);
}

BusinessException

package com.miaosha3.error;

public class BusinessException extends Exception implements CommonError{

    private CommonError commonError;

    //直接接收BusinessException的传参,用于构造业务异常
    public BusinessException(CommonError commonError){
        super();
        this.commonError = commonError;
    }

    //直接接收BusinessException的传参,用于构造业务异常
    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;
    }
}

EmBusinessError

package com.miaosha3.error;

public enum EmBusinessError implements CommonError {
    //通用错误类型 00001
    PAEAMETER_VALIDATION_ERROR(10001,"参数不合法"),
    UNKNOWN_ERROR(10002,"未知错误"),


    //10000开头为用户信息错误
    USER_NOT_EXIST(20001,"用户不存在"),
    USER_LOGIN_FAIL(20002,"用户手机号或者密码不正确")

            ;

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

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

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

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

如何使用:

//如果获取的用户信息不存在
        if (userModel == null){
            throw new BusinessException(EmBusinessError.USER_NOT_EXIST);
        }

四、通过springmvc自带的handlerException去通用的异常处理
定义exceptionhandler解决未被controller层吸收的exception异常
对于web来说controller层在某种意义上是业务处理的最后一道关口,所以要定义一种处理机制、
因为这一段是所有controller都需要的通用逻辑,所以单独学到一个类BaseController,让controller继承,
package com.miaosha3.controller;

import com.miaosha3.error.BusinessException;
import com.miaosha3.error.EmBusinessError;
import com.miaosha3.response.CommonReturnType;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

public class BaseController {

public static final String CONTENT_TYPE_FORMED = "application/x-www-form-urlencoded";
//定义exceptionhandler解决未被controller层吸收的exception异常
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Object handlerException(HttpServletRequest request, Exception e){
    Map<String,Object> responseData = new HashMap<>();
    if (e instanceof BusinessException){
        BusinessException businessException = (BusinessException)e;
        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");

}

}

五、注册与登录
1 otp短信获取
首先需要按照一定的规格生成otp验证码,然后将otp验证码同对应用户的手机号关联,使用httpsession的方式绑定,然后将opt验证码通过短信通道发送给用户

//用户获取opt短信接口
    @RequestMapping(value = "/getotp",method = {RequestMethod.POST},consumes = {CONTENT_TYPE_FORMED})
    @ResponseBody
    public CommonReturnType getOpt(@RequestParam(name="telphone")String telphone){
        //需要按照一定的规格生成otp验证码
        Random random = new Random();
        int randomint = random.nextInt(99999);
        randomint +=10000;
        String otpCode = String.valueOf(randomint);

        //将otp验证码同对应用户的手机号关联,使用httpsession的方式绑定
        httpServletRequest.getSession().setAttribute(telphone,otpCode);



        //将opt验证码通过短信通道发送给用户,涉及到短信通道 省略
        System.out.println("telphone=" + telphone + " & otpCode = "+otpCode);
        return CommonReturnType.create(null);
    }

写好后台后可以访问地址 来测试接口是否正确,如果正确,接下来创建一个getotp.html编写前端代码,略。

2 注册
要先验证手机号和otp符合,然后才是用户的注册流程。把前端传过来的字段填充称为usermodel,再传递给service来处理。在service中判断手机号是否已经注册,如果没注册则把信息保存到用户数据库中,同时取出用户id,保存到密码表中。此处记得用事务。
判断手机号是否已经注册
在表中给telphone字段创建索引,设成唯一的,在插入语句捕获异常,如果有异常则抛出异常提示手机号或者密码不正确。
跨域
前端发送ajax请求的时候,要在data{}中加上 xhrFields:{withCredentials:true},/*允许跨域的授信请求*/
后端控制器要在类的开头加注解@CrossOrigin(allowCredentials = "true",allowedHeaders = "*") //跨域

取出用户id
在mapper.xml的insert方法加上keyProperty=“id” useGeneratedKeys=“true”

  <insert id="insertSelective" parameterType="com.miaosha3.dataobject.userDO" keyProperty="id" useGeneratedKeys="true">

//用户注册接口
    @RequestMapping(value = "/register",method = {RequestMethod.POST},consumes = {CONTENT_TYPE_FORMED})
    @ResponseBody
    public CommonReturnType register(@RequestParam(name = "telphone")String telphone,
                                     @RequestParam(name = "otpCode")String otpCode,
                                     @RequestParam(name = "name")String name,
                                     @RequestParam(name = "gender")Integer gender,
                                     @RequestParam(name = "age")Integer age,
                                     @RequestParam(name = "password")String password) throws BusinessException, UnsupportedEncodingException, NoSuchAlgorithmException {

        //验证手机号和otp符合
        String inSessionotpCode = (String) this.httpServletRequest.getSession().getAttribute(telphone);
        if (!com.alibaba.druid.util.StringUtils.equals(otpCode,inSessionotpCode)){
            //首先会对null进行判断 如果两个都为null就返回true,不然调用String的equals方法
            throw new BusinessException(EmBusinessError.PAEAMETER_VALIDATION_ERROR,"短信验证码不符合");
        }
        //用户的注册流程
        UserModel userModel = new UserModel();
        userModel.setName(name);
        userModel.setAge(age);
        userModel.setGender(gender);
        userModel.setTelphont(telphone);
        userModel.setRegisterMode("byPhone");
        userModel.setPassword(EncodeMd5(password));

        userService.register(userModel);
        return CommonReturnType.create(null);

    }

    public String EncodeMd5(String str) throws NoSuchAlgorithmException, UnsupportedEncodingException {
        //确定一个计算方法
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        BASE64Encoder base64Encoder = new BASE64Encoder();
        //加密字符串
        String newstr = base64Encoder.encode(md5.digest(str.getBytes("utf-8")));
        return newstr;
    }

@Override
@Transactional
public void register(UserModel userModel) throws BusinessException {
if (userModel==null){
throw new BusinessException(EmBusinessError.PAEAMETER_VALIDATION_ERROR);
}
if (StringUtils.isEmpty(userModel.getName())||userModel.getAge() == null
|| userModel.getGender() == null || StringUtils.isEmpty(userModel.getTelphont())){
throw new BusinessException(EmBusinessError.PAEAMETER_VALIDATION_ERROR);
}

  /* ValidationResult validationResult = validator.validate(userModel);
   if (validationResult.isHasError()){
       //有错
       throw new BusinessException(EmBusinessError.PAEAMETER_VALIDATION_ERROR,validationResult.getErrMsg());
   }*/
    //实现model->dataobject
    userDO userDO = converFromModel(userModel);
    try{
        userDOMapper.insertSelective(userDO);
    }catch (DuplicateKeyException e){
        throw new BusinessException(EmBusinessError.PAEAMETER_VALIDATION_ERROR,"手机号已经注册");
    }
    //为什么用insertSelective而不是insert:
    //insert 就是原本的insert语句,如果字段为null,在数据库中就是null
    //insertSelective会一个个字段判断,不为null就inser,
    // 为null就不insert(完全依赖于数据库,数据库提供什么默认值就是什么)
    //在数据库设计中尽量避免使用null字段
    userModel.setId(userDO.getId());
    UserPasswordDO userPasswordDO = converPasswordFromModel(userModel);
    userPasswordDOMapper.insertSelective(userPasswordDO);

}
private UserPasswordDO converPasswordFromModel(UserModel userModel){
    if (userModel == null){
        return null;
    }
    UserPasswordDO userPasswordDO = new UserPasswordDO();
    //BeanUtils.copyProperties(userDO,userModel);
    userPasswordDO.setPassword(userModel.getPassword());
    userPasswordDO.setUserId(userModel.getId());
    return userPasswordDO;
}

前端略
3 手机登录
//用户登录接口
@RequestMapping(value = “/login”,method = {RequestMethod.POST},consumes = {CONTENT_TYPE_FORMED})
@ResponseBody
public CommonReturnType login(@RequestParam(name = “telphone”)String telphone,
@RequestParam(name = “password”)String password) throws BusinessException, UnsupportedEncodingException, NoSuchAlgorithmException {
if (org.apache.commons.lang3.StringUtils.isEmpty(telphone)||
StringUtils.isEmpty(password)){
throw new BusinessException(EmBusinessError.PAEAMETER_VALIDATION_ERROR);

    }
    //校验登录
    UserModel userModel = userService.validateLogin(telphone,EncodeMd5(password));

    //把登录凭证加入到用户登录成功的session内
    this.httpServletRequest.getSession().setAttribute("IS_LOGIN",true);
    this.httpServletRequest.getSession().setAttribute("LOGIN_USER",userModel);
    return CommonReturnType.create(null);

}

@Override
public UserModel validateLogin(String telphone, String password) throws BusinessException {
//通过用户手机获取用户信息
userDO userDO = userDOMapper.selectByTelphont(telphone);
if (userDO==null){
throw new BusinessException(EmBusinessError.USER_LOGIN_FAIL);//直接告诉用户手机号未注册很可能会被攻击
}
UserPasswordDO userPasswordDO = userPasswordDOMapper.selectByUserId(userDO.getId());
UserModel userModel = converFronDataObject(userDO,userPasswordDO);
//比对密码
if (!StringUtils.equals(password,userModel.getPassword())){
throw new BusinessException(EmBusinessError.USER_LOGIN_FAIL);
}
return userModel;

}

六、校验规则

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章