Files
rxthinkcmf-wiki/RXThinkCMF_Laravel13_AntdVue/zh/core/attributes.md
T
2026-10-08 17:50:29 +08:00

11 KiB
Raw Blame History

5.17 注解系统总览

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

注解清单

注解 文件 作用域 读取方 用途 必填参数
#[OperationLog] OperationLog.php(275 行) 方法 LogOperation 中间件 操作日志记录 title
#[Permission] Permission.php 方法 Authenticate 中间件 权限校验 code
#[DemoAllow] DemoAllow.php 方法 DemoMiddleware 演示环境放行 无

注解执行顺序

请求进入
    │
    ▼
ProxyMiddleware / Cors / ForceJsonResponse(全局 api 组)
    │
    ▼
Authenticate(auth.jwt)
    │ 反射读取 #[Permission] 注解(方法级)
    │ 校验权限码 → 无权限返回 HTTP 403
    │
    ▼
ResolveTenant(tenant)
    │
    ▼
DemoMiddleware(demo,认证路由组内)
    │ 反射读取 #[DemoAllow] 注解
    │ 演示环境 + 写操作 + 无注解 → 拦截返回 403
    │
    ▼
LogOperation(log.operation)
    │ handle:反射读取 #[OperationLog] 注解,判断是否记录
    │ terminate:响应后写入操作日志
    │
    ▼
Controller 方法执行

#[OperationLog] 操作日志注解

文件: app/Attributes/OperationLog.php 读取方: LogOperation 中间件(ReflectionMethod,微秒级,无缓存) 作用域: 方法

基本用法

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

参数说明

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

构造函数签名(来自项目实际代码):

public function __construct(
    public string $title,
    public int $type = 0,
    public string $description = '',
) {}

使用示例

// 新增 / 修改 / 删除
#[OperationLog('用户管理-新增', OperationLog::TYPE_ADD, '新增用户:{username}')]
#[OperationLog('用户管理-修改', OperationLog::TYPE_UPDATE, '修改用户ID:{id}')]
#[OperationLog('用户管理-删除', OperationLog::TYPE_DELETE, '删除用户ID:{id}')]

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

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

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

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

// 清理缓存
#[OperationLog('字典管理-刷新缓存', OperationLog::TYPE_CLEAR)]

// 查询(GET + TYPE_QUERY 不落库,仅作语义标注)
#[OperationLog('用户管理-查询分页', OperationLog::TYPE_QUERY)]

占位符替换

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

// {id} 会被替换为请求参数中 id 的值
// 实际记录:"删除文章ID:42"

操作类型常量(30 种)

常量 值 说明 GET 是否落库
TYPE_ADD 1 新增 —
TYPE_UPDATE 2 修改 —
TYPE_DELETE 3 删除 —
TYPE_QUERY 4 查询 ✗
TYPE_IMPORT 5 导入 ✓
TYPE_EXPORT 6 导出 ✓
TYPE_APPROVE 7 审批 —
TYPE_REJECT 8 驳回 —
TYPE_SUBMIT 9 提交 —
TYPE_WITHDRAW 10 撤回 —
TYPE_CLOSE / TYPE_OPEN 11 / 12 关闭 / 开启 —
TYPE_ENABLE / TYPE_DISABLE 13 / 14 启用 / 禁用 —
TYPE_UPLOAD / TYPE_DOWNLOAD 15 / 16 上传 / 下载 下载 ✓
TYPE_SEND / TYPE_SYNC 17 / 18 发送 / 同步 —
TYPE_GENERATE / TYPE_CLEAR 19 / 20 生成 / 清理 —
TYPE_RESTORE 21 恢复 —
TYPE_LOGIN / TYPE_LOGOUT 22 / 23 登录 / 登出 —
TYPE_RESET 24 重置 —
TYPE_ASSIGN / TYPE_AUTHORIZE 25 / 26 分配 / 授权 —
TYPE_EXECUTE 27 执行 —
TYPE_PAUSE / TYPE_RESUME 28 / 29 暂停 / 恢复执行 —
TYPE_OTHER 99 其他 —

(完整注释见 app/Attributes/OperationLog.php;GET 仅 IMPORT/EXPORT/DOWNLOAD 三类落库,见 5.14 操作日志。)

#[Permission] 权限注解

文件: app/Attributes/Permission.php 读取方: Authenticate 中间件(ReflectionMethod) 作用域: 方法

