本文从前端讲解数据流转过程
【前端如下】:首先输入手机号并点击发送验证码,将手机号数据进行提交到服务器
【服务器】如下:客户端 发送的请求和携带的数据【手机号】12345678910。
【第一步】首先会被拦截器拦截,这里配置了两个拦截器,根据拦截器拦截内容,第一个拦截器会进行释放,第二个拦截器由于拦截范围原因也会进行释放。
【第二步】数据【手机号】12345678910,会流转到验证码发送业务。
- 首先判断手机号是否符合要求
- 生成验证码
- Redis进行全局数据缓存,将验证码信息缓冲到Redis当中
@Override
public Result sendCode(String phone, HttpSession session) {
// 1. 验证手机号
if (RegexUtils.isPhoneInvalid(phone)) {
// 如果不符合要求直接返回
return Result.fail("手机号不正确");
}
// 2.生成验证码
String code = RandomUtil.randomNumbers(6);
// 3.保存验证码到Redis中--验证码设置过期时间为60s
template.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
// 4.发送验证码
log.debug("验证码是:{}", code);
return Result.ok();
}
【第三步】用户点击登录按钮,数据【手机号+验证码】都会流转到登录业务
- 首先进行手机号和 验证码的校验。
- 如果校验成功判断是否是新用户
- 如果是新用户,则将信息添加到 数据库中
- 然后将用户信息存入Redis,采用的是Hash结构,注意设置过期时间和数据类型的转换。
- 将生成的TokenKey返回给前端。
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
// 1. 验证手机号
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手机号格式错误");
}
// 2. 从Redis中获取验证码
String code = template.opsForValue().get(LOGIN_CODE_KEY + phone);
if (code == null || !code.equals(loginForm.getCode())) {
return Result.fail("验证码错误");
}
// 3.根据手机号查询用户信息
User user = query().eq("phone", phone).one();
// 4. 用户不存在则创建用户到数据库
if (user == null) {
user = CreateNewUser(phone);
}
// 5.保存用户到Redis
String token = UUID.randomUUID(false).toString();
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
// 设置Long类型的数据转换为字符串类型的。
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
CopyOptions.create().setIgnoreNullValue(true).
setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));
String tokenKey = LOGIN_USER_KEY + token;
template.opsForHash().putAll(tokenKey,userMap);
template.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);
// 6.返回Token
return Result.ok(token);
}
private User CreateNewUser(String phone) {
User user = new User();
user.setPhone(phone);
user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(6));
save(user);
return user;
}
【第四步】登录状态校验
首先数据会被我们之前提到的拦截器进行拦截
- 获取上一步存入Redis中用户的信息,
- 将信息转为DTO对象
- 将对象保存在ThreadLocal中,保证线程安全,
- 然后对Redis数据的有效期进行刷新
第一个拦截器代码如下:
package com.hmdp.utils;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
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;
public class RefleshInterceptor implements HandlerInterceptor {
private StringRedisTemplate template;
public RefleshInterceptor(StringRedisTemplate template) {
this.template = template;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.获取token
String token = request.getHeader("authorization");
if (StrUtil.isBlank(token)) {
return true;
}
String tokenKey = LOGIN_USER_KEY + token;
Map<Object, Object> userMap = template.opsForHash().entries(tokenKey);
// 2.判断用户是否存在
if (userMap.isEmpty()) {
return true;
}
// 3. 获取用户并转换为UserDTO
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
//4.存在保存到ThreadLocal
UserHolder.saveUser(userDTO);
// 5. 刷新token的有效期
template.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
UserHolder.removeUser();
}
}
【第五步】
从第一个拦截器中出来以后,会进入第二个拦截器
- 检查是否有用户信息
- 如果不存在则直接拦截
- 存在则放行
至此所有登录相关校验执行关闭。
package com.hmdp.utils;
import com.hmdp.dto.UserDTO;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
UserHolder.removeUser();
}
}
【完整流程图如下】:首先进行短信验证码登录,注册--->校验登录状态。