80 lines
2.8 KiB
PHP
80 lines
2.8 KiB
PHP
<?php
|
||
// +----------------------------------------------------------------------
|
||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||
// +----------------------------------------------------------------------
|
||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||
// +----------------------------------------------------------------------
|
||
// | Author: ywxapp<admin@ywxapp.cn>
|
||
// +----------------------------------------------------------------------
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace ywxapp\middleware;
|
||
|
||
use think\facade\Config;
|
||
use ywxapp\service\AddonPerformanceMonitor;
|
||
|
||
/**
|
||
* 插件性能监控中间件(全局注册于 app/middleware.php)
|
||
*
|
||
* 设计要点:
|
||
* - 从请求路径首段解析插件名(插件路由统一为 /<插件名>/<group>/...),
|
||
* 仅对「已启用插件」的请求做性能采样,核心应用(admin/index/api 等)零开销;
|
||
* - 用 AddonPerformanceMonitor::measure() 包裹 $next($request),把每次插件
|
||
* HTTP 请求的真实耗时/内存/成功失败写入缓存,使 addon:health 的性能项有真数据;
|
||
* - measure() 的 finally 已防御性吞掉缓存异常,本中间件无需再包 try/catch,
|
||
* 且绝不二次调用 $next(避免重复执行业务);
|
||
* - 控制器抛异常时 measure() 记录为失败并原样向外抛出,框架错误处理不受影响。
|
||
*
|
||
* @author ywxapp <admin@ywxapp.cn>
|
||
*/
|
||
class AddonPerformanceMiddleware
|
||
{
|
||
public function handle($request, \Closure $next)
|
||
{
|
||
$addon = $this->resolveAddon($request);
|
||
if ($addon === null) {
|
||
return $next($request);
|
||
}
|
||
|
||
$action = $this->resolveAction($request);
|
||
|
||
return AddonPerformanceMonitor::measure($addon, $action, function () use ($next, $request) {
|
||
return $next($request);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 从路径首段解析插件名,并确认其为已启用插件(Config::get('addon') 仅含 state=1)
|
||
*/
|
||
private function resolveAddon($request): ?string
|
||
{
|
||
$path = trim((string) $request->pathinfo(), '/');
|
||
if ($path === '') {
|
||
return null;
|
||
}
|
||
$segments = explode('/', $path);
|
||
$maybe = $segments[0];
|
||
|
||
$addon = Config::get('addon', []);
|
||
foreach ($addon as $info) {
|
||
if (($info['name'] ?? '') === $maybe) {
|
||
return $maybe;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 动作标签:HTTP 方法 + 插件名之后的剩余路径(如 GET backend/addonreview/index)
|
||
*/
|
||
private function resolveAction($request): string
|
||
{
|
||
$path = trim((string) $request->pathinfo(), '/');
|
||
$segments = explode('/', $path);
|
||
array_shift($segments); // 去掉插件名首段
|
||
$rest = implode('/', $segments);
|
||
return ($request->method() ?? 'GET') . ' ' . ($rest === '' ? 'index' : $rest);
|
||
}
|
||
}
|