mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
user service refactor
This commit is contained in:
@@ -10,17 +10,10 @@ public class CollectionsMarshalTest
|
||||
[TestMethod]
|
||||
public void DictionaryMarshalGetValueRefOrNullRefIsNullRef()
|
||||
{
|
||||
#if NET8_0_OR_GREATER
|
||||
Dictionary<uint, string> dictionaryValueKeyRefValue = [];
|
||||
Dictionary<uint, uint> dictionaryValueKeyValueValue = [];
|
||||
Dictionary<string, uint> dictionaryRefKeyValueValue = [];
|
||||
Dictionary<string, string> dictionaryRefKeyRefValue = [];
|
||||
#else
|
||||
Dictionary<uint, string> dictionaryValueKeyRefValue = new();
|
||||
Dictionary<uint, uint> dictionaryValueKeyValueValue = new();
|
||||
Dictionary<string, uint> dictionaryRefKeyValueValue = new();
|
||||
Dictionary<string, string> dictionaryRefKeyRefValue = new();
|
||||
#endif
|
||||
|
||||
Assert.IsTrue(Unsafe.IsNullRef(ref CollectionsMarshal.GetValueRefOrNullRef(dictionaryValueKeyRefValue, 1U)));
|
||||
Assert.IsTrue(Unsafe.IsNullRef(ref CollectionsMarshal.GetValueRefOrNullRef(dictionaryValueKeyValueValue, 1U)));
|
||||
@@ -31,17 +24,10 @@ public class CollectionsMarshalTest
|
||||
[TestMethod]
|
||||
public void DictionaryMarshalGetValueRefOrAddDefaultIsDefault()
|
||||
{
|
||||
#if NET8_0_OR_GREATER
|
||||
Dictionary<uint, string> dictionaryValueKeyRefValue = [];
|
||||
Dictionary<uint, uint> dictionaryValueKeyValueValue = [];
|
||||
Dictionary<string, uint> dictionaryRefKeyValueValue = [];
|
||||
Dictionary<string, string> dictionaryRefKeyRefValue = [];
|
||||
#else
|
||||
Dictionary<uint, string> dictionaryValueKeyRefValue = new();
|
||||
Dictionary<uint, uint> dictionaryValueKeyValueValue = new();
|
||||
Dictionary<string, uint> dictionaryRefKeyValueValue = new();
|
||||
Dictionary<string, string> dictionaryRefKeyRefValue = new();
|
||||
#endif
|
||||
|
||||
Assert.IsTrue(CollectionsMarshal.GetValueRefOrAddDefault(dictionaryValueKeyRefValue, 1U, out _) == default);
|
||||
Assert.IsTrue(CollectionsMarshal.GetValueRefOrAddDefault(dictionaryValueKeyValueValue, 1U, out _) == default);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
@@ -7,8 +8,6 @@ namespace Snap.Hutao.Test.BaseClassLibrary;
|
||||
[TestClass]
|
||||
public sealed class JsonSerializeTest
|
||||
{
|
||||
public TestContext? TestContext { get; set; }
|
||||
|
||||
private readonly JsonSerializerOptions AlowStringNumberOptions = new()
|
||||
{
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString,
|
||||
@@ -36,7 +35,7 @@ public sealed class JsonSerializeTest
|
||||
[TestMethod]
|
||||
public void DelegatePropertyCanSerialize()
|
||||
{
|
||||
Sample sample = JsonSerializer.Deserialize<Sample>(SmapleObjectJson)!;
|
||||
SampleDelegatePropertyClass sample = JsonSerializer.Deserialize<SampleDelegatePropertyClass>(SmapleObjectJson)!;
|
||||
Assert.AreEqual(sample.B, 1);
|
||||
}
|
||||
|
||||
@@ -44,7 +43,7 @@ public sealed class JsonSerializeTest
|
||||
[ExpectedException(typeof(JsonException))]
|
||||
public void EmptyStringCannotSerializeAsNumber()
|
||||
{
|
||||
StringNumberSample sample = JsonSerializer.Deserialize<StringNumberSample>(SmapleEmptyStringObjectJson)!;
|
||||
SampleStringReadWriteNumberPropertyClass sample = JsonSerializer.Deserialize<SampleStringReadWriteNumberPropertyClass>(SmapleEmptyStringObjectJson)!;
|
||||
Assert.AreEqual(sample.A, 0);
|
||||
}
|
||||
|
||||
@@ -58,37 +57,28 @@ public sealed class JsonSerializeTest
|
||||
[TestMethod]
|
||||
public void ByteArraySerializeAsBase64()
|
||||
{
|
||||
byte[] array =
|
||||
#if NET8_0_OR_GREATER
|
||||
[1, 2, 3, 4, 5];
|
||||
#else
|
||||
{ 1, 2, 3, 4, 5 };
|
||||
#endif
|
||||
ByteArraySample sample = new()
|
||||
SampleByteArrayPropertyClass sample = new()
|
||||
{
|
||||
Array = array,
|
||||
Array = [1, 2, 3, 4, 5],
|
||||
};
|
||||
|
||||
string result = JsonSerializer.Serialize(sample);
|
||||
TestContext!.WriteLine($"ByteArray Serialize Result: {result}");
|
||||
Assert.AreEqual(result, """
|
||||
{"Array":"AQIDBAU="}
|
||||
""");
|
||||
Assert.AreEqual(result, """{"Array":"AQIDBAU="}""");
|
||||
}
|
||||
|
||||
private sealed class Sample
|
||||
private sealed class SampleDelegatePropertyClass
|
||||
{
|
||||
public int A { get => B; set => B = value; }
|
||||
public int B { get; set; }
|
||||
}
|
||||
|
||||
private sealed class StringNumberSample
|
||||
private sealed class SampleStringReadWriteNumberPropertyClass
|
||||
{
|
||||
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)]
|
||||
public int A { get; set; }
|
||||
}
|
||||
|
||||
private sealed class ByteArraySample
|
||||
private sealed class SampleByteArrayPropertyClass
|
||||
{
|
||||
public byte[]? Array { get; set; }
|
||||
}
|
||||
|
||||
@@ -9,11 +9,10 @@ public sealed class LinqTest
|
||||
{
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void LinqOrderByWithWrapperStruct()
|
||||
public void LinqOrderByWithWrapperStructThrow()
|
||||
{
|
||||
List<MyUInt32> list = [1, 5, 2, 6, 3, 7, 4, 8];
|
||||
string result = string.Join(", ", list.OrderBy(i => i).Select(i => i.Value));
|
||||
|
||||
Console.WriteLine(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -159,19 +159,11 @@ public sealed class GeniusInvokationDecoding
|
||||
result.CopyTo(resultArray);
|
||||
|
||||
ushort[] testKnownResult =
|
||||
#if NET8_0_OR_GREATER
|
||||
[
|
||||
060, 019, 001, 079, 120, 120, 129, 151, 151, 153, 153,
|
||||
181, 184, 184, 185, 185, 194, 194, 200, 200, 201, 201,
|
||||
217, 217, 219, 241, 241, 244, 244, 245, 245, 270, 270,
|
||||
];
|
||||
#else
|
||||
{
|
||||
060, 019, 001, 079, 120, 120, 129, 151, 151, 153, 153,
|
||||
181, 184, 184, 185, 185, 194, 194, 200, 200, 201, 201,
|
||||
217, 217, 219, 241, 241, 244, 244, 245, 245, 270, 270,
|
||||
};
|
||||
#endif
|
||||
|
||||
CollectionAssert.AreEqual(resultArray, testKnownResult);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public class SpiralAbyssScheduleIdTest
|
||||
DateTimeOffset dateTimeOffset = new(2020, 7, 1, 4, 0, 0, Utc8);
|
||||
Console.WriteLine($"2020-07-01 04:00:00 为第 {GetForDateTimeOffset(dateTimeOffset)} 期");
|
||||
}
|
||||
|
||||
public static int GetForDateTimeOffset(DateTimeOffset dateTimeOffset)
|
||||
{
|
||||
// Force time in UTC+08
|
||||
|
||||
@@ -167,7 +167,7 @@ internal static partial class EnumerableExtension
|
||||
return results;
|
||||
}
|
||||
|
||||
public static async ValueTask<List<TResult>> SelectListAsync<TSource, TResult>(this List<TSource> list, Func<TSource, CancellationToken, ValueTask<TResult>> selector, CancellationToken token)
|
||||
public static async ValueTask<List<TResult>> SelectListAsync<TSource, TResult>(this List<TSource> list, Func<TSource, CancellationToken, ValueTask<TResult>> selector, CancellationToken token = default)
|
||||
{
|
||||
List<TResult> results = new(list.Count);
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.ViewModel.User;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using System.Collections.ObjectModel;
|
||||
using BindingUser = Snap.Hutao.ViewModel.User.User;
|
||||
|
||||
namespace Snap.Hutao.Service.User;
|
||||
|
||||
internal interface IUserCollectionService
|
||||
{
|
||||
BindingUser? CurrentUser { get; set; }
|
||||
|
||||
ValueTask<ObservableCollection<UserAndUid>> GetUserAndUidCollectionAsync();
|
||||
|
||||
ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync();
|
||||
|
||||
UserGameRole? GetUserGameRoleByUid(string uid);
|
||||
|
||||
ValueTask RemoveUserAsync(BindingUser user);
|
||||
ValueTask<ValueResult<UserOptionResult, string>> TryCreateAndAddUserFromCookieAsync(Cookie cookie, bool isOversea);
|
||||
bool TryGetUserByMid(string mid, [NotNullWhen(true)] out BindingUser? user);
|
||||
}
|
||||
@@ -50,13 +50,6 @@ internal interface IUserService
|
||||
/// <returns>处理的结果</returns>
|
||||
ValueTask<ValueResult<UserOptionResult, string>> ProcessInputCookieAsync(Cookie cookie, bool isOversea);
|
||||
|
||||
/// <summary>
|
||||
/// 异步刷新 Cookie 的 CookieToken
|
||||
/// </summary>
|
||||
/// <param name="user">用户</param>
|
||||
/// <returns>是否刷新成功</returns>
|
||||
ValueTask<bool> RefreshCookieTokenAsync(BindingUser user);
|
||||
|
||||
ValueTask<bool> RefreshCookieTokenAsync(Model.Entity.User user);
|
||||
|
||||
/// <summary>
|
||||
|
||||
185
src/Snap.Hutao/Snap.Hutao/Service/User/UserCollectionService.cs
Normal file
185
src/Snap.Hutao/Snap.Hutao/Service/User/UserCollectionService.cs
Normal file
@@ -0,0 +1,185 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Message;
|
||||
using Snap.Hutao.ViewModel.User;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
using System.Collections.ObjectModel;
|
||||
using BindingUser = Snap.Hutao.ViewModel.User.User;
|
||||
|
||||
namespace Snap.Hutao.Service.User;
|
||||
|
||||
[ConstructorGenerated]
|
||||
[Injection(InjectAs.Singleton, typeof(IUserCollectionService))]
|
||||
internal sealed partial class UserCollectionService : IUserCollectionService
|
||||
{
|
||||
private readonly ScopedDbCurrent<BindingUser, Model.Entity.User, UserChangedMessage> dbCurrent;
|
||||
private readonly IUserInitializationService userInitializationService;
|
||||
private readonly IUserDbService userDbService;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly IMessenger messenger;
|
||||
|
||||
private readonly Throttler throttler = new();
|
||||
|
||||
private ObservableCollection<BindingUser>? userCollection;
|
||||
private Dictionary<string, BindingUser>? midUserMap;
|
||||
|
||||
private ObservableCollection<UserAndUid>? userAndUidCollection;
|
||||
private Dictionary<string, UserGameRole>? uidUserGameRoleMap;
|
||||
|
||||
public BindingUser? CurrentUser
|
||||
{
|
||||
get => dbCurrent.Current;
|
||||
set => dbCurrent.Current = value;
|
||||
}
|
||||
|
||||
public async ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync()
|
||||
{
|
||||
using (await throttler.ThrottleAsync().ConfigureAwait(false))
|
||||
{
|
||||
if (userCollection is null)
|
||||
{
|
||||
List<Model.Entity.User> entities = await userDbService.GetUserListAsync().ConfigureAwait(false);
|
||||
List<BindingUser> users = await entities.SelectListAsync(userInitializationService.ResumeUserAsync).ConfigureAwait(false);
|
||||
|
||||
midUserMap = [];
|
||||
foreach (BindingUser user in users)
|
||||
{
|
||||
if (user.Entity.Mid is not null)
|
||||
{
|
||||
midUserMap[user.Entity.Mid] = user;
|
||||
}
|
||||
|
||||
if (user.NeedDbUpdateAfterResume)
|
||||
{
|
||||
await userDbService.UpdateUserAsync(user.Entity).ConfigureAwait(false);
|
||||
user.NeedDbUpdateAfterResume = false;
|
||||
}
|
||||
}
|
||||
|
||||
userCollection = users.ToObservableCollection();
|
||||
|
||||
try
|
||||
{
|
||||
CurrentUser = users.SelectedOrDefault();
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
ThrowHelper.UserdataCorrupted(SH.ServiceUserCurrentMultiMatched, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return userCollection;
|
||||
}
|
||||
|
||||
public async ValueTask<ObservableCollection<UserAndUid>> GetUserAndUidCollectionAsync()
|
||||
{
|
||||
if (userAndUidCollection is null)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
ObservableCollection<BindingUser> users = await GetUserCollectionAsync().ConfigureAwait(false);
|
||||
List<UserAndUid> roles = [];
|
||||
uidUserGameRoleMap = [];
|
||||
|
||||
foreach (BindingUser user in users)
|
||||
{
|
||||
foreach (UserGameRole role in user.UserGameRoles)
|
||||
{
|
||||
roles.Add(UserAndUid.From(user.Entity, role));
|
||||
uidUserGameRoleMap[role.GameUid] = role;
|
||||
}
|
||||
}
|
||||
|
||||
userAndUidCollection = roles.ToObservableCollection();
|
||||
}
|
||||
|
||||
return userAndUidCollection;
|
||||
}
|
||||
|
||||
public async ValueTask RemoveUserAsync(BindingUser user)
|
||||
{
|
||||
// Sync cache
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ArgumentNullException.ThrowIfNull(userCollection);
|
||||
userCollection.Remove(user);
|
||||
userAndUidCollection?.RemoveWhere(r => r.User.Mid == user.Entity.Mid);
|
||||
if (user.Entity.Mid is not null)
|
||||
{
|
||||
midUserMap?.Remove(user.Entity.Mid);
|
||||
}
|
||||
|
||||
// Sync database
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
await userDbService.DeleteUserByIdAsync(user.Entity.InnerId).ConfigureAwait(false);
|
||||
|
||||
messenger.Send(new UserRemovedMessage(user.Entity));
|
||||
}
|
||||
|
||||
public UserGameRole? GetUserGameRoleByUid(string uid)
|
||||
{
|
||||
if (uidUserGameRoleMap is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return uidUserGameRoleMap[uid];
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Sequence contains more than one matching element
|
||||
// TODO: return a specialize UserGameRole to indicate error
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public bool TryGetUserByMid(string mid, [NotNullWhen(true)] out BindingUser? user)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(midUserMap);
|
||||
return midUserMap.TryGetValue(mid, out user);
|
||||
}
|
||||
|
||||
public async ValueTask<ValueResult<UserOptionResult, string>> TryCreateAndAddUserFromCookieAsync(Cookie cookie, bool isOversea)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
BindingUser? newUser = await userInitializationService.CreateUserFromCookieOrDefaultAsync(cookie, isOversea).ConfigureAwait(false);
|
||||
|
||||
if (newUser is null)
|
||||
{
|
||||
return new(UserOptionResult.Invalid, SH.ServiceUserProcessCookieRequestUserInfoFailed);
|
||||
}
|
||||
|
||||
await GetUserCollectionAsync().ConfigureAwait(false);
|
||||
ArgumentNullException.ThrowIfNull(userCollection);
|
||||
|
||||
// Sync cache
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
userCollection.Add(newUser);
|
||||
if (newUser.Entity.Mid is not null)
|
||||
{
|
||||
midUserMap?.Add(newUser.Entity.Mid, newUser);
|
||||
}
|
||||
|
||||
if (userAndUidCollection is not null)
|
||||
{
|
||||
foreach (UserGameRole role in newUser.UserGameRoles)
|
||||
{
|
||||
userAndUidCollection.Add(new(newUser.Entity, role));
|
||||
uidUserGameRoleMap?.Add(role.GameUid, role);
|
||||
}
|
||||
}
|
||||
|
||||
// Sync database
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
await userDbService.AddUserAsync(newUser.Entity).ConfigureAwait(false);
|
||||
ArgumentNullException.ThrowIfNull(newUser.UserInfo);
|
||||
return new(UserOptionResult.Added, newUser.UserInfo.Uid);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||
using Snap.Hutao.Core.ExceptionService;
|
||||
using Snap.Hutao.Message;
|
||||
using Snap.Hutao.ViewModel.User;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Passport;
|
||||
@@ -23,39 +19,20 @@ namespace Snap.Hutao.Service.User;
|
||||
[Injection(InjectAs.Singleton, typeof(IUserService))]
|
||||
internal sealed partial class UserService : IUserService, IUserServiceUnsafe
|
||||
{
|
||||
private readonly Throttler throttler = new();
|
||||
|
||||
private readonly ScopedDbCurrent<BindingUser, Model.Entity.User, UserChangedMessage> dbCurrent;
|
||||
private readonly IUserInitializationService userInitializationService;
|
||||
private readonly IUserCollectionService userCollectionService;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly IUserDbService userDbService;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly IMessenger messenger;
|
||||
|
||||
private ObservableCollection<BindingUser>? userCollection;
|
||||
private ObservableCollection<UserAndUid>? userAndUidCollection;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public BindingUser? Current
|
||||
{
|
||||
get => dbCurrent.Current;
|
||||
set => dbCurrent.Current = value;
|
||||
get => userCollectionService.CurrentUser;
|
||||
set => userCollectionService.CurrentUser = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask RemoveUserAsync(BindingUser user)
|
||||
public ValueTask RemoveUserAsync(BindingUser user)
|
||||
{
|
||||
// Sync cache
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
ArgumentNullException.ThrowIfNull(userCollection);
|
||||
userCollection.Remove(user);
|
||||
userAndUidCollection?.RemoveWhere(r => r.User.Mid == user.Entity.Mid);
|
||||
|
||||
// Sync database
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
await userDbService.DeleteUserByIdAsync(user.Entity.InnerId).ConfigureAwait(false);
|
||||
|
||||
messenger.Send(new UserRemovedMessage(user.Entity));
|
||||
return userCollectionService.RemoveUserAsync(user);
|
||||
}
|
||||
|
||||
public async ValueTask UnsafeRemoveUsersAsync()
|
||||
@@ -64,76 +41,21 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe
|
||||
await userDbService.RemoveUsersAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync()
|
||||
public ValueTask<ObservableCollection<BindingUser>> GetUserCollectionAsync()
|
||||
{
|
||||
using (await throttler.ThrottleAsync().ConfigureAwait(false))
|
||||
{
|
||||
if (userCollection is null)
|
||||
{
|
||||
List<Model.Entity.User> entities = await userDbService.GetUserListAsync().ConfigureAwait(false);
|
||||
List<BindingUser> users = await entities.SelectListAsync(userInitializationService.ResumeUserAsync, default).ConfigureAwait(false);
|
||||
|
||||
foreach (BindingUser user in users)
|
||||
{
|
||||
if (user.NeedDbUpdateAfterResume)
|
||||
{
|
||||
await userDbService.UpdateUserAsync(user.Entity).ConfigureAwait(false);
|
||||
user.NeedDbUpdateAfterResume = false;
|
||||
}
|
||||
}
|
||||
|
||||
userCollection = users.ToObservableCollection();
|
||||
|
||||
try
|
||||
{
|
||||
Current = users.SelectedOrDefault();
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
ThrowHelper.UserdataCorrupted(SH.ServiceUserCurrentMultiMatched, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return userCollection;
|
||||
return userCollectionService.GetUserCollectionAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<ObservableCollection<UserAndUid>> GetRoleCollectionAsync()
|
||||
public ValueTask<ObservableCollection<UserAndUid>> GetRoleCollectionAsync()
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
if (userAndUidCollection is null)
|
||||
{
|
||||
ObservableCollection<BindingUser> users = await GetUserCollectionAsync().ConfigureAwait(false);
|
||||
userAndUidCollection = users
|
||||
.SelectMany(user => user.UserGameRoles.Select(role => UserAndUid.From(user.Entity, role)))
|
||||
.ToObservableCollection();
|
||||
}
|
||||
|
||||
return userAndUidCollection;
|
||||
return userCollectionService.GetUserAndUidCollectionAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public UserGameRole? GetUserGameRoleByUid(string uid)
|
||||
{
|
||||
if (userCollection is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return userCollection.SelectMany(u => u.UserGameRoles).SingleOrDefault(r => r.GameUid == uid);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Sequence contains more than one matching element
|
||||
// TODO: return a specialize UserGameRole to indicate error
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
return userCollectionService.GetUserGameRoleByUid(uid);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<ValueResult<UserOptionResult, string>> ProcessInputCookieAsync(Cookie cookie, bool isOversea)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
@@ -145,33 +67,22 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe
|
||||
}
|
||||
|
||||
// 检查 mid 对应用户是否存在
|
||||
ArgumentNullException.ThrowIfNull(userCollection);
|
||||
if (TryGetUser(userCollection, mid, out BindingUser? user))
|
||||
if (!userCollectionService.TryGetUserByMid(mid, out BindingUser? user))
|
||||
{
|
||||
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 userDbService.UpdateUserAsync(user.Entity).ConfigureAwait(false);
|
||||
return new(UserOptionResult.Updated, mid);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new(UserOptionResult.Invalid, SH.ServiceUserProcessCookieNoSToken);
|
||||
}
|
||||
return await userCollectionService.TryCreateAndAddUserFromCookieAsync(cookie, isOversea).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
|
||||
if (!cookie.TryGetSToken(isOversea, out Cookie? stoken))
|
||||
{
|
||||
return await TryCreateUserAndAddAsync(cookie, isOversea).ConfigureAwait(false);
|
||||
return new(UserOptionResult.Invalid, SH.ServiceUserProcessCookieNoSToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<bool> RefreshCookieTokenAsync(BindingUser user)
|
||||
{
|
||||
return RefreshCookieTokenAsync(user.Entity);
|
||||
user.SToken = stoken;
|
||||
user.LToken = cookie.TryGetLToken(out Cookie? ltoken) ? ltoken : user.LToken;
|
||||
user.CookieToken = cookie.TryGetCookieToken(out Cookie? cookieToken) ? cookieToken : user.CookieToken;
|
||||
|
||||
await userDbService.UpdateUserAsync(user.Entity).ConfigureAwait(false);
|
||||
return new(UserOptionResult.Updated, mid);
|
||||
}
|
||||
|
||||
public async ValueTask<bool> RefreshCookieTokenAsync(Model.Entity.User user)
|
||||
@@ -183,65 +94,20 @@ internal sealed partial class UserService : IUserService, IUserServiceUnsafe
|
||||
.GetCookieAccountInfoBySTokenAsync(user)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (cookieTokenResponse.IsOk())
|
||||
{
|
||||
string cookieToken = cookieTokenResponse.Data.CookieToken;
|
||||
|
||||
// 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).ConfigureAwait(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
if (!cookieTokenResponse.IsOk())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetUser(ObservableCollection<BindingUser> users, string mid, [NotNullWhen(true)] out BindingUser? user)
|
||||
{
|
||||
// TODO: System.InvalidOperationException: Sequence contains more than one matching element
|
||||
user = users.SingleOrDefault(u => u.Entity.Mid == mid);
|
||||
return user is not null;
|
||||
}
|
||||
string cookieToken = cookieTokenResponse.Data.CookieToken;
|
||||
|
||||
private async ValueTask<ValueResult<UserOptionResult, string>> TryCreateUserAndAddAsync(Cookie cookie, bool isOversea)
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
BindingUser? newUser = await userInitializationService.CreateUserFromCookieOrDefaultAsync(cookie, isOversea).ConfigureAwait(false);
|
||||
// Check null and create a new one to avoid System.NullReferenceException
|
||||
user.CookieToken ??= new();
|
||||
|
||||
if (newUser is not null)
|
||||
{
|
||||
// Sync cache
|
||||
if (userCollection is not null)
|
||||
{
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
{
|
||||
userCollection.Add(newUser);
|
||||
// Sync ui and database
|
||||
user.CookieToken[Cookie.COOKIE_TOKEN] = cookieToken;
|
||||
await userDbService.UpdateUserAsync(user).ConfigureAwait(false);
|
||||
|
||||
if (userAndUidCollection is not null)
|
||||
{
|
||||
foreach (UserGameRole role in newUser.UserGameRoles)
|
||||
{
|
||||
userAndUidCollection.Add(new(newUser.Entity, role));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sync database
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
await userDbService.AddUserAsync(newUser.Entity).ConfigureAwait(false);
|
||||
ArgumentNullException.ThrowIfNull(newUser.UserInfo);
|
||||
return new(UserOptionResult.Added, newUser.UserInfo.Uid);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new(UserOptionResult.Invalid, SH.ServiceUserProcessCookieRequestUserInfoFailed);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using BindingUser = Snap.Hutao.ViewModel.User.User;
|
||||
|
||||
namespace Snap.Hutao.Service.User;
|
||||
|
||||
internal static class UserServiceExtension
|
||||
{
|
||||
public static ValueTask<bool> RefreshCookieTokenAsync(this IUserService userService, BindingUser user)
|
||||
{
|
||||
return userService.RefreshCookieTokenAsync(user.Entity);
|
||||
}
|
||||
}
|
||||
@@ -52,14 +52,12 @@ internal sealed partial class UserViewModel : ObservableObject
|
||||
get => selectedUser ??= userService.Current;
|
||||
set
|
||||
{
|
||||
// Pre select the chosen role to avoid multiple UserChangedMessage
|
||||
value?.SetSelectedUserGameRole(value.UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen), false);
|
||||
|
||||
if (SetProperty(ref selectedUser, value))
|
||||
{
|
||||
userService.Current = value;
|
||||
|
||||
if (value is not null)
|
||||
{
|
||||
value.SelectedUserGameRole = value.UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user