Files
easygoadmin-wiki/EasyGoAdmin_GoFrame_Layui/zh/core/attributes.md
T
2026-10-20 21:19:44 +08:00

9.6 KiB
Raw Blame History

5.18 注解系统总览

:::tip 概述 项目使用 PHP 8 原生注解(Attribute)实现声明式配置,目前有三个注解类,由 AttributeService 通过反射读取。注解标注在控制器方法或类上,中间件在请求处理过程中读取并执行对应逻辑(日志记录、权限校验、演示放行)。 :::

注解清单

注解 作用域 读取方 用途 必填参数
#[Log] 方法 LogMiddleware 操作日志记录 title
#[Permission] 方法/类 AuthMiddleware 权限校验 code
#[DemoAllow] 方法 DemoMiddleware 演示环境放行 无

注解执行顺序

请求进入
    │
    ▼
CorsMiddleware(全局)
    │
    ▼
AuthMiddleware
    │ 读取 #[Permission] 注解
    │ 校验权限码 → 无权限返回 403
    │
    ▼
TenantMiddleware
    │
    ▼
DemoMiddleware
    │ 读取 #[DemoAllow] 注解
    │ 演示环境 + 写操作 + 无注解 → 拦截返回 403
    │
    ▼
LogMiddleware
    │ 读取 #[Log] 注解
    │ 记录操作日志
    │
    ▼
Controller 方法执行

#[Log] 操作日志注解

文件: app/attribute/Log.php 读取方: LogMiddleware 作用域: 方法

基本用法

#[Log('用户管理-新增记录', Log::TYPE_ADD, '新增用户:{username}')]
//      │                    │               │
//      │                    │               └── 描述(支持 {param} 占位符)
//      │                    └── 操作类型(40种常量)
//      └── 操作标题

参数说明

参数 类型 必填 说明
title string ✓ 操作标题,如"用户管理-新增记录"
type int ✗ 操作类型常量,默认 0(由中间件按 HTTP 方法推断)
description string ✗ 详细描述,支持 {param} 占位符

使用示例

// 新增
#[Log('用户管理-新增记录', Log::TYPE_ADD, '新增用户:{username}')]

// 修改
#[Log('用户管理-修改记录', Log::TYPE_UPDATE, '修改用户ID:{id}')]

// 删除
#[Log('用户管理-删除记录', Log::TYPE_DELETE, '删除用户ID:{id}')]

// 批量删除
#[Log('用户管理-批量删除记录', Log::TYPE_DELETE)]

// 导出(GET 请求也会记录)
#[Log('用户管理-导出数据', Log::TYPE_EXPORT)]

// 导入
#[Log('用户管理-导入数据', Log::TYPE_IMPORT, '通过Excel导入用户数据')]

// 重置密码
#[Log('用户管理-重置密码', Log::TYPE_RESET, '重置用户ID:{id}的密码')]

// 登录(TYPE_LOGIN)
#[Log('系统登录-用户登录', Log::TYPE_LOGIN)]

// 登出(TYPE_LOGOUT)
#[Log('系统登录-用户登出', Log::TYPE_LOGOUT)]

// 查询(GET 默认不记录,除非是导出/下载/导入类型)
#[Log('用户管理-查询分页记录', Log::TYPE_QUERY)]

占位符替换

#[Log('文章管理-删除记录', Log::TYPE_DELETE, '删除文章ID:{id}')]

// {id} 会被替换为 $request->param('id') 的值
// 实际记录:"删除文章ID:42"

操作类型常量

常量 值 说明 GET 是否记录
TYPE_ADD 1 新增 —
TYPE_UPDATE 2 修改 —
TYPE_DELETE 3 删除 —
TYPE_QUERY 4 查询 ✗
TYPE_IMPORT 5 导入 ✓
TYPE_EXPORT 6 导出 ✓
TYPE_DOWNLOAD 7 下载 ✓
TYPE_APPROVE 8 审批 —
TYPE_REJECT 9 驳回 —
TYPE_SUBMIT 10 提交 —
TYPE_WITHDRAW 11 撤回 —
TYPE_LOGIN 21 登录 —
TYPE_LOGOUT 22 登出 —
TYPE_RESET 24 重置 —
TYPE_OTHER 99 其他 —

完整常量列表见 app/attribute/Log.php(共 40 种)。

#[Permission] 权限注解

文件: app/attribute/Permission.php 读取方: AuthMiddleware 作用域: 方法 + 类(方法优先)

基本用法

#[Permission('sys:user:add', '添加用户')]
//              │                  │
//              │                  └── 权限名称(用于提示和日志)
//              └── 权限编码

参数说明

参数 类型 必填 说明
code string ✓ 权限编码,如 sys:user:add
name string ✗ 权限名称,用于提示和日志

使用示例

// 方法级注解(最常用)
#[Permission('sys:user:add', '添加用户')]
public function add(): Json { ... }

#[Permission('sys:user:page', '用户分页')]
public function page(): Json { ... }

