一、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;
}
六、校驗規則