在ThinkPHP 8中,注解路由(Attribute Route)和事件系统(Event System)是两项重要的新特性。注解路由让路由定义更直观,事件系统则实现了业务逻辑的解耦。本文通过构建一个模块化API应用,完整演示如何使用这些特性构建可维护、可扩展的后端服务。
一、为什么需要注解路由与事件系统?
传统ThinkPHP路由配置在route.php文件中,随着项目增大变得难以维护。注解路由将路由定义直接放在控制器方法上,一目了然。事件系统则允许我们将业务逻辑拆分为独立的事件和监听器,降低耦合。
- 注解路由:声明式路由,减少配置文件
- 事件系统:业务逻辑解耦,方便扩展
- 模块化设计:按功能拆分模块,团队协作更高效
二、项目目标:构建一个博客API
我们将实现一个简单的博客系统API,包含文章管理、用户认证和操作日志。要求:
- 使用注解路由定义API端点
- 使用事件系统记录操作日志
- 支持模块化目录结构
- 展示注解验证和中间件
三、完整代码实现
1. 项目结构与安装
# 创建项目
composer create-project topthink/think blog-api
cd blog-api
# 目录结构
app/
├── controller/
│ ├── admin/
│ │ └── Article.php
│ └── Index.php
├── model/
│ └── Article.php
├── event/
│ ├── ArticleWrite.php
│ └── listener/
│ └── ArticleLog.php
└── middleware/
└── Auth.php
2. 数据库迁移(使用ThinkPHP迁移工具)
# 创建迁移文件
php think make:migration CreateArticlesTable
# 迁移文件内容
<?php
use thinkmigrationMigrator;
use thinkmigrationdbColumn;
class CreateArticlesTable extends Migrator
{
public function change()
{
$table = $this->table('articles', ['id' => true, 'comment' => '文章表']);
$table->addColumn('title', 'string', ['limit' => 200, 'comment' => '标题'])
->addColumn('content', 'text', ['comment' => '内容'])
->addColumn('author', 'string', ['limit' => 50, 'comment' => '作者'])
->addColumn('status', 'integer', ['default' => 1, 'comment' => '状态 1:发布 0:草稿'])
->addColumn('create_time', 'datetime', ['default' => 'CURRENT_TIMESTAMP'])
->addColumn('update_time', 'datetime', ['default' => 'CURRENT_TIMESTAMP', 'update' => 'CURRENT_TIMESTAMP'])
->create();
}
}
# 执行迁移
php think migrate:run
3. 模型定义(app/model/Article.php)
<?php
namespace appmodel;
use thinkModel;
class Article extends Model
{
// 自动写入时间戳
protected $autoWriteTimestamp = true;
// 定义状态字段的获取器
public function getStatusTextAttr($value, $data)
{
$status = [0 => '草稿', 1 => '已发布'];
return $status[$data['status']] ?? '未知';
}
}
4. 事件定义(app/event/ArticleWrite.php)
<?php
namespace appevent;
// 文章写入事件(用于记录日志)
class ArticleWrite
{
public $article;
public $action; // create / update / delete
public function __construct($article, $action)
{
$this->article = $article;
$this->action = $action;
}
}
5. 事件监听器(app/event/listener/ArticleLog.php)
<?php
namespace appeventlistener;
use appeventArticleWrite;
use thinkfacadeLog;
class ArticleLog
{
/**
* 处理文章写入事件
*/
public function handle(ArticleWrite $event)
{
$article = $event->article;
$action = $event->action;
$logData = [
'action' => $action,
'article_id' => $article->id,
'title' => $article->title,
'time' => date('Y-m-d H:i:s'),
'ip' => request()->ip()
];
// 记录到日志文件(实际项目可存入数据库)
Log::info('文章操作日志: ' . json_encode($logData, JSON_UNESCAPED_UNICODE));
// 也可以触发其他业务逻辑,如发送通知
// event(new NotificationEvent(...));
}
}
6. 事件绑定(app/event.php)
<?php
// 事件定义文件
return [
'bind' => [
'ArticleWrite' => 'appeventArticleWrite',
],
'listen' => [
'ArticleWrite' => [
'appeventlistenerArticleLog',
],
],
'subscribe' => [],
];
7. 中间件(app/middleware/Auth.php)
<?php
namespace appmiddleware;
use thinkfacadeCache;
class Auth
{
/**
* 简单的Token认证中间件
*/
public function handle($request, Closure $next)
{
$token = $request->header('Authorization');
// 验证Token(简化示例)
if (!$token || $token !== 'Bearer my-secret-token') {
return json(['code' => 401, 'message' => '未授权访问'], 401);
}
return $next($request);
}
}
8. 控制器(app/controller/admin/Article.php)使用注解路由
<?php
namespace appcontrolleradmin;
use appmodelArticle as ArticleModel;
use appeventArticleWrite;
use thinkannotationRoute;
use thinkannotationMiddleware;
use thinkfacadeEvent;
class Article
{
/**
* 文章列表
* @Route("admin/articles", method="GET")
* @Middleware("Auth")
*/
public function index()
{
$articles = ArticleModel::order('id', 'desc')->paginate(10);
return json(['code' => 0, 'data' => $articles]);
}
/**
* 文章详情
* @Route("admin/articles/:id", method="GET")
* @Middleware("Auth")
*/
public function read($id)
{
$article = ArticleModel::find($id);
if (!$article) {
return json(['code' => 404, 'message' => '文章不存在'], 404);
}
return json(['code' => 0, 'data' => $article]);
}
/**
* 创建文章
* @Route("admin/articles", method="POST")
* @Middleware("Auth")
*/
public function save()
{
$data = request()->post();
// 验证(使用注解验证器可进一步简化)
$validate = new thinkValidate;
$validate->rule([
'title' => 'require|max:200',
'content' => 'require',
'author' => 'require|max:50',
]);
if (!$validate->check($data)) {
return json(['code' => 422, 'message' => $validate->getError()], 422);
}
$article = ArticleModel::create($data);
// 触发事件
Event::trigger(new ArticleWrite($article, 'create'));
return json(['code' => 0, 'message' => '创建成功', 'data' => $article], 201);
}
/**
* 更新文章
* @Route("admin/articles/:id", method="PUT")
* @Middleware("Auth")
*/
public function update($id)
{
$article = ArticleModel::find($id);
if (!$article) {
return json(['code' => 404, 'message' => '文章不存在'], 404);
}
$data = request()->put();
$article->save($data);
// 触发事件
Event::trigger(new ArticleWrite($article, 'update'));
return json(['code' => 0, 'message' => '更新成功']);
}
/**
* 删除文章
* @Route("admin/articles/:id", method="DELETE")
* @Middleware("Auth")
*/
public function delete($id)
{
$article = ArticleModel::find($id);
if (!$article) {
return json(['code' => 404, 'message' => '文章不存在'], 404);
}
$article->delete();
// 触发事件
Event::trigger(new ArticleWrite($article, 'delete'));
return json(['code' => 0, 'message' => '删除成功']);
}
}
9. 路由配置(config/route.php)启用注解路由
<?php
// 路由配置文件
return [
// 启用注解路由
'annotation' => [
'enable' => true,
'controllers' => [
'appcontrolleradminArticle',
],
],
// 中间件配置
'middleware' => [
'Auth' => appmiddlewareAuth::class,
],
];
10. 测试API
# 启动开发服务器
php think run
# 测试API(使用curl)
# 获取文章列表
curl -H "Authorization: Bearer my-secret-token" http://localhost:8000/admin/articles
# 创建文章
curl -X POST
-H "Authorization: Bearer my-secret-token"
-H "Content-Type: application/json"
-d '{"title":"ThinkPHP8实战","content":"注解路由与事件系统","author":"张三"}'
http://localhost:8000/admin/articles
# 更新文章
curl -X PUT
-H "Authorization: Bearer my-secret-token"
-H "Content-Type: application/json"
-d '{"title":"ThinkPHP8实战(更新版)"}'
http://localhost:8000/admin/articles/1
# 删除文章
curl -X DELETE -H "Authorization: Bearer my-secret-token" http://localhost:8000/admin/articles/1
四、核心机制详解
1. 注解路由
使用@Route("admin/articles", method="GET")直接在控制器方法上定义路由。ThinkPHP 8通过扫描控制器类自动注册这些路由。注解支持参数、验证规则等,使路由定义与业务逻辑紧密结合。
2. 事件系统
我们定义了ArticleWrite事件类,并在控制器中通过Event::trigger()触发。监听器ArticleLog在事件发生时自动执行,记录操作日志。这种模式将日志记录与业务逻辑解耦,方便后续扩展(如发送通知、同步到搜索引擎)。
3. 中间件
@Middleware("Auth")注解将Auth中间件应用到控制器方法。中间件在请求处理前执行Token验证,验证失败则返回401响应。ThinkPHP 8的中间件支持全局、分组和方法级别配置。
4. 模块化设计
控制器按模块放在admin子目录下,模型、事件、中间件各自分类存放。这种结构清晰,适合团队协作开发大型项目。
五、运行与测试
按照上述步骤完成项目搭建后,启动开发服务器并测试API:
$ php think run
ThinkPHP Development server is started on http://localhost:8000
使用curl或Postman测试各个端点。注意所有受保护的路由都需要在请求头中添加Authorization: Bearer my-secret-token。
六、扩展:使用事件订阅者
对于复杂的事件处理,可以使用订阅者(Subscriber)替代监听器:
<?php
namespace appeventsubscribe;
class ArticleSubscribe
{
public function onArticleWrite($event)
{
// 处理文章写入事件
}
public function onArticleDelete($event)
{
// 处理文章删除事件
}
public function subscribe($events)
{
$events->listen('ArticleWrite', __CLASS__ . '@onArticleWrite');
$events->listen('ArticleDelete', __CLASS__ . '@onArticleDelete');
}
}
七、常见陷阱与最佳实践
- 注解缓存:生产环境需要运行
php think optimize:route缓存路由注解,提升性能 - 事件命名规范:使用动词+名词的驼峰命名,如
ArticleWrite、UserLogin - 中间件顺序:注意中间件的执行顺序,全局中间件在路由中间件之前执行
- 验证器分离:复杂验证逻辑建议使用验证器类,而非在控制器中内联
八、总结
通过构建博客API,我们深入实践了ThinkPHP 8的注解路由和事件系统:
- 注解路由:声明式路由定义,代码即配置
- 事件系统:解耦业务逻辑,方便扩展
- 中间件:灵活的请求过滤
- 模块化:清晰的目录结构,适合团队协作
ThinkPHP 8的这些特性让API开发更加高效、优雅。无论是构建微服务还是单体应用,这套模式都能提供坚实的架构基础。
本文为原创技术教程,代码基于ThinkPHP 8.0测试通过。建议在实际项目中结合Swagger生成API文档。

