整体登陆流程

1、验证码生成与发送机制
用户点击”发送验证码”后,系统首先进行手机号格式校验,校验通过后生成6位数字验证码并存储到Redis中,设置5分钟过期时间。Redis的key采用规范化格式:login:code:{手机号}
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public Result sendCode(String phone, HttpSession session) { if (RegexUtils.isPhoneInvalid(phone)) { return Result.fail("手机号格式错误!"); }
String code = RandomUtil.randomNumbers(6);
redisTemplate.opsForValue().set("login:code:" + phone, code, 5, TimeUnit.MINUTES);
log.debug("发送验证码成功!验证码为:{}", code);
return Result.ok(); }
|
2、根据验证码登陆。
收到验证码填写后,进行登陆。首先还是先校验手机号。校验失败返回错误提示。校验成功后开始校验验证码,验证码从redis里面通过手机号phone取,校验失败返回错误提示。校验成功后,通过手机号查询user表里是否有用户,如果没有则自动创建新用户。随后生成token,键为uuid,值为用户信息,并存储到redis中,设置过期时间30分钟。并把token返回给前端,后续前端的任何请求都会携带token。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| public Result login(LoginFormDTO loginForm, HttpSession session) { String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) { return Result.fail("手机号格式错误!"); }
String code = loginForm.getCode(); String cacheCode = redisTemplate.opsForValue().get("login:code:" + phone);
if (cacheCode == null || !cacheCode.equals(code)) { return Result.fail("验证码错误!"); }
User user = query().eq("phone", phone).one();
if (user == null) { user = createUserWithPhone(phone); }
String token = UUID.randomUUID().toString(true);
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO); userMap.replaceAll((k, v) -> v.toString());
redisTemplate.opsForHash().putAll("login:token:" + token, userMap); redisTemplate.expire("login:token:" + token, 30, TimeUnit.MINUTES);
return Result.ok(token); }
private User createUserWithPhone(String phone) { User user = new User(); user.setPhone(phone); user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10)); save(user); return user; }
|
3、登陆状态验证

登陆成功后还没完成。需要通过token来验证是否是登陆中。使用两层拦截器。第一层拦截器拦截所有请求,如果token存在且redis中token的值没有过期,则从redis中取出用户并保存到ThreadLocal,并且刷新token有效期。否则直接放行。第二层拦截器就判断ThreadLocal是否有值,如果没有说明没有登陆,直接拦截。否则就放行。这样就保证了在使用的过程中token会不断刷新有效期,只有没有使用的时候token才会过期。
第一层拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| package com.hmdp.interceptor;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.StrUtil; import com.hmdp.dto.UserDTO; import com.hmdp.utils.UserHolder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; 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;
@Component public class RefreshInterceptor implements HandlerInterceptor { @Autowired private StringRedisTemplate redisTemplate;
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("authorization"); if (StrUtil.isBlank(token)) { return true; }
Map<Object, Object> userEntries = redisTemplate.opsForHash().entries("login:token:" + token);
if (userEntries.isEmpty()) { return true; }
UserDTO userDTO = BeanUtil.fillBeanWithMap(userEntries, new UserDTO(), false);
UserHolder.saveUser(userDTO);
redisTemplate.expire("login:token:" + token, 30 * 60, TimeUnit.SECONDS);
return true; }
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { UserHolder.removeUser(); } }
|
第二层拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package com.hmdp.interceptor;
import com.hmdp.utils.UserHolder;
import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
@Component public class TokenInterceptor implements HandlerInterceptor {
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (UserHolder.getUser() == null) { response.setStatus(401); return false; } return true; }
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { UserHolder.removeUser(); } }
|
拦截器配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.hmdp.config;
import com.hmdp.interceptor.RefreshInterceptor; import com.hmdp.interceptor.TokenInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration public class MvcConfig implements WebMvcConfigurer { @Autowired private TokenInterceptor tokenInterceptor; @Autowired private RefreshInterceptor refreshInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(tokenInterceptor). excludePathPatterns("/user/code","/user/login", "/shop/**", "/blog/hot", "/shop-type/**").order(1); registry.addInterceptor(refreshInterceptor).addPathPatterns("/**").order(0); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public Result queryById(Long id) { // 1.查询Redis String shopJson = stringRedisTemplate.opsForValue().get("cache:shop:" + id); // 2.判断是否存在 if (StrUtil.isNotBlank(shopJson)) { // 3.存在,直接返回 Shop shop1 = JSONUtil.toBean(shopJson, Shop.class); return Result.ok(shop1); }
// 4.不存在,根据id查询数据库 Shop shop = getById(id); if(shop==null){ return Result.fail("店铺不存在"); } stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop));
return Result.ok(shop); }
|