一,基于Session实现登录
思维导图 
1.1 发送短信验证码
-
控制层 :
java@Resource private IUserService userService; /** * 发送手机验证码 */ @PostMapping("code") public Result sendCode(@RequestParam("phone") String phone, HttpSession session) { // 发送短信验证码并保存验证码 return userService.sendCode(phone,session); } -
业务层
java@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.info("发送短信验证码成功,验证码:{}",code); //返回ok return Result.ok(); }
1.2 验证码登录
-
数据接收对象
java@Data public class LoginFormDTO { //手机号 private String phone; //验证码 private String code; //密码(因为支持密码登录也支持验证码登录) private String password; } -
控制层
java/** * 登录功能 * @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码 */ @PostMapping("/login") public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){ //实现登录功能 return userService.login(loginForm,session); } -
业务层
java@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"); if(cacheCode==null || !cacheCode.toString().equals(loginForm.getCode())){ //3.不一致,报错 return Result.fail("验证码错误!"); } //4.一致,根据手机号查询用户 User user = query().eq("phone", phone).one(); //5.判断用户是否存在 if(user==null){ //6.不存在,则创建新用户并保持 user=createUserWithPhone(phone); } //6.保持用户信息到session中 session.setAttribute("user",user); return Result.ok(); } private User createUserWithPhone(String phone) { //1.创建用户 User user = new User(); user.setPhone(phone); user.setNickName("User_"+RandomUtil.randomNumbers(10)); //2.保存用户 save(user); return user; }
1.3 登录验证功能
拦截器 实现登录校验 
java
public class LoginInterceptor implements HandlerInterceptor {
private static final ThreadLocal<User> t1= new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1. 获取session
HttpSession session = request.getSession();
//2. 获取session中的用户
Object user = session.getAttribute("");
//3.判断用户是否存在
if(user == null){
//4. 不存在,拦截,返回401状态码
response.setStatus(401);
return false;
}
//5. 存在,保存用户信息到ThreadLocal
t1.set( (User) user);
//6. 放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除用户
t1.remove();
}
}
二,集群的Session共享问题

三,基于Redis代替Session实现共享登录

3.1 用Redis替换Session发送短信验证码
唯一的区别就是将code存储从session变成了redis里面存储
java
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result sendCode(String phone, HttpSession session) {
//1.校验手机号
if(RegexUtils.isPhoneInvalid(phone)){
//2.如果不符合返回错误信息
return Result.fail("手机号格式错误!");
}
//3.符合就生成验证码
String code = RandomUtil.randomNumbers(6);
//4.保存验证码到redis中
stringRedisTemplate.opsForValue().set("login:code"+phone,code,2, TimeUnit.MINUTES);
//5.发送验证码(需要调用阿里云短信发送三方平台,不是重点,跳过即可)
log.info("发送短信验证码成功,验证码:{}",code);
//返回ok
return Result.ok();
}
3.2 用Redis替换Session验证码登录,注册
区别就是将用户数据存到了Redis里面而不是Session里面
java
public Result login(LoginFormDTO loginForm, HttpSession session) {
//1.校验手机号
String phone = loginForm.getPhone();
if(RegexUtils.isPhoneInvalid(phone)){
//2.如果不符合,返回错误信息
return Result.fail("手机号错误!");
}
//3.从redis中获取code进行校验
String cacheCode = stringRedisTemplate.opsForValue().get("login:code" + phone);
if(cacheCode==null || !cacheCode.equals(loginForm.getCode())){
//4.不一致,报错
return Result.fail("验证码错误!");
}
//5.一致,根据手机号查询用户
User user = query().eq("phone", phone).one();
//6.判断用户是否存在
if(user==null){
//7.不存在,则创建新用户并保持
user=createUserWithPhone(phone);
}
//8.保持用户信息到redis中
//生成随机token作为登录令牌
String token = UUID.randomUUID().toString();
//将User对象转化为hash存储
UserDTO userDto=BeanUtil.copyProperties(user, UserDTO.class);
//我们用的是StringRedisTemplate,他要求key和value都是String类型
//我们的这个UserDTO里面的id是Long类型的,直接转会失败,需要我们手动修改
Map<String, Object> userMap = BeanUtil.beanToMap(userDto,new HashMap<>(),
//创建做出拷贝时的选项
CopyOptions.create()
//忽略一些空的值
.setIgnoreNullValue(true)
//对字段值的修改器
.setFieldValueEditor(
//将Long修改为String类型
(fieldName,fieldValue)-> fieldValue.toString()
)
);
stringRedisTemplate.opsForHash().putAll("login:token:"+token,userMap);
//设置key的有效期为30分钟
stringRedisTemplate.expire("login:token:"+token,30,TimeUnit.MINUTES);
//返回token
return Result.ok(token);
}
3.3 用Redis替换Session登录验证功能
java
public class LoginInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
//构造方法注入,需要在拦截器配置里那边传入
public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1. 获取请求头中的token
String token = request.getHeader("authorization");
if(StrUtil.isBlank(token)){
//不存在,拦截返回401状态码
response.setStatus(401);
return false;
}
//2.基于token获取user信息
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries("login:token:" + token);
//3.判断用户是否存在
if(userMap.isEmpty()){
response.setStatus(401);
return false;
}
//4.将Map转化为UserDto对象
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
//5.将用户信息存储到ThreadLocal
UserHolder.saveUser(userDTO);
//6.刷新token有效期
stringRedisTemplate.expire("login:token:" + token,30, TimeUnit.MINUTES);
//7.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除用户
UserHolder.removeUser();
}
}