handle($request); $duration_ms = (int) ((microtime(true) - $start) * 1000); // 在父协程中提取 user_id(子协程不继承 Swoole Context) $user_id = null; try { $user_id = $this->auth->guard('jwt')->user()?->getId(); } catch (\Throwable) { // 未认证请求,user_id 保持 null } $method = $request->getMethod(); $path = $request->getUri()->getPath(); $status_code = $response->getStatusCode(); $ip = self::getClientIp($request); $user_agent = $request->getHeaderLine('User-Agent') ?: null; // GET/DELETE 请求不记录请求体 $request_body = null; if (!in_array($method, ['GET', 'DELETE', 'HEAD', 'OPTIONS'], true)) { $parsed_body = $request->getParsedBody(); if (is_array($parsed_body) && !empty($parsed_body)) { $request_body = self::sanitizeBody($parsed_body); } } $response_code = self::extractResponseCode($response); // 异步协程写库,不阻塞响应返回 Coroutine::create(static function () use ( $user_id, $method, $path, $status_code, $ip, $user_agent, $request_body, $response_code, $duration_ms ): void { try { ApiRequestLog::query()->create([ 'user_id' => $user_id, 'method' => $method, 'path' => $path, 'status_code' => $status_code, 'ip' => $ip, 'user_agent' => $user_agent, 'request_body' => $request_body, 'response_code' => $response_code, 'duration_ms' => $duration_ms, ]); } catch (\Throwable $e) { Log::get()->error('RequestLogMiddleware: 写入请求日志失败', [ 'error' => $e->getMessage(), 'path' => $path, ]); } }); return $response; } /** * 递归替换敏感字段为 *** */ public static function sanitizeBody(array $body): array { $sensitive_keys = ['password', 'old_password', 'new_password', 'password_confirmation']; foreach ($body as $key => $value) { if (in_array($key, $sensitive_keys, true)) { $body[$key] = '***'; } elseif (is_array($value)) { $body[$key] = self::sanitizeBody($value); } } return $body; } /** * 从 JSON 响应体中提取 code 字段 */ public static function extractResponseCode(ResponseInterface $response): ?int { $content_type = $response->getHeaderLine('Content-Type'); if (stripos($content_type, 'application/json') === false) { return null; } try { $body = (string) $response->getBody(); $data = json_decode($body, true); if (is_array($data) && isset($data['code'])) { return (int) $data['code']; } } catch (\Throwable) { // 解析失败返回 null } return null; } /** * 获取客户端真实 IP * * 优先级:X-Forwarded-For → X-Real-IP → ServerParams remote_addr */ public static function getClientIp(ServerRequestInterface $request): ?string { // X-Forwarded-For 可能包含多个 IP,取第一个 $forwarded_for = $request->getHeaderLine('X-Forwarded-For'); if ($forwarded_for !== '') { $ips = explode(',', $forwarded_for); return trim($ips[0]); } $real_ip = $request->getHeaderLine('X-Real-IP'); if ($real_ip !== '') { return trim($real_ip); } $server_params = $request->getServerParams(); return $server_params['remote_addr'] ?? null; } }