improvement: 통계 서비스 범위 정책 고정 (#229) #230

Merged
seonkyu.kim merged 1 commits from improvement/#229-stats-service-scope-policy into develop 2026-02-25 06:50:54 +00:00
4 changed files with 21 additions and 16 deletions

View File

@ -19,7 +19,7 @@ public class StatsController : ControllerBase
}
[HttpPost("daily")]
[SwaggerOperation(Summary = "일별 통계 조회", Description = "기간별 일별 발송/성공/실패/열람 통계를 조회합니다.")]
[SwaggerOperation(Summary = "일별 통계 조회", Description = "기간별 일별 발송/성공/실패/열람 통계를 조회합니다. X-Service-Code 헤더 미지정 시 전체 서비스 통계를 조회합니다.")]
public async Task<IActionResult> GetDailyAsync([FromBody] DailyStatRequestDto request)
{
var serviceId = GetOptionalServiceId();
@ -28,7 +28,7 @@ public class StatsController : ControllerBase
}
[HttpPost("summary")]
[SwaggerOperation(Summary = "요약 통계 조회", Description = "대시보드 요약 통계를 조회합니다.")]
[SwaggerOperation(Summary = "요약 통계 조회", Description = "대시보드 요약 통계를 조회합니다. X-Service-Code 헤더 미지정 시 전체 서비스 통계를 조회합니다.")]
public async Task<IActionResult> GetSummaryAsync()
{
var serviceId = GetOptionalServiceId();
@ -37,7 +37,7 @@ public class StatsController : ControllerBase
}
[HttpPost("message")]
[SwaggerOperation(Summary = "메시지별 통계 조회", Description = "특정 메시지의 발송 통계를 조회합니다.")]
[SwaggerOperation(Summary = "메시지별 통계 조회", Description = "특정 메시지의 발송 통계를 조회합니다. X-Service-Code 헤더 미지정 시 전체 서비스 통계를 조회합니다.")]
public async Task<IActionResult> GetMessageStatAsync([FromBody] MessageStatRequestDto request)
{
var serviceId = GetOptionalServiceId();
@ -46,7 +46,7 @@ public class StatsController : ControllerBase
}
[HttpPost("hourly")]
[SwaggerOperation(Summary = "시간대별 통계 조회", Description = "시간대별 발송 추이를 조회합니다.")]
[SwaggerOperation(Summary = "시간대별 통계 조회", Description = "시간대별 발송 추이를 조회합니다. X-Service-Code 헤더 미지정 시 전체 서비스 통계를 조회합니다.")]
public async Task<IActionResult> GetHourlyAsync([FromBody] HourlyStatRequestDto request)
{
var serviceId = GetOptionalServiceId();
@ -55,7 +55,7 @@ public class StatsController : ControllerBase
}
[HttpPost("device")]
[SwaggerOperation(Summary = "디바이스 통계 조회", Description = "플랫폼/모델별 디바이스 분포를 조회합니다.")]
[SwaggerOperation(Summary = "디바이스 통계 조회", Description = "플랫폼/모델별 디바이스 분포를 조회합니다. X-Service-Code 헤더 미지정 시 전체 서비스 통계를 조회합니다.")]
public async Task<IActionResult> GetDeviceStatAsync()
{
var serviceId = GetOptionalServiceId();
@ -64,7 +64,7 @@ public class StatsController : ControllerBase
}
[HttpPost("export")]
[SwaggerOperation(Summary = "통계 리포트 다운로드", Description = "일별/시간대별/플랫폼별 통계를 엑셀(.xlsx) 파일로 다운로드합니다.")]
[SwaggerOperation(Summary = "통계 리포트 다운로드", Description = "일별/시간대별/플랫폼별 통계를 엑셀(.xlsx) 파일로 다운로드합니다. X-Service-Code 헤더 미지정 시 전체 서비스 통계를 조회합니다.")]
public async Task<IActionResult> ExportReportAsync([FromBody] StatsExportRequestDto request)
{
var serviceId = GetOptionalServiceId();
@ -74,7 +74,7 @@ public class StatsController : ControllerBase
}
[HttpPost("failure")]
[SwaggerOperation(Summary = "실패원인 통계 조회", Description = "실패 원인별 집계를 상위 N개로 조회합니다.")]
[SwaggerOperation(Summary = "실패원인 통계 조회", Description = "실패 원인별 집계를 상위 N개로 조회합니다. X-Service-Code 헤더 미지정 시 전체 서비스 통계를 조회합니다.")]
public async Task<IActionResult> GetFailureStatAsync([FromBody] FailureStatRequestDto request)
{
var serviceId = GetOptionalServiceId();
@ -83,7 +83,7 @@ public class StatsController : ControllerBase
}
[HttpPost("send-log")]
[SwaggerOperation(Summary = "발송 상세 로그 조회", Description = "특정 메시지의 개별 디바이스별 발송 상세 로그를 조회합니다.")]
[SwaggerOperation(Summary = "발송 상세 로그 조회", Description = "특정 메시지의 개별 디바이스별 발송 상세 로그를 조회합니다. X-Service-Code 헤더 미지정 시 전체 서비스 통계를 조회합니다.")]
public async Task<IActionResult> GetSendLogDetailAsync([FromBody] SendLogDetailRequestDto request)
{
var serviceId = GetOptionalServiceId();

View File

@ -16,14 +16,15 @@ public class SpmsHeaderOperationFilter : IOperationFilter
operation.Parameters ??= new List<OpenApiParameter>();
// v1/in/* 중 X-Service-Code 대상 경로 판별
var isStatsOrDeviceList = relativePath.StartsWith("v1/in/stats") ||
relativePath == "v1/in/device/list";
var isRequired = relativePath.StartsWith("v1/in/message") ||
var isOptional = relativePath.StartsWith("v1/in/stats") ||
relativePath == "v1/in/device/list" ||
relativePath == "v1/in/message/list";
var isRequired = (relativePath.StartsWith("v1/in/message") && !isOptional) ||
relativePath.StartsWith("v1/in/push") ||
relativePath.StartsWith("v1/in/file");
var isDeviceNonList = relativePath.StartsWith("v1/in/device") && !isStatsOrDeviceList;
var isDeviceNonList = relativePath.StartsWith("v1/in/device") && !isOptional;
if (isStatsOrDeviceList)
if (isOptional)
{
operation.Parameters.Add(new OpenApiParameter
{

View File

@ -443,13 +443,13 @@ public class StatsService : IStatsService
private static (DateOnly Start, DateOnly End) ParseDateRange(string startStr, string endStr)
{
if (!DateOnly.TryParseExact(startStr, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var start))
throw new SpmsException(ErrorCodes.BadRequest, "start_date 형식이 올바르지 않습니다. (yyyy-MM-dd)", 400);
throw new SpmsException(ErrorCodes.StatsDateRangeInvalid, "start_date 형식이 올바르지 않습니다. (yyyy-MM-dd)", 400);
if (!DateOnly.TryParseExact(endStr, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var end))
throw new SpmsException(ErrorCodes.BadRequest, "end_date 형식이 올바르지 않습니다. (yyyy-MM-dd)", 400);
throw new SpmsException(ErrorCodes.StatsDateRangeInvalid, "end_date 형식이 올바르지 않습니다. (yyyy-MM-dd)", 400);
if (start > end)
throw new SpmsException(ErrorCodes.BadRequest, "start_date가 end_date보다 클 수 없습니다.", 400);
throw new SpmsException(ErrorCodes.StatsDateRangeInvalid, "start_date가 end_date보다 클 수 없습니다.", 400);
return (start, end);
}

View File

@ -51,6 +51,10 @@ public static class ErrorCodes
public const string JobNotFound = "163";
public const string JobAlreadyCompleted = "164";
// === Stats (7) ===
public const string StatsDateRangeInvalid = "171";
public const string StatsServiceScopeInvalid = "172";
// === File (8) ===
public const string FileNotFound = "181";
public const string FileTypeNotAllowed = "182";