diff --git a/SPMS.API/Middlewares/ExceptionMiddleware.cs b/SPMS.API/Middlewares/ExceptionMiddleware.cs new file mode 100644 index 0000000..297db4c --- /dev/null +++ b/SPMS.API/Middlewares/ExceptionMiddleware.cs @@ -0,0 +1,50 @@ +using SPMS.Domain.Common; +using SPMS.Domain.Exceptions; + +namespace SPMS.API.Middlewares; + +public class ExceptionMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + public ExceptionMiddleware(RequestDelegate next, ILogger logger) + { + _next = next; + _logger = logger; + } + + public async Task InvokeAsync(HttpContext context) + { + try + { + await _next(context); + } + catch (SpmsException ex) + { + _logger.LogWarning(ex, "Business exception: {ErrorCode} {Message}", ex.ErrorCode, ex.Message); + await HandleSpmsExceptionAsync(context, ex); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unhandled exception: {Message}", ex.Message); + await HandleUnknownExceptionAsync(context, ex); + } + } + + private static async Task HandleSpmsExceptionAsync(HttpContext context, SpmsException exception) + { + context.Response.StatusCode = exception.HttpStatusCode; + context.Response.ContentType = "application/json"; + var response = ApiResponse.Fail(exception.ErrorCode, exception.Message); + await context.Response.WriteAsJsonAsync(response); + } + + private static async Task HandleUnknownExceptionAsync(HttpContext context, Exception exception) + { + context.Response.StatusCode = 500; + context.Response.ContentType = "application/json"; + var response = ApiResponse.Fail(ErrorCodes.InternalError, "서버 내부 오류가 발생했습니다."); + await context.Response.WriteAsJsonAsync(response); + } +} diff --git a/SPMS.API/Program.cs b/SPMS.API/Program.cs index 49e38cc..81da982 100644 --- a/SPMS.API/Program.cs +++ b/SPMS.API/Program.cs @@ -1,10 +1,11 @@ using Microsoft.EntityFrameworkCore; +using SPMS.API.Middlewares; using SPMS.Infrastructure; var builder = WebApplication.CreateBuilder(new WebApplicationOptions { - WebRootPath = Environment.GetEnvironmentVariable("ASPNETCORE_WEBROOT") + WebRootPath = Environment.GetEnvironmentVariable("ASPNETCORE_WEBROOT") ?? "wwwroot" }); @@ -19,6 +20,9 @@ builder.Services.AddDbContext(options => var app = builder.Build(); +// ── 1. 예외 처리 (최외곽 — 이후 모든 미들웨어 예외 포착) ── +app.UseMiddleware(); + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { @@ -40,7 +44,7 @@ app.UseHttpsRedirection(); app.UseRouting(); // [4] 요청 처리 -app.MapControllers(); +app.MapControllers(); app.MapFallbackToFile("index.html"); app.Run(); \ No newline at end of file diff --git a/SPMS.Domain/Exceptions/SpmsException.cs b/SPMS.Domain/Exceptions/SpmsException.cs new file mode 100644 index 0000000..23ae4b0 --- /dev/null +++ b/SPMS.Domain/Exceptions/SpmsException.cs @@ -0,0 +1,33 @@ +using SPMS.Domain.Common; + +namespace SPMS.Domain.Exceptions; + +public class SpmsException : Exception +{ + public string ErrorCode { get; } + public int HttpStatusCode { get; } + + public SpmsException(string errorCode, string message, int httpStatusCode = 400) + : base(message) + { + ErrorCode = errorCode; + HttpStatusCode = httpStatusCode; + } + + // === 팩토리 메서드 === + + public static SpmsException BadRequest(string message) + => new(ErrorCodes.BadRequest, message, 400); + + public static SpmsException Unauthorized(string message) + => new(ErrorCodes.Unauthorized, message, 401); + + public static SpmsException NotFound(string message) + => new(ErrorCodes.NotFound, message, 404); + + public static SpmsException Conflict(string message) + => new(ErrorCodes.Conflict, message, 409); + + public static SpmsException LimitExceeded(string message) + => new(ErrorCodes.LimitExceeded, message, 429); +}