feat: SpmsException 및 글로벌 예외 처리 미들웨어 구현 (#16) #17

Merged
seonkyu.kim merged 1 commits from feature/#16-exception-handling into develop 2026-02-09 05:38:00 +00:00
3 changed files with 89 additions and 2 deletions

View File

@ -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<ExceptionMiddleware> _logger;
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> 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);
}
}

View File

@ -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<AppDbContext>(options =>
var app = builder.Build();
// ── 1. 예외 처리 (최외곽 — 이후 모든 미들웨어 예외 포착) ──
app.UseMiddleware<ExceptionMiddleware>();
// 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();

View File

@ -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);
}