黑马点评学习笔记04(Redis 实现登陆界面)
本文探讨了传统Session方式在登录验证中的局限性,并介绍了基于Redis的实现方案。传统Session存在集群环境不兼容、移动端不支持、用户禁用Cookie等问题。文章通过对比流程图展示了两种方案的差异,并提供了完整的代码实现,包括发送验证码、登录验证等功能。Redis方案通过存储验证码和用户信息到Redis,解决了Session的固有缺陷,同时使用随机生成的token作为登录凭证,提高了系统
·
前言
前面写了基于Session实现登录,我们都知道这种传统的会话技术,在现在的企业开发当中是不是会存在很多的问题:
- 服务器集群环境下无法直接使用Session
- 移动端APP(Android、IOS)中无法使用Cookie
- 用户可以自己禁用Cookie
- Cookie不能跨域(Session 底层是基于Cookie实现的会话跟踪,如果Cookie不可用,则该方案,也就失效了)
来看看基于Redis实现登陆界面
来看看二者流程图对比:

来看看登陆界面的代码实现(我把Session的实现代码没删掉都注释起来了):
UserController类 :
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private IUserService userService;
/**
* 发送手机验证码
*/
@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
// TODO 发送短信验证码并保存验证码
return userService.sendCode(phone, session);
}
/**
* 登录功能
* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
*/
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
// TODO 实现登录功能
return userService.login(loginForm, session);
}
IUserService 接口:
package com.hmdp.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.entity.User;
import javax.servlet.http.HttpSession;
/**
* <p>
* 服务类
* </p>
*
* @author 虎哥
* @since 2021-12-22
*/
public interface IUserService extends IService<User> {
Result sendCode(String phone, HttpSession session);
Result login(LoginFormDTO loginForm, HttpSession session);
}
IUserService 接口实现类:
package com.hmdp.service.impl;
import
/**
* <p>
* 服务实现类
* </p>
*
* @author 虎哥
* @since 2021-12-22
*/
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@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.保存验证码到session
// session.setAttribute("code", code);
//4.保存验证码到redis
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
//5.发送验证码
System.out.println("发送短信验证码成功,验证码:" + code);
//5.返回ok
return Result.ok();
}
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//1.校验手机号
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) {
// 2.如果不符合,返回错误信息
return Result.fail("手机号格式错误!");
}
//2.校验验证码 不一致直接报错 从session中获取
// Object cachecode = session.getAttribute("code");
// String code = loginForm.getCode();
// if (cachecode == null || !cachecode.toString().equals(code)) {
// return Result.fail("验证码错误!");
// }
//2.校验验证码 从redis中获取 不一致直接报错
String cachecode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
String code = loginForm.getCode();
if (cachecode == null || !cachecode.toString().equals(code)) {
return Result.fail("验证码错误!");
}
//3.一致,根据手机号查询用户 select * from tb_user where phone = ?
User user = query().eq("phone", phone).one();
//4.判断用户是否存在
if (user == null) {
//5.不存在,创建新用户
user = createUserWithPhone(phone);
}
//6.存在,保存用户信息到session
// session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
//6.存在,保存用户信息到redis
//6.1 随机生成token,作为登录令牌
String token = UUID.randomUUID().toString(true);
//6.2 将用户对象转为hash存储
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
CopyOptions.create()
.setIgnoreNullValue( true)
.setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));
//6.3 存储
String tokenkey = LOGIN_USER_KEY + token;
stringRedisTemplate.opsForHash().putAll( tokenkey, userMap);
//6.4设置token有效期
stringRedisTemplate.expire( tokenkey, LOGIN_USER_TTL, TimeUnit.MINUTES);
//6.4返回token
return Result.ok(token);
}
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;
}
}
拦截器:
package com.hmdp.utils;
import ...
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.获取请求头中的 token
String token = request.getHeader("authorization");
if (token == null || token.isEmpty()) {
//4.不存在,拦截,返回401状态码
response.setStatus(401);
return false;
}
//2.基于token获取redis中的用户
String key = RedisConstants.LOGIN_USER_KEY + token;
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
//3.判断用户是否存在
if (userMap.isEmpty()) {
//4.不存在,拦截,返回401状态码
response.setStatus(401);
return false;
}
//4.存在,将查询到的Hash数据转为UserDTO对象,保存用户信息到ThreadLocal
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
UserHolder.saveUser( userDTO );
//6.刷新 token有效期
stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
//7.放行
return true;
}
}
🧩 Redis 知识点全面解析(基于代码)
一、Redis 数据结构的使用
- String 类型:用于存储验证码
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
- 数据结构:String
- Key:login:code:13812345678 //手机号码为Key
- Value:“123456”(验证码)
- TTL:设置过期时间
- Hash 类型:用于存储用户登录信息
stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
- 数据结构:Hash
- Key:login:token:abc123-def456…
- Value:{ “id”: “1”, “phone”: “138…”, “nickName”: “user_xxx” }
- Key 命名建议使用冒号分隔(namespace 风格)
package com.hmdp.utils;
public class RedisConstants {
public static final String LOGIN_CODE_KEY = "login:code:";
public static final Long LOGIN_CODE_TTL = 2L;
public static final String LOGIN_USER_KEY = "login:token:";
public static final Long LOGIN_USER_TTL = 36000L;
public static final Long CACHE_NULL_TTL = 2L;
}
- Token 设置过期时间
stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES); // 例如 30 分钟
实现“自动退出”功能。
避免用户长期登录占用内存。
二、Bean → Map 转换与 Hash 存储
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString())
);
这行代码的作用是:
将一个 Java 对象(userDTO)转换为一个 Map<String, Object>,并进行自定义处理:忽略 null 值字段,并将所有字段值转为字符串。
这里也用到了Hutool
本文是学习黑马程序员—黑马点评项目的课程笔记,小白啊!!!写的不好轻喷啊🤯如果觉得写的不好,点个赞吧🤪(批评是我写作的动力)
…。。。。。。。。。。。…
…。。。。。。。。。。。…
网易易盾是国内领先的数字内容风控服务商,依托网易二十余年的先进技术和一线实践经验沉淀,为客户提供专业可靠的安全服务,涵盖内容安全、业务安全、应用安全、安全专家服务四大领域,全方位保障客户业务合规、稳健和安全运营。
更多推荐


所有评论(0)