Skip to content

Commit 705822f

Browse files
authored
feat(TeamStateManager): add support for WASM and Server modes (#772)
- Implemented ITeamStateManager interface for managing team states. - Added registration logic for team state manager in ServiceCollectionExtensions.cs. - Created ServerTeamStateManager and WasmTeamStateManager to handle team state management in respective environments. - Updated User.razor to utilize the new team state manager for fetching user team information.
1 parent b3a7d06 commit 705822f

File tree

5 files changed

+212
-4
lines changed

5 files changed

+212
-4
lines changed

src/Masa.Stack.Components/Extensions/ServiceCollectionExtensions.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,27 @@ private static void AddMasaStackComponentsService(IServiceCollection services, M
3333
services.TryAddScoped<CookieStorage>();
3434
services.TryAddScoped<LocalStorage>();
3535
services.TryAddScoped<JsInitVariables>();
36+
37+
// 注册团队状态管理器 - 根据运行时环境选择合适的实现
38+
services.AddScoped<ITeamStateManager>(sp =>
39+
{
40+
// 检测是否为 WASM 模式
41+
if (IsWebAssemblyEnvironment(sp))
42+
{
43+
// WASM 模式:使用页面刷新获取最新的 token 和 claims
44+
var authStateProvider = sp.GetRequiredService<AuthenticationStateProvider>();
45+
var navigationManager = sp.GetRequiredService<NavigationManager>();
46+
return new WasmTeamStateManager(authStateProvider, navigationManager);
47+
}
48+
else
49+
{
50+
// Server 模式:直接操作身份验证状态
51+
var authStateManager = sp.GetRequiredService<AuthenticationStateManager>();
52+
var authStateProvider = sp.GetRequiredService<AuthenticationStateProvider>();
53+
return new ServerTeamStateManager(authStateManager, authStateProvider);
54+
}
55+
});
56+
3657
services.AddAutoInject();
3758
services.AddMemoryCache();
3859
services.AddMasaIdentity(options =>
@@ -150,6 +171,25 @@ private static void AddObservable(IServiceCollection services, bool isMasa, Masa
150171
return output;
151172
}
152173

174+
/// <summary>
175+
/// 检测是否为 WebAssembly 环境
176+
/// </summary>
177+
private static bool IsWebAssemblyEnvironment(IServiceProvider serviceProvider)
178+
{
179+
try
180+
{
181+
// 在 WASM 模式下,IJSRuntime 实现了 IJSInProcessRuntime 接口
182+
// 在 Server 模式下,由于网络延迟,JS 调用都是异步的,不会实现 IJSInProcessRuntime
183+
var jsRuntime = serviceProvider.GetService<IJSRuntime>();
184+
return jsRuntime is IJSInProcessRuntime;
185+
}
186+
catch
187+
{
188+
// 如果出现异常,默认为 Server 模式
189+
return false;
190+
}
191+
}
192+
153193
public static async Task InitializeMasaStackApplicationAsync(
154194
[NotNull] this IServiceProvider serviceProvider)
155195
{
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace Masa.Stack.Components.Infrastructure.Identity;
2+
3+
/// <summary>
4+
/// 团队状态管理接口,用于兼容 Server 和 WASM 模式
5+
/// </summary>
6+
public interface ITeamStateManager
7+
{
8+
/// <summary>
9+
/// 设置当前团队ID
10+
/// </summary>
11+
/// <param name="teamId">团队ID</param>
12+
/// <returns></returns>
13+
Task SetCurrentTeamAsync(Guid teamId);
14+
15+
/// <summary>
16+
/// 获取当前团队ID
17+
/// </summary>
18+
/// <returns>团队ID,如果没有设置则返回 Guid.Empty</returns>
19+
Task<Guid> GetCurrentTeamAsync();
20+
21+
/// <summary>
22+
/// 清除团队状态
23+
/// </summary>
24+
/// <returns></returns>
25+
Task ClearTeamStateAsync();
26+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
namespace Masa.Stack.Components.Infrastructure.Identity;
2+
3+
/// <summary>
4+
/// Server 模式的团队状态管理器
5+
/// 使用 AuthenticationStateManager 来管理团队状态
6+
/// </summary>
7+
public class ServerTeamStateManager : ITeamStateManager, IScopedDependency
8+
{
9+
private readonly AuthenticationStateManager _authenticationStateManager;
10+
private readonly AuthenticationStateProvider _authenticationStateProvider;
11+
12+
public ServerTeamStateManager(
13+
AuthenticationStateManager authenticationStateManager,
14+
AuthenticationStateProvider authenticationStateProvider)
15+
{
16+
_authenticationStateManager = authenticationStateManager;
17+
_authenticationStateProvider = authenticationStateProvider;
18+
}
19+
20+
/// <summary>
21+
/// 通过更新身份验证状态中的声明来设置当前团队ID
22+
/// </summary>
23+
public async Task SetCurrentTeamAsync(Guid teamId)
24+
{
25+
await _authenticationStateManager.UpsertClaimAsync(IdentityClaimConsts.CURRENT_TEAM, teamId.ToString());
26+
}
27+
28+
/// <summary>
29+
/// 从身份验证状态中获取当前团队ID
30+
/// </summary>
31+
public async Task<Guid> GetCurrentTeamAsync()
32+
{
33+
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
34+
var teamIdClaim = authState.User.FindFirst(IdentityClaimConsts.CURRENT_TEAM);
35+
36+
if (teamIdClaim != null && Guid.TryParse(teamIdClaim.Value, out var teamId))
37+
{
38+
return teamId;
39+
}
40+
41+
return Guid.Empty;
42+
}
43+
44+
/// <summary>
45+
/// 清除团队状态
46+
/// </summary>
47+
public async Task ClearTeamStateAsync()
48+
{
49+
await _authenticationStateManager.UpsertClaimAsync(IdentityClaimConsts.CURRENT_TEAM, Guid.Empty.ToString());
50+
}
51+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
namespace Masa.Stack.Components.Infrastructure.Identity;
2+
3+
/// <summary>
4+
/// WASM 模式的团队状态管理器
5+
/// 在 WASM 模式下,通过页面刷新获取包含最新团队信息的 token
6+
/// 经测试发现 RequestAccessToken() 只返回缓存的 token,不会真正刷新,所以使用页面刷新确保可靠性
7+
/// </summary>
8+
public class WasmTeamStateManager : ITeamStateManager, IScopedDependency
9+
{
10+
private readonly AuthenticationStateProvider _authenticationStateProvider;
11+
private readonly NavigationManager _navigationManager;
12+
13+
public WasmTeamStateManager(
14+
AuthenticationStateProvider authenticationStateProvider,
15+
NavigationManager navigationManager)
16+
{
17+
_authenticationStateProvider = authenticationStateProvider;
18+
_navigationManager = navigationManager;
19+
}
20+
21+
/// <summary>
22+
/// 在 WASM 模式下设置团队后,获取最新的身份验证状态
23+
/// </summary>
24+
public async Task SetCurrentTeamAsync(Guid teamId)
25+
{
26+
try
27+
{
28+
// 在 WASM 模式下,最可靠的方式是直接刷新页面
29+
// 因为 RequestAccessToken() 通常只返回缓存的 token,不会真正刷新
30+
// 而团队信息的更新需要服务端重新颁发包含最新 claims 的 token
31+
32+
// 短暂延迟确保后端团队信息更新完成
33+
await Task.Delay(100);
34+
35+
// 直接使用页面刷新,这是最可靠的方式
36+
// 页面刷新会重新初始化 OIDC 客户端,获取最新的 token 和 claims
37+
_navigationManager.NavigateTo(_navigationManager.Uri, true);
38+
}
39+
catch (Exception)
40+
{
41+
// 确保在任何异常情况下都能刷新页面
42+
_navigationManager.NavigateTo(_navigationManager.Uri, true);
43+
}
44+
}
45+
46+
/// <summary>
47+
/// 从当前身份验证状态中获取团队ID
48+
/// </summary>
49+
public async Task<Guid> GetCurrentTeamAsync()
50+
{
51+
try
52+
{
53+
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
54+
var teamIdClaim = authState.User.FindFirst(IdentityClaimConsts.CURRENT_TEAM);
55+
56+
if (teamIdClaim != null && Guid.TryParse(teamIdClaim.Value, out var teamId))
57+
{
58+
return teamId;
59+
}
60+
}
61+
catch (Exception)
62+
{
63+
// 如果获取失败,返回空的 GUID
64+
}
65+
66+
return Guid.Empty;
67+
}
68+
69+
/// <summary>
70+
/// 清除团队状态
71+
/// </summary>
72+
public async Task ClearTeamStateAsync()
73+
{
74+
await SetCurrentTeamAsync(Guid.Empty);
75+
}
76+
}

src/Masa.Stack.Components/Shared/Layouts/Components/User.razor

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
@inject IJSRuntime JS
66
@inject ILogger<User> Logger
77
@inject AuthenticationStateManager AuthenticationStateManager
8+
@inject ITeamStateManager TeamStateManager
89
@inject SignOutSessionStateManager SignOutManager
910

1011
<MMenu @bind-Value="@MenuVisible" OffsetY NudgeTop="-8" CloseOnContentClick="false"
@@ -148,18 +149,31 @@
148149
try
149150
{
150151
_teams = await AuthClient.TeamService.GetUserTeamsAsync();
151-
if (MasaUser.CurrentTeamId == Guid.Empty && _teams.Any())
152+
153+
// 从身份验证状态中获取当前团队信息(在 WASM 模式下来自 token)
154+
var currentTeamId = await TeamStateManager.GetCurrentTeamAsync();
155+
if (currentTeamId != Guid.Empty && _teams.Any(t => t.Id == currentTeamId))
152156
{
157+
// 如果从身份验证状态中找到有效的团队信息,使用它
158+
MasaUser.CurrentTeamId = currentTeamId;
159+
GlobalConfig.CurrentTeamId = currentTeamId;
160+
}
161+
else if (MasaUser.CurrentTeamId == Guid.Empty && _teams.Any())
162+
{
163+
// 如果没有团队信息,则使用第一个团队
153164
await CurrentTeamChanged(_teams.First().Id);
154165
}
166+
else if (MasaUser.CurrentTeamId != Guid.Empty && _teams.Any(t => t.Id == MasaUser.CurrentTeamId))
167+
{
168+
// 如果 MasaUser 中有有效的团队信息,使用它
169+
GlobalConfig.CurrentTeamId = MasaUser.CurrentTeamId;
170+
}
155171
}
156172
catch (Exception e)
157173
{
158174
Logger.LogError(e, "AuthClient.TeamService.GetUserTeamsAsync");
159175
}
160176

161-
MasaUser.CurrentTeamId = CurrentTeam.Id;
162-
GlobalConfig.CurrentTeamId = CurrentTeam.Id;
163177
StateHasChanged();
164178
}
165179
await base.OnAfterRenderAsync(firstRender);
@@ -184,7 +198,8 @@
184198
await AuthClient.UserService.SetCurrentTeamAsync(teamId);
185199
}
186200

187-
await AuthenticationStateManager.UpsertClaimAsync(IdentityClaimConsts.CURRENT_TEAM, teamId.ToString());
201+
// 使用新的团队状态管理器,兼容 Server 和 WASM 模式
202+
await TeamStateManager.SetCurrentTeamAsync(teamId);
188203
GlobalConfig.CurrentTeamId = teamId;
189204
MasaUser.CurrentTeamId = teamId;
190205
_showChangeTeam = false;

0 commit comments

Comments
 (0)