[♻️] PUSH API 리팩토링 진행 중_3 : 대용량 발신 위한 버전 작성 중

This commit is contained in:
김선규 2025-02-27 17:09:35 +09:00
parent 59fa5bd014
commit 242f1a48df
8 changed files with 142 additions and 89 deletions

View File

@ -12,6 +12,7 @@
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
<PackageReference Include="Polly" Version="8.5.2" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="7.1.0" />

View File

@ -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();

View File

@ -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<PushController> _logger;
public PushController(IWebHostEnvironment env, ILogger<PushController> logger)
private readonly IPushQueue _pushQueue;
public PushController(IWebHostEnvironment env, ILogger<PushController> 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<object>))]
public async Task<IActionResult> SendPush(string deviceToken, [FromBody] Payload? payload)
public async Task<IActionResult> 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());
// }
}
}

View File

@ -43,10 +43,16 @@ public class Alert
/// <summary>
/// 푸시 등록하기 위한 여러 데이터 목록
/// </summary>
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; }
}

View File

@ -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<PushFileSetting> options)
{
_httpClient = httpClient;
_setting = options.Value;
}
public async Task<bool> 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<Dictionary<string, string>>(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<Dictionary<string, string>>(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<HttpRequestException>()
.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}");
// }
}
}

View File

@ -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 | 일시적 장애 및 예외상황 대처용| |

View File

@ -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"
}
}

View File

@ -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": "*"
}