APNsService.cs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. using System.Security.Claims;
  2. using System.Security.Cryptography;
  3. using Microsoft.IdentityModel.Tokens;
  4. using System.IdentityModel.Tokens.Jwt;
  5. using static System.Net.Mime.MediaTypeNames;
  6. using Microsoft.Net.Http.Headers;
  7. using Microsoft.Extensions.Configuration;
  8. using NPOI.SS.Formula.Functions;
  9. using System.Net.Http;
  10. using Flurl.Http.Configuration;
  11. using System.Net;
  12. using QuzrtzJob.Factory;
  13. namespace OASystem.API.OAMethodLib.APNs
  14. {
  15. public enum NotificationType : int
  16. {
  17. Alert = 0,
  18. Sound = 1,
  19. Badge = 2,
  20. Silent = 3
  21. }
  22. /// <summary>
  23. /// APNs 生成 JWT token,添加服务的时候,使用单利
  24. /// </summary>
  25. public class APNsService : IAPNsService
  26. {
  27. static string token = null;
  28. static string baseUrl = null;
  29. private readonly ILogger<APNsService> _logger;
  30. //private static readonly HttpClient _httpClientFactory = new HttpClient { BaseAddress = new Uri("https://api.push.apple.com:443/3/device/") };
  31. private readonly IConfiguration _configuration;
  32. //private readonly IHttpClientFactory _httpClientFactory;
  33. public APNsService(ILogger<APNsService> logger,IConfiguration configuration)
  34. {
  35. this._configuration = configuration;
  36. //this._httpClientFactory = httpClientFactory;
  37. //APNsService.baseUrl = this._configuration["apple:pushNotificationServer"];
  38. //APNsService.baseUrl = this._configuration["apple:pushNotificationServer_Production"];
  39. //
  40. //APNsService.baseUrl = string.Format("https://api.push.apple.com:443/3/device/");
  41. _logger = logger;
  42. }
  43. /// <summary>
  44. /// 生成 APNs JWT token
  45. /// </summary>
  46. /// <returns></returns>
  47. public string GetnerateAPNsJWTToken()
  48. {
  49. return this.GetnerateAPNsJWTToken(APNsService.token);
  50. }
  51. /// <summary>
  52. /// 生成 APNs JWT token
  53. /// </summary>
  54. /// <returns></returns>
  55. private string GetnerateAPNsJWTToken(string oldToken)
  56. {
  57. var tokenHandler = new JwtSecurityTokenHandler();
  58. var iat = ((DateTime.UtcNow.Ticks - new DateTime(1970, 1, 1).Ticks) / TimeSpan.TicksPerSecond);
  59. // 判断原 token 是否超过 50 分钟,如果未超过,直接返回
  60. if (string.IsNullOrWhiteSpace(oldToken) == false)
  61. {
  62. JwtPayload oldPayload = tokenHandler.ReadJwtToken(oldToken).Payload;
  63. var oldIat = oldPayload.Claims.FirstOrDefault(c => c.Type == "iat");
  64. if (oldIat != null)
  65. {
  66. if (long.TryParse(oldIat.Value, out long oldIatValue) == true)
  67. {
  68. // 两次间隔小于 50 分钟,使用原 token
  69. if ((iat - oldIatValue) < (50 * 60))
  70. {
  71. return oldToken;
  72. }
  73. }
  74. }
  75. }
  76. var kid = _configuration["apple:kid"];
  77. var securityKey = _configuration["apple:securityKey"].Replace("\n", "");
  78. var iss = _configuration["apple:iss"];
  79. var claims = new Claim[]
  80. {
  81. new Claim("iss", iss),
  82. new Claim("iat", iat.ToString())
  83. };
  84. try
  85. {
  86. var eCDsa = ECDsa.Create();
  87. eCDsa.ImportPkcs8PrivateKey(Convert.FromBase64String(securityKey), out _);
  88. var key = new ECDsaSecurityKey(eCDsa);
  89. key.KeyId = kid;
  90. var signingCredentials = new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256);
  91. var jwtHeader = new JwtHeader(signingCredentials);
  92. var jwtPayload = new JwtPayload(claims);
  93. var jwtSecurityToken = new JwtSecurityToken(jwtHeader, jwtPayload);
  94. APNsService.token = tokenHandler.WriteToken(jwtSecurityToken);
  95. }
  96. catch (Exception ex)
  97. {
  98. return $"[ECDsa] ExMsg:[{ex.Message}]";
  99. }
  100. return APNsService.token;
  101. }
  102. /// <summary>
  103. /// 发送推送通知
  104. /// </summary>
  105. /// <param name="apnsTopic">APP Id</param>
  106. /// <param name="deviceToken">设备标识</param>
  107. /// <param name="type">通知类型</param>
  108. /// <param name="title">标题</param>
  109. /// <param name="subtitle">子标题</param>
  110. /// <param name="body">通知内容</param>
  111. /// <returns></returns>
  112. public async Task<Result> PushNotification(string apnsTopic, string deviceToken, NotificationType type, string title, string subtitle, string body)
  113. {
  114. Result result = new Result() { Code = -1, Msg = "[PushNotification] Start" };
  115. result.Msg += "\r\n[PushNotification] Start";
  116. _logger.LogInformation($"[PushNotification] Start");
  117. var responseData = FailedAPNsReponseData();
  118. var token = this.GetnerateAPNsJWTToken();
  119. try
  120. {
  121. var _httpClientFactory = new HttpClient { BaseAddress = new Uri("https://api.push.apple.com:443/3/device/") };
  122. result.Msg += $"\r\n[PushNotification] --> [HttpClient] --> Init --> jsonStr:{JsonConvert.SerializeObject(_httpClientFactory)}";
  123. var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, APNsService.baseUrl + deviceToken)
  124. {
  125. Headers =
  126. {
  127. { HeaderNames.Authorization, "bearer " + token },
  128. { "apns-topic", apnsTopic },
  129. { "apns-expiration", "0" }
  130. },
  131. Version = new Version(2, 0)
  132. };
  133. result.Msg += $"\r\n[PushNotification] --> [httpRequestMessage] --> Init --> jsonStr:{JsonConvert.SerializeObject(httpRequestMessage)}";
  134. var notContent = new
  135. {
  136. aps = new
  137. {
  138. alert = new
  139. {
  140. title = title,
  141. subtitle = subtitle,
  142. body = body
  143. }
  144. }
  145. };
  146. //var content = new StringContent(JsonSerializerTool.SerializeDefault(notContent), System.Text.Encoding.UTF8, Application.Json);
  147. var content = new StringContent(System.Text.Json.JsonSerializer.Serialize(notContent));
  148. httpRequestMessage.Content = content;
  149. result.Msg += $"\r\n[PushNotification] --> [httpRequestMessage] --> Content --> jsonStr:{JsonConvert.SerializeObject(httpRequestMessage)}";
  150. try
  151. {
  152. //ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;
  153. //HttpClientHandler handler = new HttpClientHandler()
  154. //{
  155. // Proxy = new WebProxy("https://api.push.apple.com:443/3/device/"),
  156. // SslProtocols = System.Security.Authentication.SslProtocols.Tls12, // Enforce TLS 1.2
  157. // UseProxy = true
  158. //};
  159. //HttpClient client = new HttpClient(handler);
  160. //var httpResponseMessage = await client.SendAsync(httpRequestMessage);
  161. var httpResponseMessage = await _httpClientFactory.SendAsync(httpRequestMessage);
  162. if (httpResponseMessage.IsSuccessStatusCode)
  163. {
  164. responseData.Code = 200;
  165. result.Code = 0;
  166. result.Msg = "";
  167. result.Data = responseData;
  168. result.Msg += $"\r\n[PushNotification] End jsonStr[{JsonConvert.SerializeObject(result)}]";
  169. return result;
  170. }
  171. else
  172. {
  173. responseData.Data = httpResponseMessage.StatusCode;
  174. result.Code = -2;
  175. result.Msg = "";
  176. result.Data = responseData;
  177. result.Msg += $"\r\n[PushNotification] End jsonStr[{JsonConvert.SerializeObject(result)}]";
  178. return result;
  179. }
  180. }
  181. catch (Exception e)
  182. {
  183. responseData.Data = e.Message;
  184. result.Code = -3;
  185. result.Msg = "";
  186. result.Data = responseData;
  187. result.Msg += $"\r\n[PushNotification] --> [httpClientFactory] ExceptionMsg:{e.Message}";
  188. result.Msg += $"\r\n[PushNotification] --> [httpClientFactory] InnerExceptionData:{e.InnerException}";
  189. result.Msg += $"\r\n[PushNotification] --> [httpClientFactory] InnerExceptionMsg:{e.InnerException.Message}";
  190. return result;
  191. }
  192. }
  193. catch (Exception ex)
  194. {
  195. result.Msg += $"\r\n[PushNotification] InnerExceptionData:{JsonConvert.SerializeObject(ex.InnerException)}";
  196. result.Msg += $"\r\n[PushNotification] InnerExceptionMsg:{ex.InnerException.Message}";
  197. }
  198. return result;
  199. }
  200. public APNsReponseData FailedAPNsReponseData()
  201. {
  202. return new APNsReponseData() { Code = 400, Data = "" };
  203. }
  204. }
  205. public class APNsReponseData
  206. {
  207. public int Code { get; set; } = 0;
  208. public object Data { get; set; } = "";
  209. }
  210. }