- 后端
- 微服务
【免费下载链接】hyperf
🚀 A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.
HTTP 是一种无状态协议,服务器默认不会保留与客户端交易时的任何状态。要在 Hyperf 项目中实现多个请求之间用户数据的共享,最常用的方式就是引入 Session 会话管理组件。本文以官方文档 docs/zh-cn/session.md 为主线,结合仓库内 src/session 组件的真实源码与配置,完整讲解 Session 的安装、配置、驱动选型、常用 API 以及底层工作原理,帮助你在 Hyperf 应用中快速、正确地落地 Session 功能。
一、Session 解决什么问题
HTTP 请求之间彼此独立,服务端无从判断两次请求是否来自同一个用户。Session 的核心思路是:在服务端保存一份与用户绑定的数据,并通过一个唯一标识(Session ID)在客户端(通常以 Cookie 形式)与服务器之间建立关联。此后,每个请求带着这个标识到达服务端时,服务端就能取出对应的会话数据,实现"登录状态保持""购物车""表单回显"等跨请求的数据共享。
在 Hyperf 中,Session 功能由 hyperf/session 组件提供,官方文档明确:组件当前主要适配了文件和Redis两种存储驱动,默认使用文件驱动;在生产环境下,强烈建议使用Redis驱动,因为其性能更好,也更符合集群架构下的使用场景。
二、安装组件
在 Hyperf Skeleton 项目根目录执行以下命令安装 Session 组件:
composer require hyperf/session安装完成后,组件会通过ConfigProvider自动完成相关配置的注册与依赖注入绑定。若需要将默认配置文件发布到项目的config/autoload/目录,可执行:
php bin/hyperf.php vendor:publish hyperf/session发布命令会生成config/autoload/session.php配置文件,即 Session 组件的主要配置存放位置。
三、配置详解
3.1 配置文件结构与默认值
发布后的config/autoload/session.php内容与组件内置的 publish/session.php 一致,完整结构如下:
<?php use Hyperf\Session\Handler; return [ 'handler' => Handler\FileHandler::class, 'options' => [ 'connection' => 'default', 'path' => BASE_PATH . '/runtime/session', 'gc_maxlifetime' => 1200, 'session_name' => 'HYPERF_SESSION_ID', 'domain' => null, 'cookie_lifetime' => 5 * 60 * 60, 'cookie_same_site' => 'lax', ], ];各配置项的含义与默认值整理如下:
| 配置项 | 默认值 | 说明 |
|---|---|---|
handler | Hyperf\Session\Handler\FileHandler::class | Session 存储驱动的 Handler 类名,可改为RedisHandler等 |
options.connection | default | Redis 驱动使用的连接名,需与 hyperf/redis 组件config/autoload/redis.php中的连接 key 对应 |
options.path | BASE_PATH . '/runtime/session' | 文件驱动下 Session 数据文件的存放目录 |
options.gc_maxlifetime | 1200 | 会话有效期(单位:秒),超期数据将被视为失效 |
options.session_name | HYPERF_SESSION_ID | Session Cookie 的名称,也是浏览器 Cookie 的 key |
options.domain | null | Cookie 的 Domain,为null时由中间件取当前请求的 Host |
options.cookie_lifetime | 5 * 60 * 60 | Cookie 的过期时间(单位:秒),默认 5 小时 |
options.cookie_same_site | lax | Cookie 的 SameSite 属性,用于 CSRF 防护 |
从源码 FileHandlerFactory.php 可以看到gc_maxlifetime直接决定了文件驱动下会话的有效期:工厂通过$config->get('session.options.gc_maxlifetime', 1200)读取该值并传入 Handler,作为"分钟数"参与读取校验;而 FileHandler.php 的read()方法会使用Carbon::now()->subMinutes($this->minutes)比较文件的最后修改时间,超时的 Session 文件将被视为不存在并返回空数据。
3.2 配置 Session 中间件
Session 组件必须通过中间件介入请求流程,才能完成 Session 的启动、读写、保存与 Cookie 下发。因此需要将Hyperf\Session\Middleware\SessionMiddleware注册为 HTTP Server 的全局中间件,配置文件config/autoload/middlewares.php示例如下:
<?php return [ // 这里的 http 对应默认的 server name,如您需要在其它 server 上使用 Session,需要对应的配置全局中间件 'http' => [ \Hyperf\Session\Middleware\SessionMiddleware::class, ], ];注意:如果您的应用启用了多个 Server(如同时监听
http与tcp),需要在每个需要使用 Session 的 Server 对应的 server name 下分别配置该中间件。
中间件的执行逻辑在 SessionMiddleware.php 中清晰可见:
- 先通过
$this->config->has('session.handler')判断 Session 是否已配置,未配置则直接放行(isSessionAvailable()); - 调用
SessionManager::start($request)启动会话,解析请求 Cookie 中的 Session ID,并加载历史数据; - 执行后续请求处理链,在
finally块中调用SessionManager::end($session)(内部执行save()持久化); - 通过
addCookieToResponse()将 Session Cookie 写入响应。
其中storeCurrentUrl()会在 GET 请求时把当前完整 URL 记录到 Session 中,供previousUrl()读取;Cookie 的secure属性会根据请求是否为 HTTPS 自动判定,httpOnly固定为true,SameSite则取自配置项options.cookie_same_site。
3.3 使用文件存储驱动
文件存储驱动是默认驱动,配置方式为将handler设置为Hyperf\Session\Handler\FileHandler:
<?php use Hyperf\Session\Handler; return [ 'handler' => Handler\FileHandler::class, 'options' => [ 'path' => BASE_PATH . '/runtime/session', 'gc_maxlifetime' => 1200, ], ];options.path:所有 Session 数据文件都会被生成并存储在该目录下,默认是根目录下的runtime/session文件夹;- 从源码看,FileHandler.php 在构造时会自动检查目录是否存在,不存在则通过 Filesystem 以
0755权限递归创建; - 每个会话对应一个以 Session ID 命名的文件,文件内容为 PHP
serialize()序列化后的会话数据; gc()清理过期文件时使用 Symfony Finder 按文件修改时间过滤,删除超过gc_maxlifetime秒的会话文件。
3.4 使用 Redis 存储驱动
使用 Redis 驱动前,需要先安装 hyperf/redis 组件:
composer require hyperf/redis然后将handler改为Hyperf\Session\Handler\RedisHandler,并通过options.connection指定要使用的 Redis 连接(该值与config/autoload/redis.php配置中的 key 命名匹配):
<?php use Hyperf\Session\Handler; return [ 'handler' => Handler\RedisHandler::class, 'options' => [ 'connection' => 'default', 'gc_maxlifetime' => 1200, ], ];从源码 RedisHandlerFactory.php 可以看到,工厂会从容器中获取Hyperf\Redis\RedisFactory,再调用$redisFactory->get($connection)得到指定连接,连同gc_maxlifetime一起构造RedisHandler。
RedisHandler.php 的实现非常简洁高效:
read($id):执行redis->get($id),取不到返回空字符串;write($id, $data):执行redis->setEx($id, $this->gcMaxLifeTime, $data),即写入的同时以gc_maxlifetime为过期时间,天然实现会话过期,无需额外的 GC 清理(gc()直接返回 0);destroy($id):执行redis->del($id)删除会话;- 构造函数还会校验传入的 Redis 客户端必须是
Redis、RedisArray、RedisCluster、Predis\Client或Hyperf\Redis\Redis之一,否则抛出InvalidArgumentException。
由于数据统一存储在 Redis 中、不依赖单机文件系统,因此多节点部署时可以共享同一份会话数据,这正是文档建议生产环境优先使用 Redis 驱动的原因。
补充:从当前仓库源码结构看,src/session/src/Handler 目录下还提供了
DatabaseHandler(数据库存储驱动,配合 Hyperf 数据库组件使用)与NullHandler(空实现,常用于测试)等 Handler 实现,以及与之对应的*Factory工厂类。若需要自定义驱动,实现SessionHandlerInterface并提供工厂类、修改handler配置即可。
四、Session 的基本使用
4.1 获得 Session 对象
在控制器或其他由容器管理的类中,通过属性注入Hyperf\Contract\SessionInterface即可获得 Session 对象,直接调用接口定义的方法:
<?php namespace App\Controller; use Hyperf\Di\Annotation\Inject; use Hyperf\Contract\SessionInterface; class IndexController { #[Inject] private SessionInterface $session; public function index() { // 直接通过 $this->session 来使用 } }SessionInterface定义在 src/contract/src/SessionInterface.php,是 Hyperf 对 Session 能力的统一抽象;默认由 Session.php 实现。需要说明的是,Session 对象是"每个请求一个实例"的数据类(源码注释明确要求每次请求创建新实例),由SessionManager在中间件中创建并放入协程上下文Context,请求内随处可注入使用。
4.2 储存数据
使用set(string $name, $value): void方法储存数据:
<?php $this->session->set('foo', 'bar');从 Session.php 的实现看,set()内部通过data_set()写入$attributes数组,因此支持点号嵌套路径,例如$this->session->set('user.name', 'hyperf')会写入到['user' => ['name' => 'hyperf']]结构中。若需一次性写入多个键值对,可使用put($key, $value = null),传入数组时批量写入:
<?php $this->session->put(['foo' => 'bar', 'user' => ['id' => 1]]);4.3 获取数据
使用get(string $name, $default = null)获取数据,支持点号路径,未命中时返回传入的默认值(默认为null):
<?php $this->session->get('foo', $default = null);一次性获取所有已储存数据,使用all(): array:
<?php $data = $this->session->all();all()返回的是整个$attributes数组,即当前会话全部数据的快照。
4.4 判断 Session 中是否存在某个值
使用has(string $name): bool判断某个值是否存在。只要该值存在且不为null,has方法就会返回true:
<?php if ($this->session->has('foo')) { // }4.5 获取并删除一条数据
使用remove(string $name)一步完成"获取并删除":
<?php $data = $this->session->remove('foo');该方法返回被移除的值,若不存在则返回null。源码中通过Arr::pull($this->attributes, $name)实现。
4.6 删除一条或多条数据
使用forget(string|array $name): void删除数据:传入字符串表示删除一条,传入 key 字符串数组表示删除多条:
<?php $this->session->forget('foo'); $this->session->forget(['foo', 'bar']);4.7 清空当前 Session 数据
使用clear(): void清空当前 Session 里的所有数据:
<?php $this->session->clear();注意:clear()只清空内存中的属性数组,下一次save()时会以空数组覆盖存储层数据;如果需要"清空并重新生成会话 ID、同时删除旧会话",应使用invalidate()(详见下文)。
4.8 获取当前的 Session ID
当需要拿 Session ID 自行处理一些逻辑时,使用getId(): string:
<?php $sessionId = $this->session->getId();Session ID 由Session构造时生成:generateSessionId()使用Str::random(40)生成 40 位随机字符串,且isValidId()要求 ID 必须为 40 位字母数字(ctype_alnum校验),相关逻辑在 Session.php 与 SessionManagerTest.php 中均有覆盖。
五、接口提供的更多能力(源码级扩充)
除文档列出的基础方法外,SessionInterface与Session实现还提供了若干常用能力,在实战中同样高频出现:
| 方法 | 签名 | 说明 |
|---|---|---|
replace | replace(array $attributes): void | 用新数据整体合并覆盖现有属性 |
migrate | migrate(bool $destroy = false, ?int $lifetime = null): bool | 迁移会话到新的 Session ID(保持数据不变);$destroy = true时先销毁旧会话 |
invalidate | invalidate(?int $lifetime = null): bool | 注销当前会话:先clear()清空数据,再migrate(true)销毁旧会话并生成新 ID,常用于登出逻辑 |
save | save(): void | 强制保存并关闭会话(正常情况下请求结束由中间件自动调用) |
isStarted | isStarted(): bool | 判断会话是否已启动 |
token/regenerateToken | token(): string/regenerateToken(): string | 读取/重新生成 CSRF Token(存于_token键) |
previousUrl/setPreviousUrl | previousUrl(): ?string/setPreviousUrl(string $url): void | 读取/写入上一页 URL(中间件会自动为 GET 请求记录) |
push | push(string $key, $value): void | 向 Session 中的某个数组键追加一个值 |
此外,Session类通过use FlashTrait(见 FlashTrait.php)还内置了Flash 一次性数据机制,常用于"表单校验错误提示"这类只在下一个请求有效的场景:
flash($key, $value = true):写入数据,并标记为"新 Flash 数据",下次请求后自动过期;now($key, $value):写入仅对当前请求有效的 Flash 数据;reflash():将所有 Flash 数据保留到下一个请求;keep($keys = null):仅保留指定的 Flash 键;flashInput(array $value):将输入数据(如表单旧值)写入_old_input,配合"校验失败回显"使用。
数据在save()时通过ageFlashData()完成"新旧 Flash 数据轮转"(旧的过期、新的转旧),这是 Flash 机制能自动失效的核心。
六、底层原理:一次请求的完整会话流程
结合 SessionManager.php、SessionMiddleware.php 与 Session.php,一次带 Session 的 HTTP 请求大致经历以下流程:
- 启动:中间件调用
SessionManager::start($request)。SessionManager通过parseSessionId()遍历请求 Cookie,查找名为session_name(默认HYPERF_SESSION_ID)的 Cookie 值作为 Session ID;未找到时构造一个全新 ID。 - 构建 Handler:
buildSessionHandler()读取session.handler配置并从容器解析对应 Handler 实例(若配置非法会抛出InvalidArgumentException)。 - 加载数据:
Session::start()内部调用loadSession(),通过readFromHandler()从 Handler 读取序列化数据并unserialize()还原为属性数组。 - 业务处理:请求进入控制器等业务代码,开发者通过注入的
SessionInterface读写会话数据。 - 保存:请求处理完毕后,中间件
finally中调用SessionManager::end($session)即save(),将serialize($this->attributes)后的数据交给 Handler 的write()持久化(Redis 驱动同时设置过期时间)。 - 下发 Cookie:
addCookieToResponse()构造Cookie对象写入响应,Cookie 的过期时间由options.cookie_lifetime(默认 5 小时)或options.expire_on_close决定。
由于中间件基于 PSR-15 规范实现,且 Session 实例存放在协程上下文中,在 Hyperf 的常驻内存 + 协程模型下,每个请求都能获得独立、隔离的会话实例,互不串扰。
七、测试与验证
仓库内 src/session/tests 提供了完整的单元测试,可作为理解组件行为与自定义 Handler 时的参考范本:
- SessionTest.php:覆盖 Session 对象的创建、ID 校验、属性读写(
set/get/has/remove/put/forget/clear/replace)等核心 API; - FileHandlerTest.php:验证文件驱动的写入、读取与过期清理;
- SessionManagerTest.php:验证会话名获取、Session ID 解析与 Handler 构建;
- SessionMiddlewareTest.php:验证中间件的启动、Cookie 下发与请求放行逻辑。
例如SessionTest中通过Str::random(40)构造 ID 后断言isValidId()返回true,并逐一验证各种数据类型的存取一致性,与本文前述 API 行为完全对应。
八、生产实践建议
- 驱动选型:单机开发调试可用默认文件驱动;多实例部署或对性能有要求的场景务必切换到
Redis驱动,并确保options.connection指向正确的 Redis 连接。 - 过期时间:
gc_maxlifetime(存储层过期)与cookie_lifetime(Cookie 过期)是两个独立概念,建议根据业务登录态时长统一规划,避免出现"Cookie 还在、服务端数据已过期"的不一致体验。 - 安全属性:默认 Cookie 已开启
httpOnly,HTTPS 下自动开启secure;cookie_same_site默认lax,可有效缓解 CSRF 风险。如需跨子域共享会话,可配置options.domain。 - 登出实现:登出时建议调用
invalidate()而非仅clear(),以便同时销毁服务端旧会话并重新生成 ID,降低会话固定(Session Fixation)风险。 - 自定义驱动:如需接入其他存储(如内存表、第三方缓存),实现
SessionHandlerInterface并提供对应的工厂类、修改handler配置即可,组件其余流程无需改动。
- 后端
- 微服务
【免费下载链接】hyperf
🚀 A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.
相关推荐
Hyperf Session 会话管理完全指南:从中间件配置到多驱动存储实战
Hyperf Session 会话管理完全指南:从中间件配置到多驱动存储实战 HTTP 是一种无状态协议,服务器不保留与客户端交易时的任何状态,因此开发 HTT
后端Web框架微服务RPC框架异步编程Hyperf Session 会话管理实战指南:文件与 Redis 双驱动、中间件配置与 API 全解析
Hyperf Session 会话管理实战指南:文件与 Redis 双驱动、中间件配置与 API 全解析 HTTP 是一种无状态协议,服务器不会保留与客户端交易
后端微服务Hyperf 会话管理(Session)实战指南:安装、配置、存储驱动与完整 API 使用
Hyperf 会话管理(Session)实战指南:安装、配置、存储驱动与完整 API 使用 HTTP 协议本身是无状态的,服务端无法天然地在多次请求之间保留用户
后端Web框架微服务RPC框架异步编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考