using SharedKernel.Constants; using Application.Abstractions.Data; using Domain.Entities.Director; using System.Diagnostics; namespace Admin.Middlewares; public class AdminAccessLogMiddleware(RequestDelegate next) { // 로깅 제외 경로 private static readonly string[] ExcludePrefixes = [ "/lib/", "/css/", "/js/", "/images/", "/favicon", "/_framework", "/_blazor", "/.well-known/", "/Identity/Account" ]; // 로깅 제외 확장자 private static readonly string[] ExcludeExtensions = [ ".css", ".js", ".map", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".woff", ".woff2", ".ttf", ".eot" ]; public async Task InvokeAsync(HttpContext context) { var path = context.Request.Path.Value ?? ""; // 정적 파일, Identity 페이지 제외 if (ShouldSkip(path)) { await next(context); return; } // 인증되지 않은 사용자 제외 if (context.User.Identity is not { IsAuthenticated: true }) { await next(context); return; } var sw = Stopwatch.StartNew(); // 시작 시간 await next(context); sw.Stop(); // 종료 시간 try { var db = context.RequestServices.GetRequiredService(); var userID = context.User.Identity.Name ?? "-"; var userName = context.User.FindFirst(System.Security.Claims.ClaimTypes.GivenName)?.Value; var method = context.Request.Method; var queryString = context.Request.QueryString.HasValue ? context.Request.QueryString.Value : null; var statusCode = context.Response.StatusCode; var elapsedMs = sw.ElapsedMilliseconds; var menuName = ResolveMenuName(path); var ipAddress = context.Connection.RemoteIpAddress?.ToString(); var userAgent = context.Request.Headers.UserAgent.ToString(); var log = AdminAccessLog.Create( userID, userName, method, path, queryString, statusCode, elapsedMs, menuName, ipAddress, userAgent); await db.AdminAccessLog.AddAsync(log); await db.SaveChangesAsync(); } catch { } } // 로깅 제외 경로 및 확장자 확인 private static bool ShouldSkip(string path) { foreach (var prefix in ExcludePrefixes) { if (path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { return true; } } foreach (var ext in ExcludeExtensions) { if (path.EndsWith(ext, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } // 경로에 해당하는 메뉴 이름 찾기 private static string? ResolveMenuName(string path) { var menus = Menus.GetMenus(); return FindMenuName(menus, path.TrimEnd('/')); } // 재귀적으로 메뉴 트리 탐색 private static string? FindMenuName(List menus, string path) { foreach (var menu in menus) { if (!string.IsNullOrEmpty(menu.Path)) { var menuPath = menu.Path.TrimEnd('/'); if (path.Equals(menuPath, StringComparison.OrdinalIgnoreCase) || path.StartsWith(menuPath + "/", StringComparison.OrdinalIgnoreCase)) { return menu.Name; } } if (menu.Children is { Count: > 0 }) { var childResult = FindMenuName(menu.Children, path); if (childResult is not null) { return childResult; } } } return null; } }