using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Runtime.Caching; using System.Text; using System.Threading; using System.Threading.Tasks; using TradeManageNew.APIClients.FedexAPI.Models; using Newtonsoft.Json; // ReSharper disable InconsistentNaming namespace TradeManageNew.APIClients.FedexAPI.Services { public class RequestService { private static Lazy _settings = new Lazy(CreateSerializerSettings, true); private JsonSerializerSettings _instanceSettings; protected RequestService(FedexCredential credential) { BaseUrl = credential.Host; ClientId = credential.ClientId; ClientSecret = credential.ClientSecret; ChildKey = credential.ChildKey; ChildSecret = credential.ChildSecret; ReadResponseAsString = credential.ReadResponseAsString; Initialize(); } protected string _baseUrl; private string BaseUrl { get => _baseUrl; set { _baseUrl = value; if (!string.IsNullOrEmpty(_baseUrl) && !_baseUrl.EndsWith("/")) _baseUrl += '/'; } } private string ClientId { get; set; } private string ClientSecret { get; set; } private string ChildKey { get; set; } private string ChildSecret { get; set; } private bool ReadResponseAsString { get; set; } private static void Initialize() { //启用TLS1.2 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; } private static JsonSerializerSettings CreateSerializerSettings() { var settings = new JsonSerializerSettings(); return settings; } protected JsonSerializerSettings JsonSerializerSettings => _instanceSettings ?? _settings.Value; protected void PrepareRequest(HttpClient client, HttpRequestMessage request, string url) { var token = GetAccessTokenAsync() .ConfigureAwait(false) .GetAwaiter() .GetResult(); //添加授权信息 request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); } protected void PrepareRequest(HttpClient client, HttpRequestMessage request, StringBuilder urlBuilder) { } protected static void ProcessResponse(HttpClient client, HttpResponseMessage response) { } private async Task GetAccessTokenAsync() { MemoryCache memoryCache = MemoryCache.Default; string fedexAccessTokenKey = MemoryCacheKeys.FEDEX_ACCESS_TOKEN_KEY; string fedexAccessTokenValue = memoryCache.Get(fedexAccessTokenKey) as string; if (string.IsNullOrWhiteSpace(fedexAccessTokenValue)) { int tryCount = 0; int maxTryCount = 3; while (tryCount < maxTryCount) { try { var fedexOauthTokenResp = await OauthTokenAsync(); // 设置缓存策略(如过期时间) CacheItemPolicy policy = new CacheItemPolicy { AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(fedexOauthTokenResp.Expires_in - 60) }; // 添加数据到缓存 memoryCache.Set(fedexAccessTokenKey, fedexOauthTokenResp.Access_token, policy); return fedexOauthTokenResp.Access_token; } catch (Exception ex) { tryCount++; if (tryCount == maxTryCount) { throw new Exception($"获取Fedex AccessToken失败,错误信息:{ex.Message}"); } } } } return fedexAccessTokenValue; } private async Task OauthTokenAsync() { var client_ = new HttpClient(); var disposeClient_ = true; try { using (var request_ = new HttpRequestMessage()) { var postData = new List> { new KeyValuePair("client_id", ClientId), new KeyValuePair("client_secret", ClientSecret), new KeyValuePair("grant_type", "client_credentials"), }; var content_ = new FormUrlEncodedContent(postData); content_.Headers.ContentType = MediaTypeHeaderValue.Parse("application/x-www-form-urlencoded"); request_.Content = content_; request_.Method = new HttpMethod("POST"); request_.Headers.Accept.Add( MediaTypeWithQualityHeaderValue.Parse("application/json")); var urlBuilder_ = new StringBuilder(); if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl); // Operation Path: "oauth/token" urlBuilder_.Append("oauth/token"); var url_ = urlBuilder_.ToString(); request_.RequestUri = new Uri(url_, UriKind.RelativeOrAbsolute); var response_ = await client_.SendAsync(request_, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None); var disposeResponse_ = true; try { var headers_ = new Dictionary>(); foreach (var item_ in response_.Headers) headers_[item_.Key] = item_.Value; if (response_.Content != null && response_.Content.Headers != null) { foreach (var item_ in response_.Content.Headers) headers_[item_.Key] = item_.Value; } ProcessResponse(client_, response_); var status_ = (int)response_.StatusCode; if (status_ == 200) { var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, CancellationToken.None).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new FedexAPIException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } return objectResponse_.Object; } else if (status_ == 401) { var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, CancellationToken.None).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new FedexAPIException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } throw new FedexAPIException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 500) { var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, CancellationToken.None).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new FedexAPIException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } throw new FedexAPIException("Failure", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else if (status_ == 503) { var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, CancellationToken.None).ConfigureAwait(false); if (objectResponse_.Object == null) { throw new FedexAPIException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null); } throw new FedexAPIException("Service Unavailable", status_, objectResponse_.Text, headers_, objectResponse_.Object, null); } else { var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); throw new FedexAPIException( "The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null); } } finally { if (disposeResponse_) response_.Dispose(); } } } finally { if (disposeClient_) client_.Dispose(); } } protected struct ObjectResponseResult { public ObjectResponseResult(T responseObject, string responseText) { Object = responseObject; Text = responseText; } public T Object { get; } public string Text { get; } } protected async Task> ReadObjectResponseAsync( HttpResponseMessage response, IReadOnlyDictionary> headers, CancellationToken cancellationToken) { if (response == null || response.Content == null) { return new ObjectResponseResult(default(T), string.Empty); } if (ReadResponseAsString) { var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false); try { var typedBody = JsonConvert.DeserializeObject(responseText, JsonSerializerSettings); return new ObjectResponseResult(typedBody, responseText); } catch (JsonException exception) { var message = "Could not deserialize the response body string as " + typeof(T).FullName + "."; throw new FedexAPIException(message, (int)response.StatusCode, responseText, headers, exception); } } try { using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) using (var streamReader = new StreamReader(responseStream)) using (var jsonTextReader = new JsonTextReader(streamReader)) { var serializer = JsonSerializer.Create(JsonSerializerSettings); var typedBody = serializer.Deserialize(jsonTextReader); return new ObjectResponseResult(typedBody, string.Empty); } } catch (JsonException exception) { var message = "Could not deserialize the response body stream as " + typeof(T).FullName + "."; throw new FedexAPIException(message, (int)response.StatusCode, string.Empty, headers, exception); } } } /// /// This is the response of OAuth token and having access token details. /// [GeneratedCode("NJsonSchema", "14.1.0.0 (NJsonSchema v11.0.2.0 (Newtonsoft.Json v13.0.0.0))")] public class FedexOauthTokenResp { /// /// This is an encrypted OAuth token used to authenticate your API requests. Use it in the authorization header of your API requests.<br>Example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpX…… /// [JsonProperty("access_token", Required = Required.Default, NullValueHandling = NullValueHandling.Ignore)] public string Access_token { get; set; } /// /// This is a token type. In this case, it is 'bearer authentication'. /// [JsonProperty("token_type", Required = Required.Default, NullValueHandling = NullValueHandling.Ignore)] public string Token_type { get; set; } /// /// Indicates the token expiration time in seconds. The standard token expiration time is one hour. <br>Example: 3600 /// [JsonProperty("expires_in", Required = Required.Default, NullValueHandling = NullValueHandling.Ignore)] public int Expires_in { get; set; } /// /// Indicates the scope of authorization provided to the consumer.<br> Example: CXS /// [JsonProperty("scope", Required = Required.Default, NullValueHandling = NullValueHandling.Ignore)] public string Scope { get; set; } } }