You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Blazor+Identity的在线用户列表实现方案咨询

Blazor + Identity 实现可靠在线用户列表的解决方案

一、优化SignalR的断开检测与主动通知

浏览器关闭时的unload/beforeunload事件存在被浏览器中断的可能,SignalR默认的心跳机制也可能延迟检测到断开,可通过以下方式优化:

  • 强化服务器端超时配置
    在Program.cs中调整SignalR Hub的超时与心跳参数,让服务器更快识别断开的客户端:

    builder.Services.AddSignalR()
        .AddHubOptions<OnlineUserHub>(options =>
        {
            options.ClientTimeoutInterval = TimeSpan.FromSeconds(30); // 客户端30秒无响应则判定断开
            options.KeepAliveInterval = TimeSpan.FromSeconds(10); // 每10秒发送心跳包
        });
    
  • 客户端主动发送断开信号
    利用navigator.sendBeacon(浏览器支持的可靠后台发送API)在页面卸载时通知服务器,避免异步操作被中断:

    1. 在Blazor组件的OnAfterRenderAsync中注册卸载监听:
      protected override async Task OnAfterRenderAsync(bool firstRender)
      {
          if (firstRender)
          {
              var userId = (await _userManager.GetUserAsync(User))?.Id;
              await JS.InvokeVoidAsync("registerUnloadDisconnect", userId);
          }
      }
      
    2. 在wwwroot/js/site.js中添加JS逻辑:
      function registerUnloadDisconnect(userId) {
          window.addEventListener('beforeunload', () => {
              const data = new Blob([JSON.stringify({ userId: userId })], { type: 'application/json' });
              navigator.sendBeacon('/api/online-users/disconnect', data);
          });
      }
      
    3. 后端新增API接口处理断开请求:
      [ApiController]
      [Route("api/online-users")]
      public class OnlineUsersController : ControllerBase
      {
          private readonly UserManager<IdentityUser> _userManager;
      
          public OnlineUsersController(UserManager<IdentityUser> userManager)
          {
              _userManager = userManager;
          }
      
          [HttpPost("disconnect")]
          public async Task<IActionResult> Disconnect([FromBody] UserDisconnectRequest request)
          {
              var user = await _userManager.FindByIdAsync(request.UserId);
              if (user != null)
              {
                  // 示例:更新用户最后活跃时间为超时阈值之前
                  user.LastActiveTime = DateTime.UtcNow.AddMinutes(-1);
                  await _userManager.UpdateAsync(user);
              }
              return Ok();
          }
      
          public class UserDisconnectRequest
          {
              public string UserId { get; set; }
          }
      }
      

二、基于数据库的心跳与定时清理机制

通过数据库记录用户活跃状态,结合定时任务清理离线用户,是最可靠的兜底方案:

  • 扩展用户状态存储
    给IdentityUser添加LastActiveTime字段(或单独创建UserOnlineStatus表):

    public class ApplicationUser : IdentityUser
    {
        public DateTime LastActiveTime { get; set; } = DateTime.UtcNow;
    }
    
  • 客户端定时发送心跳
    在Blazor组件中使用定时器,每隔10秒更新用户最后活跃时间:

    private Timer _heartbeatTimer;
    
    protected override async Task OnInitializedAsync()
    {
        await UpdateLastActive();
        _heartbeatTimer = new Timer(async _ => await UpdateLastActive(), null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
    }
    
    private async Task UpdateLastActive()
    {
        var user = await _userManager.GetUserAsync(User);
        if (user != null)
        {
            user.LastActiveTime = DateTime.UtcNow;
            await _userManager.UpdateAsync(user);
        }
    }
    
    public void Dispose()
    {
        _heartbeatTimer?.Dispose();
    }
    
  • 服务器端定时清理离线用户
    创建后台服务,每隔1分钟扫描数据库,将超过30秒未活跃的用户标记为离线:

    public class OnlineUserCleanupService : BackgroundService
    {
        private readonly IServiceScopeFactory _scopeFactory;
        private readonly TimeSpan _offlineThreshold = TimeSpan.FromSeconds(30);
    
        public OnlineUserCleanupService(IServiceScopeFactory scopeFactory)
        {
            _scopeFactory = scopeFactory;
        }
    
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                using var scope = _scopeFactory.CreateScope();
                var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
    
                var offlineUsers = await userManager.Users
                    .Where(u => DateTime.UtcNow - u.LastActiveTime > _offlineThreshold)
                    .ToListAsync(stoppingToken);
    
                foreach (var user in offlineUsers)
                {
                    // 可根据需求更新状态,比如添加IsOnline字段
                    // user.IsOnline = false;
                    await userManager.UpdateAsync(user);
                }
    
                await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
            }
        }
    }
    

    在Program.cs中注册后台服务:

    builder.Services.AddHostedService<OnlineUserCleanupService>();
    

三、Blazor Server专属:利用Circuit状态跟踪

Blazor Server的每个用户会话对应一个Circuit,可通过ICircuitHandler监听Circuit的开闭事件:

  • 实现Circuit处理器:
    public class OnlineCircuitHandler : CircuitHandler
    {
        private readonly IServiceScopeFactory _scopeFactory;
    
        public OnlineCircuitHandler(IServiceScopeFactory scopeFactory)
        {
            _scopeFactory = scopeFactory;
        }
    
        public override async Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken)
        {
            using var scope = _scopeFactory.CreateScope();
            var httpContextAccessor = scope.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
            var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
    
            var user = await userManager.GetUserAsync(httpContextAccessor.HttpContext.User);
            if (user != null)
            {
                user.LastActiveTime = DateTime.UtcNow;
                await userManager.UpdateAsync(user);
            }
    
            await base.OnCircuitOpenedAsync(circuit, cancellationToken);
        }
    
        public override async Task OnCircuitClosedAsync(Circuit circuit, CancellationToken cancellationToken)
        {
            using var scope = _scopeFactory.CreateScope();
            var httpContextAccessor = scope.ServiceProvider.GetRequiredService<IHttpContextAccessor>();
            var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
    
            var user = await userManager.GetUserAsync(httpContextAccessor.HttpContext.User);
            if (user != null)
            {
                user.LastActiveTime = DateTime.UtcNow.AddMinutes(-1);
                await userManager.UpdateAsync(user);
            }
    
            await base.OnCircuitClosedAsync(circuit, cancellationToken);
        }
    }
    
    在Program.cs中注册:
    builder.Services.AddScoped<CircuitHandler, OnlineCircuitHandler>();
    

方案组合建议

实际项目中推荐SignalR主动断开+数据库心跳+定时清理的组合方案:

  1. 用SignalR实现在线列表的实时推送
  2. 客户端通过心跳和主动断开保证状态及时更新
  3. 服务器端定时清理作为兜底,处理浏览器崩溃、网络异常等极端情况

内容的提问来源于stack exchange,提问作者Chardeux

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 19:53:11