可行,但需注意切面生效时机和请求上下文获取方式;自定义注解须继承AbstractAnnotation并声明目标类型;切面类必须通过$annotations显式关联注解类;权限校验应从RequestInterface获取用户信息而非全局变量;切面不生效时需清理runtime缓存并检查配置。

Hyperf 里用自定义注解 + AOP 做权限控制,可行,但必须注意切面生效时机和请求上下文获取方式——直接在 process 里调 RequestContextHolder 会报错,因为 Hyperf 没这玩意儿。
自定义注解必须继承 AbstractAnnotation 并声明目标类型
Hyperf 的注解不是 PHP 原生 #[Attribute](虽然新版本支持,但生态主流仍用旧方式)。你得新建 app/Annotation/PreAuthorization.php,内容核心就三块:
- 继承
HyperfDiAnnotationAbstractAnnotation - 加上
@Target({Target::CLASS, Target::METHOD}),否则扫描器不认 - 加
@Retention(Retention::RUNTIME),保证运行时能读到value参数
别漏掉 public $value; 属性——它会自动绑定注解里的 @PreAuthorization("user:delete") 值。如果想支持多个权限,得手动解析字符串或改用数组参数,Hyperf 不自动展开。
PreAuthorizationAspect 必须通过 $annotations 关联注解类
切面类不能靠“猜”,必须显式告诉框架:“我只管 PreAuthorization 注解”。所以 $annotations = [PreAuthorization::class]; 这行不能少,也不能写成字符串 "AppAnnotationPreAuthorization"——类名必须是真实类引用。
另外两个常见错误:
-
$classes和$annotations是“或”关系,不是“且”。填了$classes就会忽略$annotations,别俩都写还指望同时生效 - 切面构造函数里依赖的
RequestInterface、ContainerInterface必须用@Inject或构造注入,不能手动 new——否则容器没托管,后续取不到用户信息
权限校验逻辑里不能硬写 $_SERVER 或全局变量
Hyperf 是协程环境,$_SERVER、$_SESSION 不可靠,get_current_user() 更是完全没用。正确姿势是:
- 从
RequestInterface的getAttribute('user')取登录态(前提是你的 Auth 中间件已把用户塞进去) - 或者用
$this->container->get(JwtTokenService::class)->parse()手动解析 token(如果你没走中间件) - 千万别在
process里调Co::usleep()或阻塞 IO——协程会卡死整个 worker
示例判断逻辑长这样:
public function process(ProceedingJoinPoint $proceedingJoinPoint)
{
$user = $this->request->getAttribute('user');
if (!$user) {
throw new UnauthorizedException('未登录');
}
$requiredPermission = $this->getRequiredPermission($proceedingJoinPoint);
if (!in_array($requiredPermission, $user['permissions'] ?? [])) {
throw new ForbiddenException('权限不足');
}
return $proceedingJoinPoint->process();
}切面不生效?先清 runtime/container/proxy/ 缓存
Hyperf 启动时会为每个被切的类生成代理类,缓存在 runtime/container/proxy/ 下。改完切面代码不生效,90% 是因为缓存没清。执行:
rm -rf runtime/container/proxy/ php bin/hyperf.php start
另外两个高频原因:
-
config/autoload/annotations.php里没把你放切面类的目录加进scan列表,比如漏了'App\Aspect\' - 切面类没加
#[Aspect]或@Aspect注解,或者注解写在了namespace上而不是class上
真正麻烦的是:权限校验失败后抛异常,但 HTTP 状态码还是 200——因为切面没接管响应流程。你得在异常处理器里统一转成 401/403,否则前端永远收不到正确状态。

















