开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情
1.内容概述
最近在学习黑马的一些redis课程,其中有一个内容是利用redis来进行短信登录。在引入这部分内容之前,先介绍了如何用session实现短信登录,以及使用session的不足之处。再此对这部分知识进行梳理。
2.实现思路
2.1发送验证码
用手机号码进行登录或者注册,那么我们便需要验证用户是否拥有这个手机号,所以我们需要准备一个接口来生成验证码和发送验证码。具体流程如下所示:
伪代码如下:
说明:
1.校验手机验证码用的正则表达式工具类可以自己写,如果觉得麻烦可以参考其他同学的代码; 2.生成验证码用的也是工具类,这也可以在网上找到许多参考代码;
3.因为我们将验证码存放在session对象里面,所以接口应该有两个参数,一个接收手机号码,一个是HttpSession对象
@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
// TODO 发送短信验证码并保存验证码
return userService.sendCode(phone,session);
}
//发送验证码
@Override
public Result sendCode(String phone, HttpSession session) {
//1.校验手机号
if (RegexUtils.isPhoneInvalid(phone)){
//2.如果不符合,返回错误信息
return Result.fail("手机号格式错误");
}
//3.符合,生成验证码
String code = RandomUtil.randomNumbers(6);
//4. 保存验证码到session
session.setAttribute("code",code);
//5.发送验证码
log.debug("发送短信验证码成功,验证码:{}",code);
// 返回ok
return Result.ok();
}
2.2短信验证码登录或注册
在用户用手机号码和验证码进行登录的时候,我们可以判断用户是否已经注册,如果没有注册,我们便帮用户注册,如果用户已经注册,那么我们将用户信息存放到session对象里面,具体流程如下:
伪代码
说明:
- 校验验证码的方式是根据用户提交的验证码和生成验证码时保存在session里的验证码进行比对
- 如果用户未注册的话,我们可以帮用户进行注册,注册的时候只需保存用户手机号和一个随机的昵称即可。用户密码之类的内容可以让用户随后自己设置
- 用户注册成功或者在数据库查询得到,我们便将这个用户的信息保存到session对象中。原因是:以后判断用户是否登录,我们是从session中能否取出用户来进行判断的。有些同学可能发现了,保存到session对象的用户信息我们进行了一个转化,这里把用户信息转化到了UserDTO对象里面,原因是如果直接保存用户的所有信息可能有安全隐患,所以我们用hutool-all提供的工具类,将user对象的一些信息封装到UserDTO对象里面,并将UserDTO对象保存到session对象里面。
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
// 实现登录功能
return userService.login(loginForm,session);
}
//登录
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//1.校验手机号
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)){
//2.如果不符合,返回错误信息
return Result.fail("手机号格式错误");
}
//2.校验验证码
Object cacheCode = session.getAttribute("code");
String code = loginForm.getCode();
if (cacheCode == null | !cacheCode.toString().equals(code)) {
// 3.不一致,报错
return Result.fail("验证码错误");
}
//4.一致,根据手机号查询用户 select * from tb_user where phone = ?
User user = query().eq("phone", phone).one();
//5.判断用户是否存在
if (user == null){
//6.不存在,创建用户并保存
user = createUserWithPhone(phone);
}
//7.保存用户信息到session
session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
return Result.ok();
}
//创建用户
private User createUserWithPhone(String phone) {
//1.创建用户
User user = new User();
user.setPhone(phone);
user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
//2.保存用户
save(user);
return user;
}
2.3 拦截器
如果需要使用一些登录之后才能进行操作的功能时,我们总是需要再写一遍获取session对象来判断用户是否登录,那么会很麻烦。为此,我们创建一个拦截器,将登录等少数无需登录即可使用对应功能的接口放通,其他接口在拦截器中拦截下来,统一在拦截器里用session来判断用户是否已经登录。
伪代码:
说明:
- 拦截器的功能很简单,就是当用户操作的时候,根据拦截器的配置,将某些接口拦截下来并判断是否用户已经登录,如果登录,那边放行,否则拦截
- 有同学可能看到拦截器里有一个UserHolder类。这个类其实封装了ThreadLocal类,目的是保存用户信息,以后如果有接口需要获取用户信息,那么便可以通过UserHolder获取用户信息。
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new Logininterceptor())
.excludePathPatterns(
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
);
}
}
@Slf4j
public class Logininterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.获取session
HttpSession session = request.getSession();
//2.获取session中的用户
Object user = session.getAttribute("user");
//3.判断用户是否存在
if (user == null) {
//4.不存在,拦截
response.setStatus(401);
return false;
}
//5.存在,保存用户信息到ThreadLocal
UserHolder.saveUser((UserDTO) user);
//6.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 移除用户
UserHolder.removeUser();
}
}
2.4获取用户信息
说明:在拦截器中,如果用户已经登录,我们便将用户信息保存到ThreadLocal里面,所以获取用户信息,直接通过ThreadLocal获取即可。在本例中,UserHolder封装了ThreadLocal。
@GetMapping("/me")
public Result me(){
// TODO 获取当前登录的用户并返回
UserDTO user = UserHolder.getUser();
return Result.ok(user);
}
3.不足之处和解决办法
如果采用了Session来进行用户信息,短信验证码的保存,并以此来判断用户是否登录,在非集群的模式下是可以的。如果是集群模式的话,那么便产生了新的问题,问题在于如何进行session的共享。其实为了解决这个问题,我们可以用redis来代替session,这样子的话,无论你是否是集群的布置项目,你只需将用户信息,短信验证码等保存在redis。需要这些信息时,在redis里获取即可。