From a1a51914a298a3166fae9689294bd7ea26a59b52 Mon Sep 17 00:00:00 2001 From: kuaifan Date: Wed, 30 Jul 2025 18:32:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E4=B8=8A=E4=B8=8B=E6=96=87=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Middleware/WebApi.php | 2 +- app/Module/ClientContext.php | 83 ++++++++++++++++ app/Module/Doo.php | 46 ++++++--- app/Services/RequestContext.php | 162 +++++++++++++++++++++----------- 4 files changed, 227 insertions(+), 66 deletions(-) create mode 100644 app/Module/ClientContext.php diff --git a/app/Http/Middleware/WebApi.php b/app/Http/Middleware/WebApi.php index 172a4d231..79ee4296a 100644 --- a/app/Http/Middleware/WebApi.php +++ b/app/Http/Middleware/WebApi.php @@ -76,6 +76,6 @@ class WebApi public function terminate() { // 请求结束后清理上下文 - RequestContext::clear(); + RequestContext::clean(); } } diff --git a/app/Module/ClientContext.php b/app/Module/ClientContext.php new file mode 100644 index 000000000..4a66a7752 --- /dev/null +++ b/app/Module/ClientContext.php @@ -0,0 +1,83 @@ +createdAt = microtime(true); + $this->updatedAt = microtime(true); + } + + /** + * 设置上下文 + * @param string $key + * @param mixed $value + * @return void + */ + public function set(string $key, mixed $value): void + { + $this->context[$key] = $value; + $this->updatedAt = microtime(true); + } + + /** + * 批量设置上下文 + * @param array $data + * @return void + */ + public function setMultiple(array $data): void + { + foreach ($data as $key => $value) { + $this->context[$key] = $value; + } + $this->updatedAt = microtime(true); + } + + /** + * 获取上下文 + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get(string $key, mixed $default = null): mixed + { + return $this->context[$key] ?? $default; + } + + /** + * 判断上下文是否存在 + * @param string $key + * @return bool + */ + public function has(string $key): bool + { + return isset($this->context[$key]); + } + + /** + * 更新上下文 + * @return void + */ + public function update(): void + { + $this->updatedAt = microtime(true); + } + + /** + * 清除上下文 + * @return void + */ + public function clear(): void + { + $this->context = []; + } +} diff --git a/app/Module/Doo.php b/app/Module/Doo.php index fb2e79b43..100e37cfc 100644 --- a/app/Module/Doo.php +++ b/app/Module/Doo.php @@ -4,14 +4,17 @@ namespace App\Module; use App\Exceptions\ApiException; use App\Models\User; +use App\Services\RequestContext; use Cache; use Carbon\Carbon; use FFI; +use FFI\CData; +use FFI\Exception; class Doo { - private static $doo; - private static $userLanguage = ""; + private const DOO_INSTANCE = 'doo_instance'; + private const DOO_LANGUAGE = 'doo_language'; /** * char转为字符串 @@ -20,17 +23,26 @@ class Doo */ private static function string($text): string { - return FFI::string($text); + if (!($text instanceof CData)) { + return ""; + } + + try { + return FFI::string($text); + } catch (Exception) { + return ""; + } } /** * 装载 * @param $token * @param $language + * @return FFI */ public static function load($token = null, $language = null) { - self::$doo = FFI::cdef(<<initialize("/var/www", $token, $language); + $instance->initialize("/var/www", $token, $language); + + RequestContext::set(self::DOO_INSTANCE, $instance); + RequestContext::set(self::DOO_LANGUAGE, $language); + + return $instance; } /** @@ -65,10 +82,11 @@ class Doo */ public static function doo($token = null, $language = null) { - if (self::$doo == null) { - self::load($token, $language); + $instance = RequestContext::get(self::DOO_INSTANCE); + if ($instance === null) { + $instance = self::load($token, $language); } - return self::$doo; + return $instance; } /** @@ -277,19 +295,25 @@ class Doo */ public static function translate($text, string $lang = ""): string { - return self::string(self::doo()->translate($text, $lang ?: self::$userLanguage)); + if (empty($text)) { + return ""; + } + if (empty($lang)) { + $lang = RequestContext::get(self::DOO_LANGUAGE); + } + return self::string(self::doo()->translate($text, $lang)); } /** * 设置语言 - * @param string|integer $lang 语言 或 会员ID + * @param string|int $lang 语言 或 会员ID * @return void */ public static function setLanguage($lang) { if (Base::isNumber($lang)) { $lang = User::find(intval($lang))?->lang ?: ""; } - self::$userLanguage = $lang; + RequestContext::set(self::DOO_LANGUAGE, $lang); } /** diff --git a/app/Services/RequestContext.php b/app/Services/RequestContext.php index 817b93533..e503ec00d 100644 --- a/app/Services/RequestContext.php +++ b/app/Services/RequestContext.php @@ -2,33 +2,84 @@ namespace App\Services; +use App\Module\ClientContext; use Illuminate\Http\Request; +use Swoole\Coroutine; +/** + * 请求上下文 + */ class RequestContext { - /** @var array> */ - private static array $context = []; - private const REQUEST_ID_PREFIX = 'req_'; + /** @var string 请求ID前缀 */ + private const REQUEST_ID_PREFIX = 'req'; + + /** @var int 上下文的TTL(生存时间) */ + private const TTL_SECONDS = 3600; // 上下文 TTL 为 1 小时 + + /** @var array 存储每个请求的上下文数据 */ + private static array $context = []; /** * 生成请求唯一ID */ public static function generateRequestId(): string { - return self::REQUEST_ID_PREFIX . uniqid() . mt_rand(10000, 99999); + $pid = getmypid(); + $cid = Coroutine::getCid() ?? 0; + $microtime = str_replace('.', '', microtime(true)); + return self::REQUEST_ID_PREFIX . '_' . $pid . '_' . $cid . '_' . $microtime . '_' . mt_rand(1000, 9999); } /** * 获取当前请求ID */ - private static function getCurrentRequestId(): ?string + public static function getCurrentRequestId($requestId = null): ?string { - /** @var Request $request */ - $request = request(); - return $request?->requestId; + return $requestId ?? request()?->requestId; } + /** + * 获取当前请求的上下文示例 + */ + public static function getCurrentRequestContext($requestId = null): ?ClientContext + { + $requestId = self::getCurrentRequestId($requestId); + if ($requestId === null) { + return null; + } + + if (!isset(self::$context[$requestId])) { + // 如果上下文不存在,则创建一个新的上下文 + self::$context[$requestId] = new ClientContext(); + } else { + // 如果上下文已存在,更新访问时间 + self::$context[$requestId]->update(); + } + + return self::$context[$requestId]; + } + + /** + * 清理过期上下文数据,防止内存泄漏 + */ + public static function cleanExpired(): void + { + $now = microtime(true); + + // 清理过期的上下文 + foreach (self::$context as $requestId => $context) { + if ($now - $context->updatedAt > self::TTL_SECONDS) { + unset(self::$context[$requestId]); + } + } + } + + /** ***************************************************************************************** */ + /** ***************************************************************************************** */ + /** ***************************************************************************************** */ + /** * 设置请求上下文 * @@ -39,13 +90,34 @@ class RequestContext */ public static function set(string $key, mixed $value, ?string $requestId = null): void { - $requestId = $requestId ?? self::getCurrentRequestId(); - if ($requestId === null) { + $context = self::getCurrentRequestContext($requestId); + if ($context === null) { return; } - self::$context[$requestId] ??= []; - self::$context[$requestId][$key] = $value; + $context->set($key, $value); + + // 概率性清理,避免频繁清理影响性能 + if (mt_rand(1, 100) === 1) { + self::cleanExpired(); + } + } + + /** + * 批量设置上下文数据 + * + * @param array $data + * @param string|null $requestId + * @return void + */ + public static function setMultiple(array $data, ?string $requestId = null): void + { + $context = self::getCurrentRequestContext($requestId); + if ($context === null) { + return; + } + + $context->setMultiple($data); } // 与 set 方法的区别是,save 方法会返回传入的 value 值 @@ -65,12 +137,28 @@ class RequestContext */ public static function get(string $key, mixed $default = null, ?string $requestId = null): mixed { - $requestId = $requestId ?? self::getCurrentRequestId(); - if ($requestId === null) { + $context = self::getCurrentRequestContext($requestId); + if ($context === null) { return $default; } - return self::$context[$requestId][$key] ?? $default; + return $context->get($key, $default); + } + + /** + * 获取当前请求的所有上下文数据 + * + * @param string|null $requestId + * @return array + */ + public static function getAll(?string $requestId = null): array + { + $context = self::getCurrentRequestContext($requestId); + if ($context === null) { + return []; + } + + return $context->context ?? []; } /** @@ -82,12 +170,12 @@ class RequestContext */ public static function has(string $key, ?string $requestId = null): bool { - $requestId = $requestId ?? self::getCurrentRequestId(); - if ($requestId === null) { + $context = self::getCurrentRequestContext($requestId); + if ($context === null) { return false; } - return isset(self::$context[$requestId][$key]); + return $context->has($key); } /** @@ -96,9 +184,9 @@ class RequestContext * @param string|null $requestId * @return void */ - public static function clear(?string $requestId = null): void + public static function clean(?string $requestId = null): void { - $requestId = $requestId ?? self::getCurrentRequestId(); + $requestId = self::getCurrentRequestId($requestId); if ($requestId === null) { return; } @@ -106,40 +194,6 @@ class RequestContext unset(self::$context[$requestId]); } - /** - * 获取当前请求的所有上下文数据 - * - * @param string|null $requestId - * @return array - */ - public static function getAll(?string $requestId = null): array - { - $requestId = $requestId ?? self::getCurrentRequestId(); - if ($requestId === null) { - return []; - } - - return self::$context[$requestId] ?? []; - } - - /** - * 批量设置上下文数据 - * - * @param array $data - * @param string|null $requestId - * @return void - */ - public static function setMultiple(array $data, ?string $requestId = null): void - { - $requestId = $requestId ?? self::getCurrentRequestId(); - if ($requestId === null) { - return; - } - - self::$context[$requestId] ??= []; - self::$context[$requestId] = array_merge(self::$context[$requestId], $data); - } - /** ***************************************************************************************** */ /** ***************************************************************************************** */ /** ***************************************************************************************** */