電商秒殺項目-用戶模塊開發

一、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;

}

六、校驗規則

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