// 类级注解(该类所有方法都需此权限)
#[Permission('sys:user:manage', '用户管理')]
class UserController extends BaseController { ... }

优先级规则

1. 方法上有 #[Permission] → 使用方法的权限码
2. 方法上无 #[Permission],类上有 → 使用类的权限码
3. 方法和类都没有 → 跳过权限校验(所有已登录用户可访问)

超级管理员

uid=1 的用户为超级管理员,跳过所有权限校验。
AuthMiddleware 检测到 uid=1 时直接放行,不查询权限表。

#[DemoAllow] 演示放行注解

文件: app/attribute/DemoAllow.php 读取方: DemoMiddleware 作用域: 方法 参数: 无

基本用法

// 标注后,演示环境下该写操作不受拦截
#[DemoAllow]
public function refreshCache(): Json { ... }

#[DemoAllow]
public function login(): Json { ... }

工作原理

DemoMiddleware 检查逻辑:
  1. APP_DEMO = false → 放行(非演示环境,env('app_demo'))
  2. APP_DEMO = true + GET 请求 → 放行
  3. APP_DEMO = true + 写操作(POST/PUT/DELETE)
     ├─ 方法有 #[DemoAllow] → 放行
     └─ 方法无 #[DemoAllow] → 拦截,返回 403

典型场景

// 登录接口:演示环境也需要允许登录
#[DemoAllow]
public function login(): Json { ... }

// 缓存刷新:演示环境也需要允许
#[DemoAllow]
public function refreshCache(): Json { ... }

// 用户新增:演示环境禁止(无 #[DemoAllow])
public function add(): Json { ... }

注解组合使用

// 操作日志 + 权限校验(最常见组合)
#[Log('用户管理-新增记录', Log::TYPE_ADD, '新增用户:{username}')]
#[Permission('sys:user:add', '添加用户')]
public function add(): Json { ... }

// 操作日志 + 权限校验 + 演示放行
#[Log('系统登录-用户登录', Log::TYPE_LOGIN)]
#[Permission('sys:login:do', '登录')]
#[DemoAllow]
public function login(): Json { ... }

// 仅权限校验(查询接口不需要日志)
#[Permission('sys:user:page', '用户分页')]
public function page(): Json { ... }

// 仅日志(不需要权限校验的公开接口)
#[Log('系统登录-获取验证码', Log::TYPE_OTHER)]
public function captcha(): Json { ... }

AttributeService

文件: app/service/AttributeService.php 职责: 通过反射读取注解,带缓存机制

方法清单

方法 说明 返回值
getLog($class, $method) 获取方法上的 Log 注解 Log|null
getPermission($class, $method) 获取方法/类上的 Permission 注解 Permission|null
hasDemoAllow($class, $method) 判断方法是否有 DemoAllow 注解 bool

使用示例

// 获取方法上的 Log 注解
$log = AttributeService::getLog('app\controller\UserController', 'add');
if ($log) {
    $title = $log->title;       // '用户管理-新增记录'
    $type = $log->type;         // 1 (TYPE_ADD)
    $desc = $log->description;  // '新增用户:{username}'
}

// 获取方法上的 Permission 注解(方法优先,其次类)
$perm = AttributeService::getPermission('app\controller\UserController', 'add');
if ($perm) {
    $code = $perm->code;  // 'sys:user:add'
    $name = $perm->name;  // '添加用户'
}

// 判断是否有 DemoAllow 注解
$allowed = AttributeService::hasDemoAllow('app\controller\DictController', 'refreshCache');
// true / false

缓存机制

缓存键:类名@方法名
缓存位置:static::$localCache(请求级缓存)
缓存策略:同一请求内不重复反射,跨请求重新解析

示例:
  "app\controller\UserController@add" → Log 实例 + Permission 实例

创建自定义注解

// 1. 定义注解类
// app/attribute/RateLimit.php
namespace app\attribute;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD)]
class RateLimit
{
    public function __construct(
        public int $max = 100,      // 最大请求数
        public int $window = 60,    // 窗口期(秒)
    ) {}
}

// 2. 在控制器方法上使用
#[RateLimit(max: 10, window: 60)]
public function sendSms(): Json { ... }

// 3. 在中间件中读取
public function handle($request, \Closure $next)
{
    $ref = new \ReflectionMethod($controller, $method);
    $attrs = $ref->getAttributes(RateLimit::class);
    if (!empty($attrs)) {
        $rateLimit = $attrs[0]->newInstance();
        // $rateLimit->max, $rateLimit->window
    }
}

最佳实践

实践 说明
注解即文档 通过注解即可了解接口的操作类型、权限要求
写操作必须标注 #[Log] 删除、重置密码等敏感操作必须记录日志
权限码统一格式 使用 sys:{module}:{action} 格式
描述加关键参数 用 {param} 占位符记录业务主键,便于追溯
查询不标注 #[Log] 普通查询不记录日志,避免日志膨胀
组合使用 #[Log] + #[Permission] 组合是最常见的模式