发送验证码接口
1.Controller层
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private IUserService userService;
@PostMapping("/code")
public Result sendCode(@RequestParam("phone") String phone) {
return userService.sendCode(phone);
}
}
2.Service层实现
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result sendCode(String phone) {
// 1. 验证手机号格式
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手机号格式错误!");
}
// 2. 生成6位随机验证码
String code = RandomUtil.randomNumbers(6);
// 3. 保存验证码到Redis(2分钟过期)
String key = RedisConstants.LOGIN_CODE_KEY + phone; // "login:code:13800138000"
stringRedisTemplate.opsForValue().set(key, code,
RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);
// 4. 模拟发送短信(实际项目调用短信服务)
log.debug("发送短信验证码成功,验证码:{}", code);
return Result.ok();
}
}
登录接口
1.Controller层
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm) {
return userService.login(loginForm);
}
2.Service层实现
@Override
public Result login(LoginFormDTO loginForm) {
String phone = loginForm.getPhone();
// 1. 验证手机号格式
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手机号格式错误!");
}
// 2. 从Redis获取验证码
String cacheKey = RedisConstants.LOGIN_CODE_KEY + phone;
String cacheCode = stringRedisTemplate.opsForValue().get(cacheKey);
String inputCode = loginForm.getCode();
// 3. 验证码校验
if (cacheCode == null || !cacheCode.equals(inputCode)) {
return Result.fail("验证码错误");
}
// 4. 查询用户(MySQL)
User user = query().eq("phone", phone).one();
// 5. 用户不存在则创建
if (user == null) {
user = createUserWithPhone(phone);
}
// 6. 生成Token(UUID去掉连字符)
String token = UUID.randomUUID().toString().replace("-", "");
// 7. 转换为UserDTO(脱敏)
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
// 8. 用户信息转为Map(Hash存储)
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((fieldName, fieldValue) ->
fieldValue == null ? "" : fieldValue.toString()
)
);
// 9. 保存到Redis(Hash结构,30分钟过期)
String tokenKey = RedisConstants.LOGIN_USER_KEY + token; // "login:token:abc123"
stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
stringRedisTemplate.expire(tokenKey,
RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
// 10. 删除已使用的验证码
stringRedisTemplate.delete(cacheKey);
// 11. 返回token给前端
return Result.ok(token);
}
3.拦截器实现
@Component
public class RefreshTokenInterceptor implements HandlerInterceptor {
@Resource
private StringRedisTemplate stringRedisTemplate;
@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; // 没有token,放行(由LoginInterceptor处理)
}
// 2. 从Redis获取用户信息
String key = RedisConstants.LOGIN_USER_KEY + token;
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
// 3. 用户不存在,放行
if (userMap.isEmpty()) {
return true;
}
// 4. Hash转UserDTO对象
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
// 5. 保存到ThreadLocal(线程隔离)
UserHolder.saveUser(userDTO);
// 6. 刷新Token过期时间(续期30分钟)
stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) throws Exception {
// 请求结束后移除用户,防止内存泄漏
UserHolder.removeUser();
}
}
内存泄漏(Memory Leak) 是指程序在申请内存后,无法释放已不再使用的内存空间,导致可用内存逐渐减少,最终可能引发程序崩溃或系统性能下降。
LoginInterceptor
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 1. 从ThreadLocal获取用户
UserDTO user = UserHolder.getUser();
// 2. 用户不存在,返回401未授权
if (user == null) {
response.setStatus(401);
return false;
}
// 3. 用户存在,放行
return true;
}
}
实现两个拦截器的原因
**第一个拦截器(RefreshTokenInterceptor)**的工作:
-
每次你访问网站,它都会检查你的"令牌"(token)
-
如果令牌有效,就把你的用户信息记下来
-
同时帮你延长令牌有效期(从30分钟重新开始计算)
-
它从不拒绝任何人访问
**第二个拦截器(LoginInterceptor)**的工作:
两个拦截器通过职责分离(一个只管刷新token,一个只管检查登录),实现了自动续期登录状态的同时精准控制接口权限,既保证用户体验(无感续期)又确保系统安全。
-
只在你访问需要登录的页面时才工作
-
检查第一个拦截器有没有记下你的信息
-
如果有信息,让你通过
-
如果没有信息,让你去登录