mirror of
https://github.com/real-zony/ZonyLrcToolsX.git
synced 2025-09-06 05:36:53 +00:00
refactor: Move some of the dependency injection code to the Common Library.
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZonyLrcTools.Common.Infrastructure.Extensions;
|
||||
|
||||
namespace ZonyLrcTools.Common.Infrastructure.DependencyInject
|
||||
{
|
||||
public static class AutoDependencyInjectExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 开始进行自动依赖注入。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 会根据实现了 <see cref="ITransientDependency"/> 或 <see cref="ISingletonDependency"/> 的接口进行自动注入。
|
||||
/// </remarks>
|
||||
/// <param name="services">服务定义集合。</param>
|
||||
/// <typeparam name="TAssemblyType">需要注入的任意类型。</typeparam>
|
||||
public static IServiceCollection BeginAutoDependencyInject<TAssemblyType>(this IServiceCollection services)
|
||||
{
|
||||
var allTypes = typeof(TAssemblyType).Assembly
|
||||
.GetTypes()
|
||||
.Where(t => t.IsClass && !t.IsAbstract && !t.IsGenericType)
|
||||
.ToArray();
|
||||
|
||||
var transientTypes = allTypes.Where(t => typeof(ITransientDependency).IsAssignableFrom(t));
|
||||
var singletonTypes = allTypes.Where(t => typeof(ISingletonDependency).IsAssignableFrom(t));
|
||||
|
||||
transientTypes.Foreach(t =>
|
||||
{
|
||||
foreach (var exposedService in GetDefaultExposedTypes(t))
|
||||
{
|
||||
services.Add(CreateServiceDescriptor(t, exposedService, ServiceLifetime.Transient));
|
||||
}
|
||||
});
|
||||
|
||||
singletonTypes.Foreach(t =>
|
||||
{
|
||||
foreach (var exposedService in GetDefaultExposedTypes(t))
|
||||
{
|
||||
services.Add(CreateServiceDescriptor(t, exposedService, ServiceLifetime.Singleton));
|
||||
}
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static List<Type> GetDefaultExposedTypes(Type type)
|
||||
{
|
||||
var serviceTypes = new List<Type>();
|
||||
|
||||
foreach (var interfaceType in type.GetTypeInfo().GetInterfaces())
|
||||
{
|
||||
var interfaceName = interfaceType.Name;
|
||||
|
||||
if (interfaceName.StartsWith("I"))
|
||||
{
|
||||
interfaceName = interfaceName.Substring(1, interfaceName.Length - 1);
|
||||
}
|
||||
|
||||
if (type.Name.EndsWith(interfaceName))
|
||||
{
|
||||
serviceTypes.Add(interfaceType);
|
||||
serviceTypes.Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
return serviceTypes;
|
||||
}
|
||||
|
||||
public static ServiceDescriptor CreateServiceDescriptor(Type implementationType,
|
||||
Type exposingServiceType,
|
||||
ServiceLifetime lifetime)
|
||||
{
|
||||
return new ServiceDescriptor(exposingServiceType, implementationType, lifetime);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
namespace ZonyLrcTools.Common.Infrastructure.DependencyInject
|
||||
{
|
||||
/// <summary>
|
||||
/// 继承了本接口的类都会以单例的形式注入到 IoC 容器当中。
|
||||
/// </summary>
|
||||
public interface ISingletonDependency
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
namespace ZonyLrcTools.Common.Infrastructure.DependencyInject
|
||||
{
|
||||
/// <summary>
|
||||
/// 继承了本接口的类都会以瞬时的形式注入到 IoC 容器当中。
|
||||
/// </summary>
|
||||
public interface ITransientDependency
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
using System.Net;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ZonyLrcTools.Common.Configuration;
|
||||
using ZonyLrcTools.Common.Infrastructure.Network;
|
||||
|
||||
namespace ZonyLrcTools.Common.Infrastructure.DependencyInject
|
||||
{
|
||||
/// <summary>
|
||||
/// Service 注入的扩展方法。
|
||||
/// </summary>
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置工具会用到的服务。
|
||||
/// </summary>
|
||||
public static IServiceCollection ConfigureToolService(this IServiceCollection services)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
services.AddHttpClient(DefaultWarpHttpClient.HttpClientNameConstant)
|
||||
.ConfigurePrimaryHttpMessageHandler(provider =>
|
||||
{
|
||||
var option = provider.GetRequiredService<IOptions<GlobalOptions>>().Value;
|
||||
|
||||
return new HttpClientHandler
|
||||
{
|
||||
UseProxy = option.NetworkOptions.IsEnable,
|
||||
Proxy = new WebProxy($"{option.NetworkOptions.Ip}:{option.NetworkOptions.Port}")
|
||||
};
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置工具关联的配置信息(<see cref="IConfiguration"/>)。
|
||||
/// </summary>
|
||||
public static IServiceCollection ConfigureConfiguration(this IServiceCollection services)
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
.AddYamlFile("config.yaml")
|
||||
.Build();
|
||||
|
||||
services.Configure<GlobalOptions>(configuration.GetSection("globalOption"));
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
namespace ZonyLrcTools.Common.Infrastructure.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 带错误码的异常实现。
|
||||
/// </summary>
|
||||
public class ErrorCodeException : Exception
|
||||
{
|
||||
public int ErrorCode { get; }
|
||||
|
||||
public object AttachObject { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 构建一个新的 <see cref="ErrorCodeException"/> 对象。
|
||||
/// </summary>
|
||||
/// <param name="errorCode">错误码,参考 <see cref="ErrorCodes"/> 类的定义。</param>
|
||||
/// <param name="message">错误信息。</param>
|
||||
/// <param name="attachObj">附加的对象数据。</param>
|
||||
public ErrorCodeException(int errorCode, string message = null, object attachObj = null) : base(message)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
AttachObject = attachObj;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace ZonyLrcTools.Common.Infrastructure.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 错误码相关的帮助类。
|
||||
/// </summary>
|
||||
public static class ErrorCodeHelper
|
||||
{
|
||||
public static Dictionary<int, string> ErrorMessages { get; }
|
||||
|
||||
static ErrorCodeHelper()
|
||||
{
|
||||
ErrorMessages = new Dictionary<int, string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 err_msg.json 文件加载错误信息。
|
||||
/// </summary>
|
||||
public static void LoadErrorMessage()
|
||||
{
|
||||
// 防止重复加载。
|
||||
if (ErrorMessages.Count != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var jsonPath = Path.Combine(Directory.GetCurrentDirectory(), "Resources", "error_msg.json");
|
||||
using var jsonReader = new JsonTextReader(File.OpenText(jsonPath));
|
||||
var jsonObj = JObject.Load(jsonReader);
|
||||
|
||||
var errors = jsonObj.SelectTokens("$.Error.*");
|
||||
var warnings = jsonObj.SelectTokens("$.Warning.*");
|
||||
errors.Union(warnings).Select(m => m.Parent).OfType<JProperty>().ToList()
|
||||
.ForEach(m => ErrorMessages.Add(int.Parse(m.Name), m.Value.Value<string>()));
|
||||
}
|
||||
|
||||
public static string GetMessage(int errorCode) => ErrorMessages[errorCode];
|
||||
}
|
||||
}
|
@@ -0,0 +1,96 @@
|
||||
namespace ZonyLrcTools.Common.Infrastructure.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 错误码。
|
||||
/// </summary>
|
||||
public static class ErrorCodes
|
||||
{
|
||||
#region > 错误信息 <
|
||||
|
||||
/// <summary>
|
||||
/// 文本: 待搜索的后缀不能为空。
|
||||
/// </summary>
|
||||
public const int FileSuffixIsEmpty = 10001;
|
||||
|
||||
/// <summary>
|
||||
/// 文本: 需要扫描的目录不存在,请确认路径是否正确。。
|
||||
/// </summary>
|
||||
public const int DirectoryNotExist = 10002;
|
||||
|
||||
/// <summary>
|
||||
/// 文本: 不能获取文件的后缀信息。
|
||||
/// </summary>
|
||||
public const int UnableToGetTheFileExtension = 10003;
|
||||
|
||||
/// <summary>
|
||||
/// 文本: 没有扫描到任何音乐文件。
|
||||
/// </summary>
|
||||
public const int NoFilesWereScanned = 10004;
|
||||
|
||||
/// <summary>
|
||||
/// 文本: 指定的编码不受支持,请检查配置,所有受支持的编码名称。
|
||||
/// </summary>
|
||||
public const int NotSupportedFileEncoding = 10005;
|
||||
|
||||
#endregion
|
||||
|
||||
#region > 警告信息 <
|
||||
|
||||
/// <summary>
|
||||
/// 文本: 扫描文件时出现了错误。
|
||||
/// </summary>
|
||||
public const int ScanFileError = 50001;
|
||||
|
||||
/// <summary>
|
||||
/// 文本: 歌曲名称或歌手名称均为空,无法进行搜索。
|
||||
/// </summary>
|
||||
public const int SongNameAndArtistIsNull = 50002;
|
||||
|
||||
/// <summary>
|
||||
/// 文本: 歌曲名称不能为空,无法进行搜索。
|
||||
/// </summary>
|
||||
public const int SongNameIsNull = 50003;
|
||||
|
||||
/// <summary>
|
||||
/// 文本: 下载器没有搜索到对应的歌曲信息。
|
||||
/// </summary>
|
||||
public const int NoMatchingSong = 50004;
|
||||
|
||||
/// <summary>
|
||||
/// 文本: 下载请求的返回值不合法,可能是服务端故障。
|
||||
/// </summary>
|
||||
public const int TheReturnValueIsIllegal = 50005;
|
||||
|
||||
/// <summary>
|
||||
/// 文本: 标签信息读取器为空,无法解析音乐 Tag 信息。
|
||||
/// </summary>
|
||||
public const int LoadTagInfoProviderError = 50006;
|
||||
|
||||
/// <summary>
|
||||
/// 文本: TagLib 标签读取器出现了预期之外的异常。
|
||||
/// </summary>
|
||||
public const int TagInfoProviderLoadInfoFailed = 50007;
|
||||
|
||||
/// <summary>
|
||||
/// 文本: 服务接口限制,无法进行请求,请尝试使用代理服务器。
|
||||
/// </summary>
|
||||
public const int ServiceUnavailable = 50008;
|
||||
|
||||
/// <summary>
|
||||
/// 文本: 对目标服务器执行 Http 请求失败。
|
||||
/// </summary>
|
||||
public const int HttpRequestFailed = 50009;
|
||||
|
||||
/// <summary>
|
||||
/// 文本: Http 请求的结果反序列化为 Json 失败。
|
||||
/// </summary>
|
||||
public const int HttpResponseConvertJsonFailed = 50010;
|
||||
|
||||
/// <summary>
|
||||
/// 文本: 目前仅支持 NCM 格式的歌曲转换操作。
|
||||
/// </summary>
|
||||
public const int OnlySupportNcmFormatFile = 50011;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
namespace ZonyLrcTools.Common.Infrastructure.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Linq 相关的扩展方法。
|
||||
/// </summary>
|
||||
public static class LinqHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用 Lambda 的形式遍历指定的迭代器。
|
||||
/// </summary>
|
||||
/// <param name="items">等待遍历的迭代器实例。</param>
|
||||
/// <param name="action">遍历时需要执行的操作。</param>
|
||||
public static void Foreach<T>(this IEnumerable<T> items, Action<T> action)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
action(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,151 @@
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using ZonyLrcTools.Common.Infrastructure.DependencyInject;
|
||||
using ZonyLrcTools.Common.Infrastructure.Exceptions;
|
||||
|
||||
namespace ZonyLrcTools.Common.Infrastructure.Network
|
||||
{
|
||||
public class DefaultWarpHttpClient : IWarpHttpClient, ITransientDependency
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
public const string HttpClientNameConstant = "WarpClient";
|
||||
|
||||
public DefaultWarpHttpClient(IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
public async ValueTask<string> PostAsync(string url,
|
||||
object parameters = null,
|
||||
bool isQueryStringParam = false,
|
||||
Action<HttpRequestMessage> requestOption = null)
|
||||
{
|
||||
var parametersStr = isQueryStringParam ? BuildQueryString(parameters) : BuildJsonBodyString(parameters);
|
||||
var requestMessage = new HttpRequestMessage(HttpMethod.Post, new Uri(url));
|
||||
requestMessage.Content = new StringContent(parametersStr);
|
||||
|
||||
requestOption?.Invoke(requestMessage);
|
||||
|
||||
using var responseMessage = await BuildHttpClient().SendAsync(requestMessage);
|
||||
var responseContentString = await responseMessage.Content.ReadAsStringAsync();
|
||||
|
||||
return ValidateHttpResponse(responseMessage, parameters, responseContentString);
|
||||
}
|
||||
|
||||
public async ValueTask<TResponse> PostAsync<TResponse>(string url,
|
||||
object parameters = null,
|
||||
bool isQueryStringParam = false,
|
||||
Action<HttpRequestMessage> requestOption = null)
|
||||
{
|
||||
var responseString = await PostAsync(url, parameters, isQueryStringParam, requestOption);
|
||||
return ConvertHttpResponseToObject<TResponse>(parameters, responseString);
|
||||
}
|
||||
|
||||
public async ValueTask<string> GetAsync(string url,
|
||||
object parameters = null,
|
||||
Action<HttpRequestMessage> requestOption = null)
|
||||
{
|
||||
var requestParamsStr = BuildQueryString(parameters);
|
||||
var requestMsg = new HttpRequestMessage(HttpMethod.Get, new Uri($"{url}?{requestParamsStr}"));
|
||||
requestOption?.Invoke(requestMsg);
|
||||
|
||||
using var responseMessage = await BuildHttpClient().SendAsync(requestMsg);
|
||||
var responseContentString = await responseMessage.Content.ReadAsStringAsync();
|
||||
|
||||
return ValidateHttpResponse(responseMessage, parameters, responseContentString);
|
||||
}
|
||||
|
||||
public async ValueTask<TResponse> GetAsync<TResponse>(string url,
|
||||
object parameters = null,
|
||||
Action<HttpRequestMessage> requestOption = null)
|
||||
{
|
||||
var responseString = await GetAsync(url, parameters, requestOption);
|
||||
return ConvertHttpResponseToObject<TResponse>(parameters, responseString);
|
||||
}
|
||||
|
||||
protected virtual HttpClient BuildHttpClient()
|
||||
{
|
||||
return _httpClientFactory.CreateClient(HttpClientNameConstant);
|
||||
}
|
||||
|
||||
private string BuildQueryString(object parameters)
|
||||
{
|
||||
if (parameters == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var type = parameters.GetType();
|
||||
if (type == typeof(string))
|
||||
{
|
||||
return parameters as string;
|
||||
}
|
||||
|
||||
var properties = type.GetProperties();
|
||||
var paramBuilder = new StringBuilder();
|
||||
|
||||
foreach (var propertyInfo in properties)
|
||||
{
|
||||
var jsonProperty = propertyInfo.GetCustomAttribute<JsonPropertyAttribute>();
|
||||
var propertyName = jsonProperty != null ? jsonProperty.PropertyName : propertyInfo.Name;
|
||||
|
||||
paramBuilder.Append($"{propertyName}={propertyInfo.GetValue(parameters)}&");
|
||||
}
|
||||
|
||||
return paramBuilder.ToString().TrimEnd('&');
|
||||
}
|
||||
|
||||
private string BuildJsonBodyString(object parameters)
|
||||
{
|
||||
if (parameters == null) return string.Empty;
|
||||
if (parameters is string result) return result;
|
||||
|
||||
return JsonConvert.SerializeObject(parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验 Http 响应 <see cref="HttpResponseMessage"/> 是否合法。
|
||||
/// </summary>
|
||||
/// <param name="responseMessage">执行 Http 请求之后的响应实例。</param>
|
||||
/// <param name="requestParameters">执行 Http 请求时传递的参数。</param>
|
||||
/// <param name="responseString">执行 Http 请求之后响应内容。</param>
|
||||
/// <returns>如果响应正常,则返回具体的响应内容。</returns>
|
||||
/// <exception cref="ErrorCodeException">如果 Http 响应不正常,则可能抛出本异常。</exception>
|
||||
private string ValidateHttpResponse(HttpResponseMessage responseMessage, object requestParameters, string responseString)
|
||||
{
|
||||
return responseMessage.StatusCode switch
|
||||
{
|
||||
HttpStatusCode.OK => responseString,
|
||||
HttpStatusCode.ServiceUnavailable => throw new ErrorCodeException(ErrorCodes.ServiceUnavailable),
|
||||
_ => throw new ErrorCodeException(ErrorCodes.HttpRequestFailed, attachObj: new { requestParameters, responseString })
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 Http 响应的字符串反序列化为指定类型 <see cref="TResponse"/> 的对象。
|
||||
/// </summary>
|
||||
/// <param name="requestParameters">执行 Http 请求时传递的参数。</param>
|
||||
/// <param name="responseString">执行 Http 请求之后响应内容。</param>
|
||||
/// <typeparam name="TResponse">需要将响应结果反序列化的目标类型。</typeparam>
|
||||
/// <exception cref="ErrorCodeException">如果反序列化失败,则可能抛出本异常。</exception>
|
||||
private TResponse ConvertHttpResponseToObject<TResponse>(object requestParameters, string responseString)
|
||||
{
|
||||
var throwException = new ErrorCodeException(ErrorCodes.HttpResponseConvertJsonFailed, attachObj: new { requestParameters, responseString });
|
||||
|
||||
try
|
||||
{
|
||||
var responseObj = JsonConvert.DeserializeObject<TResponse>(responseString);
|
||||
if (responseObj != null) return responseObj;
|
||||
|
||||
throw throwException;
|
||||
}
|
||||
catch (JsonSerializationException)
|
||||
{
|
||||
throw throwException;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
namespace ZonyLrcTools.Common.Infrastructure.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// 基于 <see cref="IHttpClientFactory"/> 封装的 HTTP 请求客户端。
|
||||
/// </summary>
|
||||
public interface IWarpHttpClient
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据指定的配置执行 POST 请求,并以 <see cref="string"/> 作为返回值。
|
||||
/// </summary>
|
||||
/// <param name="url">请求的 URL 地址。</param>
|
||||
/// <param name="parameters">请求的参数。</param>
|
||||
/// <param name="isQueryStringParam">是否以 QueryString 形式携带参数。</param>
|
||||
/// <param name="requestOption">请求时的配置动作。</param>
|
||||
/// <returns>服务端的响应结果。</returns>
|
||||
ValueTask<string> PostAsync(string url,
|
||||
object parameters = null,
|
||||
bool isQueryStringParam = false,
|
||||
Action<HttpRequestMessage> requestOption = null);
|
||||
|
||||
/// <summary>
|
||||
/// 根据指定的配置执行 POST 请求,并将结果反序列化为 <see cref="TResponse"/> 对象。
|
||||
/// </summary>
|
||||
/// <param name="url">请求的 URL 地址。</param>
|
||||
/// <param name="parameters">请求的参数。</param>
|
||||
/// <param name="isQueryStringParam">是否以 QueryString 形式携带参数。</param>
|
||||
/// <param name="requestOption">请求时的配置动作。</param>
|
||||
/// <typeparam name="TResponse">需要将响应结果反序列化的目标类型。</typeparam>
|
||||
/// <returns>服务端的响应结果。</returns>
|
||||
ValueTask<TResponse> PostAsync<TResponse>(string url,
|
||||
object parameters = null,
|
||||
bool isQueryStringParam = false,
|
||||
Action<HttpRequestMessage> requestOption = null);
|
||||
|
||||
/// <summary>
|
||||
/// 根据指定的配置执行 GET 请求,并以 <see cref="string"/> 作为返回值。
|
||||
/// </summary>
|
||||
/// <param name="url">请求的 URL 地址。</param>
|
||||
/// <param name="parameters">请求的参数。</param>
|
||||
/// <param name="requestOption">请求时的配置动作。</param>
|
||||
/// <returns>服务端的响应结果。</returns>
|
||||
ValueTask<string> GetAsync(string url,
|
||||
object parameters = null,
|
||||
Action<HttpRequestMessage> requestOption = null);
|
||||
|
||||
/// <summary>
|
||||
/// 根据指定的配置执行 GET 请求,并将结果反序列化为 <see cref="TResponse"/> 对象。
|
||||
/// </summary>
|
||||
/// <param name="url">请求的 URL 地址。</param>
|
||||
/// <param name="parameters">请求的参数。</param>
|
||||
/// <param name="requestOption">请求时的配置动作。</param>
|
||||
/// <typeparam name="TResponse">需要将响应结果反序列化的目标类型。</typeparam>
|
||||
/// <returns>服务端的响应结果。</returns>
|
||||
ValueTask<TResponse> GetAsync<TResponse>(
|
||||
string url,
|
||||
object parameters = null,
|
||||
Action<HttpRequestMessage> requestOption = null);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user