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