improvement: 이력 엑셀 내보내기 API 추가 (#191) #235
|
|
@ -109,6 +109,16 @@ public class StatsController : ControllerBase
|
||||||
return Ok(ApiResponse<HistoryDetailResponseDto>.Success(result, "조회 성공"));
|
return Ok(ApiResponse<HistoryDetailResponseDto>.Success(result, "조회 성공"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("history/export")]
|
||||||
|
[SwaggerOperation(Summary = "이력 엑셀 내보내기", Description = "이력 목록과 동일한 필터 기준으로 발송 이력을 엑셀(.xlsx)로 내보냅니다. X-Service-Code 헤더 미지정 시 전체 서비스 이력을 내보냅니다.")]
|
||||||
|
public async Task<IActionResult> ExportHistoryAsync([FromBody] HistoryExportRequestDto request)
|
||||||
|
{
|
||||||
|
var serviceId = GetOptionalServiceId();
|
||||||
|
var fileBytes = await _statsService.ExportHistoryAsync(serviceId, request);
|
||||||
|
var fileName = $"history_export_{DateTime.UtcNow:yyyyMMdd}.xlsx";
|
||||||
|
return File(fileBytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName);
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost("send-log")]
|
[HttpPost("send-log")]
|
||||||
[SwaggerOperation(Summary = "발송 상세 로그 조회", Description = "특정 메시지의 개별 디바이스별 발송 상세 로그를 조회합니다. X-Service-Code 헤더 미지정 시 전체 서비스 통계를 조회합니다.")]
|
[SwaggerOperation(Summary = "발송 상세 로그 조회", Description = "특정 메시지의 개별 디바이스별 발송 상세 로그를 조회합니다. X-Service-Code 헤더 미지정 시 전체 서비스 통계를 조회합니다.")]
|
||||||
public async Task<IActionResult> GetSendLogDetailAsync([FromBody] SendLogDetailRequestDto request)
|
public async Task<IActionResult> GetSendLogDetailAsync([FromBody] SendLogDetailRequestDto request)
|
||||||
|
|
|
||||||
18
SPMS.Application/DTOs/Stats/HistoryExportRequestDto.cs
Normal file
18
SPMS.Application/DTOs/Stats/HistoryExportRequestDto.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace SPMS.Application.DTOs.Stats;
|
||||||
|
|
||||||
|
public class HistoryExportRequestDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("keyword")]
|
||||||
|
public string? Keyword { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("status")]
|
||||||
|
public string? Status { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("start_date")]
|
||||||
|
public string? StartDate { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("end_date")]
|
||||||
|
public string? EndDate { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -15,4 +15,5 @@ public interface IStatsService
|
||||||
Task<DashboardResponseDto> GetDashboardAsync(long? serviceId, DashboardRequestDto request);
|
Task<DashboardResponseDto> GetDashboardAsync(long? serviceId, DashboardRequestDto request);
|
||||||
Task<HistoryListResponseDto> GetHistoryListAsync(long? serviceId, HistoryListRequestDto request);
|
Task<HistoryListResponseDto> GetHistoryListAsync(long? serviceId, HistoryListRequestDto request);
|
||||||
Task<HistoryDetailResponseDto> GetHistoryDetailAsync(long? serviceId, HistoryDetailRequestDto request);
|
Task<HistoryDetailResponseDto> GetHistoryDetailAsync(long? serviceId, HistoryDetailRequestDto request);
|
||||||
|
Task<byte[]> ExportHistoryAsync(long? serviceId, HistoryExportRequestDto request);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -546,6 +546,50 @@ public class StatsService : IStatsService
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<byte[]> ExportHistoryAsync(long? serviceId, HistoryExportRequestDto request)
|
||||||
|
{
|
||||||
|
DateTime? startDate = ParseOptionalDate(request.StartDate);
|
||||||
|
DateTime? endDate = ParseOptionalDate(request.EndDate);
|
||||||
|
|
||||||
|
var items = await _pushSendLogRepository.GetMessageHistoryAllAsync(
|
||||||
|
serviceId, request.Keyword, request.Status, startDate, endDate);
|
||||||
|
|
||||||
|
using var workbook = new XLWorkbook();
|
||||||
|
var ws = workbook.Worksheets.Add("발송 이력");
|
||||||
|
|
||||||
|
// 헤더
|
||||||
|
ws.Cell(1, 1).Value = "메시지코드";
|
||||||
|
ws.Cell(1, 2).Value = "제목";
|
||||||
|
ws.Cell(1, 3).Value = "서비스명";
|
||||||
|
ws.Cell(1, 4).Value = "발송일시";
|
||||||
|
ws.Cell(1, 5).Value = "대상수";
|
||||||
|
ws.Cell(1, 6).Value = "성공";
|
||||||
|
ws.Cell(1, 7).Value = "실패";
|
||||||
|
ws.Cell(1, 8).Value = "오픈율(%)";
|
||||||
|
ws.Cell(1, 9).Value = "상태";
|
||||||
|
ws.Row(1).Style.Font.Bold = true;
|
||||||
|
|
||||||
|
var row = 2;
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
ws.Cell(row, 1).Value = item.MessageCode;
|
||||||
|
ws.Cell(row, 2).Value = item.Title;
|
||||||
|
ws.Cell(row, 3).Value = item.ServiceName;
|
||||||
|
ws.Cell(row, 4).Value = item.FirstSentAt.ToString("yyyy-MM-dd HH:mm:ss");
|
||||||
|
ws.Cell(row, 5).Value = item.TotalSendCount;
|
||||||
|
ws.Cell(row, 6).Value = item.SuccessCount;
|
||||||
|
ws.Cell(row, 7).Value = item.FailCount;
|
||||||
|
ws.Cell(row, 8).Value = 0;
|
||||||
|
ws.Cell(row, 9).Value = SendStatus.Determine(item.TotalSendCount, item.SuccessCount);
|
||||||
|
row++;
|
||||||
|
}
|
||||||
|
ws.Columns().AdjustToContents();
|
||||||
|
|
||||||
|
using var stream = new MemoryStream();
|
||||||
|
workbook.SaveAs(stream);
|
||||||
|
return stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetFailReasonDescription(string failReason)
|
private static string GetFailReasonDescription(string failReason)
|
||||||
{
|
{
|
||||||
return failReason switch
|
return failReason switch
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,11 @@ public interface IPushSendLogRepository : IRepository<PushSendLog>
|
||||||
long? serviceId, int page, int size,
|
long? serviceId, int page, int size,
|
||||||
string? keyword = null, string? status = null,
|
string? keyword = null, string? status = null,
|
||||||
DateTime? startDate = null, DateTime? endDate = null);
|
DateTime? startDate = null, DateTime? endDate = null);
|
||||||
|
Task<IReadOnlyList<MessageHistorySummary>> GetMessageHistoryAllAsync(
|
||||||
|
long? serviceId,
|
||||||
|
string? keyword = null, string? status = null,
|
||||||
|
DateTime? startDate = null, DateTime? endDate = null,
|
||||||
|
int maxCount = 100000);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HourlyStatRaw
|
public class HourlyStatRaw
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,37 @@ public class PushSendLogRepository : Repository<PushSendLog>, IPushSendLogReposi
|
||||||
long? serviceId, int page, int size,
|
long? serviceId, int page, int size,
|
||||||
string? keyword = null, string? status = null,
|
string? keyword = null, string? status = null,
|
||||||
DateTime? startDate = null, DateTime? endDate = null)
|
DateTime? startDate = null, DateTime? endDate = null)
|
||||||
|
{
|
||||||
|
var grouped = BuildMessageHistoryQuery(serviceId, keyword, status, startDate, endDate);
|
||||||
|
|
||||||
|
var totalCount = await grouped.CountAsync();
|
||||||
|
|
||||||
|
var items = await grouped
|
||||||
|
.OrderByDescending(g => g.FirstSentAt)
|
||||||
|
.Skip((page - 1) * size)
|
||||||
|
.Take(size)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return (items, totalCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<MessageHistorySummary>> GetMessageHistoryAllAsync(
|
||||||
|
long? serviceId,
|
||||||
|
string? keyword = null, string? status = null,
|
||||||
|
DateTime? startDate = null, DateTime? endDate = null,
|
||||||
|
int maxCount = 100000)
|
||||||
|
{
|
||||||
|
var grouped = BuildMessageHistoryQuery(serviceId, keyword, status, startDate, endDate);
|
||||||
|
|
||||||
|
return await grouped
|
||||||
|
.OrderByDescending(g => g.FirstSentAt)
|
||||||
|
.Take(maxCount)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IQueryable<MessageHistorySummary> BuildMessageHistoryQuery(
|
||||||
|
long? serviceId, string? keyword, string? status,
|
||||||
|
DateTime? startDate, DateTime? endDate)
|
||||||
{
|
{
|
||||||
var query = _dbSet
|
var query = _dbSet
|
||||||
.Include(l => l.Message)
|
.Include(l => l.Message)
|
||||||
|
|
@ -209,15 +240,7 @@ public class PushSendLogRepository : Repository<PushSendLog>, IPushSendLogReposi
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var totalCount = await grouped.CountAsync();
|
return grouped;
|
||||||
|
|
||||||
var items = await grouped
|
|
||||||
.OrderByDescending(g => g.FirstSentAt)
|
|
||||||
.Skip((page - 1) * size)
|
|
||||||
.Take(size)
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
return (items, totalCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<FailureStatRaw>> GetFailureStatsByMessageAsync(long serviceId, long messageId, int limit)
|
public async Task<List<FailureStatRaw>> GetFailureStatsByMessageAsync(long serviceId, long messageId, int limit)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user