Really fast

This commit is contained in:
Lightczx
2023-08-03 23:39:35 +08:00
parent 0ee875d28d
commit fd52334c13
18 changed files with 190 additions and 129 deletions

View File

@@ -105,6 +105,7 @@ internal sealed class ConstructorGenerator : IIncrementalGenerator
foreach (IFieldSymbol fieldSymbol in fields)
{
if(fieldSymbol.Kind == SymbolKind.Field)
if (fieldSymbol.IsReadOnly && !fieldSymbol.IsStatic)
{
switch (fieldSymbol.Type.ToDisplayString())

View File

@@ -17,6 +17,7 @@ internal static class SelectableExtension
/// <typeparam name="TSource">源类型</typeparam>
/// <param name="source">源</param>
/// <returns>选中的值或默认值</returns>
/// <exception cref="InvalidOperationException">存在多个选中的值</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TSource? SelectedOrDefault<TSource>(this IEnumerable<TSource> source)
where TSource : ISelectable

View File

@@ -105,6 +105,30 @@ internal static partial class EnumerableExtension
return results;
}
public static async ValueTask<List<TResult>> SelectListAsync<TSource, TResult>(this List<TSource> list, Func<TSource, ValueTask<TResult>> selector)
{
List<TResult> results = new(list.Count);
foreach (TSource item in list)
{
results.Add(await selector(item).ConfigureAwait(false));
}
return results;
}
public static async ValueTask<List<TResult>> SelectListAsync<TSource, TResult>(this List<TSource> list, Func<TSource, CancellationToken, ValueTask<TResult>> selector, CancellationToken token)
{
List<TResult> results = new(list.Count);
foreach (TSource item in list)
{
results.Add(await selector(item, token).ConfigureAwait(false));
}
return results;
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static List<TSource> SortBy<TSource, TKey>(this List<TSource> list, Func<TSource, TKey> keySelector)
where TKey : IComparable

View File

@@ -83,9 +83,9 @@ internal sealed partial class AnnouncementService : IAnnouncementService
}
/// <summary>
/// 匹配特殊的时间格式: <t>(.*?)</t>
/// 匹配特殊的时间格式: <t.*?>(.*?)</t>
/// </summary>
/// <returns>正则</returns>
[GeneratedRegex("&lt;t.*?&gt;(.*?)&lt;/t&gt;", RegexOptions.Multiline)]
[GeneratedRegex("&lt;t class=\"t_gl\".*?&gt;(.*?)&lt;/t&gt;", RegexOptions.Multiline)]
private static partial Regex XmlTagRegex();
}

View File

