优质网站建设价格,济南网站建设第六网建,广告运营推广,wordpress个人简历模板项目介绍 导入黑马点评项目 项目架构 基于Session实现登录
基本流程 实现发送短信验证码功能 controller层中 /*** 发送手机验证码*/PostMapping(code)public Result sendCode(RequestParam(phone) String phone, HttpSession session) {// 发送短信…项目介绍 导入黑马点评项目 项目架构 基于Session实现登录
基本流程 实现发送短信验证码功能 controller层中 /*** 发送手机验证码*/PostMapping(code)public Result sendCode(RequestParam(phone) String phone, HttpSession session) {// 发送短信验证码并保存验证码return userService.sendCode(phone,session);}
Service层中
真的发送的话要接入阿里云或腾讯云的短信发送功能这里假装发送成功。
Slf4j
Service
public class UserServiceImpl extends ServiceImplUserMapper, User implements IUserService {Overridepublic Result sendCode(String phone, HttpSession session) {//1.校验手机号if(RegexUtils.isPhoneInvalid(phone)){//2.如果不符合返回错误信息return Result.fail(手机号格式错误!);}//3.符合生成验证码String code RandomUtil.randomNumbers(6);//4.保存验证码session.setAttribute(code,code);//5.发送验证码log.debug(发送短信验证码成功,验证码:{},code);return Result.ok();}
}
再次点击发送就可以看见如下 实现验证码登录和注册功能 Controller层中 /*** 登录功能* param loginForm 登录参数包含手机号、验证码或者手机号、密码*/PostMapping(/login)public Result login(RequestBody LoginFormDTO loginForm, HttpSession session){// 实现登录功能return userService.login(loginForm,session);}
Service层中 Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {//1.验证手机号String phoneloginForm.getPhone();if(RegexUtils.isPhoneInvalid(phone)){//1.1不符合返回报错return Result.fail(手机号格式错误!);}//2.校验验证码Object cacheCode session.getAttribute(code);String code loginForm.getCode();if(cacheCodenull||!cacheCode.toString().equals(code)){//3.不一致报错return Result.fail(验证码错误);}//4.一致根据手机号查询用户 select * from tb_user where phone?User user query().eq(phone, phone).one();//5.判断用户是否存在if(usernull){//6.不存在创建新用户并保存user createUserWithPhone(phone);}//7.存在保存用户信息到session中session.setAttribute(user,user);return Result.ok();}private User createUserWithPhone(String phone) {//1.创建用户User usernew User();user.setPhone(phone);user.setNickName(USER_NICK_NAME_PREFIXRandomUtil.randomString(10));//2.保存save(user);return user;}
这里的session之所以能够获取到发送验证码请求时存入的验证码是因为这是在同一个会话当中session会被返回给前端下一次请求也会携带同一个session过来。所以才有之前保存的信息在里面。
实现登录校验拦截器 接口多了之后就要拦截器进行统一校验然后为了传递用户信息给后序的业务需要将用户信息存入Threadlocal里面的.Threadlocal是每个请求线程的一个独立保存空间。
在拦截器的最后一个方法中清空thread local的信息第一可以做到退出登录第二可以防止内存泄露
新建一个拦截器
public class LoginInterceptor implements HandlerInterceptor {Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.获取sessionHttpSession session request.getSession();//2.获取session中的用户Object user session.getAttribute(user);//3.判断用户是否存在if(usernull){//4.不存在拦截response.setStatus(401);return false;}//5.存在保存用户信息到ThreadLocalUserHolder.saveUser((UserDTO) user);//6.放行return true;}Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//移除用户UserHolder.removeUser();}
} 新建配置项
将上面的拦截器投入使用并且指定不拦截的请求。
Configuration
public class MvcConfig implements WebMvcConfigurer {Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).excludePathPatterns(/shop/**,/shop-type/**,/upload/**,/blog/hot,/user/code ,/user/login);}
}登录校验接口 GetMapping(/me)public Result me(){// 获取当前登录的用户并返回UserDTO user UserHolder.getUser();return Result.ok(user);}
隐藏用户敏感信息
在前面代码里返回给前端的是用户完整信息这是不合理的所以改成如下。
在session存的时候就只存部分信息。然后上面登录校验逻辑因为黑马资料的问题已经是隐藏之后的了的所以只改这里一个地方即可。 Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {........//7.存在保存用户信息到session中session.setAttribute(user, BeanUtil.copyProperties(user, UserDTO.class));return Result.ok();}
集群的session共享问题 使用Redis解决Session共享问题。
基于Redis实现共享session登录
业务流程 基于redis实现短信登录
发送短信验证码部分的代码修改:
生成验证码之后将验证码存入Redis Resourceprivate StringRedisTemplate stringRedisTemplate;Overridepublic Result sendCode(String phone, HttpSession session) {//1.校验手机号...//3.符合生成验证码...//4.保存验证码到RedisstringRedisTemplate.opsForValue().set(login:code:phone,code,2, TimeUnit.MINUTES);//5.发送验证码...}
短信验证码登录注册功能部分的代码修改: Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {//1.验证手机号...// 2.从redis获取 校验验证码String cacheCode stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEYphone);...//4.一致根据手机号查询用户 select * from tb_user where phone?...//5.判断用户是否存在...//7.存在保存用户信息到Redis中//7.1.随机生成token作为登录令牌String token UUID.randomUUID().toString(true);//7.2将User对象转为Hash存储UserDTO userDTOBeanUtil.copyProperties(user, UserDTO.class);MapString, Object userMap BeanUtil.beanToMap(userDTO);//7.3存储String tokenKeylogin::token:token;stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);//7.4设置token有效期stringRedisTemplate.expire(tokenKey,30,TimeUnit.MINUTES);//8.返回tokenreturn Result.ok(token);}
登录拦截器代码修改 public class LoginInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplatestringRedisTemplate;}Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.获取请求头中的tokenString token request.getHeader(authorization);if (StrUtil.isBlank(token)) {//4.不存在拦截response.setStatus(401);return false;}//2.基于token获取redis中的用户String tokenKeyRedisConstants.LOGIN_USER_KEY token;MapObject, Object userMap stringRedisTemplate.opsForHash().entries(tokenKey);//3.判断用户是否存在if(userMap.isEmpty()){//4.不存在拦截response.setStatus(401);return false;}//5.将查询到的Hash数据转为UserDTO对象UserDTO userDTO BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);//6.存在保存用户信息到ThreadLocalUserHolder.saveUser( userDTO);//7.刷新token有效期stringRedisTemplate.expire(tokenKey,RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);//8.放行return true;}Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//移除用户...}
}因为这里不能用注解注入stringRedisTemplate要在MVC配置器哪里进行注入
Configuration
public class MvcConfig implements WebMvcConfigurer {Resourceprivate StringRedisTemplate stringRedisTemplate;Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns(/shop/**,/shop-type/**,/upload/**,/blog/hot,/user/code ,/user/login);}
}出现Long转String的报错 在登录逻辑里面将对象转为Map时做多点工作 //7.2将User对象转为Hash存储UserDTO userDTOBeanUtil.copyProperties(user, UserDTO.class);MapString, Object userMap BeanUtil.beanToMap(userDTO,new HashMap(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue)-fieldValue.toString())); 可以看见对应东西 解决登录状态刷新问题
登录状态的刷新只会在访问需要拦截的请求时才刷新如果是不需要拦截的请求就不会刷新.
为了拦截所有请求这里新加一个拦截器 新增刷新拦截器:
public class RefreshTokenInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplatestringRedisTemplate;}Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.获取请求头中的tokenString token request.getHeader(authorization);if (StrUtil.isBlank(token)) {return true;}//2.基于token获取redis中的用户String tokenKeyRedisConstants.LOGIN_USER_KEY token;MapObject, Object userMap stringRedisTemplate.opsForHash().entries(tokenKey);//3.判断用户是否存在if(userMap.isEmpty()){return true;}//5.将查询到的Hash数据转为UserDTO对象UserDTO userDTO BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);//6.存在保存用户信息到ThreadLocalUserHolder.saveUser( userDTO);//7.刷新token有效期stringRedisTemplate.expire(tokenKey,RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);//8.放行return true;}Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//移除用户UserHolder.removeUser();}
}修改原本的登录拦截器
public class LoginInterceptor implements HandlerInterceptor {Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.判断是否需要拦截(ThreadLocal中是否有用户)if(UserHolder.getUser()null){//没有需要拦截response.setStatus(401);return false;}return true;}Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//移除用户UserHolder.removeUser();}
}
MVC配置类当中
需要添加order保证刷新拦截器先执行。
Configuration
public class MvcConfig implements WebMvcConfigurer {Resourceprivate StringRedisTemplate stringRedisTemplate;Overridepublic void addInterceptors(InterceptorRegistry registry) {//登录拦截器registry.addInterceptor(new LoginInterceptor()).excludePathPatterns(/shop/**,/shop-type/**,/upload/**,/blog/hot,/user/code ,/user/login).order(1);//刷新拦截器registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns(/**).order(0);}
}