目录
四、实战篇-短信登录
4.1 导入黑马点评项目
1、后端:
2、前端
4.2 基于Session实现登录
1、发送验证码
2、短信验证码登录注册
3、校验登录状态
4.3 集群的session共享问题
4.4 基于Redis实现共享session登录
1、发送验证码
2、短信验证码登录注册
3、校验登录状态
四、实战篇-短信登录
4.1 导入黑马点评项目
项目的架构:
1、后端:
-
导入项目后后端,创建数据库,加载sql
-
修改配置文件
-
启动
启动后,访问http://localhost:8081/shop-type/list
2、前端
在nginx所在目录下打开一个CMD窗口,输入命令:
start nginx.exe
4.2 基于Session实现登录
1、发送验证码
@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+phone,code);
//5.发送验证码 真实是调用阿里云等平台短信服务的API
log.info("发送验证码成功,验证码为: {}",code);
//6.结束 返回ok
return Result.ok();
}
2、短信验证码登录注册
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//1.校验手机号是否格式是否正确
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手机号格式不正确");
}
//2.从session中取出验证码,校验验证码是否正确
Object attribute = session.getAttribute(CODE+phone);
if (attribute == null || !attribute.toString().equals(loginForm.getCode())) {
//3.验证码不正确,直接返回
return Result.fail("验证码不正确");
}
//4.通过,通过手机号查询用户是否存在
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(User::getPhone,phone);
User user = baseMapper.selectOne(lambdaQueryWrapper);
//5.不存在进行注册
if (user == null) {
user = registerUser(phone);
}
//6.存在 保证用户到session中
//注意这里需要进行脱敏
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
session.setAttribute(USER_INFO,userDTO);
return Result.ok();
}
3、校验登录状态
上面的逻辑,不光光是一个Controller要进行校验,其他Controller也需要校验,因此我们将其业务写入到拦截器中,如果有用户就放行,没有就不放行,然后在各自需要的Controller中获取用户,返回用户信息即可。如图所示
-
创建拦截器类实现HandlerInterceptor,
重写 preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
和afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
package com.hmdp.utils; import com.hmdp.dto.UserDTO; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import static com.hmdp.utils.SystemConstants.USER_INFO; /** * @packageName: com.hmdp.utils * @author: winter * @date: 2023/4/7 17:15 * @version: 1.0 * @email 1660420659@qq.com * @description: 拦截器 */ public class LoginInterceptor implements HandlerInterceptor { /** * 请求进入preHandle之前执行 * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //1.通过session,获取用户 Object attribute = request.getSession().getAttribute(USER_INFO); if (attribute == null) { //2.用户不存在,拦截器拦截 response.setStatus(401); return false; } //3.将用户保证在ThreadLocal中 拦截器放行 UserHolder.saveUser((UserDTO) attribute); return true; } /** * 响应结束后,执行这个方法 * @param request * @param response * @param handler * @param ex * @throws Exception */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //移除 防止内存泄漏 UserHolder.removeUser(); } }
-
配置Mvc配置类,拦截器的白名单
package com.hmdp.config; import com.hmdp.utils.LoginInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @packageName: com.hmdp.config * @author: winter * @date: 2023/4/7 17:39 * @version: 1.0 * @email 1660420659@qq.com * @description: mvc配置类 */ @Configuration public class MvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/**") .excludePathPatterns( "/user/login", "/user/code", "/shop/**", "/shop-type/**", "/voucher/**", "/upload/**", "/blog/hot" ); } }
-
将UserController中的me方法进行获取用户,返回信息
@GetMapping("/me") public Result me(){ // 获取当前登录的用户并返回 UserDTO user = UserHolder.getUser(); return Result.ok(user); }
4.3 集群的session共享问题
session共享问题:当并发访问量多的时候,我们的解决办法可以说添加tomcat集群,但是就会出现问题
多态tomcat并不共享session存储空间,当请求切换到不同tomcat服务时导致数据丢失问题
如图所示,因为使用nginx轮训方式,可能一个用户两次访问的tomcat并不是同一个,并且session不共享,会出现登录进去后,往往提示还需登录,用户的体验感极差,因此出现了解决方案,将数据都存储到redis集群中
4.4 基于Redis实现共享session登录
1、发送验证码
public static final String LOGIN_CODE_KEY = "login:code:";
public static final Long LOGIN_CODE_TTL = 2L;
@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中,并设置过期时间为2分钟
stringRedisTemplate.opsForValue().
set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);
//5.发送验证码
log.info("发送验证码成功,验证码为: {}",code);
//6.结束 返回ok
return Result.ok();
}
2、短信验证码登录注册
常量中的值
public static final String LOGIN_USER_KEY = "login:token:";
public static final Long LOGIN_USER_TTL = 36000L;
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//1.校验手机号是否格式是否正确(其实这里应该也将手机号加入到session中,然手机号和验证码一一对应)
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手机号格式不正确");
}
//2.从redsi中取出验证码,校验验证码是否正确
String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
if (cacheCode == null || !cacheCode.equals(loginForm.getCode())) {
//3.验证码不正确,直接返回
return Result.fail("验证码不正确");
}
//4.通过,通过手机号查询用户是否存在
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(User::getPhone,phone);
User user = baseMapper.selectOne(lambdaQueryWrapper);
//5.不存在进行注册
if (user == null) {
user = registerUser(phone);
}
//6.存在 保证用户到redis中
//6.1 注意这里需要进行脱敏
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
//6.2 转换生成Map
//这里因为使用的StringRedisTemplate 因此都是String
//这里如果不装换成string map中存储userDto对象的id是long类型
//存储在redis的hash时 会出现String转换long 数字转换异常
Map<String, Object> map = BeanUtil.beanToMap(userDTO,new HashMap<>(),
CopyOptions.create().setIgnoreNullValue(true)
.setFieldValueEditor((fieldName,fieldValue) ->
fieldValue.toString()));
//6.3.生成一个随机的token
String token = UUID.fastUUID().toString(true);
//6.4 存到到redis中
String key = LOGIN_USER_KEY+token;
stringRedisTemplate.opsForHash().putAll(key,map);
//设置有效时间
stringRedisTemplate.expire(key,LOGIN_USER_TTL,TimeUnit.MINUTES);
return Result.ok(token);
}
/**
* 注册
* @param phone
* @return
*/
private User registerUser(String phone) {
User user = new User();
user.setPhone(phone);
String nickName = RandomUtil.randomString(8);
user.setNickName(USER_NICK_NAME_PREFIX+nickName);
//保存用户到数据库
baseMapper.insert(user);
return user;
}
3、校验登录状态
这里需要进行优化一下,需要使用两个拦截器,因为session的时候使用一个拦截器,只有登录等被拦截的请求,才会增加redis键值的时间,例如访问主页不经过拦截器,可能半小时后键值就自动销毁的,这样是不符合业务要求的,因此要在增加一个拦截器
-
拦截器1 拦截一切路径
package com.hmdp.utils;
import cn.hutool.core.bean.BeanUtil;
import com.hmdp.dto.UserDTO;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static com.hmdp.utils.RedisConstants.LOGIN_USER_KEY;
import static com.hmdp.utils.RedisConstants.LOGIN_USER_TTL;
/**
* @packageName: com.hmdp.utils
* @author: winter
* @date: 2023/4/7 17:15
* @version: 1.0
* @email 1660420659@qq.com
* @description: 拦截器所有请求
*/
public class ExtraTimeInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
public ExtraTimeInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
/**
* 请求进入preHandle之前执行
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.通过请求获取到token信息
String token = request.getHeader("authorization");
//2.通过token从redis中获取对象信息
Map<Object, Object> map = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + token);
if (map.isEmpty()) {
return true;
}
//将map转换为对象
UserDTO userDTO = BeanUtil.fillBeanWithMap(map, new UserDTO()
, false);
//3.将用户保证在ThreadLocal中 拦截器放行
UserHolder.saveUser( userDTO);
//4.刷新用户存储有效期
stringRedisTemplate.expire(LOGIN_USER_KEY + token,LOGIN_USER_TTL, TimeUnit.MINUTES);
return true;
}
/**
* 响应结束后,执行这个方法
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除 防止内存泄漏
UserHolder.removeUser();
}
}
-
拦截器2 拦截部分需要登录才能访问的请求
package com.hmdp.utils;
import com.hmdp.dto.UserDTO;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @packageName: com.hmdp.utils
* @author: winter
* @date: 2023/4/7 17:15
* @version: 1.0
* @email 1660420659@qq.com
* @description: 拦截部门请求,拦截需要登录的路径
*/
public class LoginInterceptor implements HandlerInterceptor {
/**
* 请求进入preHandle之前执行
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.取到用户
UserDTO user = UserHolder.getUser();
//2.判断用户是否为空
if (user == null) {
response.setStatus(401);
return false;
}
return true;
}
}
-
mvc配置一下拦截器需要拦截什么请求文章来源:https://www.toymoban.com/news/detail-414446.html
package com.hmdp.config;
import com.hmdp.utils.ExtraTimeInterceptor;
import com.hmdp.utils.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
/**
* @packageName: com.hmdp.config
* @author: winter
* @date: 2023/4/7 17:39
* @version: 1.0
* @email 1660420659@qq.com
* @description: mvc配置类
*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//默认先添加执行 ,也可以设置order 值越小越先执行
registry.addInterceptor(new ExtraTimeInterceptor(stringRedisTemplate))
.addPathPatterns("/**").order(0);
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**")
.excludePathPatterns(
"/user/login",
"/user/code",
"/shop/**",
"/shop-type/**",
"/voucher/**",
"/upload/**",
"/blog/hot"
).order(1);
}
}
具体代码gitee上:
redis实战篇-hmdp-短信登录: 存放黑马点评中redis进行短信登录的代码 ,包括前端后后端文章来源地址https://www.toymoban.com/news/detail-414446.html
到了这里,关于黑马的redis实战篇-短信登录的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!