diff --git a/src/Snap.Hutao/Snap.Hutao/Core/TypeNameHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/TypeNameHelper.cs new file mode 100644 index 00000000..a5a1b77c --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/TypeNameHelper.cs @@ -0,0 +1,212 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text; + +namespace Snap.Hutao.Core; + +/// +/// 类型名称帮助类 +/// Directly copied from .NET Runtime library +/// +internal static class TypeNameHelper +{ + private const char DefaultNestedTypeDelimiter = '+'; + + private static readonly Dictionary BuiltInTypeNames = new() + { + { typeof(void), "void" }, + { typeof(bool), "bool" }, + { typeof(byte), "byte" }, + { typeof(char), "char" }, + { typeof(decimal), "decimal" }, + { typeof(double), "double" }, + { typeof(float), "float" }, + { typeof(int), "int" }, + { typeof(long), "long" }, + { typeof(object), "object" }, + { typeof(sbyte), "sbyte" }, + { typeof(short), "short" }, + { typeof(string), "string" }, + { typeof(uint), "uint" }, + { typeof(ulong), "ulong" }, + { typeof(ushort), "ushort" }, + }; + + /// + /// 获取对象类型的显示名称 + /// + /// 物品 + /// 是否全名 + /// 对象类型的显示名称 + [return: NotNullIfNotNull(nameof(item))] + public static string? GetTypeDisplayName(object? item, bool fullName = true) + { + return item == null ? null : GetTypeDisplayName(item.GetType(), fullName); + } + + /// + /// Pretty print a type name. + /// + /// The . + /// true to print a fully qualified name. + /// true to include generic parameter names. + /// true to include generic parameters. + /// Character to use as a delimiter in nested type names + /// The pretty printed type name. + public static string GetTypeDisplayName(Type type, bool fullName = true, bool includeGenericParameterNames = false, bool includeGenericParameters = true, char nestedTypeDelimiter = DefaultNestedTypeDelimiter) + { + StringBuilder? builder = null; + string? name = ProcessType(ref builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames, includeGenericParameters, nestedTypeDelimiter)); + return name ?? builder?.ToString() ?? string.Empty; + } + + private static string? ProcessType(ref StringBuilder? builder, Type type, in DisplayNameOptions options) + { + if (type.IsGenericType) + { + Type[] genericArguments = type.GetGenericArguments(); + builder ??= new StringBuilder(); + ProcessGenericType(builder, type, genericArguments, genericArguments.Length, options); + } + else if (type.IsArray) + { + builder ??= new StringBuilder(); + ProcessArrayType(builder, type, options); + } + else if (BuiltInTypeNames.TryGetValue(type, out string? builtInName)) + { + if (builder is null) + { + return builtInName; + } + + builder.Append(builtInName); + } + else if (type.IsGenericParameter) + { + if (options.IncludeGenericParameterNames) + { + if (builder is null) + { + return type.Name; + } + + builder.Append(type.Name); + } + } + else + { + string name = options.FullName ? type.FullName! : type.Name; + + if (builder is null) + { + if (options.NestedTypeDelimiter != DefaultNestedTypeDelimiter) + { + return name.Replace(DefaultNestedTypeDelimiter, options.NestedTypeDelimiter); + } + + return name; + } + + builder.Append(name); + if (options.NestedTypeDelimiter != DefaultNestedTypeDelimiter) + { + builder.Replace(DefaultNestedTypeDelimiter, options.NestedTypeDelimiter, builder.Length - name.Length, name.Length); + } + } + + return null; + } + + private static void ProcessArrayType(StringBuilder builder, Type type, in DisplayNameOptions options) + { + Type innerType = type; + while (innerType.IsArray) + { + innerType = innerType.GetElementType()!; + } + + ProcessType(ref builder!, innerType, options); + + while (type.IsArray) + { + builder.Append('['); + builder.Append(',', type.GetArrayRank() - 1); + builder.Append(']'); + type = type.GetElementType()!; + } + } + + private static void ProcessGenericType(StringBuilder builder, Type type, Type[] genericArguments, int length, in DisplayNameOptions options) + { + int offset = 0; + if (type.IsNested) + { + offset = type.DeclaringType!.GetGenericArguments().Length; + } + + if (options.FullName) + { + if (type.IsNested) + { + ProcessGenericType(builder, type.DeclaringType!, genericArguments, offset, options); + builder.Append(options.NestedTypeDelimiter); + } + else if (!string.IsNullOrEmpty(type.Namespace)) + { + builder.Append(type.Namespace); + builder.Append('.'); + } + } + + int genericPartIndex = type.Name.IndexOf('`'); + if (genericPartIndex <= 0) + { + builder.Append(type.Name); + return; + } + + builder.Append(type.Name, 0, genericPartIndex); + + if (options.IncludeGenericParameters) + { + builder.Append('<'); + for (int i = offset; i < length; i++) + { + ProcessType(ref builder!, genericArguments[i], options); + if (i + 1 == length) + { + continue; + } + + builder.Append(','); + if (options.IncludeGenericParameterNames || !genericArguments[i + 1].IsGenericParameter) + { + builder.Append(' '); + } + } + + builder.Append('>'); + } + } + + private readonly struct DisplayNameOptions + { + public DisplayNameOptions(bool fullName, bool includeGenericParameterNames, bool includeGenericParameters, char nestedTypeDelimiter) + { + FullName = fullName; + IncludeGenericParameters = includeGenericParameters; + IncludeGenericParameterNames = includeGenericParameterNames; + NestedTypeDelimiter = nestedTypeDelimiter; + } + + public bool FullName { get; } + + public bool IncludeGenericParameters { get; } + + public bool IncludeGenericParameterNames { get; } + + public char NestedTypeDelimiter { get; } + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs index 00839239..e6c46e68 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClient.cs @@ -14,7 +14,8 @@ namespace Snap.Hutao.Web.Hoyolab.Bbs.User; /// [HighQuality] [UseDynamicSecret] -[HttpClient(HttpClientConfiguration.XRpc, typeof(IUserClient))] +[HttpClient(HttpClientConfiguration.XRpc)] +[Injection(InjectAs.Transient, typeof(IUserClient))] internal sealed class UserClient : IUserClient { private readonly HttpClient httpClient; @@ -24,12 +25,13 @@ internal sealed class UserClient : IUserClient /// /// 构造一个新的用户信息客户端 /// - /// http客户端 + /// http客户端工厂 /// Json序列化选项 /// 日志器 - public UserClient(HttpClient httpClient, JsonSerializerOptions options, ILogger logger) + public UserClient(IHttpClientFactory httpClientFactory, JsonSerializerOptions options, ILogger logger) { - this.httpClient = httpClient; + httpClient = httpClientFactory.CreateClient(nameof(UserClient)); + this.options = options; this.logger = logger; } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClientOversea.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClientOversea.cs index 76d0f237..4a706076 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClientOversea.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Bbs/User/UserClientOversea.cs @@ -13,7 +13,8 @@ namespace Snap.Hutao.Web.Hoyolab.Bbs.User; /// 用户信息客户端 Hoyolab版 /// [UseDynamicSecret] -[HttpClient(HttpClientConfiguration.XRpc, typeof(IUserClient))] +[HttpClient(HttpClientConfiguration.XRpc)] +[Injection(InjectAs.Transient, typeof(IUserClient))] internal sealed class UserClientOversea : IUserClient { private readonly HttpClient httpClient; @@ -23,12 +24,13 @@ internal sealed class UserClientOversea : IUserClient /// /// 构造一个新的用户信息客户端 /// - /// http客户端 + /// http客户端工厂 /// Json序列化选项 /// 日志器 - public UserClientOversea(HttpClient httpClient, JsonSerializerOptions options, ILogger logger) + public UserClientOversea(IHttpClientFactory httpClientFactory, JsonSerializerOptions options, ILogger logger) { - this.httpClient = httpClient; + httpClient = httpClientFactory.CreateClient(nameof(UserClientOversea)); + this.options = options; this.logger = logger; } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs index bf5019bc..ba9e1b50 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClient2.cs @@ -16,7 +16,8 @@ namespace Snap.Hutao.Web.Hoyolab.Passport; /// [HighQuality] [UseDynamicSecret] -[HttpClient(HttpClientConfiguration.XRpc2, typeof(IPassportClient))] +[HttpClient(HttpClientConfiguration.XRpc2)] +[Injection(InjectAs.Transient, typeof(IPassportClient))] internal sealed class PassportClient2 : IPassportClient { private readonly HttpClient httpClient; @@ -26,12 +27,13 @@ internal sealed class PassportClient2 : IPassportClient /// /// 构造一个新的通行证客户端 /// - /// http客户端 + /// http客户端工厂 /// Json序列化选项 /// 日志器 - public PassportClient2(HttpClient httpClient, JsonSerializerOptions options, ILogger logger) + public PassportClient2(IHttpClientFactory httpClientFactory, JsonSerializerOptions options, ILogger logger) { - this.httpClient = httpClient; + httpClient = httpClientFactory.CreateClient(nameof(PassportClient2)); + this.options = options; this.logger = logger; } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClientOversea.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClientOversea.cs index ecc4eb02..5f0d7b5c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClientOversea.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Passport/PassportClientOversea.cs @@ -15,7 +15,8 @@ namespace Snap.Hutao.Web.Hoyolab.Passport; /// /// 通行证客户端 XRPC 版 /// -[HttpClient(HttpClientConfiguration.XRpc3, typeof(IPassportClient))] +[HttpClient(HttpClientConfiguration.XRpc3)] +[Injection(InjectAs.Transient, typeof(IPassportClient))] internal sealed class PassportClientOversea : IPassportClient { private readonly HttpClient httpClient; @@ -25,12 +26,13 @@ internal sealed class PassportClientOversea : IPassportClient /// /// 构造一个新的国际服通行证客户端 /// - /// http客户端 + /// http客户端工厂 /// Json序列化选项 /// 日志器 - public PassportClientOversea(HttpClient httpClient, JsonSerializerOptions options, ILogger logger) + public PassportClientOversea(IHttpClientFactory httpClientFactory, JsonSerializerOptions options, ILogger logger) { - this.httpClient = httpClient; + httpClient = httpClientFactory.CreateClient(nameof(PassportClientOversea)); + this.options = options; this.logger = logger; } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs index 8482fe22..d6114539 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClient.cs @@ -16,8 +16,9 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord; /// [HighQuality] [UseDynamicSecret] -[HttpClient(HttpClientConfiguration.XRpc, typeof(IGameRecordClient))] +[HttpClient(HttpClientConfiguration.XRpc)] [PrimaryHttpMessageHandler(UseCookies = false)] +[Injection(InjectAs.Transient, typeof(IGameRecordClient))] internal sealed class GameRecordClient : IGameRecordClient { private readonly IServiceProvider serviceProvider; @@ -28,14 +29,14 @@ internal sealed class GameRecordClient : IGameRecordClient /// /// 构造一个新的游戏记录提供器 /// - /// 请求器 + /// 请求器工厂 /// 访问提供器 - public GameRecordClient(HttpClient httpClient, IServiceProvider serviceProvider) + public GameRecordClient(IHttpClientFactory httpClientFactory, IServiceProvider serviceProvider) { options = serviceProvider.GetRequiredService(); logger = serviceProvider.GetRequiredService>(); + httpClient = httpClientFactory.CreateClient(nameof(GameRecordClient)); - this.httpClient = httpClient; this.serviceProvider = serviceProvider; } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClientOversea.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClientOversea.cs index 215e3b4a..4a27e8c9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClientOversea.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/GameRecord/GameRecordClientOversea.cs @@ -15,8 +15,9 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord; /// Hoyoverse game record provider /// [UseDynamicSecret] -[HttpClient(HttpClientConfiguration.XRpc3, typeof(IGameRecordClient))] +[HttpClient(HttpClientConfiguration.XRpc3)] [PrimaryHttpMessageHandler(UseCookies = false)] +[Injection(InjectAs.Transient, typeof(IGameRecordClient))] internal sealed class GameRecordClientOversea : IGameRecordClient { private readonly HttpClient httpClient; @@ -26,12 +27,13 @@ internal sealed class GameRecordClientOversea : IGameRecordClient /// /// 构造一个新的游戏记录提供器 /// - /// 请求器 + /// 请求器工厂 /// json序列化选项 /// 日志器 - public GameRecordClientOversea(HttpClient httpClient, JsonSerializerOptions options, ILogger logger) + public GameRecordClientOversea(IHttpClientFactory httpClientFactory, JsonSerializerOptions options, ILogger logger) { - this.httpClient = httpClient; + httpClient = httpClientFactory.CreateClient(nameof(GameRecordClientOversea)); + this.options = options; this.logger = logger; }