目录
一、session短信登录
service层业务功能:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public Result sendCode(String phone,HttpSession httpSession) {
//1.校验手机号格式
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手机格式错误");
}
//2.生成验证码
String code = RandomUtil.randomNumbers(6);
//3.将手机号和验证码存入session中防止验证码比对成功后更改手机号从而登录别人账号的bug
//存入手机号是
httpSession.setAttribute("phone",phone);
httpSession.setAttribute("code",code);
//4.发送验证码
log.debug("验证码为:"+code);
return Result.ok();
}
@Override
public Result login(LoginFormDTO loginForm,HttpSession httpSession) {
//1.校验手机号格式
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手机格式错误");
}
//2.校验验证码
String cacheCode = httpSession.getAttribute("code").toString();
String code = loginForm.getCode();
if (!code.equals(cacheCode) || code==null) {
return Result.fail("验证码错误");
}
//3.校验手机号前后是否一致
String cachePhone = httpSession.getAttribute("phone").toString();
if (!cachePhone.equals(phone) || phone==null) {
return Result.fail("手机号错误");
}
//4.根据手机号从数据库查用户 select * from tb_user where phone=?
User user = query().eq("phone", phone).one();
//4.1判断用户是否存在,不存在创建用户并保存
if (user == null) {
user = new User();
user.setPhone(phone);
user.setNickName(RandomUtil.randomString(10));
save(user);
}
//4.2将user转换成userDTO
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
//5.保存用户信息到session
httpSession.setAttribute("user",userDTO);
//不用返回用户信息coojie里有session数据
return Result.ok();
}
}
Interceptor拦截器
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的用户
UserDTO userDTO = (UserDTO)session.getAttribute("user");
//3.判断session里的user是否为空
if (userDTO == null) {
//没有,需要拦截,设置状态码
response.setStatus(401);
//拦截
return false;
}
//4.有用户,存入线程池放行
UserHolder.saveUser(userDTO);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//释放线程池
UserHolder.removeUser();
}
}
拦截器配置
@Configuration
public class LoginInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
LoginInterceptor loginInterceptor = new LoginInterceptor();
ArrayList list = new ArrayList();
list.add("/shop/**");
list.add("/vouchar/**");
list.add("/shop-type/**");
list.add("/upload/**");
list.add("/blog/hot");
list.add("/user/code");
list.add("/user/login");
registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns(list);
}
}
二、redis短信登录
service层业务功能:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public Result sendCode(String phone) {
//1.校验电话号码是否规范
boolean phoneInvalid = RegexUtils.isPhoneInvalid(phone);
// 2.不符合返回错误信息
if (phoneInvalid) {
return Result.fail("手机格式错误");
}
//3.符合,生成验证码
String code = RandomUtil.randomNumbers(6); //hutool的生成随机数方法
// Integer code = Captcha.generateCaptcha(); //自定义的随机数方法
// String s = String.valueOf(code);
// 4.保存验证码到redis中,key为phone,一个phone对应一个验证码,这样后面不用判断输入前后电话号码是否一致
//手机号加了login:code前缀,以便区分
//加了过期时间
// stringRedisTemplate.opsForValue().set("login:code:" + phone,code.toString(),2L, TimeUnit.MINUTES);
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);
// 5.发送验证码
log.debug("验证码为:"+code);
System.out.println(code);
return Result.ok();
}
@Override
public Result login(LoginFormDTO loginForm) {
// 1.校验电话号码是否规范
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) {
//2.不符合,返回错误信息
return Result.fail("手机格式错误");
}
// 3.从redis中获取验证码并校验验证码
String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
String code = loginForm.getCode();
if (code == null || !code.equals(cacheCode)) {
//不一致,报错
return Result.fail("验证码错误");
}
// 4.一致,根据手机号查询用户 select * from tb_user where phone=?
User user = query().eq("phone", phone).one();
// 5.判断用户是否存在,不存在创建用户并保存
if (user == null) {
user = new User();
user.setPhone(phone);
user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
// 保存用户到数据库
save(user);
}
// 6.保存用户信息到reids中
// 6.1随机生成token,作为登录令牌
//hutool的UUIDrandom里面的true是是否安全选项,toString里的true是是否去掉'-'
String token = UUID.randomUUID(true).toString(true);
// 6.2将User对象转为HashMap存储
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
//注意:因为hset的key,feild,value是String类型,而userDto里面的id是Integer类型,所以要用lambda转成String
Map<String, Object> map = BeanUtil.beanToMap(userDTO,new HashMap<>(),
CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((fieldName,fieldValue) -> fieldValue.toString()));
// 6.3存储
stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY + token,map);
// 6.4设置token有效期
//注意:和session有效期30min不一样,redis无论操不操作30min直接清除,所以要刷新token有效期
//怎么知道用户是活跃用户?或者说怎么知道用户访问我们资源?答:设置一级拦截器,拦截所有请求但都放行,只做token有效期刷新操作
stringRedisTemplate.expire(LOGIN_USER_KEY + token,LOGIN_USER_TTL,TimeUnit.MINUTES);
// 返回token
return Result.ok(token);
}
}
Interceptor拦截器
RefreshTokenInterceptor:一级拦截器,只作为token刷新作用
//一级拦截器,拦截所有,但都放行,只做token刷新操作
public class RefreshTokenInterceptor implements HandlerInterceptor {
//注:不能用@Autowired,因为拦截器是在spring容器初始化之前,注入了的值为null,可以通过构造器方式进行注入
private StringRedisTemplate stringRedisTemplate;
public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.获取请求头中的token
String token = request.getHeader("authorization ");
//token判断不能用token==null,因为返回的是length
if (StrUtil.isBlank(token)) {
return true;
}
// 2.基于token获取redis用户
Map<Object, Object> map = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + token);
// 3.判断用户是否存在
if (map.isEmpty()) {
return true;
}
// 4.将查询到的hash数据转为userDTO
UserDTO userDTO = BeanUtil.fillBeanWithMap(map,new UserDTO(),false);
// 5.保存用户信息到ThreadLocal
UserHolder.saveUser(userDTO);
// 6.刷新token有效期
stringRedisTemplate.expire(LOGIN_USER_KEY + token,LOGIN_USER_TTL, TimeUnit.MINUTES);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除用户
UserHolder.removeUser();
}
}
LoginInterceptor:二级拦截器
//二级拦截器
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.判断是否需要拦截(ThreadLocal中是否有用户)
UserDTO user = UserHolder.getUser();
if (user == null) {
//没有,需要拦截,设置状态码
response.setStatus(401);
//拦截
return false;
}
//有用户,放行
return true;
}
}
拦截器配置
@Configuration
public class LoginInterceptorConfig implements WebMvcConfigurer {
//Configuration类,该类可以被spring容器管理
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
LoginInterceptor loginInterceptor = new LoginInterceptor();
ArrayList list = new ArrayList();
list.add("/shop/**");
list.add("/vouchar/**");
list.add("/shop-type/**");
list.add("/upload/**");
list.add("/blog/hot");
list.add("/user/code");
list.add("/user/login");
registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
registry.addInterceptor(loginInterceptor).excludePathPatterns(list).order(1);
}
}