# 5.18 注解系统总览 :::tip 概述 项目使用 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种常量) // └── 操作标题 ``` ### 参数说明 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `title` | string | ✓ | 操作标题,如"用户管理-新增记录" | | `type` | int | ✗ | 操作类型常量,默认 0(由中间件按 HTTP 方法推断) | | `description` | string | ✗ | 详细描述,支持 `{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_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 **作用域:** 方法 + 类(方法优先) ### 基本用法 ```php #[Permission('sys:user:add', '添加用户')] // │ │ // │ └── 权限名称(用于提示和日志) // └── 权限编码 ``` ### 参数说明 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `code` | string | ✓ | 权限编码,如 `sys:user:add` | | `name` | string | ✗ | 权限名称,用于提示和日志 | ### 使用示例 ```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]` 组合是最常见的模式 |