AI代码生成工具
This commit is contained in:
156
app/Http/Middleware/ApiOpLogMiddleware.php
Normal file
156
app/Http/Middleware/ApiOpLogMiddleware.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\ApiEndpointModel;
|
||||
use App\Models\ApiOpLogModel;
|
||||
use App\Service\common\UserAgentService;
|
||||
use Closure;
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* API 操作日志中间件:响应结束后按接口注册表决定是否落库
|
||||
*/
|
||||
class ApiOpLogMiddleware
|
||||
{
|
||||
/**
|
||||
* 透传请求;真正写日志在 terminate,避免拖慢接口响应
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应结束后写操作日志
|
||||
* 匹配:去掉 /api/ 前缀的 url,且 method=0(ANY) 或等于请求方法,is_log=1、status=1
|
||||
*/
|
||||
public function terminate(Request $request, Response $response): void
|
||||
{
|
||||
try {
|
||||
$path = $this->normalizePath($request->path());
|
||||
if ($path === '') {
|
||||
return;
|
||||
}
|
||||
$noInsert = config('nl.log.no_insert', []);
|
||||
if (is_array($noInsert)) {
|
||||
$full = 'api/' . $path;
|
||||
if (in_array($path, $noInsert, true)
|
||||
|| in_array($full, $noInsert, true)
|
||||
|| in_array('/' . $full, $noInsert, true)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$methodCode = $this->mapMethod($request->method());
|
||||
$endpoint = ApiEndpointModel::where('url', $path)
|
||||
->where('deleted_at', 0)
|
||||
->where('status', 1)
|
||||
->where('is_log', 1)
|
||||
->where(function ($q) use ($methodCode) {
|
||||
$q->where('method', 0)->orWhere('method', $methodCode);
|
||||
})
|
||||
->orderByDesc('method')
|
||||
->first();
|
||||
if (!$endpoint) {
|
||||
return;
|
||||
}
|
||||
$userId = $this->resolveUserId($request);
|
||||
$body = $response->getContent();
|
||||
$result = json_decode((string) $body, true);
|
||||
if (!is_array($result)) {
|
||||
$result = ['raw' => mb_substr((string) $body, 0, 2000)];
|
||||
}
|
||||
$code = $result['code'] ?? null;
|
||||
$type = ((int) $code === 0) ? 0 : 1;
|
||||
$ua = UserAgentService::getInstance();
|
||||
ApiOpLogModel::insert([
|
||||
'user_id' => $userId,
|
||||
'url' => $path,
|
||||
'method' => $methodCode,
|
||||
'controller' => (string) ($endpoint->controller ?? ''),
|
||||
'ip' => (string) get_ip(),
|
||||
'param' => json_encode($this->collectParams($request), JSON_UNESCAPED_UNICODE),
|
||||
'result' => json_encode($result, JSON_UNESCAPED_UNICODE),
|
||||
'type' => $type,
|
||||
'result_code' => (string) ($code ?? ''),
|
||||
'platform_type' => 0,
|
||||
'belong_id' => 0,
|
||||
'user_type' => 0,
|
||||
'equipment' => $ua->parseEquipment(),
|
||||
'browser' => $ua->parseBrowser(),
|
||||
'created_at' => time(),
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
// 日志失败不影响主流程
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化路径:去掉 api/ 前缀
|
||||
*/
|
||||
private function normalizePath(string $path): string
|
||||
{
|
||||
$path = trim($path, '/');
|
||||
if (str_starts_with($path, 'api/')) {
|
||||
$path = substr($path, 4);
|
||||
}
|
||||
return trim($path, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP 方法映射:GET=1 POST=2 其他=0
|
||||
*/
|
||||
private function mapMethod(string $method): int
|
||||
{
|
||||
return match (strtoupper($method)) {
|
||||
'GET' => 1,
|
||||
'POST' => 2,
|
||||
default => 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Bearer JWT 解析 user_id;失败返回 0(不抛鉴权异常)
|
||||
*/
|
||||
private function resolveUserId(Request $request): int
|
||||
{
|
||||
try {
|
||||
$token = $request->bearerToken();
|
||||
if (empty($token)) {
|
||||
return 0;
|
||||
}
|
||||
$secret = (string) config('nl.jwt.secret', '');
|
||||
if (strlen($secret) < 32) {
|
||||
$secret = hash('sha256', $secret !== '' ? $secret : 'nl_admin_jwt_fallback_secret');
|
||||
}
|
||||
$decoded = JWT::decode($token, new Key($secret, 'HS256'));
|
||||
return (int) ($decoded->data->id ?? 0);
|
||||
} catch (\Throwable $e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集请求参数并脱敏密码类字段
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private function collectParams(Request $request): array
|
||||
{
|
||||
$all = array_merge($request->query(), $request->request->all());
|
||||
$maskKeys = [
|
||||
'password', 'old_password', 'new_password', 'confirm_password',
|
||||
'access_key', 'secret_key', 'api_key', 'token',
|
||||
];
|
||||
foreach ($maskKeys as $k) {
|
||||
if (array_key_exists($k, $all) && $all[$k] !== '' && $all[$k] !== null) {
|
||||
$all[$k] = '******';
|
||||
}
|
||||
}
|
||||
return $all;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user