feat: 즉시 발송 요청 API 구현 (#114)
- POST /v1/in/push/send (단건 발송) - POST /v1/in/push/send/tag (태그 발송) - PushService: 메시지 조회 → 변수 치환 → RabbitMQ 큐 발행 - MessageNotFound(151) 에러 코드 추가 Closes #114
This commit is contained in:
parent
b5d6c70b16
commit
73de5efd84
46
SPMS.API/Controllers/PushController.cs
Normal file
46
SPMS.API/Controllers/PushController.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Swashbuckle.AspNetCore.Annotations;
|
||||||
|
using SPMS.Application.DTOs.Push;
|
||||||
|
using SPMS.Application.Interfaces;
|
||||||
|
using SPMS.Domain.Common;
|
||||||
|
|
||||||
|
namespace SPMS.API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("v1/in/push")]
|
||||||
|
[ApiExplorerSettings(GroupName = "push")]
|
||||||
|
public class PushController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IPushService _pushService;
|
||||||
|
|
||||||
|
public PushController(IPushService pushService)
|
||||||
|
{
|
||||||
|
_pushService = pushService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("send")]
|
||||||
|
[SwaggerOperation(Summary = "단건 발송", Description = "특정 디바이스에 푸시 메시지를 즉시 발송합니다.")]
|
||||||
|
public async Task<IActionResult> SendAsync([FromBody] PushSendRequestDto request)
|
||||||
|
{
|
||||||
|
var serviceId = GetServiceId();
|
||||||
|
var result = await _pushService.SendAsync(serviceId, request);
|
||||||
|
return Ok(ApiResponse<PushSendResponseDto>.Success(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("send/tag")]
|
||||||
|
[SwaggerOperation(Summary = "태그 발송", Description = "태그 조건에 해당하는 디바이스에 푸시 메시지를 발송합니다.")]
|
||||||
|
public async Task<IActionResult> SendByTagAsync([FromBody] PushSendTagRequestDto request)
|
||||||
|
{
|
||||||
|
var serviceId = GetServiceId();
|
||||||
|
var result = await _pushService.SendByTagAsync(serviceId, request);
|
||||||
|
return Ok(ApiResponse<PushSendResponseDto>.Success(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
private long GetServiceId()
|
||||||
|
{
|
||||||
|
if (HttpContext.Items.TryGetValue("ServiceId", out var serviceIdObj) && serviceIdObj is long serviceId)
|
||||||
|
return serviceId;
|
||||||
|
|
||||||
|
throw new Domain.Exceptions.SpmsException(ErrorCodes.BadRequest, "서비스 식별 정보가 없습니다.", 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,7 @@ public static class DependencyInjection
|
||||||
services.AddScoped<IAppConfigService, AppConfigService>();
|
services.AddScoped<IAppConfigService, AppConfigService>();
|
||||||
services.AddScoped<IDeviceService, DeviceService>();
|
services.AddScoped<IDeviceService, DeviceService>();
|
||||||
services.AddScoped<IFileService, FileService>();
|
services.AddScoped<IFileService, FileService>();
|
||||||
|
services.AddScoped<IPushService, PushService>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9
SPMS.Application/Interfaces/IPushService.cs
Normal file
9
SPMS.Application/Interfaces/IPushService.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
using SPMS.Application.DTOs.Push;
|
||||||
|
|
||||||
|
namespace SPMS.Application.Interfaces;
|
||||||
|
|
||||||
|
public interface IPushService
|
||||||
|
{
|
||||||
|
Task<PushSendResponseDto> SendAsync(long serviceId, PushSendRequestDto request);
|
||||||
|
Task<PushSendResponseDto> SendByTagAsync(long serviceId, PushSendTagRequestDto request);
|
||||||
|
}
|
||||||
124
SPMS.Application/Services/PushService.cs
Normal file
124
SPMS.Application/Services/PushService.cs
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
using System.Text.Json;
|
||||||
|
using SPMS.Application.DTOs.Push;
|
||||||
|
using SPMS.Application.Interfaces;
|
||||||
|
using SPMS.Domain.Common;
|
||||||
|
using SPMS.Domain.Exceptions;
|
||||||
|
using SPMS.Domain.Interfaces;
|
||||||
|
|
||||||
|
namespace SPMS.Application.Services;
|
||||||
|
|
||||||
|
public class PushService : IPushService
|
||||||
|
{
|
||||||
|
private readonly IMessageRepository _messageRepository;
|
||||||
|
private readonly IPushQueueService _pushQueueService;
|
||||||
|
|
||||||
|
public PushService(IMessageRepository messageRepository, IPushQueueService pushQueueService)
|
||||||
|
{
|
||||||
|
_messageRepository = messageRepository;
|
||||||
|
_pushQueueService = pushQueueService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PushSendResponseDto> SendAsync(long serviceId, PushSendRequestDto request)
|
||||||
|
{
|
||||||
|
var message = await _messageRepository.GetByMessageCodeAndServiceAsync(request.MessageCode, serviceId);
|
||||||
|
if (message == null)
|
||||||
|
throw new SpmsException(ErrorCodes.MessageNotFound, "존재하지 않는 메시지 코드입니다.", 404);
|
||||||
|
|
||||||
|
var title = ApplyVariables(message.Title, request.Variables);
|
||||||
|
var body = ApplyVariables(message.Body, request.Variables);
|
||||||
|
|
||||||
|
var requestId = Guid.NewGuid().ToString("N");
|
||||||
|
|
||||||
|
var pushMessage = new PushMessageDto
|
||||||
|
{
|
||||||
|
MessageId = message.Id.ToString(),
|
||||||
|
RequestId = requestId,
|
||||||
|
ServiceId = serviceId,
|
||||||
|
SendType = "single",
|
||||||
|
Title = title,
|
||||||
|
Body = body,
|
||||||
|
ImageUrl = message.ImageUrl,
|
||||||
|
LinkUrl = message.LinkUrl,
|
||||||
|
CustomData = ParseCustomData(message.CustomData),
|
||||||
|
Target = new PushTargetDto
|
||||||
|
{
|
||||||
|
Type = "device_ids",
|
||||||
|
Value = JsonSerializer.SerializeToElement(new[] { request.DeviceId })
|
||||||
|
},
|
||||||
|
CreatedBy = message.CreatedBy,
|
||||||
|
CreatedAt = DateTime.UtcNow.ToString("o")
|
||||||
|
};
|
||||||
|
|
||||||
|
await _pushQueueService.PublishPushMessageAsync(pushMessage);
|
||||||
|
|
||||||
|
return new PushSendResponseDto
|
||||||
|
{
|
||||||
|
RequestId = requestId,
|
||||||
|
SendType = "single",
|
||||||
|
Status = "queued"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PushSendResponseDto> SendByTagAsync(long serviceId, PushSendTagRequestDto request)
|
||||||
|
{
|
||||||
|
var message = await _messageRepository.GetByMessageCodeAndServiceAsync(request.MessageCode, serviceId);
|
||||||
|
if (message == null)
|
||||||
|
throw new SpmsException(ErrorCodes.MessageNotFound, "존재하지 않는 메시지 코드입니다.", 404);
|
||||||
|
|
||||||
|
var requestId = Guid.NewGuid().ToString("N");
|
||||||
|
|
||||||
|
var pushMessage = new PushMessageDto
|
||||||
|
{
|
||||||
|
MessageId = message.Id.ToString(),
|
||||||
|
RequestId = requestId,
|
||||||
|
ServiceId = serviceId,
|
||||||
|
SendType = "group",
|
||||||
|
Title = message.Title,
|
||||||
|
Body = message.Body,
|
||||||
|
ImageUrl = message.ImageUrl,
|
||||||
|
LinkUrl = message.LinkUrl,
|
||||||
|
CustomData = ParseCustomData(message.CustomData),
|
||||||
|
Target = new PushTargetDto
|
||||||
|
{
|
||||||
|
Type = "tags",
|
||||||
|
Value = JsonSerializer.SerializeToElement(new
|
||||||
|
{
|
||||||
|
tags = request.Tags,
|
||||||
|
match = request.TagMatch
|
||||||
|
})
|
||||||
|
},
|
||||||
|
CreatedBy = message.CreatedBy,
|
||||||
|
CreatedAt = DateTime.UtcNow.ToString("o")
|
||||||
|
};
|
||||||
|
|
||||||
|
await _pushQueueService.PublishPushMessageAsync(pushMessage);
|
||||||
|
|
||||||
|
return new PushSendResponseDto
|
||||||
|
{
|
||||||
|
RequestId = requestId,
|
||||||
|
SendType = "group",
|
||||||
|
Status = "queued"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ApplyVariables(string template, Dictionary<string, string>? variables)
|
||||||
|
{
|
||||||
|
if (variables == null || variables.Count == 0)
|
||||||
|
return template;
|
||||||
|
|
||||||
|
var result = template;
|
||||||
|
foreach (var (key, value) in variables)
|
||||||
|
{
|
||||||
|
result = result.Replace($"{{{{{key}}}}}", value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, object>? ParseCustomData(string? customData)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(customData))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return JsonSerializer.Deserialize<Dictionary<string, object>>(customData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -36,6 +36,9 @@ public static class ErrorCodes
|
||||||
public const string DeviceNotFound = "141";
|
public const string DeviceNotFound = "141";
|
||||||
public const string DeviceTokenDuplicate = "142";
|
public const string DeviceTokenDuplicate = "142";
|
||||||
|
|
||||||
|
// === Message (5) ===
|
||||||
|
public const string MessageNotFound = "151";
|
||||||
|
|
||||||
// === Push (6) ===
|
// === Push (6) ===
|
||||||
public const string PushSendFailed = "161";
|
public const string PushSendFailed = "161";
|
||||||
public const string PushStateChangeNotAllowed = "162";
|
public const string PushStateChangeNotAllowed = "162";
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user