✨
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
using System.Text.Json;
|
||||
using System.Threading.RateLimiting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Polly;
|
||||
using Polly.RateLimiting;
|
||||
using Polly.Timeout;
|
||||
using StopShopping.OpenPlatform.Extensions;
|
||||
|
||||
namespace StopShopping.OpenPlatform.TencentLocationApi;
|
||||
|
||||
public class TencentDistrictService : IDistrictService
|
||||
{
|
||||
public TencentDistrictService(
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IOptions<OpenPlatformOptions> openPlatformOptions,
|
||||
ILogger<TencentDistrictService> logger)
|
||||
{
|
||||
_httpContextFactory = httpClientFactory;
|
||||
_options = openPlatformOptions.Value;
|
||||
_logger = logger;
|
||||
|
||||
_pipeline = new ResiliencePipelineBuilder()
|
||||
.AddRetry(new Polly.Retry.RetryStrategyOptions
|
||||
{
|
||||
Delay = TimeSpan.FromSeconds(2),
|
||||
MaxRetryAttempts = 5,
|
||||
ShouldHandle = (arg) =>
|
||||
ValueTask.FromResult(arg.Outcome.Exception is
|
||||
TimeoutRejectedException or RateLimiterRejectedException)
|
||||
})
|
||||
.AddRateLimiter(new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions
|
||||
{
|
||||
PermitLimit = 2,
|
||||
QueueLimit = int.MaxValue,
|
||||
SegmentsPerWindow = 5,
|
||||
Window = TimeSpan.FromSeconds(1)
|
||||
}))
|
||||
.AddTimeout(TimeSpan.FromSeconds(20))
|
||||
.Build();
|
||||
}
|
||||
|
||||
private readonly IHttpClientFactory _httpContextFactory;
|
||||
private readonly OpenPlatformOptions _options;
|
||||
private readonly ILogger<TencentDistrictService> _logger;
|
||||
|
||||
private readonly ResiliencePipeline _pipeline;
|
||||
|
||||
public async Task<DistrictResponse?> GetChildrenAsync(string code, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var uri = new Uri(
|
||||
string.Format("{0}?key={1}&id={2}"
|
||||
, _options.TencentDistrict.GetChildrenUrl!
|
||||
, Uri.EscapeDataString(_options.TencentDistrict.SecretKey!)
|
||||
, code));
|
||||
|
||||
var districts = await Get<District[]?>(uri, cancellationToken);
|
||||
|
||||
if (null == districts)
|
||||
return null;
|
||||
|
||||
DistrictResponse response = new()
|
||||
{
|
||||
IsSucced = districts.Status == 0,
|
||||
Message = $"{districts.Status}:{districts.Message}",
|
||||
Districts = districts.Result
|
||||
?.FirstOrDefault()
|
||||
?.Select(Cast)
|
||||
.ToList()
|
||||
};
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<DistrictResponse?> GetTop3LevelDistrictsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var uri = new Uri(
|
||||
string.Format("{0}?key={1}&struct_type=1"
|
||||
, _options.TencentDistrict.Top3LevelUrl!
|
||||
, _options.TencentDistrict.SecretKey!)
|
||||
);
|
||||
|
||||
var districts = await Get<District>(uri, cancellationToken);
|
||||
|
||||
if (null == districts)
|
||||
return null;
|
||||
|
||||
DistrictResponse response = new()
|
||||
{
|
||||
IsSucced = districts.Status == 0,
|
||||
Message = $"{districts.Status}:{districts.Message}",
|
||||
Districts = null != districts.Result
|
||||
? [.. RecurCast(districts.Result)] : null
|
||||
};
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private async Task<ResponseDistrict<T>?> Get<T>(Uri uri, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var httpClient = _httpContextFactory.CreateClient();
|
||||
|
||||
|
||||
var districts = await _pipeline.ExecuteAsync(async (token) =>
|
||||
{
|
||||
var jsonStream = await httpClient.GetStreamAsync(uri, token);
|
||||
|
||||
try
|
||||
{
|
||||
return await JsonSerializer.DeserializeAsync<ResponseDistrict<T>>(
|
||||
jsonStream, cancellationToken: token);
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
_logger.LogError("序列化异常:{Path}", e.Path);
|
||||
return null;
|
||||
}
|
||||
}, cancellationToken);
|
||||
|
||||
if (null == districts)
|
||||
{
|
||||
_logger.LogError("接口请求失败:{Url}", uri.ToString());
|
||||
return null;
|
||||
}
|
||||
|
||||
return districts;
|
||||
}
|
||||
|
||||
private IEnumerable<DistrictResponse.District> RecurCast(District[] districts)
|
||||
{
|
||||
foreach (var d in districts)
|
||||
{
|
||||
var cd = Cast(d);
|
||||
if (d.Districts?.Length > 0)
|
||||
cd.Districts = [.. RecurCast(d.Districts!)];
|
||||
|
||||
yield return cd;
|
||||
}
|
||||
}
|
||||
|
||||
private DistrictResponse.District Cast(District d)
|
||||
{
|
||||
return new DistrictResponse.District
|
||||
{
|
||||
Code = d.Id,
|
||||
FullName = d.FullName,
|
||||
Latitude = d.Location.Latitude,
|
||||
Level = d.Level,
|
||||
Longitude = d.Location.Longitude,
|
||||
Name = d.Name,
|
||||
PinYin = d.PinYin?.Length > 0 ? string.Join(',', d.PinYin) : null,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user