using Dpz.Core.Service.Mediator.Features.Account.Queries;
namespace Dpz.Core.Web.Library.Middleware;
/// <summary>
/// 用户资料刷新中间件
/// 每次请求时检查当前登录用户的资料是否被标记为已变更(通过 FusionCache 存储的变更时间戳),
/// 若资料已过期则重新查询用户信息并刷新登录态
/// </summary>
public class UserProfileRefreshMiddleware(
RequestDelegate next,
IFusionCache fusionCache,
ILogger<UserProfileRefreshMiddleware> logger
)
{
public async Task InvokeAsync(
HttpContext context,
IMediator mediator,
IApplicationSignInManager signInManager
)
{
if (context.User.Authenticated)
{
await RefreshProfileIfNeededAsync(context, mediator, signInManager);
}
await next(context);
}
/// <summary>
/// 检查用户资料是否需要刷新并执行刷新
/// 比较缓存中的资料变更时间与登录态中记录的最后刷新时间,
/// 若变更时间晚于刷新时间则重新查询用户信息并更新登录态
/// </summary>
private async Task RefreshProfileIfNeededAsync(
HttpContext context,
IMediator mediator,
IApplicationSignInManager signInManager
)
{
var account = context.User.NameIdentifier;
var sessionId = context.User.SessionId;
if (string.IsNullOrWhiteSpace(account) || string.IsNullOrWhiteSpace(sessionId))
{
return;
}
// 从 FusionCache 读取用户资料变更时间戳
var changedAt = await fusionCache.GetOrDefaultAsync<DateTime>(
UserSessionKeys.ChangedAtKey(account),
token: context.RequestAborted
);
if (changedAt == default)
{
return;
}
// 当前登录态中的最后刷新时间已不早于变更时间,无需刷新
var refreshedAt = ReadProfileRefreshedAt(context);
if (refreshedAt >= changedAt)
{
return;
}
// 重新查询用户最新信息
var userInfo = await mediator.Send(
new GetUserInfoRequest { Account = account },
context.RequestAborted
);
if (userInfo == null)
{
logger.LogWarning(
"User profile refresh skipped because account was not found: {Account}",
account
);
return;
}
// 更新登录态中的用户信息
userInfo.SessionId = sessionId;
await signInManager.RefreshSignInAsync(userInfo);
}
/// <summary>
/// 从当前用户 Claims 中读取最近一次资料刷新的时间
/// </summary>
private static DateTime ReadProfileRefreshedAt(HttpContext context)
{
var raw = context.User.FindFirst(UserSessionKeys.ProfileRefreshedAtClaim)?.Value;
if (
DateTime.TryParse(
raw,
CultureInfo.InvariantCulture,
DateTimeStyles.RoundtripKind,
out var refreshedAt
)
)
{
return refreshedAt;
}
return default;
}
}
/// <summary>
/// 用户资料刷新中间件的注册扩展
/// </summary>
public static class UserProfileRefreshMiddlewareExtensions
{
public static IApplicationBuilder UseUserProfileRefresh(this IApplicationBuilder app)
{
return app.UseMiddleware<UserProfileRefreshMiddleware>();
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释
这是一个 ASP.NET Core 中间件,用于在每次 HTTP 请求时自动检查并刷新已登录用户的资料信息。
核心功能
1. UserProfileRefreshMiddleware 中间件类
构造函数参数:
RequestDelegate next: 管道中的下一个中间件IFusionCache fusionCache: 分布式缓存服务ILogger: 日志记录器
主方法 InvokeAsync:
public async Task InvokeAsync(HttpContext context, IMediator mediator, IApplicationSignInManager signInManager)
- 检查当前用户是否已认证(
context.User.Authenticated) - 如果已认证,调用刷新逻辑
- 继续执行管道中的下一个中间件
2. 刷新逻辑 RefreshProfileIfNeededAsync
工作流程:
获取用户标识
- 从当前用户 Claims 中读取账号(
NameIdentifier)和会话ID(SessionId) - 如果任一为空则跳过刷新
- 从当前用户 Claims 中读取账号(
检查资料变更时间
- 从
FusionCache中读取用户资料的变更时间戳 - 缓存键格式:
UserSessionKeys.ChangedAtKey(account) - 如果没有变更时间记录则跳过
- 从
比较时间戳
- 从用户 Claims 中读取上次刷新时间(
ProfileRefreshedAtClaim) - 如果刷新时间 >= 变更时间,说明资料是最新的,无需刷新
- 从用户 Claims 中读取上次刷新时间(
重新查询并更新
- 通过 Mediator 发送
GetUserInfoRequest查询最新用户信息 - 如果用户不存在,记录警告日志并跳过
- 保留原会话ID,调用
signInManager.RefreshSignInAsync更新登录态
- 通过 Mediator 发送
3. 辅助方法 ReadProfileRefreshedAt
- 从当前用户的 Claims 中读取
ProfileRefreshedAtClaim的值 - 使用
InvariantCulture和RoundtripKind解析 DateTime - 解析失败则返回默认值(
default(DateTime))
4. 扩展方法
public static IApplicationBuilder UseUserProfileRefresh(this IApplicationBuilder app)
提供便捷的中间件注册方式,在 Startup.cs 或 Program.cs 中可以这样使用:
app.UseUserProfileRefresh();
应用场景
当用户资料在其他地方被修改(如管理员修改用户权限、用户更新个人信息等)时:
- 修改操作会在 FusionCache 中更新
ChangedAtKey的时间戳 - 用户下次请求时,中间件检测到变更
- 自动重新加载用户信息,更新 Cookie/JWT 中的 Claims
- 确保用户在当前会话中使用最新的资料和权限
设计优点
✅ 性能优化:只在资料变更时才刷新,避免每次请求都查询数据库
✅ 分布式友好:使用 FusionCache 支持多服务器场景
✅ 解耦设计:通过 Mediator 模式调用查询逻辑
✅ 可扩展性:清晰的职责分离,易于维护和测试
AI 正在分析代码…
评论加载中...