improvement: 통계 서비스 범위 정책 고정 (#229)
All checks were successful
SPMS_API/pipeline/head This commit looks good

Reviewed-on: https://git.ipstein.myds.me/SPMS/SPMS_API/pulls/230
This commit is contained in:
김선규 2026-02-25 06:50:52 +00:00
commit b177557094
4 changed files with 21 additions and 16 deletions

View File

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

View File

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

View File

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