From a9f6a436c4164be747ecbfa1387b9a84130a6acc Mon Sep 17 00:00:00 2001 From: SEAN Date: Mon, 9 Feb 2026 14:33:37 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20SpmsException=20=EB=B0=8F=20=EA=B8=80?= =?UTF-8?q?=EB=A1=9C=EB=B2=8C=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EB=AF=B8=EB=93=A4=EC=9B=A8=EC=96=B4=20=EA=B5=AC=ED=98=84=20(#1?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SPMS.API/Middlewares/ExceptionMiddleware.cs | 50 +++++++++++++++++++++ SPMS.API/Program.cs | 8 +++- SPMS.Domain/Exceptions/SpmsException.cs | 33 ++++++++++++++ 3 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 SPMS.API/Middlewares/ExceptionMiddleware.cs create mode 100644 SPMS.Domain/Exceptions/SpmsException.cs 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); +} -- 2.45.1