第二章 2.1 项目整体框架 本项目采用springBoot开发,项目架构是Spring MVC框架controller层 :与前端页面UI交互的层viewobject层 :视图,封装了向前端展示的数据,避免领域模型的某些敏感数据直接传给前端dao层 :与底层数据库交互的层,进行数据库的增删改查dataobject层 :数据库表对于Java实体类的映射,是数据库表中数据的封装model层 :领域模型,将不同的表但属于一个领域的字段的整合service层 :处于上层controller与下层dao层的中间层,处理各种逻辑业务和服务请求error层 :统一错误的格式,拦截tomcat不能处理的各类异常与错误response层 :返回给前端统一的格式(response+data)
2.2 定义通用的返回对象——返回正确信息 考虑到我们的程序如果出错,返回给前端的缺少错误信息,用户体验不好,所以我们定义一个CommonReturnType类用status+data的格式来处理json序列化,避免使用HttpStatusCode和内嵌的tomcat error页
创建CommonReturnType类 在项目目录新建一个response文件夹,创建CommonReturnType类,返回的类是status(请求处理结果)+data(错误信息)格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class CommonReturnType { private String status; 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; } }
2.3 定义通用的返回对象——返回错误的信息 在项目目录下新建一个error文件,定义标准的错误返回信息(errCode+errMsg格式)
1. 创建CommonError接口 1 2 3 4 5 public interface CommonError { public int getErrCode () ; public String getErrMsg () ; public CommonError setErrMsg (String errMsg) ; }
2. 创建EmBusinessError实现类 EmBusinessError类称为包装器业务异常类,为枚举类,也有自己的成员对象和成员函数。定义通用的错误码为10001,10002未知错误等等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public enum EmBusinessError implements CommonError{ PARAMETER_VALIDATION_ERROR(10001 ,"参数不合法" ), UNKNOW_ERROR(10002 ,"未知错误" ), USER_NOT_EXIST(20001 ,"用户不存在" ), USER_LOGIN_FAIL(20002 ,"用户手机号或密码不正确" ), USER_NOT_LOGIN(20003 ,"用户还未登陆" ), STOCK_NOT_ENOUGH(30001 ,"库存不足" ) ; private int errCode; private String errMsg; private EmBusinessError (int errCode,String errMsg) { this .errCode = errCode; this .errMsg = errMsg; } @Override public int getErrCode () { return errCode; } @Override public String getErrMsg () { return errMsg; } @Override public CommonError setErrMsg (String errMsg) { this .errMsg = errMsg; return this ; } }
3. 包装器业务异常类实现 包装器业务异常类不仅可以直接接收EmBusinessError的传参用于构造业务异常,也可以接收自定义errMsg的方式构造业务异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class BusinessException extends Exception implements CommonError { private CommonError commonError; public BusinessException (CommonError commonError) { super (); this .commonError = commonError; } 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 ; } }
2.4 定义通用的返回对象——异常处理 定义ExceptionHandler 定义exceptionHandler解决未被controller层吸收的exception
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class BaseController { @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" , EmBusinessError.UNKNOWN_ERROR.getErrCode()); responseData.put("errMsg" , EmBusinessError.UNKNOWN_ERROR.getErrMsg()); } return CommonReturnType.create(responseData, "fail" ); } }
2.5 使用SpringMVC方式开发用户信息 1. 创建UserModel模型 用户信息包括用户名name,性别gender,年龄age,手机号telphone,注册码registerMode,第三方ID thirdPartId,加密密码encrptPassword
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 int age; @NotBlank (message = "手机号不能为空" ) private String telphone; @NotBlank (message = "密码不能为空" ) private String registerMode; private String thirdPartId; private String encrptPassword; }
2.创建UserController层 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @RequestMapping ("/get" ) @ResponseBody public CommonReturnType getUser (@RequestParam(name = "id" ) Integer id) throws BusinessException { UserModel userModel = userService.getUserById(id); if (userModel == null ) { throw new BusinessException(EmBusinessError.USER_NOT_EXIST); } UserVO userVO = convertFromModel(userModel); return CommonReturnType.create(userVO); } private UserVO convertFromModel (UserModel userModel) { if (userModel == null ) { return null ; } UserVO userVO = new UserVO(); BeanUtils.copyProperties(userModel,userVO); return userVO; }
通过前端传来的id,在userService中查询userModel对象,然后由于userModel中包含有用户的密码,不能直接传递给前端,所以要定义ViewObject类UserVO
1 2 3 4 5 6 7 8 public class UserVO { private Integer id; private String name; private Byte gender; private int age; private String telphone; private String thirdPartId; }
3. 创建UserService类 UserService接口
1 UserModel getUserById (Integer id) ;
UserServiceImpl实现类 UserServiceImpl实现类首先通过用户id 查找对应用户的userDO ,然后在userPasswordDOMapper 中查询对应用户的加密密码信息,最后将userDO 和userPasswordDO 整合在一起实现converFromDataObject 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Override public UserModel getUserById (Integer id) { UserDO userDO = userDOMapper.selectByPrimaryKey(id); if (userDO == null ) { return null ; } UserPasswordDO userPasswordDO = userPasswordDOMapper.selectByUserId(userDO.getId()); return convertFromDataObject(userDO,userPasswordDO); } private UserModel convertFromDataObject (UserDO userDo, UserPasswordDO userPasswordDO) { if (userDo == null ) { return null ; } UserModel userModel = new UserModel(); BeanUtils.copyProperties(userDo,userModel); if (userPasswordDO!=null ) { userModel.setEncrptPassword(userPasswordDO.getEncriptPassword()); } return userModel; }
2.6 用户模型管理——otp验证码获取 1. 创建getotp方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class UserController extends BaseController { @Autowired private UserService userService; @Autowired private HttpServletRequest httpServletRequest; @RequestMapping ("/getotp" ) @ResponseBody public CommonReturnType getOtp (@RequestParam(name = "telphone" ) String telphone) { Random random = new Random(); int randomInt = random.nextInt(99999 ); randomInt += 10000 ; String otpCode = String.valueOf(randomInt); httpServletRequest.getSession().setAttribute(telphone, otpCode); System.out.println("telphone=" + telphone + "&otpCode=" + otpCode); return CommonReturnType.create(null ); }
2. 引入Metronic模板 新建static文件夹保存模板文件,实现前端getotp.html文件,使用jQuary与Ajax实现与后端异步通信
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <html > <head > <meta charset ="UTF-8" > <script src ="static/assets/global/plugins/jquery-1.11.0.min.js" type ="text/javascript" > </script > <title > Title</title > </head > <body > <div > <h3 > 获取otp信息</h3 > <div > <label > 手机号</label > <div > <input type ="text" placeholder ="手机号" name ="telphone" id ="telphone" /> </div > </div > <div > <button id ="getotp" type ="submit" > 获取otp短信 </button > </div > </div > </body > <script > jQuery(document ).ready(function ( ) { $("#getotp" ).on("click" ,function ( ) { var telphone=$("#telphone" ).val(); if (telphone==null || telphone=="" ) { alert("手机号不能为空" ); return false ; } $.ajax({ type:"POST" , contentType:"application/x-www-form-urlencoded" , url:"http://localhost:8080/user/getotp" , data:{ "telphone" :$("#telphone" ).val(), }, success:function (data) { if (data.status=="success" ) { alert("otp已经发送到了您的手机,请注意查收" ); }else { alert("otp发送失败,原因为" + data.data.errMsg); } }, error:function (data) { alert("otp发送失败,原因为" +data.responseText); } }); }); }); </script > </html >
进行测试,测试controller层getotp方法,但是发送失败,出现以下错误:
1 getotp.html?_ijt=cqdae6hmhq9069c9s4muooakju:1 Access to XMLHttpRequest at 'http://localhost:8080/user/getotp' from origin 'http://localhost:63342' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
经过分析发现是出现了跨域请求错误 解决办法是在Controller层上加入@CrossOrigin 注解
2.7 用户模型管理——注册功能实现 1.Controller层部分 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @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" ) Byte gender, @RequestParam (name="age" ) Integer age, @RequestParam (name="password" ) String password) throws BusinessException, UnsupportedEncodingException, NoSuchAlgorithmException { String inSessionOptCode = (String) this .httpServletRequest.getSession().getAttribute(telphone); if (!StringUtils.equals(inSessionOptCode,otpCode)){ throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"短信验证码不符合" ); } UserModel userModel = new UserModel(); userModel.setName(name); userModel.setAge(age); userModel.setGender(gender); userModel.setTelphone(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 newstr = base64Encoder.encode(md5.digest(str.getBytes("utf-8" ))); return newstr; }
引入做输入校验的依赖
1 2 3 4 5 <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-lang3</artifactId > <version > 3.7</version > </dependency >
2. Service层部分 UserService接口
1 void register (UserModel userModel) throws BusinessException ;
UserServiceImpl实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Override @Transactional public void register (UserModel userModel) throws BusinessException { if (userModel == null ) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR); } ValidationResult result = validator.validate(userModel); if (result.isHasErrors()) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,result.getErrMsg()); UserDO userDO = convertFromModel(userModel); try { userDOMapper.insertSelective(userDO); }catch (DuplicateKeyException ex) { throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"手机号已重复注册" ); } userModel.setId(userDO.getId()); UserPasswordDO userPasswordDO = convertPasswordFromModel(userModel); userPasswordDOMapper.insertSelective(userPasswordDO); return ; }
注意的有以下方面:
数据库增的请求不用insert而用insertSelective ,因为insertSelective允许输入为空
跨域请求要加上@CrossOrigin(allowCredentials = “true”,allowedHeaders = “*”) 实现session共享
为了保证数据库手机号的唯一性,还要在数据库添加UNIQUE索引
3. 前端页面 在getotp页面添加注册成功的跳转页面
1 2 3 4 5 6 7 8 success:function (data ) { if (data.status=="success" ) { alert("otp已经发送到了您的手机,请注意查收" ); window .location.href="register.html" ; }else { alert("otp发送失败,原因为" + data.data.errMsg); } },
2.8 用户模型管理——登陆功能实现 1. Controller层部分 用户登陆的主要流程包括:
入参校验
校验用户登陆是否合法
将登陆凭证加入用户登陆成功的session中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @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.PARAMETER_VALIDATION_ERROR); } UserModel userModel = userService.validateLogin(telphone,this .EncodeByMd5(password)); this .httpServletRequest.getSession().setAttribute("IS_LOGIN" ,true ); this .httpServletRequest.getSession().setAttribute("LOGIN_USER" ,userModel); return CommonReturnType.create(null ); }
2. Service部分 Service接口
1 UserModel validateLogin (String telphone,String encrptPassword) throws BusinessException ;
ServiceImpl实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override public UserModel validateLogin (String telphone, String encrptPassword) throws BusinessException { UserDO userDO = userDOMapper.selectByTelphone(telphone); if (userDO == null ) { throw new BusinessException(EmBusinessError.USER_LOGIN_FAIL); } UserPasswordDO userPasswordDO = userPasswordDOMapper.selectByUserId(userDO.getId()); UserModel userModel = convertFromDataObject(userDO,userPasswordDO); if (!StringUtils.equals(encrptPassword,userPasswordDO.getEncriptPassword())) { throw new BusinessException(EmBusinessError.USER_LOGIN_FAIL); } return userModel; }
2.9 优化校验规则 1. 引入hibernate库 引入hibernate库来实现validator方法
1 2 3 4 5 6 <dependency > <groupId > org.hibernate</groupId > <artifactId > hibernate-validator</artifactId > <version > 5.2.4.Final</version > </dependency >
新建validator文件夹目录
2. 新建ValidationResult类返回校验结果集 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class ValidationResult { private boolean hasErrors = false ; 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; } public String getErrMsg () { return StringUtils.join(errorMsgMap.values().toArray(), "," ); } }
3. 新建ValidatorImpl实现类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Component public class ValidatorImpl implements InitializingBean { private Validator validator; public ValidationResult validate (Object bean) { final ValidationResult result = new ValidationResult(); Set<ConstraintViolation<Object>> constraintViolationSet = validator.validate(bean); if (constraintViolationSet.size() > 0 ) { result.setHasErrors(true ); constraintViolationSet.forEach(constraintViolation ->{ String errMsg = constraintViolation.getMessage(); String propertyName = constraintViolation.getPropertyPath().toString(); result.getErrorMsgMap().put(propertyName, errMsg); }); } return result; } @Override public void afterPropertiesSet () throws Exception { this .validator = Validation.buildDefaultValidatorFactory().getValidator(); } }
至此以后校验模型model属性时加上相应的注解即可