基本用法

#[Permission('sys:user:add', '添加用户')]
//              │                  │
//              │                  └── 权限名称(菜单管理中显示、403 消息附带编码)
//              └── 权限编码

参数说明

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

使用示例

// 方法级注解(最常用)
#[Permission('sys:user:page', '用户分页')]
public function page(): JsonResponse { ... }

// 与日志组合(最常见模式)
#[OperationLog('用户管理-新增', OperationLog::TYPE_ADD, '新增用户:{username}')]
#[Permission('sys:user:add', '添加用户')]
public function add(UserRequest $request): JsonResponse { ... }

// 不标注 = 该接口只需登录,无需特定权限
public function getOptions(): JsonResponse { ... }

校验规则

1. 方法上有 #[Permission] → 使用方法的权限编码
2. 方法上无注解 → 跳过权限校验(所有已登录用户可访问)
3. uid=1 超级管理员 → 直接放行,不查询权限
4. 权限编码需与菜单表 permission 字段一致(菜单即权限)

详见 5.4 权限管理(RBAC)。

#[DemoAllow] 演示放行注解

文件: app/Attributes/DemoAllow.php 读取方: DemoMiddleware(反射读取,异常静默视为未标注) 作用域: 方法(仅 TARGET_METHOD,不支持类级) 参数: 无(标记型注解)

基本用法

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

工作原理

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

典型场景

// 缓存刷新:仅清缓存不改业务数据,演示环境放行
#[OperationLog('字典管理-刷新缓存', OperationLog::TYPE_CLEAR)]
#[Permission('sys:dict:update', '修改字典')]
#[DemoAllow]
public function refreshCache(): JsonResponse { ... }

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

详见 5.7 演示模式。

注解组合使用

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

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

// 操作日志 + 权限 + 演示放行(三注解齐全的特殊场景)
#[OperationLog('字典管理-刷新缓存', OperationLog::TYPE_CLEAR)]
#[Permission('sys:dict:update', '修改字典')]
#[DemoAllow]
public function refreshCache(): JsonResponse { ... }

反射读取机制

三个中间件均直接使用 ReflectionMethod 读取注解(项目没有独立的注解读取服务,逻辑内聚在各中间件中):

// 通用读取模式(Authenticate / LogOperation / DemoMiddleware 同款)
$route = $request->route();
$controllerClass = $route->getControllerClass();
$method = $route->getActionMethod();

$reflection = new \ReflectionMethod($controllerClass, $method);
$attrs = $reflection->getAttributes(Permission::class);   // 换成目标注解类

if (! empty($attrs)) {
    $permission = $attrs[0]->newInstance();   // $permission->code / ->name
}

:::tip 性能说明 反射读取开销为微秒级(单请求仅 2~3 次方法反射),且中间件对注解缺失场景做了静默降级(反射失败视为无注解,不阻断请求)——无需缓存,避免开发阶段改注解后需等缓存过期。 :::

创建自定义注解

注解类结构参考(摘自 app/Attributes/Permission.php 实际代码):

namespace App\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD)]
class Permission
{
    /**
     * @param  string  $code  权限编码(鉴权唯一依据,如 sys:user:add)
     * @param  string  $name  权限名称(用于展示,如"添加用户")
     * @param  string  $description  权限描述(可选,补充说明)
     */
    public function __construct(
        public string $code,
        public string $name = '',
        public string $description = '',
    ) {}
}

新建自定义注解的三个步骤:

  1. 定义注解类:在 app/Attributes/ 下按上述结构创建类(TARGET_METHOD 标注在方法上,构造参数即注解参数);
  2. 在控制器方法上使用:如 #[Permission('sys:user:add', '添加用户')];
  3. 在中间件中读取:用上一节的反射模式 getAttributes(你的注解类::class) 读取并处理。

最佳实践

实践 说明
注解即文档 通过注解即可了解接口的操作类型、权限要求
写操作必须标注 #[OperationLog] 删除、重置密码等敏感操作必须记录日志
权限码统一格式 使用 sys:{module}:{action} 格式,与菜单 permission 一致
描述加关键参数 用 {param} 占位符记录业务主键,便于追溯
查询注解仅作语义标注 TYPE_QUERY 不落库,勿依赖其做审计
组合使用 #[OperationLog] + #[Permission] 组合是最常见的模式