Files
kiri-router/src/Session.php
T
2025-12-17 21:11:49 +08:00

417 lines
9.9 KiB
PHP

<?php
declare(strict_types=1);
namespace Kiri\Router;
use Kiri\Di\Context;
use Psr\Http\Message\ServerRequestInterface;
/**
* Class Session
* @package Kiri\Router
*/
class Session
{
/**
* Session 数据存储键
*/
private const SESSION_KEY = 'kiri.session.data';
/**
* Session ID 键
*/
private const SESSION_ID_KEY = 'kiri.session.id';
/**
* Session 名称
*/
private const SESSION_NAME = 'KIRI_SESSION_ID';
/**
* Session 存储路径
*/
private static ?string $savePath = null;
/**
* Session 生命周期(秒)
*/
private static int $lifetime = 7200; // 默认2小时
/**
* 是否已启动
*/
private static bool $started = false;
/**
* 初始化 Session
* @param string|null $savePath Session 存储路径
* @param int $lifetime Session 生命周期(秒)
* @return void
*/
public static function init(?string $savePath = null, int $lifetime = 7200): void
{
self::$savePath = $savePath ?? storage(null, 'session');
self::$lifetime = $lifetime;
// 确保存储目录存在
if (!is_dir(self::$savePath)) {
mkdir(self::$savePath, 0755, true);
}
}
/**
* 启动 Session
* @param ServerRequestInterface|null $request
* @return void
* @throws
*/
public static function start(?ServerRequestInterface $request = null): void
{
if (self::$started) {
return;
}
if ($request === null) {
$request = \request();
}
// 获取或创建 Session ID
$sessionId = self::getSessionId($request);
// 加载 Session 数据
$sessionData = self::loadSessionData($sessionId);
// 存储到 Context
Context::set(self::SESSION_KEY, $sessionData);
Context::set(self::SESSION_ID_KEY, $sessionId);
self::$started = true;
}
/**
* 获取 Session ID
* @param ServerRequestInterface $request
* @return string
*/
private static function getSessionId(ServerRequestInterface $request): string
{
$cookies = $request->getCookieParams();
// 从 Cookie 中获取 Session ID
if (isset($cookies[self::SESSION_NAME]) && !empty($cookies[self::SESSION_NAME])) {
$sessionId = $cookies[self::SESSION_NAME];
// 验证 Session ID 格式
if (preg_match('/^[a-zA-Z0-9]{32,}$/', $sessionId)) {
return $sessionId;
}
}
// 生成新的 Session ID
return self::generateSessionId();
}
/**
* 生成 Session ID
* @return string
*/
private static function generateSessionId(): string
{
return bin2hex(random_bytes(16));
}
/**
* 加载 Session 数据
* @param string $sessionId
* @return array
*/
private static function loadSessionData(string $sessionId): array
{
$filePath = self::getSessionFilePath($sessionId);
if (!file_exists($filePath)) {
return [];
}
$data = file_get_contents($filePath);
if ($data === false) {
return [];
}
$sessionData = json_decode($data, true);
if (!is_array($sessionData)) {
return [];
}
// 检查是否过期
if (isset($sessionData['expires_at']) && $sessionData['expires_at'] < time()) {
self::destroySession($sessionId);
return [];
}
return $sessionData['data'] ?? [];
}
/**
* 保存 Session 数据
* @return void
* @throws
*/
public static function save(): void
{
if (!self::$started) {
return;
}
$sessionId = Context::get(self::SESSION_ID_KEY);
$sessionData = Context::get(self::SESSION_KEY);
if ($sessionId === null || $sessionData === null) {
return;
}
$filePath = self::getSessionFilePath($sessionId);
$data = [
'data' => $sessionData,
'expires_at' => time() + self::$lifetime,
'created_at' => time(),
];
file_put_contents($filePath, json_encode($data, JSON_UNESCAPED_UNICODE));
// 设置 Cookie
self::setSessionCookie($sessionId);
}
/**
* 设置 Session Cookie
* @param string $sessionId
* @return void
* @throws
*/
private static function setSessionCookie(string $sessionId): void
{
$response = \response();
$expires = time() + self::$lifetime;
// 获取现有的 Cookie 参数
$cookieParams = $response->getCookieParams();
// 添加 Session Cookie
// Swoole setCookie 参数: name, value, expires, path, domain, secure, httponly, samesite
$cookieParams[] = [
self::SESSION_NAME, // name
$sessionId, // value
$expires, // expires
'/', // path
'', // domain
false, // secure
true, // httponly
'Lax' // samesite
];
$response->withCookieParams($cookieParams);
}
/**
* 获取 Session 文件路径
* @param string $sessionId
* @return string
*/
private static function getSessionFilePath(string $sessionId): string
{
if (self::$savePath === null) {
self::init();
}
return self::$savePath . '/' . $sessionId . '.json';
}
/**
* 销毁 Session 文件
* @param string $sessionId
* @return void
*/
private static function destroySession(string $sessionId): void
{
$filePath = self::getSessionFilePath($sessionId);
if (file_exists($filePath)) {
unlink($filePath);
}
}
/**
* 存储 Session 数据
* @param string|array $key 键名或键值对数组
* @param mixed $value 值(当 $key 为字符串时)
* @return void
* @throws
*/
public static function put(string|array $key, mixed $value = null): void
{
if (!self::$started) {
self::start();
}
$sessionData = Context::get(self::SESSION_KEY, []);
// 支持批量设置
if (is_array($key)) {
$sessionData = array_merge($sessionData, $key);
} else {
$sessionData[$key] = $value;
}
Context::set(self::SESSION_KEY, $sessionData);
}
/**
* 获取 Session 数据
* @param string|null $key 键名,为 null 时返回所有数据
* @param mixed $default 默认值
* @return mixed
* @throws
*/
public static function get(?string $key = null, mixed $default = null): mixed
{
if (!self::$started) {
self::start();
}
$sessionData = Context::get(self::SESSION_KEY, []);
// 如果 key 为 null,返回所有数据
if ($key === null) {
return $sessionData;
}
return $sessionData[$key] ?? $default;
}
/**
* 检查 Session 中是否存在指定键
* @param string $key
* @return bool
* @throws
*/
public static function has(string $key): bool
{
if (!self::$started) {
self::start();
}
$sessionData = Context::get(self::SESSION_KEY, []);
return isset($sessionData[$key]);
}
/**
* 删除 Session 数据
* @param string $key
* @return void
* @throws
*/
public static function forget(string $key): void
{
if (!self::$started) {
self::start();
}
$sessionData = Context::get(self::SESSION_KEY, []);
unset($sessionData[$key]);
Context::set(self::SESSION_KEY, $sessionData);
}
/**
* 清空所有 Session 数据
* @return void
* @throws
*/
public static function flush(): void
{
if (!self::$started) {
self::start();
}
Context::set(self::SESSION_KEY, []);
}
/**
* 获取所有 Session 数据
* @return array
* @throws
*/
public static function all(): array
{
if (!self::$started) {
self::start();
}
return Context::get(self::SESSION_KEY, []);
}
/**
* 销毁 Session
* @return void
* @throws
*/
public static function destroy(): void
{
if (!self::$started) {
return;
}
$sessionId = Context::get(self::SESSION_ID_KEY);
if ($sessionId !== null) {
self::destroySession($sessionId);
}
Context::set(self::SESSION_KEY, []);
Context::set(self::SESSION_ID_KEY, null);
self::$started = false;
}
/**
* 重新生成 Session ID
* @return void
* @throws
*/
public static function regenerate(): void
{
if (!self::$started) {
self::start();
}
$oldSessionId = Context::get(self::SESSION_ID_KEY);
$sessionData = Context::get(self::SESSION_KEY, []);
// 生成新的 Session ID
$newSessionId = self::generateSessionId();
// 删除旧 Session 文件
if ($oldSessionId !== null) {
self::destroySession($oldSessionId);
}
// 保存新 Session
Context::set(self::SESSION_ID_KEY, $newSessionId);
self::setSessionCookie($newSessionId);
}
}