签入版本

This commit is contained in:
2026-09-29 10:14:04 +08:00
parent 3e5fc01124
commit d6b552103d
1664 changed files with 298944 additions and 0 deletions
@@ -0,0 +1,343 @@
# 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]` 组合是最常见的模式 |