diff --git a/Back.csproj b/Back.csproj index f7dec04..fc3d551 100644 --- a/Back.csproj +++ b/Back.csproj @@ -12,6 +12,7 @@ + diff --git a/Program.cs b/Program.cs index 3b7eed2..e547be6 100644 --- a/Program.cs +++ b/Program.cs @@ -114,7 +114,7 @@ else // 로컬 테스트 위한 부분 (올릴때는 꺼두기) -// builder.WebHost.UseUrls("http://0.0.0.0:5144"); +builder.WebHost.UseUrls("http://0.0.0.0:5144"); ///// ===== builder 설정 부 ===== ///// @@ -134,7 +134,7 @@ else } // 로컬 테스트 위한 부분 (올릴떄는 켜두기) -app.UseHttpsRedirection(); +// app.UseHttpsRedirection(); app.UseRouting(); // app.MapControllers(); diff --git a/Program/V1/Controllers/PushController.cs b/Program/V1/Controllers/PushController.cs index c3b1428..f62b4dd 100644 --- a/Program/V1/Controllers/PushController.cs +++ b/Program/V1/Controllers/PushController.cs @@ -4,10 +4,9 @@ using Microsoft.AspNetCore.Mvc; using AcaMate.V1.Services; - - namespace AcaMate.V1.Controllers; + [ApiController] [Route("/api/v1/in/push")] [ApiExplorerSettings(GroupName = "공통")] @@ -16,10 +15,12 @@ public class PushController : ControllerBase private readonly IWebHostEnvironment _env; private readonly ILogger _logger; - public PushController(IWebHostEnvironment env, ILogger logger) + private readonly IPushQueue _pushQueue; + public PushController(IWebHostEnvironment env, ILogger logger, IPushQueue pushQueue) { _env = env; _logger = logger; + _pushQueue = pushQueue; } [HttpGet()] @@ -44,48 +45,58 @@ public class PushController : ControllerBase [HttpPost("send")] [CustomOperation("푸시전송", "저장된 양식으로, 사용자에게 푸시를 전송한다.(로컬 테스트 불가)", "푸시")] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus))] - public async Task SendPush(string deviceToken, [FromBody] Payload? payload) + public async Task SendPush(string deviceToken, [FromBody] Payload payload) { if (string.IsNullOrWhiteSpace(deviceToken) || payload == null) return BadRequest(APIResponse.InvalidInputError()); - var isDev = _env.IsDevelopment(); - - var pushFile = new PushFile() + var pushRequest = new PushRequest { - uri = isDev ? "https://api.sandbox.push.apple.com/" : "https://api.push.apple.com/", - p12Path = isDev ? "/src/private/AM_Push_Sandbox.p12" : "/src/private/AM_Push.p12", - p12PWPath = "/src/private/appleKeys.json", - apnsTopic = "me.myds.ipstein.acamate.AcaMate" + deviceToken = deviceToken, + payload = payload }; + _pushQueue.Enqueue(pushRequest); + return Ok("[푸시] 접수 완료"); - try - { - if (await new ApnsPushService().SendPushNotificationAsync(deviceToken, pushFile, payload)) - { - return Ok(APIResponse.Send("000", "정상", Empty)); - } - else - { - return StatusCode(500, APIResponse.UnknownError()); - } - - } - catch (ServiceConnectionFailedException failEx) - { - _logger.LogError($"[푸시][에러] : {failEx}"); - return StatusCode(300, APIResponse.InternalSeverError()); - } - catch (HttpRequestException httpEx) - { - _logger.LogError($"[푸시][에러] : {httpEx}"); - return StatusCode(300, APIResponse.InternalSeverError()); - } - catch (Exception ex) - { - _logger.LogError($"[푸시][에러] : {ex}"); - return StatusCode(500, APIResponse.UnknownError()); - } + // + // + // var isDev = _env.IsDevelopment(); + // + // var pushFileSetting = new PushFileSetting() + // { + // uri = isDev ? "https://api.sandbox.push.apple.com/" : "https://api.push.apple.com/", + // p12Path = isDev ? "/src/private/AM_Push_Sandbox.p12" : "/src/private/AM_Push.p12", + // p12PWPath = "/src/private/appleKeys.json", + // apnsTopic = "me.myds.ipstein.acamate.AcaMate" + // }; + // + // try + // { + // if (await new ApnsPushService().SendPushNotificationAsync(deviceToken, pushFileSetting, payload)) + // { + // return Ok(APIResponse.Send("000", "정상", Empty)); + // } + // else + // { + // return StatusCode(500, APIResponse.UnknownError()); + // } + // + // } + // catch (ServiceConnectionFailedException failEx) + // { + // _logger.LogError($"[푸시][에러] : {failEx}"); + // return StatusCode(300, APIResponse.InternalSeverError()); + // } + // catch (HttpRequestException httpEx) + // { + // _logger.LogError($"[푸시][에러] : {httpEx}"); + // return StatusCode(300, APIResponse.InternalSeverError()); + // } + // catch (Exception ex) + // { + // _logger.LogError($"[푸시][에러] : {ex}"); + // return StatusCode(500, APIResponse.UnknownError()); + // } } } \ No newline at end of file diff --git a/Program/V1/Models/PushPayload.cs b/Program/V1/Models/PushPayload.cs index 3cee7de..eac6d51 100644 --- a/Program/V1/Models/PushPayload.cs +++ b/Program/V1/Models/PushPayload.cs @@ -43,10 +43,16 @@ public class Alert /// /// 푸시 등록하기 위한 여러 데이터 목록 /// -public class PushFile +public class PushFileSetting { public string uri { get; set; } public string p12Path { get; set; } public string p12PWPath { get; set; } public string apnsTopic { get; set; } +} + +public class PushRequest +{ + public string deviceToken { get; set; } + public Payload payload { get; set; } } \ No newline at end of file diff --git a/Program/V1/Services/PushService.cs b/Program/V1/Services/PushService.cs index 6b467b1..984d6c7 100644 --- a/Program/V1/Services/PushService.cs +++ b/Program/V1/Services/PushService.cs @@ -6,47 +6,55 @@ using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.Json; using System.Threading.Tasks; - -using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Polly; using AcaMate.V1.Models; +using Microsoft.Extensions.Options; using Version = System.Version; namespace AcaMate.V1.Services; -public class ApnsPushService + +public interface IApnsPushService { - public ApnsPushService() + Task SendPushNotificationAsync(string deviceToken, Payload payload); +} +public class ApnsPushService: IApnsPushService +{ + private readonly HttpClient _httpClient; + private readonly PushFileSetting _setting; + + public ApnsPushService(HttpClient httpClient, IOptions options) { - + _httpClient = httpClient; + _setting = options.Value; } - public async Task SendPushNotificationAsync(string deviceToken, PushFile pushFile, Payload payload) + public async Task SendPushNotificationAsync(string deviceToken, Payload payload) { // 존재 안하면 예외 던져 버리는거 - if (!File.Exists(pushFile.p12Path) || !File.Exists(pushFile.p12PWPath)) + if (!File.Exists(_setting.p12Path) || !File.Exists(_setting.p12PWPath)) throw new FileNotFoundException("[푸시] : p12 관련 파일 확인 필요"); var jsonPayload = JsonSerializer.Serialize(payload); - var keys = - JsonSerializer.Deserialize>(await File.ReadAllTextAsync(pushFile.p12PWPath)) - ?? throw new FileContentNotFoundException("[푸시] : 파일 내부의 값을 읽어오지 못함"); - - try - { - // var certificate = new X509Certificate2(pushFile.p12Path, keys["Password"]); - - var handler = new HttpClientHandler(); - handler.ClientCertificates - .Add(new X509Certificate2(pushFile.p12Path, keys["Password"])); - handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12; - - - using var client = new HttpClient(handler) - { - BaseAddress = new Uri(pushFile.uri), - Timeout = TimeSpan.FromSeconds(60) - }; + // var keys = + // JsonSerializer.Deserialize>(await File.ReadAllTextAsync(_setting.p12PWPath)) + // ?? throw new FileContentNotFoundException("[푸시] : 파일 내부의 값을 읽어오지 못함"); + // + // try + // { + // var handler = new HttpClientHandler(); + // handler.ClientCertificates + // .Add(new X509Certificate2(_setting.p12Path, keys["Password"])); + // handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12; + // + // + // using var client = new HttpClient(handler) + // { + // BaseAddress = new Uri(_setting.uri), + // Timeout = TimeSpan.FromSeconds(60) + // }; var request = new HttpRequestMessage(HttpMethod.Post, $"/3/device/{deviceToken}") { @@ -55,22 +63,36 @@ public class ApnsPushService }; // 필수 헤더 추가 - request.Headers.Add("apns-topic", pushFile.apnsTopic); + request.Headers.Add("apns-topic", _setting.apnsTopic); request.Headers.Add("apns-push-type", "alert"); - - var response = await client.SendAsync(request); - if (response.IsSuccessStatusCode) return true; - else + var policy = Policy.Handle() + .WaitAndRetryAsync(3, retryAttempt => + TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); + + await policy.ExecuteAsync(async () => { - var errorContent = await response.Content.ReadAsStringAsync(); - throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {errorContent}"); - } - } - catch (HttpRequestException httpEx) - { - Console.WriteLine($"HttpRequestException: {httpEx.Message}"); - throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {httpEx.Message}"); - } + var response = await _httpClient.SendAsync(request); + if (!response.IsSuccessStatusCode) + { + var errorContent = await response.Content.ReadAsStringAsync(); + throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {errorContent}"); + } + }); + + // var response = await client.SendAsync(request); + // + // if (response.IsSuccessStatusCode) return true; + // else + // { + // var errorContent = await response.Content.ReadAsStringAsync(); + // throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {errorContent}"); + // } + // } + // catch (HttpRequestException httpEx) + // { + // Console.WriteLine($"HttpRequestException: {httpEx.Message}"); + // throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {httpEx.Message}"); + // } } } \ No newline at end of file diff --git a/README.md b/README.md index f977ff6..ff601a8 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,11 @@ - JetBrains Rider ### 추가 패키지 -| No. | Name | Version | Description | -|:---:|:-------------------------------------------:|:-------:|:---------------------------| -| 1 | Microsoft.AspNetCore.OpenApi | 8.0.10 | OpenAPI 를 지원하기 위해 사용 | -| 2 | Microsoft.EntityFrameworkCore | 8.0.10 | 데이터베이스 작업을 간편하게 수행하기 위해 사용 | -| 3 | Pomelo.EntityFrameworkCore.MySql | 8.0.2 | MariaDB 연결 | -| 4 |Microsoft.AspNetCore.Authentication.JwtBearer| 8.0.10 | | - | 5 |Dapper|2.1.35|SQL 직접 작성| +| No. | Name | Version | Description | +|:---:|:---------------------------------------------:|:-------:|:---------------------------| +| 1 | Microsoft.AspNetCore.OpenApi | 8.0.10 | OpenAPI 를 지원하기 위해 사용 | +| 2 | Microsoft.EntityFrameworkCore | 8.0.10 | 데이터베이스 작업을 간편하게 수행하기 위해 사용 | +| 3 | Pomelo.EntityFrameworkCore.MySql | 8.0.2 | MariaDB 연결 | +| 4 | Microsoft.AspNetCore.Authentication.JwtBearer | 8.0.10 | | + | 5 | Dapper | 2.1.35 | SQL 직접 작성 | +| 6 | Polly | 8.5.2 | 일시적 장애 및 예외상황 대처용| | diff --git a/appsettings.Development.json b/appsettings.Development.json index 0c208ae..ff14d6a 100644 --- a/appsettings.Development.json +++ b/appsettings.Development.json @@ -4,5 +4,11 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } + }, + "PushFileSetting": { + "uri": "https://api.sandbox.push.apple.com/", + "p12Path": "/src/private/AM_Push_Sandbox.p12", + "p12PWPath": "/src/private/appleKeys.json", + "apnsTopic": "me.myds.ipstein.acamate.AcaMate" } } diff --git a/appsettings.json b/appsettings.json index d66442c..339b407 100644 --- a/appsettings.json +++ b/appsettings.json @@ -5,5 +5,11 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "PushFileSetting": { + "Uri": "https://api.push.apple.com/", + "P12Path": "/src/private/AM_Push.p12", + "P12PWPath": "/src/private/appleKeys.json", + "ApnsTopic": "me.myds.ipstein.acamate.AcaMate" + }, + "AllowedHosts": "*" } \ No newline at end of file