Skip to content

5.18 注解系统总览 ​

概述

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

注解清单 ​

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

注解执行顺序 ​

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

#[Log] 操作日志注解 ​

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

基本用法 ​

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

参数说明 ​

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

使用示例 ​

php
// 新增
#[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)]

占位符替换 ​

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

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

操作类型常量 ​

常量值说明GET 是否记录
TYPE_ADD1新增—
TYPE_UPDATE2修改—
TYPE_DELETE3删除—
TYPE_QUERY4查询✗
TYPE_IMPORT5导入✓
TYPE_EXPORT6导出✓
TYPE_DOWNLOAD7下载✓
TYPE_APPROVE8审批—
TYPE_REJECT9驳回—
TYPE_SUBMIT10提交—
TYPE_WITHDRAW11撤回—
TYPE_LOGIN21登录—
TYPE_LOGOUT22登出—
TYPE_RESET24重置—
TYPE_OTHER99其他—

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

#[Permission] 权限注解 ​

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

基本用法 ​

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

参数说明 ​

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

使用示例 ​

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

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

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

优先级规则 ​

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

超级管理员 ​

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

#[DemoAllow] 演示放行注解 ​

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

基本用法 ​

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

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

工作原理 ​

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

典型场景 ​

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

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

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

注解组合使用 ​

php
// 操作日志 + 权限校验(最常见组合)
#[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

使用示例 ​

php
// 获取方法上的 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

缓存机制 ​

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

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

创建自定义注解 ​

php
// 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] 组合是最常见的模式

小蚂蚁云团队 · 提供技术支持