Aop实现登录校验

这段代码完美地演示了我们刚才聊的 AOP(切面编程) 概念在真实项目里是怎么用的。你可以把它理解成奶茶店门口的 “安检员”

这个安检员不参与做奶茶(业务逻辑),他只负责在顾客进门(调用方法)前,检查你有没有会员卡(Token)

我们把它拆开揉碎,逐段翻译成“人话”。

🎯 一、代码整体身份:门禁切面

1
2
3
4
@Component("operationAspect")  // 1. 告诉Spring:这也是个受IoC管理的组件
@Aspect // 2. 告诉Spring:我是个切面(安检员),不是普通员工
@Slf4j // 3. 方便我打印日志
public class GlobalOperationAspect {
  • @Aspect:关键标签,声明这个类是 AOP 的切面。Spring 看到这个标签,就会把这个类里的代码逻辑织入到你指定的方法上。
  • @Component:把这个安检员交给 Spring 的 IoC 容器管理,这样安检员才能拿到钥匙(RedisComponent)。

🔪 二、切入点定义:这安检员站哪?

1
2
3
4
@Before("@annotation(com.easymall.annotation.GlobalInterceptor)")
public void interceptorDo(JoinPoint point) {
// ...
}
  • @Before:通知类型。表示在目标方法执行之前插一杠子。
  • @annotation(...):切入点表达式。意思是:谁头上贴了 @GlobalInterceptor 这个标签,我就站在谁门口检查。

通俗比喻
店长规定:凡是门口挂着 “需要会员” 牌子的房间(方法),安检员必须在顾客进去前拦一下。

🧩 三、方法体执行流程(安检步骤拆解)

1. 第一步:看清楚门牌上写的啥要求

1
2
3
4
5
Method method = ((MethodSignature) point.getSignature()).getMethod();
GlobalInterceptor interceptor = method.getAnnotation(GlobalInterceptor.class);
if (null == interceptor) {
return;
}
  • 作用:通过反射拿到方法上贴的那个注解对象。
  • 目的:看看这个注解里的配置是什么。比如有的房间只是打个卡,有的房间必须验身。

2. 第二步:如果要求“必须登录”,就执行检查

1
2
3
if (interceptor.checkLogin()) {
checkLogin();
}

这里调用了私有方法 checkLogin(),这是核心逻辑所在。

🔑 四、核心校验逻辑 checkLogin() 详解

这是安检员工作的具体内容:

1. 拿到顾客手里的小票(HttpServletRequest)

1
2
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String token = request.getHeader("token");
  • RequestContextHolder:Spring 提供的一个工具,让你在任何地方都能拿到当前这次 HTTP 请求的上下文。
  • getHeader("token"):前端调用接口时必须在请求头里带上 token(比如 token: abc123),就像进门要刷卡。

2. 没带卡?直接拦下报错

1
2
3
if (StringTools.isEmpty(token)) {
throw new BusinessException(ResponseCodeEnum.CODE_901);
}
  • 如果请求头里没有 token 字段,直接抛出业务异常 CODE_901(通常代表“未登录”或“Token 失效”)。被拦下后,核心业务代码根本不会执行。

3. 去 Redis(后台数据库)验卡真伪

1
TokenUserInfoDTO tokenUserInfoDto = redisComponent.getTokenInfo(token);
  • 用户登录成功后,后端会把用户信息(名字、ID)存在 Redis 里,Key 就是 token
  • 这里安检员拿着 token 去 Redis 查:“有这个钥匙对应的记录吗?”

4. 开发环境彩蛋(免检通道)

1
2
3
4
5
6
7
if (System.getProperty("dev") != null) {
tokenUserInfoDto = new TokenUserInfoDTO();
tokenUserInfoDto.setUserId("vuzPteqk");
tokenUserInfoDto.setNickName("程序员老罗");
tokenUserInfoDto.setToken(token);
redisComponent.saveTokenInfo(tokenUserInfoDto);
}
  • 作用:方便程序员本地调试。
  • 解释:如果启动参数加了 -Ddev(开发模式),安检员会假装你是个合法用户,直接给你生成一个虚拟身份塞进 Redis。这样你就不用每次重启项目都重新登录了。生产环境没有这个参数,这代码就不会执行

5. 最终判决

1
2
3
if (tokenUserInfoDto == null) {
throw new BusinessException(ResponseCodeEnum.CODE_901);
}
  • Redis 里查不到用户信息?卡是假的!同样抛出 901 异常拦下。

💡 五、为什么说这是 AOP 的“最佳实践”?

假设你要写一个查询用户钱包的接口:

❌ 没有 AOP 的写法(痛苦):

1
2
3
4
5
6
public Wallet getMyWallet() {
// 1. 手动从 Header 拿 token (代码重复100遍)
// 2. 手动查 Redis (代码重复100遍)
// 3. 手动抛异常 (代码重复100遍)
// 4. 写真正的查钱包逻辑...
}

✅ 有了这个 AOP 切面后的写法(优雅):

1
2
3
4
@GlobalInterceptor(checkLogin = true) // 贴个标签,完事!
public Wallet getMyWallet() {
// 直接写查钱包的逻辑!因为安检员已经确认过身份了。
}

🧠 六、总结这段代码的运转逻辑图

  1. 用户请求 GET /user/wallet -> 携带 Header: token=xxx
  2. Spring 拦截器机制 -> 发现方法上有 @GlobalInterceptor 注解。
  3. 织入切面 -> 执行 GlobalOperationAspect.interceptorDo()
  4. 切面逻辑
    • 拿 Token。
    • Token 为空 -> 原地爆炸(报错901),业务代码不执行。
    • Token 不为空 -> 查 Redis。
    • Redis 无数据 -> 原地爆炸(报错901)
    • Redis 有数据 -> 放行
  5. 放行后 -> 才执行真正的 getMyWallet() 业务代码。

一句话总结这段代码的核心价值:

它把 登录校验 这种每个接口都要做的“脏活累活”,从业务代码中剥离出来,变成了一个独立的 切面组件。实现了 代码复用业务逻辑解耦。这正是 AOP 的强大之处。