咱们就把自定义注解想象成现实生活中的"安检盖章"。
在你的"在线英语学习平台"中,有很多接口(比如查看成绩、修改头像)。如果没有注解,你每个接口都要手动写一遍检查 Token 的代码,就像每个登机口都要放一台巨大的安检机,太累了。 为了避免在每个 Controller 接口里重复写 Token 校验代码,我们采用了 "注解标记 + 切面拦截" 的方案。
流程
- 创建文件 :在 IDEA 里
New -> Java Class,选Annotation,命名为CheckToken。 - 写定义代码:CheckToken
- 配置拦截器 :这是最难的一步,需要写一个类实现
HandlerInterceptor接口。
1. 通俗理解:它就是一个"便利贴"
自定义注解本质上就是一张"有特殊含义的便利贴"。
- 第一步(定义) :你设计了一张红色的便利贴,上面印着"必须安检"四个字。
- 第二步(标记) :你把这张便利贴啪地一下,贴在某个接口(方法)的大门上。
- 第三步(执行) :你请了一个保安(拦截器/切面),告诉他:"凡是门上贴了红色便利贴的,必须查他有没有 Token,没带的不准进!"
2. 细致拆解:代码里的三个关键零件
要完成今天的任务,你需要写三个部分的代码。咱们一个一个看:
零件 A:定义那张"便利贴" (Annotation)
这只是个声明,告诉 Java 这是一个注解。
project.template.annotation
less
@Target(ElementType.METHOD)//贴在方法而不是类上
@Retention(RetentionPolicy.RUNTIME)//运行时生效同时让拦截器看见
public @interface CheckToken {
boolean required() default true;
}
零件 B:那个"保安" (Interceptor / AOP)
这是最细致的地方。注解本身没法干活,必须有一个程序去读取它。这就是拦截器的作用。
保安的工作流程如下:
- 看门牌 :请求过来时,先看这个方法上有没有贴
@CheckToken。 - 搜身 :如果有贴,就从请求头(Header)里翻找
token。 - 对名单 :拿着
token去 Redis 里查:
-
- 查到了 :说明 Token 没过期。保安会做一件事------ "续命" 。调用
redisTemplate.expire(key, 30, MINUTE),把你的有效期重新拨回 30 分钟。 - 查不到 :说明你是"黑户"或者过期了,保安直接把你拦住,返回错误信息(比如:
401 未登录)。
- 查到了 :说明 Token 没过期。保安会做一件事------ "续命" 。调用
less
@Aspect //告诉程序这是切面
@Component//注入Spring
@Slf4j//引入日志框架
public class TokenCheckAop {
@Autowired
private RedisService redisService;//注入Redis
@Autowired
private StringRedisTemplate redisTemplate;//用于续期
//定义切点,有@CheckToken注释的方法都要执行
@Pointcut("@annotation(com.kt.project.template.annotation.CheckToken)")
public void checkTokenPointcut() {
}
//在执行方法前拦截
@Before("checkTokenPointcut()")
public void doBefore() {
HttpServletRequest request = ((ServletRequestAttributes)Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
//从请求头Header中尝试获取token
String token = request.getHeader("token");
log.info("token校验中,获取到的Token为:{}",token);
//校验:没带token
if (token == null || token.isEmpty()) {
throw new RuntimeException("请在Header中携带token");
}
//拿token去redis查 (Redis里面的getString类型)
String userId = redisService.getString(token);
if (userId == null) {//Redis里面查不到,说明过期了或者是假token
throw new RuntimeException("Token已过期或不存在,请重新登录");
}
//查到了token,就延期时间
redisTemplate.expire(token,30, TimeUnit.MINUTES);
log.info("Token校验通过,用户{},已自动续期30分钟",userId);
}
}
编写 TokenCheckAop 切面类,充当系统的"安检保安"。它会扫描所有贴了 @CheckToken 的方法,并执行"拦截-校验-续期"的一站式逻辑。
- 拦截逻辑 :通过
Pointcut精准定位标注了@CheckToken的方法。 - 校验与续期:从请求头获取 Token,去 Redis 查询;若存在则自动延长有效期,实现"滑动过期"。
零件 C:贴贴纸 (Controller)
这是你最舒服的一步,只需要在方法上面写一行字:@CheckToken
less
@CheckToken // 自动触发 AOP 保安逻辑
@GetMapping("/get")
public String get(String key) {
// 只有经过 AOP 校验通过后,这里的业务代码才会执行
return "校验通过,查询结果为:" + redisService.getString(key);
}
3. 为什么要这么折腾?
如果你直接在 Controller 里写校验,代码会变成这样:
- 接口 A:验证 Token -> 续期 -> 查数据库 -> 返回数据。
- 接口 B:验证 Token -> 续期 -> 查数据库 -> 返回数据。
用注解后的好处(专业话术):
- 解耦:校验逻辑(保安)和业务逻辑(做题/查分)分开了,互相不干扰。
- 复用:以后再写 100 个接口,我只要在上面打个"贴纸"就行了,一秒钟搞定。