[♻️] PUSH API 리팩토링 진행 중_3 : 대용량 발신 위한 버전 작성 중
This commit is contained in:
parent
59fa5bd014
commit
242f1a48df
|
@ -12,6 +12,7 @@
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8"/>
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8"/>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
|
<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="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.1.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.1.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="7.1.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="7.1.0" />
|
||||||
|
|
|
@ -114,7 +114,7 @@ else
|
||||||
|
|
||||||
|
|
||||||
// 로컬 테스트 위한 부분 (올릴때는 꺼두기)
|
// 로컬 테스트 위한 부분 (올릴때는 꺼두기)
|
||||||
// builder.WebHost.UseUrls("http://0.0.0.0:5144");
|
builder.WebHost.UseUrls("http://0.0.0.0:5144");
|
||||||
|
|
||||||
///// ===== builder 설정 부 ===== /////
|
///// ===== builder 설정 부 ===== /////
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ else
|
||||||
}
|
}
|
||||||
|
|
||||||
// 로컬 테스트 위한 부분 (올릴떄는 켜두기)
|
// 로컬 테스트 위한 부분 (올릴떄는 켜두기)
|
||||||
app.UseHttpsRedirection();
|
// app.UseHttpsRedirection();
|
||||||
|
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
// app.MapControllers();
|
// app.MapControllers();
|
||||||
|
|
|
@ -4,10 +4,9 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
using AcaMate.V1.Services;
|
using AcaMate.V1.Services;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
namespace AcaMate.V1.Controllers;
|
namespace AcaMate.V1.Controllers;
|
||||||
|
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("/api/v1/in/push")]
|
[Route("/api/v1/in/push")]
|
||||||
[ApiExplorerSettings(GroupName = "공통")]
|
[ApiExplorerSettings(GroupName = "공통")]
|
||||||
|
@ -16,10 +15,12 @@ public class PushController : ControllerBase
|
||||||
|
|
||||||
private readonly IWebHostEnvironment _env;
|
private readonly IWebHostEnvironment _env;
|
||||||
private readonly ILogger<PushController> _logger;
|
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;
|
_env = env;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_pushQueue = pushQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet()]
|
[HttpGet()]
|
||||||
|
@ -44,48 +45,58 @@ public class PushController : ControllerBase
|
||||||
[HttpPost("send")]
|
[HttpPost("send")]
|
||||||
[CustomOperation("푸시전송", "저장된 양식으로, 사용자에게 푸시를 전송한다.(로컬 테스트 불가)", "푸시")]
|
[CustomOperation("푸시전송", "저장된 양식으로, 사용자에게 푸시를 전송한다.(로컬 테스트 불가)", "푸시")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus<object>))]
|
[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)
|
if (string.IsNullOrWhiteSpace(deviceToken) || payload == null)
|
||||||
return BadRequest(APIResponse.InvalidInputError());
|
return BadRequest(APIResponse.InvalidInputError());
|
||||||
|
|
||||||
var isDev = _env.IsDevelopment();
|
var pushRequest = new PushRequest
|
||||||
|
|
||||||
var pushFile = new PushFile()
|
|
||||||
{
|
{
|
||||||
uri = isDev ? "https://api.sandbox.push.apple.com/" : "https://api.push.apple.com/",
|
deviceToken = deviceToken,
|
||||||
p12Path = isDev ? "/src/private/AM_Push_Sandbox.p12" : "/src/private/AM_Push.p12",
|
payload = payload
|
||||||
p12PWPath = "/src/private/appleKeys.json",
|
|
||||||
apnsTopic = "me.myds.ipstein.acamate.AcaMate"
|
|
||||||
};
|
};
|
||||||
|
_pushQueue.Enqueue(pushRequest);
|
||||||
|
return Ok("[푸시] 접수 완료");
|
||||||
|
|
||||||
try
|
//
|
||||||
{
|
//
|
||||||
if (await new ApnsPushService().SendPushNotificationAsync(deviceToken, pushFile, payload))
|
// var isDev = _env.IsDevelopment();
|
||||||
{
|
//
|
||||||
return Ok(APIResponse.Send("000", "정상", Empty));
|
// var pushFileSetting = new PushFileSetting()
|
||||||
}
|
// {
|
||||||
else
|
// 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",
|
||||||
return StatusCode(500, APIResponse.UnknownError());
|
// p12PWPath = "/src/private/appleKeys.json",
|
||||||
}
|
// apnsTopic = "me.myds.ipstein.acamate.AcaMate"
|
||||||
|
// };
|
||||||
}
|
//
|
||||||
catch (ServiceConnectionFailedException failEx)
|
// try
|
||||||
{
|
// {
|
||||||
_logger.LogError($"[푸시][에러] : {failEx}");
|
// if (await new ApnsPushService().SendPushNotificationAsync(deviceToken, pushFileSetting, payload))
|
||||||
return StatusCode(300, APIResponse.InternalSeverError());
|
// {
|
||||||
}
|
// return Ok(APIResponse.Send("000", "정상", Empty));
|
||||||
catch (HttpRequestException httpEx)
|
// }
|
||||||
{
|
// else
|
||||||
_logger.LogError($"[푸시][에러] : {httpEx}");
|
// {
|
||||||
return StatusCode(300, APIResponse.InternalSeverError());
|
// return StatusCode(500, APIResponse.UnknownError());
|
||||||
}
|
// }
|
||||||
catch (Exception ex)
|
//
|
||||||
{
|
// }
|
||||||
_logger.LogError($"[푸시][에러] : {ex}");
|
// catch (ServiceConnectionFailedException failEx)
|
||||||
return StatusCode(500, APIResponse.UnknownError());
|
// {
|
||||||
}
|
// _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());
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -43,10 +43,16 @@ public class Alert
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 푸시 등록하기 위한 여러 데이터 목록
|
/// 푸시 등록하기 위한 여러 데이터 목록
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PushFile
|
public class PushFileSetting
|
||||||
{
|
{
|
||||||
public string uri { get; set; }
|
public string uri { get; set; }
|
||||||
public string p12Path { get; set; }
|
public string p12Path { get; set; }
|
||||||
public string p12PWPath { get; set; }
|
public string p12PWPath { get; set; }
|
||||||
public string apnsTopic { get; set; }
|
public string apnsTopic { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class PushRequest
|
||||||
|
{
|
||||||
|
public string deviceToken { get; set; }
|
||||||
|
public Payload payload { get; set; }
|
||||||
|
}
|
|
@ -6,47 +6,55 @@ using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Polly;
|
||||||
|
|
||||||
using AcaMate.V1.Models;
|
using AcaMate.V1.Models;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Version = System.Version;
|
using Version = System.Version;
|
||||||
|
|
||||||
namespace AcaMate.V1.Services;
|
namespace AcaMate.V1.Services;
|
||||||
public class ApnsPushService
|
|
||||||
{
|
|
||||||
public ApnsPushService()
|
|
||||||
{
|
|
||||||
|
|
||||||
|
public interface IApnsPushService
|
||||||
|
{
|
||||||
|
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 관련 파일 확인 필요");
|
throw new FileNotFoundException("[푸시] : p12 관련 파일 확인 필요");
|
||||||
|
|
||||||
var jsonPayload = JsonSerializer.Serialize(payload);
|
var jsonPayload = JsonSerializer.Serialize(payload);
|
||||||
var keys =
|
// var keys =
|
||||||
JsonSerializer.Deserialize<Dictionary<string, string>>(await File.ReadAllTextAsync(pushFile.p12PWPath))
|
// JsonSerializer.Deserialize<Dictionary<string, string>>(await File.ReadAllTextAsync(_setting.p12PWPath))
|
||||||
?? throw new FileContentNotFoundException("[푸시] : 파일 내부의 값을 읽어오지 못함");
|
// ?? throw new FileContentNotFoundException("[푸시] : 파일 내부의 값을 읽어오지 못함");
|
||||||
|
//
|
||||||
try
|
// try
|
||||||
{
|
// {
|
||||||
// var certificate = new X509Certificate2(pushFile.p12Path, keys["Password"]);
|
// var handler = new HttpClientHandler();
|
||||||
|
// handler.ClientCertificates
|
||||||
var handler = new HttpClientHandler();
|
// .Add(new X509Certificate2(_setting.p12Path, keys["Password"]));
|
||||||
handler.ClientCertificates
|
// handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12;
|
||||||
.Add(new X509Certificate2(pushFile.p12Path, keys["Password"]));
|
//
|
||||||
handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12;
|
//
|
||||||
|
// using var client = new HttpClient(handler)
|
||||||
|
// {
|
||||||
using var client = new HttpClient(handler)
|
// BaseAddress = new Uri(_setting.uri),
|
||||||
{
|
// Timeout = TimeSpan.FromSeconds(60)
|
||||||
BaseAddress = new Uri(pushFile.uri),
|
// };
|
||||||
Timeout = TimeSpan.FromSeconds(60)
|
|
||||||
};
|
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, $"/3/device/{deviceToken}")
|
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");
|
request.Headers.Add("apns-push-type", "alert");
|
||||||
|
|
||||||
var response = await client.SendAsync(request);
|
var policy = Policy.Handle<HttpRequestException>()
|
||||||
|
.WaitAndRetryAsync(3, retryAttempt =>
|
||||||
|
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
|
||||||
|
|
||||||
if (response.IsSuccessStatusCode) return true;
|
await policy.ExecuteAsync(async () =>
|
||||||
else
|
|
||||||
{
|
{
|
||||||
var errorContent = await response.Content.ReadAsStringAsync();
|
var response = await _httpClient.SendAsync(request);
|
||||||
throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {errorContent}");
|
if (!response.IsSuccessStatusCode)
|
||||||
}
|
{
|
||||||
}
|
var errorContent = await response.Content.ReadAsStringAsync();
|
||||||
catch (HttpRequestException httpEx)
|
throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {errorContent}");
|
||||||
{
|
}
|
||||||
Console.WriteLine($"HttpRequestException: {httpEx.Message}");
|
});
|
||||||
throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {httpEx.Message}");
|
|
||||||
}
|
// 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}");
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
15
README.md
15
README.md
|
@ -7,10 +7,11 @@
|
||||||
- JetBrains Rider
|
- JetBrains Rider
|
||||||
|
|
||||||
### 추가 패키지
|
### 추가 패키지
|
||||||
| No. | Name | Version | Description |
|
| No. | Name | Version | Description |
|
||||||
|:---:|:-------------------------------------------:|:-------:|:---------------------------|
|
|:---:|:---------------------------------------------:|:-------:|:---------------------------|
|
||||||
| 1 | Microsoft.AspNetCore.OpenApi | 8.0.10 | OpenAPI 를 지원하기 위해 사용 |
|
| 1 | Microsoft.AspNetCore.OpenApi | 8.0.10 | OpenAPI 를 지원하기 위해 사용 |
|
||||||
| 2 | Microsoft.EntityFrameworkCore | 8.0.10 | 데이터베이스 작업을 간편하게 수행하기 위해 사용 |
|
| 2 | Microsoft.EntityFrameworkCore | 8.0.10 | 데이터베이스 작업을 간편하게 수행하기 위해 사용 |
|
||||||
| 3 | Pomelo.EntityFrameworkCore.MySql | 8.0.2 | MariaDB 연결 |
|
| 3 | Pomelo.EntityFrameworkCore.MySql | 8.0.2 | MariaDB 연결 |
|
||||||
| 4 |Microsoft.AspNetCore.Authentication.JwtBearer| 8.0.10 | |
|
| 4 | Microsoft.AspNetCore.Authentication.JwtBearer | 8.0.10 | |
|
||||||
| 5 |Dapper|2.1.35|SQL 직접 작성|
|
| 5 | Dapper | 2.1.35 | SQL 직접 작성 |
|
||||||
|
| 6 | Polly | 8.5.2 | 일시적 장애 및 예외상황 대처용| |
|
||||||
|
|
|
@ -4,5 +4,11 @@
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,5 +5,11 @@
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"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": "*"
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user