用户模块
功能:
1.登录
controller层实现:
从前端获取传入的用户名和密码,然后通过service进行具体的业务层校验,如果通过校验,则将此时的sessionId作为token值写入cookie中,然后将登录信息写入redis中,key是sessionId,value是具体的登录信息,并设置有效期。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 @RequestMapping(value = "login.do", method = RequestMethod.POST) 2 @ResponseBody 3 public ServerResponse<User> login(String username, String password, HttpSession session, HttpServletResponse httpServletResponse) { 4 ServerResponse<User> response = iUserService.login(username, password); 5 if(response.isSuccess()) { 6 CookieUtil.writeLoginToken(httpServletResponse, session.getId()); 7 //sessionId是key,用户的登录信息是value,存在redis中 8 //sessionId是tomcat中自动生成的,针对当前项目,每启动一次,sessionId就变一次 9 //设置session超时时间 10 RedisShardedPoolUtil.setEx(session.getId(), JsonUtil.obj2String(response.getData()), Const.RedisCacheExtime.REDIS_SESSION_EXTIME); 11 12 } 13 return response; 14 }
service层实现:
先校验用户名是否存在,如果存在,再将输入的密码进行md5加密,再去校验密码是否正确,再返回校验结果。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 public ServerResponse<User> login(String username, String password) { 2 //校验用户名是否存在,如果存在,查询结果会返回1,因为用户名唯一;否则返回0 3 int resultCount = userMapper.checkUsername(username); 4 if (resultCount == 0) { 5 return ServerResponse.createByErrorMessage("用户名不存在"); 6 } 7 //密码登录MD5,将密码加密后再与数据库中的密码进行比对,因为数据库中存的不是明文密码,是md5加密后的密码 8 String md5Password = MD5Util.MD5EncodeUtf8(password); 9 //这里不是根据用户名校验密码是否正确,而是将用户名和密码同时传入dao层,在数据库中一起校验是否有当前用户存在,如果存在表明校验通过返回该用户对象 10 User user = userMapper.selectLogin(username, md5Password); 11 if (user == null) { 12 return ServerResponse.createByErrorMessage("密码错误"); 13 } 14 //将返回给controller的密码置空,是为了不让其显示出来 15 user.setPassword(StringUtils.EMPTY); 16 //校验通过,返回user对象给controller进行显示 17 return ServerResponse.createBySuccess("登录成功", user); 18 }
2.注册
controller层实现:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 @RequestMapping(value = "register.do", method = RequestMethod.POST) 2 @ResponseBody 3 public ServerResponse<String> register(User user) { 4 return iUserService.register(user); 5 }
service层实现:
先校验用户名和email的唯一性,这个唯一性是由service层保证的,而不是数据库层面由unique决定的,然后对密码进行加密后,进入dao层插入数据库进行持久化操作。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 public ServerResponse<String> register(User user) { 2 //因为下面的校验用户名和email具有相同的逻辑,所以可以集成放在一个函数中,即checkValid函数,用第二个参数辨别传入的是用户名还是email,然后再校验 3 //校验用户名是否已经存在 4 ServerResponse validResponse = this.checkValid(user.getUsername(), Const.USERNAME); 5 if (!validResponse.isSuccess()) { 6 return validResponse; 7 } 8 //校验email是否已经存在 9 validResponse = this.checkValid(user.getEmail(), Const.EMAIL); 10 if (!validResponse.isSuccess()) { 11 return validResponse; 12 } 13 //设置用户角色 14 //这里是由前端决定注册的是普通用户还是管理员角色,因为不同的角色前端跳转的后台url是不一样的,以此后台来区分角色,所以这里直接将角色赋为customer。 15 user.setRole(Const.Role.ROLE_CUSTOMER); 16 //MD5加密,加密后再存入数据库 user.setPassword(MD5Util.MD5EncodeUtf8(user.getPassword())); 17 //进入dao层进行插入操作 18 int resultCount = userMapper.insert(user); 19 if (resultCount == 0) { 20 return ServerResponse.createByErrorMessage("注册失败"); 21 } 22 return ServerResponse.createBySuccessMessage("注册成功"); 23 }
3.提交问题答案:
controler层实现:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 @RequestMapping(value = "forget_check_answer.do", method = RequestMethod.POST) 2 @ResponseBody 3 public ServerResponse<String> forgetCheckAnswer(String username, String question, String answer) { 4 return iUserService.checkAnswer(username, question, answer); 5 }
service层实现:
先校验问题的答案是否正确,如果正确,则生成一个UUID作为token值,存入redis中缓存,并设置有效期,然后将token值返回。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 public ServerResponse<String> checkAnswer(String username, String question, String answer) { 2 //校验问题答案是否正确 3 int resultCount = userMapper.checkAnswer(username, question, answer); 4 if(resultCount > 0) { 5 //说明问题及问题答案是这个用户的,并且是正确的 6 //利用UUID创建无重复token 7 String forgetToken = UUID.randomUUID().toString(); 8 //把token放入本地cache中,然后设置其有效期 9 // TokenCache.setKey(TokenCache.TOKEN_PREFIX + username, forgetToken); 10 11 RedisShardedPoolUtil.setEx(Const.TOKEN_PREFIX + username, forgetToken, 60 * 60 * 12); 12 13 //为什么这里调用的是泛型参数函数而不是string参数函数 14 //因为函数名不同啊。。。 15 return ServerResponse.createBySuccess(forgetToken); 16 } 17 return ServerResponse.createByErrorMessage("问题的答案错误"); 18 }
4.重置密码:
1)登录状态下重置密码
2)忘记密码后重置密码:如果不传入token,则很容易发生横向越权,即可以传任意用户名,然后将其密码重置,不需要通过问题校验。
controler层实现:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 @RequestMapping(value = "forget_reset_password.do", method = RequestMethod.POST) 2 @ResponseBody 3 public ServerResponse<String> forgetResetPassword(String username, String passwordNew, String forgetToken) { 4 return iUserService.forgetResetPassword(username, passwordNew, forgetToken); 5 }
service层实现:
先校验token是否正确传入,然后校验用户名是否存在(因为是忘记密码重置),然后从redis中取出缓存的token值进行比对,如果相同则说明是同一用户,对新密码进行加密,然后持久化到数据库。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 public ServerResponse<String> forgetResetPassword(String username, String passwordNew, String forgetToken) { 2 //校验token是否已经传递过来 3 if(StringUtils.isBlank(forgetToken)) { 4 return ServerResponse.createByErrorMessage("参数错误,token需要传递"); 5 } 6 //校验用户名是否存在 7 ServerResponse validResponse = this.checkValid(username, Const.USERNAME); 8 if(validResponse.isSuccess()) { 9 //用户不存在 10 return ServerResponse.createByErrorMessage("用户不存在"); 11 } 12 //从cache中获取token,根据key拿到value值 13 // String token = TokenCache.getKey(TokenCache.TOKEN_PREFIX + username); 14 15 String token = RedisShardedPoolUtil.get(Const.TOKEN_PREFIX + username); 16 17 //校验cache中的token是否有效 18 if(StringUtils.isBlank(token)) { 19 return ServerResponse.createByErrorMessage("token无效或者过期"); 20 } 21 //校验传入的forgetToken和token是否相同 22 //这里用stringUtils可以避免string.equals中string出现null的问题,即使这里传入null也是可以接受的,不会报错 23 if(StringUtils.equals(forgetToken, token)) { 24 //将新输入的密码进行md5加密后再更新到数据库中 25 String md5Password = MD5Util.MD5EncodeUtf8(passwordNew); 26 int rowCount = userMapper.updatePasswordByUsername(username, md5Password); 27 if(rowCount > 0) { 28 return ServerResponse.createBySuccessMessage("修改密码成功"); 29 } 30 } 31 else { 32 return ServerResponse.createByErrorMessage("token错误,请重新获取重置密码的token"); 33 } 34 return ServerResponse.createByErrorMessage("修改密码失败"); 35 }
5.获取用户信息
controller层实现:
先从cookie中获取是否登录信息,然后拿到这个sessionId后,去redis中根据这个sessionId,拿到user对象,根据userId从数据库中获取到具体的用户信息,返回给前端。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 @RequestMapping(value = "get_information.do", method = RequestMethod.POST) 2 @ResponseBody 3 public ServerResponse<User> getInformation(HttpServletRequest request) { 4 5 String loginToken = CookieUtil.readLoginToken(request); 6 if(StringUtils.isEmpty(loginToken)) { 7 return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户的信息"); 8 } 9 String userJsonStr = RedisShardedPoolUtil.get(loginToken); 10 User currentUser = JsonUtil.string2Obj(userJsonStr, User.class); 11 12 if(currentUser == null) { 13 return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(), "未登录,需要强制登录status=10"); 14 } 15 return iUserService.getInformation(currentUser.getId()); 16 }
6.更新用户信息
7.退出登录
掌握:
4.高复用服务响应对象的设计思想及抽象封装
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /** 2 * Created by cq on 2017/10/31. 3 */ 4 @JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL) 5 //保证序列化json的时候,如果是null的对象,key也会消失 6 //比如在创建失败的时候,只需要将status和msg显示出来,但是如果不加上面的注解会将data也显示出来,其值会显示null 7 public class ServerResponse<T> implements Serializable{ 8 9 private int status; 10 private String msg; 11 private T data; 12 13 private ServerResponse(int status) { 14 this.status = status; 15 } 16 //如果第二个参数不是string类型调用下面这个构造器 17 private ServerResponse(int status, T data) { 18 this.status = status; 19 this.data = data; 20 } 21 //如果第二个参数是string类型调用下面这个构造器 22 private ServerResponse(int status, String msg) { 23 this.status = status; 24 this.msg = msg; 25 } 26 private ServerResponse(int status, String msg, T data) { 27 this.status = status; 28 this.msg = msg; 29 this.data = data; 30 } 31 32 @JsonIgnore 33 //使之不在json序列化结果当中 34 //判断响应是否成功 35 public boolean isSuccess() { 36 return this.status == ResponseCode.SUCCESS.getCode(); 37 } 38 39 //下面三个get开头的public方法在json序列化时都会显示出来供前端看到 40 //数据格式显示也就如下: 41 /* 42 * {status:xxx, 43 * msg:xx, 44 * data:xxx} 45 * */ 46 public int getStatus() { 47 return status; 48 } 49 50 public T getData() { 51 return data; 52 } 53 54 public String getMsg() { 55 return msg; 56 } 57 58 //成功 59 //返回值是泛型<T>,传入0表示创建是成功的 60 public static <T> ServerResponse<T> createBySuccess() { 61 return new ServerResponse<T>(ResponseCode.SUCCESS.getCode()); 62 } 63 64 //创建成功传入一个msg再返回 65 public static <T> ServerResponse<T> createBySuccessMessage(String msg) { 66 return new ServerResponse<T>(ResponseCode.SUCCESS.getCode(), msg); 67 } 68 69 //创建成功传入data再返回 70 public static <T> ServerResponse<T> createBySuccess(T data) { 71 return new ServerResponse<T>(ResponseCode.SUCCESS.getCode(), data); 72 } 73 74 //如果参数是string类型,但是应该把其赋给data而不是msg该怎么做? 75 //调用下面的public方法同时传入msg和data即可 76 //创建成功传入一个msg和data再返回 77 public static <T> ServerResponse<T> createBySuccess(String msg, T data) { 78 return new ServerResponse<T>(ResponseCode.SUCCESS.getCode(), msg, data); 79 } 80 81 //失败 82 public static <T> ServerResponse<T> createByError() { 83 return new ServerResponse<T>(ResponseCode.ERROR.getCode(), ResponseCode.ERROR.getDesc()); 84 } 85 86 //创建失败传入一个错误提示信息errorMessage 87 public static <T> ServerResponse<T> createByErrorMessage(String errorMessage) { 88 return new ServerResponse<T>(ResponseCode.ERROR.getCode(), errorMessage); 89 } 90 91 //创建失败手动传入一个code和errorMessage 92 public static <T> ServerResponse<T> createByErrorCodeMessage(int errorCode, String errorMessage) { 93 return new ServerResponse<T>(errorCode, errorMessage); 94 } 95 96 }
5.session的使用
6.json注解使用