@@ -14,7 +14,7 @@ namespace Snap.Hutao.Service;
/// 存储服务相关的选项
/// </summary>
[Injection(InjectAs.Singleton)]
internal sealed class AppOptions : DbStoreOptions
internal sealed partial class AppOptions : DbStoreOptions
{
private readonly List<NameValue<BackdropType>> supportedBackdropTypes = new()
{

View File

@@ -24,8 +24,6 @@ namespace Snap.Hutao.Service.Game.Package;
internal sealed partial class PackageConverter
{
private const string PackageVersion = "pkg_version";
private const string OverseaFolder = "Oversea";
private const string ChineseFolder = "Chinese";
private readonly IServiceProvider serviceProvider;
private readonly JsonSerializerOptions options;
private readonly RuntimeOptions runtimeOptions;

View File

@@ -17,6 +17,7 @@ internal sealed class PackageReplaceStatus : ICloneable<PackageReplaceStatus>
/// <param name="description">描述</param>
public PackageReplaceStatus(string description)
{
Name = default!;
Description = description;
}

View File

@@ -0,0 +1,15 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.User;
internal interface IUserDbService
{
ValueTask AddUserAsync(Model.Entity.User user);
ValueTask DeleteUserByIdAsync(Guid id);
ValueTask<List<Model.Entity.User>> GetUserListAsync();
ValueTask UpdateUserAsync(Model.Entity.User user);
}

View File

@@ -24,7 +24,7 @@ internal interface IUserService
/// 异步获取角色与用户集合
/// </summary>
/// <returns>角色与用户集合</returns>
Task<ObservableCollection<UserAndUid>> GetRoleCollectionAsync();
ValueTask<ObservableCollection<UserAndUid>> GetRoleCollectionAsync();
/// <summary>
/// 初始化用户服务及所有用户
@@ -48,14 +48,14 @@ internal interface IUserService
/// <param name="cookie">Cookie</param>
/// <param name="isOversea">是否为国际服</param>
/// <returns>处理的结果</returns>
Task<ValueResult<UserOptionResult, string>> ProcessInputCookieAsync(Cookie cookie, bool isOversea);
ValueTask<ValueResult<UserOptionResult, string>> ProcessInputCookieAsync(Cookie cookie, bool isOversea);
/// <summary>
/// 异步刷新 Cookie 的 CookieToken
/// </summary>
/// <param name="user">用户</param>
/// <returns>是否刷新成功</returns>
Task<bool> RefreshCookieTokenAsync(BindingUser user);
ValueTask<bool> RefreshCookieTokenAsync(BindingUser user);
/// <summary>
/// 异步移除用户

View File

@@ -0,0 +1,62 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.DependencyInjection.Abstraction;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Message;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.ViewModel.User;
using Snap.Hutao.Web.Hoyolab;
using Snap.Hutao.Web.Hoyolab.Passport;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using Snap.Hutao.Web.Response;
using System.Collections.ObjectModel;
using BindingUser = Snap.Hutao.ViewModel.User.User;
namespace Snap.Hutao.Service.User;
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IUserDbService))]
internal sealed partial class UserDbService : IUserDbService
{
private readonly IServiceProvider serviceProvider;
public async ValueTask DeleteUserByIdAsync(Guid id)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.Users.ExecuteDeleteWhereAsync(u => u.InnerId == id).ConfigureAwait(false);
}
}
public async ValueTask<List<Model.Entity.User>> GetUserListAsync()
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
return await appDbContext.Users.AsNoTracking().ToListAsync().ConfigureAwait(false);
}
}
public async ValueTask UpdateUserAsync(Model.Entity.User user)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.Users.UpdateAndSaveAsync(user).ConfigureAwait(false);
}
}
public async ValueTask AddUserAsync(Model.Entity.User user)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.Users.AddAndSaveAsync(user).ConfigureAwait(false);
}
}
}

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.DependencyInjection.Abstraction;
using Snap.Hutao.Core.ExceptionService;
@@ -61,23 +62,13 @@ internal sealed partial class UserService : IUserService
await taskContext.SwitchToBackgroundAsync();
if (userCollection == null)
{
List<BindingUser> users = new();
using (IServiceScope scope = serviceProvider.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
foreach (Model.Entity.User entity in appDbContext.Users)
{
BindingUser initialized = await BindingUser.ResumeAsync(entity).ConfigureAwait(false);
users.Add(initialized);
}
}
List<Model.Entity.User> entities = await userDbService.GetUserListAsync().ConfigureAwait(false);
List<BindingUser> users = await entities.SelectListAsync(BindingUser.ResumeAsync, default).ConfigureAwait(false);
userCollection = users.ToObservableCollection();
try
{
Current = users.SingleOrDefault(user => user.IsSelected);
Current = users.SelectedOrDefault();
}
catch (InvalidOperationException ex)
{
@@ -89,22 +80,15 @@ internal sealed partial class UserService : IUserService
}
/// <inheritdoc/>
public async Task<ObservableCollection<UserAndUid>> GetRoleCollectionAsync()
public async ValueTask<ObservableCollection<UserAndUid>> GetRoleCollectionAsync()
{
await taskContext.SwitchToBackgroundAsync();
if (userAndUidCollection == null)
{
List<UserAndUid> userAndUids = new();
ObservableCollection<BindingUser> observableUsers = await GetUserCollectionAsync().ConfigureAwait(false);
foreach (BindingUser user in observableUsers)
{
foreach (UserGameRole role in user.UserGameRoles)
{
userAndUids.Add(new(user.Entity, role));
}
}
userAndUidCollection = userAndUids.ToObservableCollection();
ObservableCollection<BindingUser> users = await GetUserCollectionAsync().ConfigureAwait(false);
userAndUidCollection = users
.SelectMany(user => user.UserGameRoles.Select(role => UserAndUid.From(user.Entity, role)))
.ToObservableCollection();
}
return userAndUidCollection;
@@ -113,11 +97,10 @@ internal sealed partial class UserService : IUserService
/// <inheritdoc/>
public UserGameRole? GetUserGameRoleByUid(string uid)
{
if (userCollection != null)
if (userCollection is not null)
{
try
{
// TODO: optimize match speed.
return userCollection.SelectMany(u => u.UserGameRoles).SingleOrDefault(r => r.GameUid == uid);
}
catch (InvalidOperationException)
@@ -127,16 +110,16 @@ internal sealed partial class UserService : IUserService
}
}
return null;
return default;
}
/// <inheritdoc/>
public async Task<ValueResult<UserOptionResult, string>> ProcessInputCookieAsync(Cookie cookie, bool isOversea)
public async ValueTask<ValueResult<UserOptionResult, string>> ProcessInputCookieAsync(Cookie cookie, bool isOversea)
{
await taskContext.SwitchToBackgroundAsync();
string? mid = cookie.GetValueOrDefault(isOversea ? Cookie.STUID : Cookie.MID);
if (mid == null)
if (string.IsNullOrEmpty(mid))
{
return new(UserOptionResult.Invalid, SH.ServiceUserProcessCookieNoMid);
}
@@ -144,23 +127,18 @@ internal sealed partial class UserService : IUserService
// 检查 mid 对应用户是否存在
if (TryGetUser(userCollection!, mid, out BindingUser? user))
{
using (IServiceScope scope = serviceProvider.CreateScope())
if (cookie.TryGetSToken(isOversea, out Cookie? stoken))
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
user.SToken = stoken;
user.LToken = cookie.TryGetLToken(out Cookie? ltoken) ? ltoken : user.LToken;
user.CookieToken = cookie.TryGetCookieToken(out Cookie? cookieToken) ? cookieToken : user.CookieToken;
if (cookie.TryGetSToken(isOversea, out Cookie? stoken))
{
user.SToken = stoken;
user.LToken = cookie.TryGetLToken(out Cookie? ltoken) ? ltoken : user.LToken;
user.CookieToken = cookie.TryGetCookieToken(out Cookie? cookieToken) ? cookieToken : user.CookieToken;
await appDbContext.Users.UpdateAndSaveAsync(user.Entity).ConfigureAwait(false);
return new(UserOptionResult.Updated, mid);
}
else
{
return new(UserOptionResult.Invalid, SH.ServiceUserProcessCookieNoSToken);
}
await userDbService.UpdateUserAsync(user.Entity).ConfigureAwait(false);
return new(UserOptionResult.Updated, mid);
}
else
{
return new(UserOptionResult.Invalid, SH.ServiceUserProcessCookieNoSToken);
}
}
else
@@ -170,99 +148,71 @@ internal sealed partial class UserService : IUserService
}
/// <inheritdoc/>
public async Task<bool> RefreshCookieTokenAsync(BindingUser user)
public async ValueTask<bool> RefreshCookieTokenAsync(BindingUser user)
{
using (IServiceScope scope = serviceProvider.CreateScope())
Response<UidCookieToken> cookieTokenResponse = await serviceProvider
.GetRequiredService<IOverseaSupportFactory<IPassportClient>>()
.Create(user.Entity.IsOversea)
.GetCookieAccountInfoBySTokenAsync(user.Entity)
.ConfigureAwait(false);
if (cookieTokenResponse.IsOk())
{
Response<UidCookieToken> cookieTokenResponse = await scope.ServiceProvider
.GetRequiredService<IOverseaSupportFactory<IPassportClient>>()
.Create(user.Entity.IsOversea)
.GetCookieAccountInfoBySTokenAsync(user.Entity)
.ConfigureAwait(false);
string cookieToken = cookieTokenResponse.Data.CookieToken;
if (cookieTokenResponse.IsOk())
{
string cookieToken = cookieTokenResponse.Data.CookieToken;
// Check null and create a new one to avoid System.NullReferenceException
user.CookieToken ??= new();
// Check null and create a new one to avoid System.NullReferenceException
user.CookieToken ??= new();
// Sync ui and database
user.CookieToken[Cookie.COOKIE_TOKEN] = cookieToken!;
await userDbService.UpdateUserAsync(user.Entity).ConfigureAwait(false);
// sync ui and database
user.CookieToken[Cookie.COOKIE_TOKEN] = cookieToken!;
scope.ServiceProvider.GetRequiredService<AppDbContext>().Users.UpdateAndSave(user.Entity);
return true;
}
else
{
return false;
}
return true;
}
else
{
return false;
}
}
private static bool TryGetUser(ObservableCollection<BindingUser> users, string mid, [NotNullWhen(true)] out BindingUser? user)
{
user = users.SingleOrDefault(u => u.Entity.Mid == mid);
return user != null;
return user is not null;
}
private async Task<ValueResult<UserOptionResult, string>> TryCreateUserAndAddAsync(Cookie cookie, bool isOversea)
private async ValueTask<ValueResult<UserOptionResult, string>> TryCreateUserAndAddAsync(Cookie cookie, bool isOversea)
{
await taskContext.SwitchToBackgroundAsync();
using (IServiceScope scope = serviceProvider.CreateScope())
BindingUser? newUser = await BindingUser.CreateAsync(cookie, isOversea).ConfigureAwait(false);
if (newUser != null)
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
BindingUser? newUser = await BindingUser.CreateAsync(cookie, isOversea).ConfigureAwait(false);
if (newUser != null)
// Sync cache
if (userCollection != null)
{
// Sync cache
if (userCollection != null)
await taskContext.SwitchToMainThreadAsync();
{
await taskContext.SwitchToMainThreadAsync();
{
userCollection!.Add(newUser);
userCollection!.Add(newUser);
if (userAndUidCollection != null)
if (userAndUidCollection != null)
{
foreach (UserGameRole role in newUser.UserGameRoles)
{
foreach (UserGameRole role in newUser.UserGameRoles)
{
userAndUidCollection.Add(new(newUser.Entity, role));
}
userAndUidCollection.Add(new(newUser.Entity, role));
}
}
}
}
// Sync database
await taskContext.SwitchToBackgroundAsync();
await appDbContext.Users.AddAndSaveAsync(newUser.Entity).ConfigureAwait(false);
return new(UserOptionResult.Added, newUser.UserInfo!.Uid);
}
else
{
return new(UserOptionResult.Invalid, SH.ServiceUserProcessCookieRequestUserInfoFailed);
}
// Sync database
await taskContext.SwitchToBackgroundAsync();
await userDbService.AddUserAsync(newUser.Entity).ConfigureAwait(false);
return new(UserOptionResult.Added, newUser.UserInfo!.Uid);
}
}
}
[ConstructorGenerated]
[Injection(InjectAs.Singleton, typeof(IUserDbService))]
internal sealed partial class UserDbService : IUserDbService
{
private readonly IServiceProvider serviceProvider;
public async ValueTask DeleteUserByIdAsync(Guid id)
{
using (IServiceScope scope = serviceProvider.CreateScope())
else
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.Users.ExecuteDeleteWhereAsync(u => u.InnerId == id).ConfigureAwait(false);
return new(UserOptionResult.Invalid, SH.ServiceUserProcessCookieRequestUserInfoFailed);
}
}
}
internal interface IUserDbService
{
ValueTask DeleteUserByIdAsync(Guid id);
}

