154 lines
4.8 KiB
C#
154 lines
4.8 KiB
C#
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,
|
|
};
|
|
}
|
|
} |