Compare commits

..

No commits in common. "0af2ed3bb8b270a5f0d4db177e362803f7702e93" and "c6c56b903adff6ae86c5cc8c57c3afa806eef9c2" have entirely different histories.

8 changed files with 89 additions and 142 deletions

View File

@ -12,7 +12,6 @@
<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" />

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 설정 부 ===== ///// ///// ===== builder 설정 부 ===== /////
@ -134,7 +134,7 @@ else
} }
// 로컬 테스트 위한 부분 (올릴떄는 켜두기) // 로컬 테스트 위한 부분 (올릴떄는 켜두기)
// app.UseHttpsRedirection(); app.UseHttpsRedirection();
app.UseRouting(); app.UseRouting();
// app.MapControllers(); // app.MapControllers();

View File

@ -4,9 +4,10 @@ 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 = "공통")]
@ -15,12 +16,10 @@ public class PushController : ControllerBase
private readonly IWebHostEnvironment _env; private readonly IWebHostEnvironment _env;
private readonly ILogger<PushController> _logger; private readonly ILogger<PushController> _logger;
private readonly IPushQueue _pushQueue; public PushController(IWebHostEnvironment env, ILogger<PushController> logger)
public PushController(IWebHostEnvironment env, ILogger<PushController> logger, IPushQueue pushQueue)
{ {
_env = env; _env = env;
_logger = logger; _logger = logger;
_pushQueue = pushQueue;
} }
[HttpGet()] [HttpGet()]
@ -45,58 +44,48 @@ 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 pushRequest = new PushRequest var isDev = _env.IsDevelopment();
{
deviceToken = deviceToken,
payload = payload
};
_pushQueue.Enqueue(pushRequest);
return Ok("[푸시] 접수 완료");
// var pushFile = new PushFile()
// {
// var isDev = _env.IsDevelopment(); 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",
// var pushFileSetting = new PushFileSetting() p12PWPath = "/src/private/appleKeys.json",
// { apnsTopic = "me.myds.ipstein.acamate.AcaMate"
// 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", try
// apnsTopic = "me.myds.ipstein.acamate.AcaMate" {
// }; if (await new ApnsPushService().SendPushNotificationAsync(deviceToken, pushFile, payload))
// {
// try return Ok(APIResponse.Send("000", "정상", Empty));
// { }
// if (await new ApnsPushService().SendPushNotificationAsync(deviceToken, pushFileSetting, payload)) else
// { {
// return Ok(APIResponse.Send("000", "정상", Empty)); return StatusCode(500, APIResponse.UnknownError());
// } }
// else
// { }
// return StatusCode(500, APIResponse.UnknownError()); catch (ServiceConnectionFailedException failEx)
// } {
// _logger.LogError($"[푸시][에러] : {failEx}");
// } return StatusCode(300, APIResponse.InternalSeverError());
// catch (ServiceConnectionFailedException failEx) }
// { catch (HttpRequestException httpEx)
// _logger.LogError($"[푸시][에러] : {failEx}"); {
// return StatusCode(300, APIResponse.InternalSeverError()); _logger.LogError($"[푸시][에러] : {httpEx}");
// } return StatusCode(300, APIResponse.InternalSeverError());
// catch (HttpRequestException httpEx) }
// { catch (Exception ex)
// _logger.LogError($"[푸시][에러] : {httpEx}"); {
// return StatusCode(300, APIResponse.InternalSeverError()); _logger.LogError($"[푸시][에러] : {ex}");
// } return StatusCode(500, APIResponse.UnknownError());
// catch (Exception ex) }
// {
// _logger.LogError($"[푸시][에러] : {ex}");
// return StatusCode(500, APIResponse.UnknownError());
// }
} }
} }

View File

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

View File

@ -6,55 +6,47 @@ 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 Polly; using Microsoft.AspNetCore.Mvc;
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 interface IApnsPushService
{ {
Task SendPushNotificationAsync(string deviceToken, Payload payload); public ApnsPushService()
}
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 SendPushNotificationAsync(string deviceToken, Payload payload) public async Task<bool> SendPushNotificationAsync(string deviceToken, PushFile pushFile, Payload payload)
{ {
// 존재 안하면 예외 던져 버리는거 // 존재 안하면 예외 던져 버리는거
if (!File.Exists(_setting.p12Path) || !File.Exists(_setting.p12PWPath)) if (!File.Exists(pushFile.p12Path) || !File.Exists(pushFile.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(_setting.p12PWPath)) JsonSerializer.Deserialize<Dictionary<string, string>>(await File.ReadAllTextAsync(pushFile.p12PWPath))
// ?? throw new FileContentNotFoundException("[푸시] : 파일 내부의 값을 읽어오지 못함"); ?? throw new FileContentNotFoundException("[푸시] : 파일 내부의 값을 읽어오지 못함");
//
// try try
// { {
// var handler = new HttpClientHandler(); // var certificate = new X509Certificate2(pushFile.p12Path, keys["Password"]);
// handler.ClientCertificates
// .Add(new X509Certificate2(_setting.p12Path, keys["Password"])); var handler = new HttpClientHandler();
// handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12; 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(_setting.uri), using var client = new HttpClient(handler)
// 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}")
{ {
@ -63,36 +55,22 @@ public class ApnsPushService: IApnsPushService
}; };
// 필수 헤더 추가 // 필수 헤더 추가
request.Headers.Add("apns-topic", _setting.apnsTopic); request.Headers.Add("apns-topic", pushFile.apnsTopic);
request.Headers.Add("apns-push-type", "alert"); request.Headers.Add("apns-push-type", "alert");
var policy = Policy.Handle<HttpRequestException>() var response = await client.SendAsync(request);
.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
await policy.ExecuteAsync(async () => if (response.IsSuccessStatusCode) return true;
else
{ {
var response = await _httpClient.SendAsync(request); var errorContent = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode) throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {errorContent}");
{ }
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 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,11 +7,10 @@
- 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 | 일시적 장애 및 예외상황 대처용| |

View File

@ -4,11 +4,5 @@
"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"
} }
} }

View File

@ -5,11 +5,5 @@
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
}, },
"PushFileSetting": { "AllowedHosts": "*"
"Uri": "https://api.push.apple.com/",
"P12Path": "/src/private/AM_Push.p12",
"P12PWPath": "/src/private/appleKeys.json",
"ApnsTopic": "me.myds.ipstein.acamate.AcaMate"
},
"AllowedHosts": "*"
} }