using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using DnsClient.Internal;
using Dpz.Core.Infrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Refit;
namespace Dpz.Core.Service.Network;
public static class NetworkServicesExtension
{
public static IServiceCollection AddNetworkServices(this IServiceCollection services)
{
services.AddTransient<AuthUpyunHeaderHandler>();
var configuration = services.BuildServiceProvider().GetService<IConfiguration>();
var configHost = configuration?.GetSection("config")["Host"];
if (string.IsNullOrEmpty(configHost))
throw new BusinessException("configuration config/Host node not found");
services.AddRefitClient<IReadConfiguration>()
.ConfigureHttpClient(x => x.BaseAddress = new Uri(configHost));
services.AddRefitClient<IAppSettingsConfiguration>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri(configHost))
.AddHttpMessageHandler<AuthUpyunHeaderHandler>();
return services;
}
/// <summary>
/// 从网络资源添加配置
/// </summary>
/// <param name="configurationBuilder"></param>
/// <param name="readConfiguration"></param>
/// <param name="loggerFactory"></param>
/// <param name="timeSpan">签名过期时间,默认15秒</param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception>
public static async Task<IConfigurationBuilder> AddNetworkConfigurationAsync(
this IConfigurationBuilder configurationBuilder,
IReadConfiguration readConfiguration,
ILoggerFactory? loggerFactory = null,
TimeSpan? timeSpan = null)
{
var config = configurationBuilder.Build();
var key = config?.GetSection("config")["Key"];
if (string.IsNullOrEmpty(key))
throw new BusinessException("configuration config/Key node not found");
return await configurationBuilder
.AddNetworkJsonStreamAsync(
readConfiguration,
null,
null,
loggerFactory,
timeSpan);
}
/// <summary>
/// 根据项目、环境从网络资源添加配置
/// </summary>
/// <param name="configurationBuilder"></param>
/// <param name="readConfiguration"></param>
/// <param name="project">项目</param>
/// <param name="environmentFile">具体环境文件名</param>
/// <param name="loggerFactory"></param>
/// <param name="timeSpan">签名过期时间,默认15秒</param>
/// <returns></returns>
/// <exception cref="BusinessException"></exception>
public static async Task<IConfigurationBuilder> AddNetworkConfigurationAsync(
this IConfigurationBuilder configurationBuilder,
IReadConfiguration readConfiguration,
string project,
string environmentFile,
ILoggerFactory? loggerFactory = null,
TimeSpan? timeSpan = null)
{
if (string.IsNullOrEmpty(project))
throw new ArgumentNullException(nameof(project));
if (string.IsNullOrEmpty(environmentFile))
throw new ArgumentNullException(nameof(environmentFile));
var config = configurationBuilder.Build();
var key = config?.GetSection("config")["Key"];
if (string.IsNullOrEmpty(key))
throw new BusinessException("configuration config/Key node not found");
return await configurationBuilder
.AddNetworkJsonStreamAsync(
readConfiguration,
project,
environmentFile,
loggerFactory, timeSpan);
}
private static string CalculateSign(string key, string uri, TimeSpan? timeSpan = null)
{
timeSpan ??= TimeSpan.FromSeconds(15);
var timeStamp = DateTime.Now.Add(timeSpan.Value).ToTimeStamp();
var md5 = $"{key}&{timeStamp}&{uri}".GenerateHashMd5();
var sign = md5.Substring(12, 8) + timeStamp;
return sign;
}
private static async Task<IConfigurationBuilder> AddNetworkJsonStreamAsync(
this IConfigurationBuilder configurationBuilder,
IReadConfiguration readConfiguration,
string? project,
string? environmentFile,
ILoggerFactory? loggerFactory = null,
TimeSpan? timeSpan = null)
{
var config = configurationBuilder.Build();
var key = config?.GetSection("config")["Key"];
if (string.IsNullOrEmpty(key))
throw new BusinessException("configuration config/Key node not found");
try
{
if (string.IsNullOrEmpty(project) || string.IsNullOrEmpty(environmentFile))
{
var uri = "/config/appsettings.json";
var sign = CalculateSign(key, uri, timeSpan);
var result = await readConfiguration.ReadConfigurationAsync(sign);
configurationBuilder.AddJsonStream(result.Content);
}
else
{
var uri = $"/config/{project}/{environmentFile}";
var sign = CalculateSign(key, uri, timeSpan);
var result = await readConfiguration.ReadConfigurationAsync(project, environmentFile, sign);
configurationBuilder.AddJsonStream(result.Content);
}
}
catch (Exception e)
{
if (loggerFactory == null)
{
throw new BusinessException("add network configuration fail", e);
}
var logger = loggerFactory.CreateLogger(nameof(Dpz.Core.Service.Network.NetworkServicesExtension));
logger.LogError(e, "add network configuration fail");
}
return configurationBuilder;
}
internal static async Task SignatureAsync(this HttpRequestMessage request, UpyunOperator upyunOperator,
string? contentMd5 = null)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
if (upyunOperator == null)
throw new ArgumentNullException(nameof(upyunOperator));
var utcNow = DateTime.UtcNow;
var pwd = upyunOperator.Password.GenerateHashMd5();
var method = request.Method.ToString().ToUpper();
var uri = request.RequestUri?.OriginalString ?? "";
var message = $"{method}&{uri}&{utcNow:R}";
if (!string.IsNullOrEmpty(contentMd5))
{
message += "&" + contentMd5;
}
var keyBytes = Encoding.UTF8.GetBytes(pwd);
var sha1 = new HMACSHA1(keyBytes);
var byteArray = Encoding.UTF8.GetBytes(message);
var stream = new MemoryStream(byteArray);
var sha1Result = await sha1.ComputeHashAsync(stream);
var signBase64 = Convert.ToBase64String(sha1Result);
request.Headers.Authorization =
AuthenticationHeaderValue.Parse($"UPYUN {upyunOperator.Operator}:{signBase64}");
request.Headers.Date = utcNow;
}
}