using System.Security.Claims; using System.Text; using System.Text.Json; using Back.Program.Common.Auth; using Back.Program.Common.Model; using Back.Program.Models.Entities; using Back.Program.Repositories.V1; using Back.Program.Repositories.V1.Interfaces; using Back.Program.Services.V1.Interfaces; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.Extensions.Options; using Polly; using Version = System.Version; namespace Back.Program.Services.V1 { public class PushService : IPushService { private readonly ILogger _logger; private readonly HttpClient _httpClient; private readonly PushFileSetting _setting; private readonly JwtTokenService _jwtTokenService; private readonly IRepositoryService _repositoryService; private readonly IPushQueue _pushQueue; private readonly ILogRepository _logRepository; private readonly IPushRepository _pushRepository; public PushService(ILogger logger, HttpClient httpClient, IOptions options, IPushRepository pushRepository, IRepositoryService repositoryService, IPushQueue pushQueue, ILogRepository logRepository, JwtTokenService jwtTokenService) { _logger = logger; _httpClient = httpClient; _setting = options.Value; _pushRepository = pushRepository; _repositoryService = repositoryService; _pushQueue = pushQueue; _logRepository = logRepository; _jwtTokenService = jwtTokenService; } public async Task SendPushNotificationAsync(string deviceToken, Payload payload) { // 존재 안하면 예외 던져 버리는거 if (!File.Exists(_setting.p12Path) || !File.Exists(_setting.p12PWPath)) throw new FileNotFoundException("[푸시] : p12 관련 파일 확인 필요"); var jsonPayload = JsonSerializer.Serialize(payload); var request = new HttpRequestMessage(HttpMethod.Post, $"/3/device/{deviceToken}") { Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"), Version = new Version(2, 0) // HTTP/2 사용 }; // 필수 헤더 추가 request.Headers.Add("apns-topic", _setting.apnsTopic); request.Headers.Add("apns-push-type", "alert"); var policy = Policy.Handle() .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); await policy.ExecuteAsync(async () => { // var response = await _httpClient.SendAsync(request); try { var response = await _httpClient.SendAsync(request); var result = await response.Content.ReadAsStringAsync(); // Console.WriteLine($"[APNs 응답] StatusCode: {response.StatusCode}"); // Console.WriteLine($"[APNs 응답 본문]: {result}"); if (!response.IsSuccessStatusCode) { var errorContent = await response.Content.ReadAsStringAsync(); throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {errorContent}"); } } catch (Exception ex) { Console.WriteLine($"[푸시 전송 예외 발생] {ex.GetType().Name}: {ex.Message}"); if (ex.InnerException != null) Console.WriteLine( $"[InnerException] {ex.InnerException.GetType().Name}: {ex.InnerException.Message}"); } }); } public async Task> GetPush(string summary, string bid, string? pid, string? category) { if (!(await _pushRepository.FindAcademy(bid))) return APIResponse.Send("100", $"[{summary}], 존재하지 않는 BID", string.Empty); var pushData = await _pushRepository.FindPushList(bid, pid, category); if (pushData.Count > 0) return APIResponse.Send("000", $"[{summary}, 정상", pushData); return APIResponse.Send("001", $"[{summary}], PUSH 데이터 없음", string.Empty); } public async Task> SendPush(string summary, PushRequest pushRequest) { var payload = await _pushRepository.FindPushPayload(pushRequest.bid, pushRequest.pid); if (payload == null) return APIResponse.InvalidInputError($"[{summary}], 저장된 payload 없음"); var pushTasks = pushRequest.uids.Select(async uid => { if (!await _pushRepository.FindUserAcademy(uid, pushRequest.bid)) return; var badge = await _pushRepository.CountBadge(uid); var pushToken = await _pushRepository.FindPushToken(uid); if (pushToken == null) return; var newPayload = new Payload { aps = new Aps { alert = new Alert { title = payload.title, body = payload.body, subtitle = payload.subtitle ?? "" }, category = payload.category, badge = badge + 1 }, pid = pushRequest.pid, bid = pushRequest.bid, content = pushRequest.content ?? (payload.content ?? "") }; var pushCabinet = new PushCabinet { uid = uid, bid = pushRequest.bid, pid = pushRequest.pid, send_date = DateTime.Now, content = newPayload.content != "" ? newPayload.content : null }; var pushData = new PushData { pushToken = pushToken, payload = newPayload }; var log = new LogPush { bid = pushRequest.bid, pid = pushRequest.pid, create_date = DateTime.Now, create_uid = "System", log = "" }; var saved = await _repositoryService.SaveData(pushCabinet); log.log = saved ? $"[{summary}]: 푸시 캐비닛 저장 성공 및 푸시 전송 시도" : $"[{summary}]: 푸시 전송 실패"; var logSaved = await _repositoryService.SaveData(log); _logger.LogInformation($"[{summary}]: 캐비닛 저장 = {saved} : 로그 저장 = {logSaved}"); if(saved) _pushQueue.Enqueue(pushData); }); await Task.WhenAll(pushTasks); return APIResponse.Send("000", $"[{summary}], 정상", string.Empty); } public async Task> SetPush(string summary, string token, DBPayload request) { string uid = String.Empty; if (token == "System") uid = "System"; else { var validToken = await _jwtTokenService.ValidateToken(token); if (validToken == null) return APIResponse.AccessExpireError(); uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; } var payload = await _pushRepository.FindPushPayload(request.bid, request.pid); if (payload == null) return APIResponse.Send("100", $"[{summary}], PID, BID 또는 Cabinet 오류", string.Empty); var log = new LogPush { bid = request.bid, pid = request.pid, create_date = DateTime.Now, create_uid = uid }; if (payload.title != request.title && request.title != "") payload.title = request.title; if (payload.body != request.body && request.body != "") payload.body = request.body; if (payload.subtitle != request.subtitle) payload.subtitle = request.subtitle; if (payload.alert_yn != request.alert_yn) payload.alert_yn = request.alert_yn; if (payload.category != request.category) payload.category = request.category; if (request.content != request.content) payload.content = request.content; var saved = (await _repositoryService.SaveData(payload)); log.log = $"[{summary}] : 상태 = {saved}"; var logSaved = await _repositoryService.SaveData(log); _logger.LogInformation($"[{summary}]: 상태 = {saved} : 로그 저장 = {logSaved}"); if (!saved) return APIResponse.Send("001", $"[{summary}], 실패", string.Empty); return APIResponse.Send("000", $"[{summary}], 정상", string.Empty); } public async Task> CreatePush(string summary, string token, CreatePush request) { Func randomLetter = (letters, count) => new string(Enumerable.Range(0, count).Select(_ => letters[new Random().Next(letters.Length)]).ToArray()); var letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; var digits = "0123456789"; var frontLetters = $"{randomLetter(letters, 1)}{randomLetter(digits, 1)}{randomLetter(letters, 1)}"; var afterLetters = $"{randomLetter(letters, 1)}{randomLetter(digits, 1)}{randomLetter(letters, 1)}"; string uid = String.Empty; if (token == "System") uid = "System"; else { var validToken = await _jwtTokenService.ValidateToken(token); if (validToken == null) return APIResponse.AccessExpireError(); uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; } if (!(await _pushRepository.FindAcademy(request.bid))) return APIResponse.Send("100", $"[{summary}], 학원 정보(BID) 확인 불가", string.Empty); DBPayload payload = new DBPayload { bid = request.bid, pid = $"AP{DateTime.Now:yyyyMMdd}{frontLetters}{DateTime.Now:HHmmss}{afterLetters}", title = request.title, subtitle = request.subtitle, body = request.body, alert_yn = request.alert_yn, category = request.category, content = request.content, }; var log = new LogPush { bid = payload.bid, pid = payload.pid, create_date = DateTime.Now, create_uid = uid }; var saved = await _repositoryService.SaveData(payload); log.log = $"[{summary}] : 푸시 생성 = {saved}"; var logSaved = await _repositoryService.SaveData(log); _logger.LogInformation($"[{summary}]: 푸시 생성 = {saved} : 로그 저장 = {logSaved}"); if (!saved) return APIResponse.Send("001", $"[{summary}], 실패", string.Empty); return APIResponse.Send("000", $"[{summary}], 정상", string.Empty); } public async Task> DeletePush(string summary, string token, string bid, string pid) { string uid = String.Empty; if (token == "System") uid = "System"; else { var validToken = await _jwtTokenService.ValidateToken(token); if (validToken == null) return APIResponse.AccessExpireError(); uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; } var payload = await _pushRepository.FindPushPayload(bid, pid); if (payload == null) return APIResponse.Send("001", $"[{summary}], 삭제 할 PUSH 없음", string.Empty); var log = new LogPush { bid = payload.bid, pid = payload.pid, create_date = DateTime.Now, create_uid = uid }; var delete = await _repositoryService.DeleteData(payload); log.log = $"[{summary}] : 삭제 = {delete}"; var logSaved = await _repositoryService.SaveData(log); _logger.LogInformation($"[{summary}]: 삭제 = {delete} : 로그 저장 = {logSaved}"); if (!delete) return APIResponse.Send("002", $"[{summary}], 실패", string.Empty); return APIResponse.Send("000", $"[{summary}], 정상", string.Empty); } public async Task> DeleteListPush(string summary, string token, int id) { string uid = String.Empty; if (token == "System") uid = "System"; else { var validToken = await _jwtTokenService.ValidateToken(token); if (validToken == null) return APIResponse.AccessExpireError(); uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; } var cabinet = await _pushRepository.FindPushCabinet(id); if (cabinet == null) return APIResponse.Send("001", $"[{summary}], 삭제 할 PUSH 없음", string.Empty); var log = new LogPush { bid = cabinet.bid, pid = cabinet.pid, create_date = DateTime.Now, create_uid = uid }; var delete = await _repositoryService.DeleteData(cabinet); log.log = $"[{summary}] : {cabinet.pid} 삭제 = {delete}"; var logSaved = await _repositoryService.SaveData(log); _logger.LogInformation($"[{summary}]: {cabinet.pid} 삭제 = {delete} : 로그 저장 = {logSaved}"); if (!delete) return APIResponse.Send("002", $"[{summary}], 실패", string.Empty); return APIResponse.Send("000", $"[{summary}], 정상", string.Empty); } public async Task> SearchToUserPush(string summary, string token, int size, PushCabinet? request) { string uid = String.Empty; if (token == "System") uid = "System"; else { var validToken = await _jwtTokenService.ValidateToken(token); if (validToken == null) return APIResponse.AccessExpireError(); uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; } if (request == null) { var data = await _pushRepository.FindPushCabinet(uid, size); return APIResponse.Send("000", $"[{summary}], 정상", data); } else { var data = await _pushRepository.FindPushCabinet(request.id, size); return APIResponse.Send("000", $"[{summary}], 정상", data); } } } }