核心领域模型:UserModel
package com.miaoshaproject.service.model;
import org.hibernate.validator.constraints.NotBlank;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
public class UserModel {
private Integer id;
@NotBlank(message = "用户名不能为空") // 优化校验规则
private String name;
@NotNull(message = "性别不能不填写")
private Byte gender;
@NotNull(message = "年龄不能不填写")
@Min(value = 0, message = "年龄必须大于0岁")
@Max(value = 150,message = "年龄必须小于150岁")
private Integer age;
@NotBlank(message = "手机号不能为空")
private String telephone;
private String registerMode;
private String thirdPartyId;
@NotBlank(message = "密码不能为空")
private String encrptPassword;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Byte getGender() {
return gender;
}
public void setGender(Byte gender) {
this.gender = gender;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
public String getRegisterMode() {
return registerMode;
}
public void setRegisterMode(String registerMode) {
this.registerMode = registerMode;
}
public String getThirdPartyId() {
return thirdPartyId;
}
public void setThirdPartyId(String thirdPartyId) {
this.thirdPartyId = thirdPartyId;
}
public String getEncrptPassword() {
return encrptPassword;
}
public void setEncrptPassword(String encrptPassword) {
this.encrptPassword = encrptPassword;
}
}
对于数据库表的dataObject(UserDO):由Model建好表后,使用mybatis-generator.xml创建
用于前端展示的viewObject(UserVO):展示需要展示的Model属性
package com.miaoshaproject.controller.viewobject;
public class UserVO {
private Integer id;
private String name;
private Byte gender;
private Integer age;
private String telephone;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Byte getGender() {
return gender;
}
public void setGender(Byte gender) {
this.gender = gender;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
}
定义通用的返回对象:CommonReturnType
方便前端页面的逻辑判断和属性获取(response)
package com.miaoshaproject.response;
public class CommonReturnType {
// 表明对应请求的返回结果 "success" 或 "fail"
private String status;
// 若status=success,则data返回前端需要的json数据
// 若status=fail,则data内使用通用的错误码格式
private Object data;
// 定义一个通用的创建方法
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 void setStatus(String status) {
this.status = status;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
定义通用的错误信息
使用设计模式:包装器业务异常类实现(接口->枚举类(包含错误信息/错误码)->异常类(业务代码中throw该异常))
package com.miaoshaproject.error;
public interface CommonError {
public int getErrCode();
public String getErrMsg();
public CommonError setErrMsg(String errMsg);
}
package com.miaoshaproject.error;
public enum EmBussinessError implements CommonError{
//通用错误类型10001
PARAMETER_VALIDATION_ERROR(10001, "参数不合法"),
UNKNOWN_ERROR(10002,"未知错误"),
// 20000开头为用户信息相关错误定义
USER_NOT_EXIST(20001,"用户不存在"),
USER_LOGIN_FAIL(20002,"用户手机号或密码不正确"),
USER_NOT_LOGIN(20003,"用户还未登录"),
// 30000开头为交易信息错误定义
STOCK_NOT_ENOUGH(30001,"库存不足"),
;
private int errCode;
private String errMsg;
private EmBussinessError(int errCode, String errMsg) {
this.errCode = errCode;
this.errMsg = errMsg;
}
@Override
public int getErrCode() {
return this.errCode;
}
@Override
public String getErrMsg() {
return this.errMsg;
}
@Override
public CommonError setErrMsg(String errMsg) {
this.errMsg = errMsg;
return this;
}
}
package com.miaoshaproject.error;
// 包装器业务异常类实现
public class BusinessException extends Exception implements CommonError{
private CommonError commonError;
// 直接接收EmBusinessError的传参,用户构造业务异常
public BusinessException(CommonError commonError) {
super();
this.commonError = commonError;
}
// 接收自定义errMsg的方式构造业务异常
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;
}
}
异常处理,拦截到tomcat的异常处理,使得前端输出想要的结果
定义exceptionhandler解决未被controller层吸收的exception。业务controller继承此BaseController
package com.miaoshaproject.controller;
import com.miaoshaproject.error.BusinessException;
import com.miaoshaproject.error.EmBussinessError;
import com.miaoshaproject.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 ex) {
Map<String, Object> responseData = new HashMap<>();
if(ex instanceof BusinessException) {
BusinessException businessException = (BusinessException) ex;
responseData.put("errCode", businessException.getErrCode());
responseData.put("errMsg", businessException.getErrMsg());
} else {
responseData.put("errCode", EmBussinessError.UNKNOWN_ERROR.getErrCode());
responseData.put("errMsg", EmBussinessError.UNKNOWN_ERROR.getErrMsg());
}
return CommonReturnType.create(responseData, "fail");
}
}
用户模块管理
otp短信获取
按照一定规则生成验证码。将otp验证码同对应用户手机号关联(通过htttp session的方式关联手机号和otpcode或者使用redis存储键值对(天然覆盖value,有过期时间)),将otp验证码通过短信通道发送给用户。
用户注册功能实现
需要前端参数,验证手机号和对应的otpcode相符合。插入用户信息到表中时使用事务,因为需要向user_info和password_info都插入数据。password通过MD5加密后插入数据库。
跨域请求问题:页面url跳转时,域名、端口或协议变化时。@CrossOrigin
因为设置的主键id自增,所以在使用mybatis-generator生成的插入sql中需要加入userGeneratedKeys=“true” keyProperty=“id”。此外在建表时,防止使用telphone重复注册,telphone使用唯一索引
用户登录功能实现
首先是入参检验,这里优化校验规则,使用hibernate validator Engine类库,然后在userModel的属性上加注释即可。另外为人性化,前端入参检验是更好的,可以及时提醒用户。
注:@NotBlank必须是org.hibernate.validator.constrains包下的;@Min@Max@NotNull是javax.validation.constrans包下的。
package com.miaoshaproject.validator;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.Map;
public class ValidatorResult {
// 校验结果是否有错
private boolean hasErrors = false;
// 存放错误信息的map
private Map<String, String> errorMsgMap = new HashMap<>();
public boolean isHasErrors() {
return hasErrors;
}
public void setHasErrors(boolean hasErrors) {
this.hasErrors = hasErrors;
}
public Map<String, String> getErrorMsgMap() {
return errorMsgMap;
}
public void setErrorMsgMap(Map<String, String> errorMsgMap) {
this.errorMsgMap = errorMsgMap;
}
// 实现通用的通过格式化字符串信息获取错误结果的msg方法
public String getErrMsg() {
return StringUtils.join(errorMsgMap.values().toArray(),",");
}
}
package com.miaoshaproject.validator;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Set;
import java.util.function.Consumer;
@Component
public class ValidatorImpl implements InitializingBean {
private Validator validator;
// 实现校验方法并返回校验结果
public ValidatorResult validate(Object bean) {
final ValidatorResult validatorResult = new ValidatorResult();
Set<ConstraintViolation<Object>> violationSet = validator.validate(bean);
if(violationSet.size() > 0) {
validatorResult.setHasErrors(true);
violationSet.forEach(violation -> {
String errMsg = violation.getMessage();
String propertyName = violation.getPropertyPath().toString();
validatorResult.getErrorMsgMap().put(propertyName, errMsg);
});
}
return validatorResult;
}
@Override
public void afterPropertiesSet() throws Exception {
// 将hibernate validator通过工厂的初始化方式使其实例化
this.validator = Validation.buildDefaultValidatorFactory().getValidator();
}
}
对应的controller
package com.miaoshaproject.controller;
import com.miaoshaproject.controller.viewobject.UserVO;
import com.miaoshaproject.error.BusinessException;
import com.miaoshaproject.error.EmBussinessError;
import com.miaoshaproject.response.CommonReturnType;
import com.miaoshaproject.service.UserService;
import com.miaoshaproject.service.model.UserModel;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Param;
import org.apache.tomcat.util.security.MD5Encoder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import sun.misc.BASE64Encoder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
@Controller("user")
@RequestMapping("/user")
@CrossOrigin(allowCredentials = "true", allowedHeaders = "*", originPatterns ="*")
public class UserController extends BaseController {
@Autowired
private UserService userService;
@Autowired
private HttpServletRequest httpServletRequest; // threadlocal
// 用户登录接口
@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(StringUtils.isEmpty(telphone) || StringUtils.isEmpty(password)) {
throw new BusinessException(EmBussinessError.PARAMETER_VALIDATION_ERROR);
}
//用户登录服务,用来校验用户登录是否合法
UserModel userModel = userService.validateLogin(telphone, this.EncodeByMd5(password));
//将登录凭证加入到用户登录成功的session内
this.httpServletRequest.getSession().setAttribute("IS_LOGIN",true);
this.httpServletRequest.getSession().setAttribute("LOGIN_USER", userModel);
return CommonReturnType.create(null);
}
// 用户注册接口
@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 {
// 验证手机号和对应的otpCode相符合
String inSessionOtpCode = (String) this.httpServletRequest.getSession().getAttribute(telphone);
/* debug :为什么session里没有值
Enumeration<String> attributeNames = this.httpServletRequest.getSession().getAttributeNames();
while (attributeNames.hasMoreElements()) {
String name1 = attributeNames.nextElement().toString();
System.out.println(name1 + " " + this.httpServletRequest.getSession().getAttribute(name1));
}
System.out.println(this.httpServletRequest.getSession());
System.out.println(otpCode + " " + inSessionOtpCode);
*/
if(!StringUtils.equals(otpCode, inSessionOtpCode)) {
throw new BusinessException(EmBussinessError.PARAMETER_VALIDATION_ERROR,"短信验证码不符合");
}
// 用户的注册流程
UserModel userModel = new UserModel();
userModel.setName(name);
userModel.setGender(new Byte(String.valueOf(gender.intValue())));
userModel.setAge(age);
userModel.setTelephone(telphone);
userModel.setRegisterMode("byphone");
userModel.setEncrptPassword(this.EncodeByMd5(password));
userService.register(userModel);
return CommonReturnType.create(null); // 注册成功
}
public String EncodeByMd5(String str) throws NoSuchAlgorithmException, UnsupportedEncodingException {
// 确定一个计算方法
MessageDigest md5 = MessageDigest.getInstance("MD5");
BASE64Encoder base64Encoder = new BASE64Encoder();
// 加密字符串
String encode = base64Encoder.encode(md5.digest(str.getBytes("utf-8")));
return encode;
}
// 用户获取otp短信接口
@RequestMapping(value = "/getotp", method={RequestMethod.POST},consumes={CONTENT_TYPE_FORMED})
@ResponseBody
public CommonReturnType getOtp(@RequestParam(name="telphone")String telphone) {
// 按照一定规则生成OTP验证码
Random random = new Random();
int randomInt = random.nextInt(99999);
randomInt += 10000;
String optCode = String.valueOf(randomInt);
//将OTP验证码同对应用户手机号关联
// 1、使用http session的方式绑定手机号与optcode
// 2、(redis:覆盖value,保留最近的;且有过期时间,天然使用)
httpServletRequest.getSession().setAttribute(telphone,optCode);
//将OTP验证码通过短信通道发送给用户(省略)
System.out.println("telephone = " + telphone + " &optCode = " + optCode);
return CommonReturnType.create(null);
}
@RequestMapping("/get")
@ResponseBody
public UserModel getUser(@RequestParam(name="id") Integer id) {
// url:localhost:8090/user/get?id=1
// 调用service服务获取对应id的用户对象并返回给前端 (不是dataobject对象,是通过dataobject转化得到的model对象)
// 此对象不应该直接显示到前端
UserModel userModel = userService.getUserById(id);
return userModel;
}
@RequestMapping("/get1")
@ResponseBody
public UserVO getUser1(@RequestParam(name="id") Integer id) {
// url:localhost:8090/user/get?id=1
// 调用service服务获取对应id的用户对象并返回给前端 (不是dataobject对象,是通过dataobject转化得到的model对象)
// 此对象包含需要的信息直接显示到前端
UserModel userModel = userService.getUserById(id);
// 将核心领域模型用户对象转化为可供UI使用的viewobject
return convertFromModel(userModel);
}
// 归一化返回
@RequestMapping("/get2")
@ResponseBody
public CommonReturnType getUser2(@RequestParam(name="id") Integer id) throws BusinessException {
// url:localhost:8090/user/get?id=1
// 调用service服务获取对应id的用户对象并返回给前端 (不是dataobject对象,是通过dataobject转化得到的model对象)
// 此对象包含需要的信息直接显示到前端
UserModel userModel = userService.getUserById(id);
// 若获取的对应用户信息不存在
if(userModel == null) {
// userModel.setEncrptPassword("123");
throw new BusinessException(EmBussinessError.USER_NOT_EXIST);
}
// 将核心领域模型用户对象转化为可供UI使用的viewobject
UserVO userVO = convertFromModel(userModel);
// 返回通用对象
return CommonReturnType.create(userVO);
}
private UserVO convertFromModel(UserModel userModel) {
if(userModel == null) {
return null;
}
UserVO userVO = new UserVO();
/*userVO.setId(userModel.getId());
userVO.setName(userModel.getName());
userVO.setAge(userModel.getAge());
userVO.setGender(userModel.getGender());
userVO.setTelephone(userModel.getTelephone());*/
BeanUtils.copyProperties(userModel,userVO);
return userVO;
}
}