Redis的共享session应用
1. 项目的相关工作
导入sql文件
找到对应的sql文件即可
基本表的信息
基本架构
导入对应的项目文件,启动相关的service服务;
在nginx-1.18.0目录下启动命令行start nginx.exe;
2. 基于session实现登录的流程
这里利用到Javaweb中cookie和session的内容;
Javaweb中的对登录状态的校验:
Tomcat会自动把id当做cookie,发送个客户端浏览器;
从而浏览器解析并存入内存中;
然后该会话下一次请求时,会携带该cookie信息再次访问,并设置请求头id=**;
服务端就会内存中找是否有id相同的session完成校验登录状态的功能;
Redis采用缓存,判断用户存在之后将用户信息缓存到ThreadLocal
中
3. 实现发送短信验证码功能
写UserService的实现类,实现发送验证码的功能;
其中校验格式和生成验证码分别采用RegexUtils和RandomUtil工具类;
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public Result sendCode(String phone, HttpSession session) {
// 1. 校验手机号
if (RegexUtils.isPhoneInvalid(phone)) {
// 2. 如果不符合,返回错误信息(采用RegexUtils的isPhoneInvalid()方法,正则表达式判断手机格式是否正确)
return Result.fail("手机号格式错误");
}
// 3. 符合,生成验证码(hutool工具类中到的随机生成方法)
String code = RandomUtil.randomNumbers(6);
// 4. 保存验证码
session.setAttribute("code", code);
// 5. 发送验证码(模拟发送)
log.debug("发送短信验证码成功,验证码:{}", code);
// 返回ok
return Result.ok();
}
}
4. 实现短信验证码登录和注册功能
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
// 1. 校验手机号(从表单中获取phone)
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) {
// 如果不符合,返回错误信息(采用RegexUtils的isPhoneInvalid()方法,正则表达式判断手机格式是否正确)
return Result.fail("手机号格式错误");
}
// 2. 校验验证码
// 从之前存的session中取出验证码,然后和表单中的验证码进行比较
Object cacheCode = session.getAttribute("code");
String code = loginForm.getCode();
if(cacheCode == null || !cacheCode.toString().equals(code)){
// 3. 不一致,报错(编码风格采用反向校验,避免出现菱形代码)
return Result.fail("验证码错误");
}
// 4. 一致,根据手机号查询用户(extends ServiceImpl<UserMapper, User>,继承了MP,可以直接实现单表查询)
User user = query().eq("phone", phone).one();
// 5. 判断是否用户存在
if(user == null) {
// 6. 不存在,创建一个新用户并保存
user = creatUserWithPhone(phone);
}
// 7. 存在,保存用户信息到session中
session.setAttribute("user", user);
return Result.ok();
}
private User creatUserWithPhone(String phone) {
// 1. 创建用户
User user = new User();
user.setPhone(phone);
user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
// 2. 保存用户(MP保存用户)
save(user);
return user;
}
5. 实现登录校验功能
在每个模块中都有登录校验的过程,为了实现代码简洁高效,采用拦截器的思想;
每个控制层业务中都可能需要用到拦截的用户信息,并且要保证线程安全,所以把拦截的用户信息的请求(进入Tomcat的请求都是独立的线程)存入ThreadLocal中,开辟内存空间保存线程,线程之间互不干扰;
并且,为了避免敏感信息回传给前端,只需要给出部分信息即可;
@Data
public class UserDTO {
private Long id;
private String nickName;
private String icon;
}
之前在登录login中存储在session中的user修改,使用了BeanUtil工具类;
// 7. 存在,保存用户信息到session中, 使用BeanUtil工具类,自动将user中的属性复制到UserDTO中,UserDTO对象
session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
配置拦截器配置类,添加拦截器并且设置放行资源;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginIntercepter())
.excludePathPatterns(
"/user/code",
"/user/login",
"/blog/hot",
"/shop/**",
"/shop-type/**",
"/upload/**",
"/voucher/**"
);
}
}
创建LoginInterceptor登录状态拦截器类;
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. 不存在,拦截
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 {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
最后获取对应的用户并返回user(UserDTO)信息
@GetMapping("/me")
public Result me(){
// 获取当前登录的用户并返回
UserDTO user = UserHolder.getUser();
return Result.ok(user);
}
6. 集群的session共享问题
Redis替代session共享存储的主要优点:
-
高效性能:Redis是内存缓存数据库,具有高速访问和处理能力,可以更快地读取和写入共享的会话数据。
-
可扩展性:Redis能够负载均衡处理大量的并发请求,适用于分布式系统的扩展需求。
-
可靠性:Redis的数据存储方式比传统的文件存储更加可靠,具有数据自动备份、容错等机制,更加适用于生产环境下的应用。
因此,在分布式系统中,使用Redis作为共享存储可以提高系统的性能、可扩展性和可靠性,是一种很好的选择。