SPMS_API/SPMS.Application/Services/MessageService.cs
seonkyu.kim fc7ab51fa3 feat: 메시지 CRUD API 구현 (#128)
- 메시지 저장 API (POST /v1/in/message/save)
- 메시지 목록 조회 API (POST /v1/in/message/list)
- 메시지 상세 조회 API (POST /v1/in/message/info)
- 메시지 삭제 API (POST /v1/in/message/delete)
- message_code 자동 생성 (접두3+순번4+접미3)
- 변수 추출 ({{변수명}} 패턴)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 22:38:24 +09:00

210 lines
7.6 KiB
C#

using System.Text.Json;
using System.Text.RegularExpressions;
using SPMS.Application.DTOs.Message;
using SPMS.Application.DTOs.Notice;
using SPMS.Application.Interfaces;
using SPMS.Domain.Common;
using SPMS.Domain.Exceptions;
using SPMS.Domain.Interfaces;
namespace SPMS.Application.Services;
public class MessageService : IMessageService
{
private readonly IMessageRepository _messageRepository;
private readonly IUnitOfWork _unitOfWork;
private static readonly Regex VariablePattern = new(@"\{\{(\w+)\}\}", RegexOptions.Compiled);
public MessageService(IMessageRepository messageRepository, IUnitOfWork unitOfWork)
{
_messageRepository = messageRepository;
_unitOfWork = unitOfWork;
}
public async Task<MessageSaveResponseDto> SaveAsync(long serviceId, long adminId, MessageSaveRequestDto request)
{
if (string.IsNullOrWhiteSpace(request.Title))
throw new SpmsException(ErrorCodes.BadRequest, "제목은 필수입니다.", 400);
if (string.IsNullOrWhiteSpace(request.Body))
throw new SpmsException(ErrorCodes.BadRequest, "본문은 필수입니다.", 400);
var messageCode = await GenerateMessageCodeAsync(serviceId);
string? customData = null;
if (request.Data != null)
{
customData = request.Data is JsonElement jsonElement
? jsonElement.GetRawText()
: JsonSerializer.Serialize(request.Data);
}
var message = new Domain.Entities.Message
{
ServiceId = serviceId,
MessageCode = messageCode,
Title = request.Title,
Body = request.Body,
ImageUrl = request.ImageUrl,
LinkUrl = request.LinkUrl,
LinkType = request.LinkType,
CustomData = customData,
CreatedAt = DateTime.UtcNow,
CreatedBy = adminId,
IsDeleted = false
};
await _messageRepository.AddAsync(message);
await _unitOfWork.SaveChangesAsync();
return new MessageSaveResponseDto
{
MessageCode = messageCode,
CreatedAt = message.CreatedAt
};
}
public async Task<MessageListResponseDto> GetListAsync(long serviceId, MessageListRequestDto request)
{
var page = Math.Max(1, request.Page);
var size = Math.Clamp(request.Size, 1, 100);
var (items, totalCount) = await _messageRepository.GetPagedByServiceAsync(
serviceId, page, size,
m => (request.Keyword == null || m.Title.Contains(request.Keyword) || m.Body.Contains(request.Keyword))
&& (request.IsActive == null || m.IsDeleted != request.IsActive));
var totalPages = (int)Math.Ceiling((double)totalCount / size);
return new MessageListResponseDto
{
Items = items.Select(m => new MessageSummaryDto
{
MessageCode = m.MessageCode,
Title = m.Title,
IsActive = !m.IsDeleted,
CreatedAt = m.CreatedAt
}).ToList(),
Pagination = new PaginationDto
{
Page = page,
Size = size,
TotalCount = totalCount,
TotalPages = totalPages
}
};
}
public async Task<MessageInfoResponseDto> GetInfoAsync(long serviceId, MessageInfoRequestDto request)
{
if (string.IsNullOrWhiteSpace(request.MessageCode))
throw new SpmsException(ErrorCodes.BadRequest, "메시지 코드는 필수입니다.", 400);
var message = await _messageRepository.GetByMessageCodeAndServiceAsync(request.MessageCode, serviceId);
if (message == null)
throw new SpmsException(ErrorCodes.MessageNotFound, "존재하지 않는 메시지 코드입니다.", 404);
var variables = ExtractVariables(message.Title, message.Body);
object? data = null;
if (!string.IsNullOrEmpty(message.CustomData))
{
data = JsonSerializer.Deserialize<JsonElement>(message.CustomData);
}
return new MessageInfoResponseDto
{
MessageCode = message.MessageCode,
Title = message.Title,
Body = message.Body,
ImageUrl = message.ImageUrl,
LinkUrl = message.LinkUrl,
LinkType = message.LinkType,
Data = data,
Variables = variables,
IsActive = !message.IsDeleted,
CreatedAt = message.CreatedAt
};
}
public async Task DeleteAsync(long serviceId, MessageDeleteRequestDto request)
{
if (string.IsNullOrWhiteSpace(request.MessageCode))
throw new SpmsException(ErrorCodes.BadRequest, "메시지 코드는 필수입니다.", 400);
var message = await _messageRepository.GetByMessageCodeAndServiceAsync(request.MessageCode, serviceId);
if (message == null)
throw new SpmsException(ErrorCodes.MessageNotFound, "존재하지 않는 메시지 코드입니다.", 404);
message.IsDeleted = true;
message.DeletedAt = DateTime.UtcNow;
_messageRepository.Update(message);
await _unitOfWork.SaveChangesAsync();
}
public async Task<MessagePreviewResponseDto> PreviewAsync(long serviceId, MessagePreviewRequestDto 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);
return new MessagePreviewResponseDto
{
Title = title,
Body = body,
ImageUrl = message.ImageUrl,
LinkUrl = message.LinkUrl
};
}
private async Task<string> GenerateMessageCodeAsync(long serviceId)
{
var today = DateTime.UtcNow.Date;
var seed = (int)(today.Ticks ^ serviceId);
var random = new Random(seed);
var prefix = GenerateRandomChars(random, 3);
var suffix = GenerateRandomChars(new Random(seed + 1), 3);
var sequence = await _messageRepository.GetTodaySequenceAsync(serviceId) + 1;
if (sequence > 9999)
throw new SpmsException(ErrorCodes.LimitExceeded, "일일 메시지 생성 한도(9999건)를 초과했습니다.", 429);
return $"{prefix}{sequence:D4}{suffix}";
}
private static string GenerateRandomChars(Random random, int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var result = new char[length];
for (var i = 0; i < length; i++)
result[i] = chars[random.Next(chars.Length)];
return new string(result);
}
private static List<string> ExtractVariables(string title, string body)
{
var variables = new HashSet<string>();
foreach (Match match in VariablePattern.Matches(title))
variables.Add(match.Groups[1].Value);
foreach (Match match in VariablePattern.Matches(body))
variables.Add(match.Groups[1].Value);
return variables.ToList();
}
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;
}
}