一、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;
   
}
六、校验规则
 