View File

@@ -3,6 +3,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.DependencyInjection.Abstraction;
using Snap.Hutao.Model;
using Snap.Hutao.Web.Hoyolab;
@@ -18,7 +19,7 @@ namespace Snap.Hutao.ViewModel.User;
/// 用于视图绑定的用户
/// </summary>
[HighQuality]
internal sealed class User : ObservableObject, IEntityOnly<EntityUser>
internal sealed class User : ObservableObject, IEntityOnly<EntityUser>, ISelectable
{
private readonly EntityUser inner;
@@ -61,6 +62,8 @@ internal sealed class User : ObservableObject, IEntityOnly<EntityUser>
}
}
public Guid InnerId { get => inner.InnerId; }
/// <inheritdoc cref="EntityUser.IsSelected"/>
public bool IsSelected
{
@@ -105,7 +108,7 @@ internal sealed class User : ObservableObject, IEntityOnly<EntityUser>
/// <param name="inner">数据库实体</param>
/// <param name="token">取消令牌</param>
/// <returns>用户</returns>
internal static async Task<User> ResumeAsync(EntityUser inner, CancellationToken token = default)
internal static async ValueTask<User> ResumeAsync(EntityUser inner, CancellationToken token = default)
{
User user = new(inner);

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Web.Hoyolab;
using EntityUser = Snap.Hutao.Model.Entity.User;
@@ -12,7 +13,7 @@ namespace Snap.Hutao.ViewModel.User;
/// 抽象此类用于简化这类调用
/// </summary>
[HighQuality]
internal sealed class UserAndUid
internal sealed class UserAndUid : IMappingFrom<UserAndUid, EntityUser, PlayerUid>
{
/// <summary>
/// 构造一个新的实体用户与角色
@@ -35,6 +36,11 @@ internal sealed class UserAndUid
/// </summary>
public PlayerUid Uid { get; private set; }
public static UserAndUid From(EntityUser user, PlayerUid role)
{
return new(user, role);
}
/// <summary>
/// 尝试转换到用户与角色
/// </summary>

View File

@@ -14,7 +14,7 @@ namespace Snap.Hutao.Web.Hoyolab.Bbs.User;
/// </summary>
[HighQuality]
[UseDynamicSecret]
[ConstructorGenerated]
[ConstructorGenerated(ResolveHttpClient = true)]
[HttpClient(HttpClientConfiguration.XRpc)]
internal sealed partial class UserClient : IUserClient
{

View File

@@ -5,7 +5,7 @@ using Snap.Hutao.Core.DependencyInjection.Abstraction;
namespace Snap.Hutao.Web.Hoyolab.Bbs.User;
[Injection(InjectAs.Transient)]
[Injection(InjectAs.Transient, typeof(IOverseaSupportFactory<IUserClient>))]
[ConstructorGenerated(CallBaseConstructor = true)]
internal sealed partial class UserClientFactory : OverseaSupportFactory<IUserClient, UserClient, UserClientOversea>
{

View File

@@ -13,7 +13,7 @@ namespace Snap.Hutao.Web.Hoyolab.Bbs.User;
/// 用户信息客户端 Hoyolab版
/// </summary>
[UseDynamicSecret]
[ConstructorGenerated]
[ConstructorGenerated(ResolveHttpClient = true)]
[HttpClient(HttpClientConfiguration.Default)]
internal sealed partial class UserClientOversea : IUserClient
{

View File

@@ -16,7 +16,7 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
/// </summary>
[HighQuality]
[UseDynamicSecret]
[ConstructorGenerated]
[ConstructorGenerated(ResolveHttpClient = true)]
[HttpClient(HttpClientConfiguration.XRpc)]
[PrimaryHttpMessageHandler(UseCookies = false)]
internal sealed partial class GameRecordClient : IGameRecordClient

View File

@@ -15,7 +15,7 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
/// Hoyoverse game record provider
/// </summary>
[UseDynamicSecret]
[ConstructorGenerated]
[ConstructorGenerated(ResolveHttpClient = true)]
[HttpClient(HttpClientConfiguration.XRpc3)]
[PrimaryHttpMessageHandler(UseCookies = false)]
internal sealed partial class GameRecordClientOversea : IGameRecordClient