【基于 Swoole+Hyperf 的微服务实战】第一周·周四:正式踏入 Hyperf 的世界

今天将正式踏入 Hyperf 的世界。Hyperf 是一个基于 Swoole 的、高性能的协程框架,它将之前几天我们手写的各种机制(HTTP 服务、协程、依赖注入等)进行了企业级的封装和抽象。理解它的骨架和生命周期,是后续微服务开发的前提。
今日目标
- 使用 Composer 创建 Hyperf 项目,并成功启动服务。
- 彻底理解 Hyperf 的骨架目录结构,知道每块代码应该放在哪里。
- 搞懂 Hyperf 一次请求的完整生命周期:Request → 路由 → 中间件 → 控制器 → Response。
- 能够编写一个自定义路由和控制器,并访问成功。
- 观察常驻内存下,修改代码后需要手动重启的现象,加深对 Swoole 特性的理解。
一、环境准备与 Hyperf 安装 (约 1 小时)
我们继续使用之前的 swoole-course 目录和 Docker 容器,但今天的内容放在一个全新的 Hyperf 项目中。
1. 进入容器并检查环境
cd swoole-course
docker-compose exec swoole bash
确认 Swoole 扩展和 PHP 版本:
php -v # 应为 8.2.x
php -m | grep swoole # 必须有 swoole
Hyperf 需要一些基础扩展,我们的 phpswoole/swoole:5.1-php8.2 镜像已经全部包含,无需额外安装。
2. 使用 Composer 创建 Hyperf 项目
我们将项目创建在容器内的 /var/www/hyperf-app 目录,由于目录已挂载,宿主机也能直接编辑代码。
cd /var/www
composer create-project hyperf/hyperf-skeleton hyperf-app
如果网络较慢,可先配置 Composer 中国镜像:
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
安装过程中会提示一些选项,对于第一次体验,我们可以全部按回车使用默认值(比如数据库选择 hyperf/database 等,后续会用到)。如果不想交互,可以使用 --no-dev 选项,但建议保留开发依赖。等待安装完成,看到 success 即可。
3. 检查项目结构
进入项目目录,看看生成了什么:
cd hyperf-app
ls -la
你会看到如下主要目录和文件:
app/ # 应用核心代码
bin/ # 启动脚本
config/ # 所有配置文件
runtime/ # 运行时日志、缓存
vendor/ # Composer 依赖
.env # 环境变量
composer.json
4. 配置环境变量
复制默认环境文件:
cp .env.example .env
打开 .env,确认 APP_ENV=dev 和 DB_ 相关配置(今天不用数据库,可以不管)。关键点:Hyperf 默认监听 0.0.0.0:9501,与之前我们手工创建的 Swoole HTTP 服务器端口一致。
二、知识核心:Hyperf 骨架与生命周期 (约 1.5 小时)
1. Hyperf 与 Swoole 的关系
Hyperf 是完全建立在 Swoole 协程之上的。它不替代 Swoole,而是提供了:
- 依赖注入容器(PSR-11)
- AOP 面向切面编程
- 注解机制
- 丰富的协程客户端(MySQL、Redis、RPC 等)
你可以把 Hyperf 看作一个装备精良的“太空舱”,而我们周一至周三写的代码好比是“手工打造的火箭零件”。今天我们要钻进去看看内部构造。
2. 骨架目录深度剖析
| 目录/文件 | 作用 | 你将放置的内容 |
|---|---|---|
app/ | 业务核心,包含控制器、模型、中间件、自定义注解等 | 控制器 Controller、模型 Model、中间件 Middleware 等 |
app/Controller/ | 默认控制器目录 | 所有 HTTP 控制器,每个方法对应一个路由 |
config/ | 所有配置文件,覆盖框架默认值 | autoload/ 下的文件会被自动加载,如数据库、Redis、路由配置 |
config/routes.php | 路由配置文件 | 定义 URL 到控制器的映射 |
config/autoload/dependencies.php | 依赖注入的绑定关系 | 接口到实现的映射 |
bin/hyperf.php | 应用启动入口 | 一般不需要修改 |
runtime/ | 日志、缓存、编译后的代理类等 | 所有运行时产生的文件,可随时删除 |
vendor/ | Composer 依赖 | 绝不要手动修改 |
.env | 环境变量 | 数据库密码、应用密钥等 |
重点:config/ 目录下的配置分为 autoload/ 和其他。autoload/ 下的配置文件不需要显式引入,框架会自动扫描合并。这是 Hyperf 的约定。
3. 一次请求的生命周期(核心!)
理解这个流程,后续开发才能游刃有余。我们以一个访问 /index/index 的请求为例:
- Swoole HTTP 服务器接收到请求,触发
onRequest回调。 - Hyperf 框架核心接管,从容器中解析
Hyperf\HttpServer\Server。 - 创建协程上下文:为该请求创建全新的协程和专属的 Context。
- 路由匹配:根据
config/routes.php找到对应的控制器和方法。 - 全局中间件执行(
CoreMiddleware):处理请求和响应,如解析 JSON 体、设置协程上下文等。 - 路由级中间件:按顺序执行,可以进行权限校验、日志记录等。
- 控制器方法调用:依赖注入容器会自动解析控制器构造函数和方法的参数,然后执行。
- 返回响应:控制器返回值或
Response对象交给 Swoole 的$response->end()。 - 清理协程上下文:请求结束,协程销毁。
图解:
Request → [路由匹配] → [全局中间件] → [路由中间件1] → [路由中间件2] → [控制器] → Response
中间件可以在控制器之前或之后执行(通过 $handler->handle($request) 分割)。这个机制我们明天会亲手实践。
三、实战:启动项目并编写第一个接口 (约 2 小时)
1. 启动 Hyperf 服务
在项目根目录下执行:
php bin/hyperf.php start
你会看到熟悉的 Swoole 启动信息,显示 Worker 进程数、监听端口等。
[INFO] Server listening on 0.0.0.0:9501
注意:如果之前的手写 Swoole 服务器还在运行,先 Ctrl+C 关闭,端口才不会冲突。
现在打开浏览器访问 http://localhost:9501,会看到 Hyperf 的欢迎页面,显示 “Hello Hyperf.”。这说明框架已成功运行。
2. 分析默认路由与控制器
打开 config/routes.php:
<?php
use Hyperf\HttpServer\Router\Router;
Router::get('/favicon.ico', function () {
return '';
});
Router::get('/', function () {
return 'Hello Hyperf.';
});
可见,默认是闭包路由。我们再看看控制器路由的样子(被注释掉的):
Router::get('/hello-hyperf', [App\Controller\IndexController::class, 'index']);
取消这行注释(如果存在),然后打开 app/Controller/IndexController.php:
<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\RequestMapping;
class IndexController extends AbstractController
{
public function index()
{
$user = $this->request->input('user', 'Hyperf');
$method = $this->request->getMethod();
return [
'method' => $method,
'message' => "Hello {$user}.",
];
}
}
注意:Hyperf 控制器继承了 AbstractController,可以直接使用 $this->request 和 $this->response。
3. 动手:添加一个新路由和控制器
我们现在自己创建一个接口。
第一步:创建控制器 app/Controller/UserController.php
<?php
declare(strict_types=1);
namespace App\Controller;
class UserController extends AbstractController
{
public function info(int $id)
{
return [
'code' => 200,
'data' => [
'id' => $id,
'name' => 'Swoole',
'email' => 'swoole@hyperf.io',
]
];
}
}
第二步:在 config/routes.php 中添加路由(在文件最底部):
// 添加一个 GET 路由,带参数
Router::get('/user/{id:\d+}', [App\Controller\UserController::class, 'info']);
4. 测试新接口
保存文件后,你会发现并没有自动生效。这是因为 Swoole 是常驻内存的,代码只在启动时加载。必须重启服务才能看到新代码。
回到终端,Ctrl+C 停止服务,然后重新执行 php bin/hyperf.php start。或者使用 Hyperf 的 watch 热重启(后续会学)。
现在访问:
curl http://localhost:9501/user/123
输出:
{"code":200,"data":{"id":123,"name":"Swoole","email":"swoole@hyperf.io"}}
成功!你已经完成了第一个 Hyperf 接口。
5. 使用注解路由(可选,提前感受)
Hyperf 还支持注解方式定义路由,这样不需要修改 routes.php。将 UserController 改成:
<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\RequestMapping;
#[Controller(prefix: '/user')]
class UserController extends AbstractController
{
#[RequestMapping(path: 'info/{id:\d+}', methods: 'get')]
public function info(int $id)
{
// ...
}
}
然后删除 routes.php 中对应路由,重启服务,一样有效。注解是 Hyperf 强大的特性之一,我们将在第二周深入学习。
四、成果测试与生命周期观察 (约 1 小时)
1. 观察常驻内存与变量共享
我们做一个小实验来理解常驻内存的影响。
修改 UserController,在类中增加一个静态属性:
class UserController extends AbstractController
{
private static int $count = 0;
public function info(int $id)
{
self::$count++;
return [
'count' => self::$count,
'data' => ['id' => $id]
];
}
}
重启服务,用 curl 连续访问 http://localhost:9501/user/1,你会看到 count 依次递增:1, 2, 3…
{"count":1,"data":{"id":1}}
{"count":2,"data":{"id":1}}
这说明静态属性在多个请求之间是共享的!这就是常驻内存的危险与优势:你可以用来做计数器,但也要小心全局状态污染。在生产中,应该使用协程安全的工具(如 Context、Redis)来存储请求间数据,而不是静态变量。
2. 生命周期验证:追踪中间件和控制器执行
Hyperf 提供了非常方便的日志通道,我们可以在控制器和未来的中间件中打印日志,观察执行顺序。但今天我们可以简单地:
在 app/Controller/IndexController.php 的 index 方法中,添加 var_dump('Controller executed');,然后重启服务,访问,终端会直接输出。这在实际开发中非常有用。
3. 测试清单
| 检验项 | 方法 | 通过标准 |
|---|---|---|
| Hyperf 成功启动 | php bin/hyperf.php start | 无报错,显示监听 9501 |
| 欢迎页 | 浏览器访问 http://localhost:9501 | 显示 “Hello Hyperf.” |
| 自定义路由访问 | curl http://localhost:9501/user/5 | 返回 JSON 包含用户信息 |
| 静态变量递增现象 | 连续 curl 同一接口 | count 字段每次递增 |
| 重启后生效 | 修改控制器返回值,重启后 curl | 返回修改后的内容 |
| 热重启缺失 | 修改代码不重启,curl | 返回结果不变,证明常驻内存 |
4. 进阶思考与自测
- 问题:为什么 Hyperf 启动后,修改代码必须重启?Swoole 的
reload机制是怎样的?你会看到bin/hyperf.php start启动的是 Master 和 Manager 进程,Worker 进程负责处理请求。修改文件后可以发送SIGUSR1信号给 Manager 实现热重启。Hyperf 提供了php bin/hyperf.php server:watch命令支持文件监听(需要额外安装hyperf/watcher组件,我们之后会用到)。 - 任务:尝试在
config/autoload/中新建一个custom.php,返回一个数组配置,并在控制器中通过config('custom.key')读取(使用Hyperf\Utils\ApplicationContext或注入ConfigInterface)。
五、今日作业与学习产出
- 提交代码:将整个
hyperf-app项目提交到 Git 仓库(注意.env不要提交敏感信息,可提交.env.example)。 - 绘制生命周期图:使用流程图工具或手绘,画出从 HTTP 请求进入到响应用户的完整过程,标注核心中间件、路由、控制器位置。
- 学习笔记:记录 Hyperf 骨架目录的作用,以及常驻内存带来的优缺点。
- 挑战任务:实现一个
GET /time接口,返回当前服务器时间,但要求时间格式通过配置文件config/autoload/custom.php中的time_format项控制。考验你对配置读取的掌握。
经过今天的学习,你已经能熟练搭建 Hyperf 工程,理解其运行机制,为明天深入依赖注入、注解、AOP 等核心特性打下了坚实基础。
更多推荐
所有评论(0)