using System.Security.Claims;
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using static System.Net.Mime.MediaTypeNames;
using Microsoft.Net.Http.Headers;
using Microsoft.Extensions.Configuration;
using NPOI.SS.Formula.Functions;
using System.Net.Http;
using Flurl.Http.Configuration;
using System.Net;
using QuzrtzJob.Factory;
namespace OASystem.API.OAMethodLib.APNs
{
public enum NotificationType : int
{
Alert = 0,
Sound = 1,
Badge = 2,
Silent = 3
}
///
/// APNs 生成 JWT token,添加服务的时候,使用单利
///
public class APNsService : IAPNsService
{
static string token = null;
static string baseUrl = null;
private readonly ILogger _logger;
//private static readonly HttpClient _httpClientFactory = new HttpClient { BaseAddress = new Uri("https://api.push.apple.com:443/3/device/") };
private readonly IConfiguration _configuration;
//private readonly IHttpClientFactory _httpClientFactory;
public APNsService(ILogger logger,IConfiguration configuration)
{
this._configuration = configuration;
//this._httpClientFactory = httpClientFactory;
//APNsService.baseUrl = this._configuration["apple:pushNotificationServer"];
//APNsService.baseUrl = this._configuration["apple:pushNotificationServer_Production"];
//
//APNsService.baseUrl = string.Format("https://api.push.apple.com:443/3/device/");
_logger = logger;
}
///
/// 生成 APNs JWT token
///
///
public string GetnerateAPNsJWTToken()
{
return this.GetnerateAPNsJWTToken(APNsService.token);
}
///
/// 生成 APNs JWT token
///
///
private string GetnerateAPNsJWTToken(string oldToken)
{
var tokenHandler = new JwtSecurityTokenHandler();
var iat = ((DateTime.UtcNow.Ticks - new DateTime(1970, 1, 1).Ticks) / TimeSpan.TicksPerSecond);
// 判断原 token 是否超过 50 分钟,如果未超过,直接返回
if (string.IsNullOrWhiteSpace(oldToken) == false)
{
JwtPayload oldPayload = tokenHandler.ReadJwtToken(oldToken).Payload;
var oldIat = oldPayload.Claims.FirstOrDefault(c => c.Type == "iat");
if (oldIat != null)
{
if (long.TryParse(oldIat.Value, out long oldIatValue) == true)
{
// 两次间隔小于 50 分钟,使用原 token
if ((iat - oldIatValue) < (50 * 60))
{
return oldToken;
}
}
}
}
var kid = _configuration["apple:kid"];
var securityKey = _configuration["apple:securityKey"].Replace("\n", "");
var iss = _configuration["apple:iss"];
var claims = new Claim[]
{
new Claim("iss", iss),
new Claim("iat", iat.ToString())
};
try
{
var eCDsa = ECDsa.Create();
eCDsa.ImportPkcs8PrivateKey(Convert.FromBase64String(securityKey), out _);
var key = new ECDsaSecurityKey(eCDsa);
key.KeyId = kid;
var signingCredentials = new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256);
var jwtHeader = new JwtHeader(signingCredentials);
var jwtPayload = new JwtPayload(claims);
var jwtSecurityToken = new JwtSecurityToken(jwtHeader, jwtPayload);
APNsService.token = tokenHandler.WriteToken(jwtSecurityToken);
}
catch (Exception ex)
{
return $"[ECDsa] ExMsg:[{ex.Message}]";
}
return APNsService.token;
}
///
/// 发送推送通知
///
/// APP Id
/// 设备标识
/// 通知类型
/// 标题
/// 子标题
/// 通知内容
///
public async Task PushNotification(string apnsTopic, string deviceToken, NotificationType type, string title, string subtitle, string body)
{
Result result = new Result() { Code = -1, Msg = "[PushNotification] Start" };
result.Msg += "\r\n[PushNotification] Start";
_logger.LogInformation($"[PushNotification] Start");
var responseData = FailedAPNsReponseData();
var token = this.GetnerateAPNsJWTToken();
try
{
var _httpClientFactory = new HttpClient { BaseAddress = new Uri("https://api.push.apple.com:443/3/device/") };
result.Msg += $"\r\n[PushNotification] --> [HttpClient] --> Init --> jsonStr:{JsonConvert.SerializeObject(_httpClientFactory)}";
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, APNsService.baseUrl + deviceToken)
{
Headers =
{
{ HeaderNames.Authorization, "bearer " + token },
{ "apns-topic", apnsTopic },
{ "apns-expiration", "0" }
},
Version = new Version(2, 0)
};
result.Msg += $"\r\n[PushNotification] --> [httpRequestMessage] --> Init --> jsonStr:{JsonConvert.SerializeObject(httpRequestMessage)}";
var notContent = new
{
aps = new
{
alert = new
{
title = title,
subtitle = subtitle,
body = body
}
}
};
//var content = new StringContent(JsonSerializerTool.SerializeDefault(notContent), System.Text.Encoding.UTF8, Application.Json);
var content = new StringContent(System.Text.Json.JsonSerializer.Serialize(notContent), System.Text.Encoding.UTF8, Application.Json);
httpRequestMessage.Content = content;
result.Msg += $"\r\n[PushNotification] --> [httpRequestMessage] --> Content --> jsonStr:{JsonConvert.SerializeObject(httpRequestMessage)}";
try
{
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;
//HttpClientHandler handler = new HttpClientHandler()
//{
// Proxy = new WebProxy("https://api.push.apple.com:443/3/device/"),
// SslProtocols = System.Security.Authentication.SslProtocols.Tls12, // Enforce TLS 1.2
// UseProxy = true
//};
//HttpClient client = new HttpClient(handler);
//var httpResponseMessage = await client.SendAsync(httpRequestMessage);
var httpResponseMessage = await _httpClientFactory.SendAsync(httpRequestMessage);
if (httpResponseMessage.IsSuccessStatusCode)
{
responseData.Code = 200;
result.Code = 0;
result.Msg = "";
result.Data = responseData;
result.Msg += $"\r\n[PushNotification] End jsonStr[{JsonConvert.SerializeObject(result)}]";
return result;
}
else
{
responseData.Data = httpResponseMessage.StatusCode;
result.Code = -2;
result.Msg = "";
result.Data = responseData;
result.Msg += $"\r\n[PushNotification] End jsonStr[{JsonConvert.SerializeObject(result)}]";
return result;
}
}
catch (Exception e)
{
responseData.Data = e.Message;
result.Code = -3;
result.Msg = "";
result.Data = responseData;
result.Msg += $"\r\n[PushNotification] --> [httpClientFactory] ExceptionMsg:{e.Message}";
result.Msg += $"\r\n[PushNotification] --> [httpClientFactory] InnerExceptionData:{e.InnerException}";
result.Msg += $"\r\n[PushNotification] --> [httpClientFactory] InnerExceptionMsg:{e.InnerException.Message}";
return result;
}
}
catch (Exception ex)
{
result.Msg += $"\r\n[PushNotification] InnerExceptionData:{JsonConvert.SerializeObject(ex.InnerException)}";
result.Msg += $"\r\n[PushNotification] InnerExceptionMsg:{ex.InnerException.Message}";
}
return result;
}
public APNsReponseData FailedAPNsReponseData()
{
return new APNsReponseData() { Code = 400, Data = "" };
}
}
public class APNsReponseData
{
public int Code { get; set; } = 0;
public object Data { get; set; } = "";
}
}