tuiwucarrer/Admin.NET/Admin.NET.Core/Hub/WeMinPro/WeMinProHub.cs

319 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using Admin.NET.Core;
using Furion.InstantMessaging;
using Microsoft.AspNetCore.SignalR;
using UAParser;
using System.Security.Claims;
using Admin.NET.Core.Service;
using MongoDB.Driver.Core.Connections;
using MaxMind.GeoIP2.Model;
using Flurl.Http;
using System.Security.Cryptography;
namespace AAdmin.NET.Core
{
/// <summary>
/// 小程序进入房间时间统计
/// </summary>
[MapHub("/hubs/weminpro")]
public class WeMinProHub : Hub<IWeMinProHub>
{
private readonly IHubContext<WeMinProHub, IWeMinProHub> _hubContext;
private readonly SysCacheService _cache;
private readonly IServiceScopeFactory _scopeFactory;
// private const int MAX_MINUTES = 60;
private const int WARN_MINUTES = 15;
private const string CACHE_PREFIX = "MPUSER_";
public const string CACHE_ROOM = "ROOM_";
public WeMinProHub(
IHubContext<WeMinProHub, IWeMinProHub> hubContext,
SysCacheService cache,
IServiceScopeFactory scopeFactory)
{
_hubContext = hubContext;
_cache = cache;
_scopeFactory = scopeFactory;
}
/// <summary>
/// 用户连接时,首次上线或重连
/// </summary>
/// <returns></returns>
public override async Task OnConnectedAsync()
{
var context = Context.GetHttpContext();
var token = context.Request.Query["access_token"];
var roomId = context.Request.Query["roomId"];
var claims = JWTEncryption.ReadJwtToken(token)?.Claims;
var client = Parser.GetDefault().Parse(context.Request.Headers["User-Agent"]);
var userIdStr = claims?.FirstOrDefault(c => c.Type == ClaimConst.UserId)?.Value;
var tenantIdStr = claims?.FirstOrDefault(c => c.Type == ClaimConst.TenantId)?.Value;
if (!long.TryParse(userIdStr, out var userId)) return;
var tenantId = string.IsNullOrWhiteSpace(tenantIdStr) ? 0 : 1300000000001;
// 检查缓存中是否已有在线用户信息
var cacheKey = CACHE_PREFIX + userId;
var cachedUser = _cache.Get<SysOnlineUser>(cacheKey);
if (cachedUser == null)
{
using var scope = _scopeFactory.CreateScope();
var _onlineUserService = scope.ServiceProvider.GetRequiredService<SqlSugarRepository<SysOnlineUser>>();
var user = await _onlineUserService.GetByIdAsync(userId);
if (user == null)
{
user = new SysOnlineUser
{
UserId = userId,
TenantId = tenantId,
UserName = claims?.FirstOrDefault(c => c.Type == ClaimConst.Account)?.Value,
RealName = claims?.FirstOrDefault(c => c.Type == ClaimConst.RealName)?.Value,
Time = DateTime.Now,
ConnectionId = Context.ConnectionId,
Ip = context.Connection.RemoteIpAddress?.MapToIPv4().ToString(),
Browser = client.UA.Family + client.UA.Major,
Os = client.OS.Family + client.OS.Major,
TotalOnlineSeconds = 0
};
await _onlineUserService.InsertAsync(user);
}
else
{
if (user.LastOfflineTime.HasValue)
{
var lastOnlineSpan = (user.LastOfflineTime.Value - user.Time.GetValueOrDefault()).TotalSeconds;
user.TotalOnlineSeconds += (int)Math.Max(0, lastOnlineSpan);
}
user.ConnectionId = Context.ConnectionId;
user.LastOfflineTime = null;
await _onlineUserService.UpdateAsync(user);
}
_cache.Set(cacheKey, user, TimeSpan.FromHours(1));
cachedUser = user;
var _weChatUserScope = scope.ServiceProvider.GetRequiredService<SqlSugarRepository<SysWeChatUserExtend>>();
var wechatUserinfo = await _weChatUserScope.GetFirstAsync(c => c.WxId == user.UserId);
if (wechatUserinfo != null)
{
_cache.Set("WxVIP_" + user.UserId, wechatUserinfo.IsVIP, TimeSpan.FromHours(1));
}
if (!_cache.ExistKey(CACHE_ROOM + user.UserId))
{
_cache.Set(CACHE_ROOM + user.UserId, roomId, TimeSpan.FromHours(1));
}
}
// 启动监控线程
// _ = MonitorUserOnlineAsync(cachedUser);
await base.OnConnectedAsync();
}
/// <summary>
/// 退出房间
/// </summary>
/// <param name="userId"></param>
/// <returns></returns>
[NonAction]
private async Task OfflineRoom(string userId)
{
var roomId = _cache.Get<string>(CACHE_ROOM + userId);
if (!string.IsNullOrWhiteSpace(roomId))
{
await "https://aigc.ycymedu.com/api/proxyaigc?Name=stop&Action=StopVoiceChat&Version=2024-12-01".PostJsonAsync(new WeMinProOfficeDto()
{
AppId = "67e11a296ff39301ed7429aa",
RoomId = roomId,
TaskId = userId
});
_cache.Remove(CACHE_ROOM + userId);
}
}
public override async Task OnDisconnectedAsync(Exception exception)
{
var context = Context.GetHttpContext();
var token = context.Request.Query["access_token"];
var claims = JWTEncryption.ReadJwtToken(token)?.Claims;
var userIdStr = claims?.FirstOrDefault(c => c.Type == ClaimConst.UserId)?.Value;
if (!long.TryParse(userIdStr, out var userId)) return;
var connectionId = Context.ConnectionId;
_cache.Remove(CACHE_PREFIX + userId);
using (var scope = _scopeFactory.CreateScope())
{
var _onlineUserService = scope.ServiceProvider.GetRequiredService<SqlSugarRepository<SysOnlineUser>>();
var user = await _onlineUserService.GetFirstAsync(c => c.UserId == userId);
if (user != null)
{
var onlineSpan = (DateTime.Now - user.Time.GetValueOrDefault()).TotalSeconds;
user.TotalOnlineSeconds += (int)onlineSpan;
user.LastOfflineTime = DateTime.Now;
await _onlineUserService.UpdateAsync(user);
}
}
await base.OnDisconnectedAsync(exception);
}
/// <summary>
/// 强制下线用户
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
//public async Task ForceOffline(OnlineUserHubInput input)
//{
// var context = Context.GetHttpContext();
// var token = context.Request.Query["access_token"];
// var claims = JWTEncryption.ReadJwtToken(token)?.Claims;
// var userIdStr = claims?.FirstOrDefault(c => c.Type == ClaimConst.UserId)?.Value;
// if (!long.TryParse(userIdStr, out var userId)) return;
// _cache.Remove(CACHE_PREFIX + input.ConnectionId);
// using (var scope = _scopeFactory.CreateScope())
// {
// var _onlineUserService = scope.ServiceProvider.GetRequiredService<SqlSugarRepository<SysOnlineUser>>();
// var user = await _onlineUserService.GetFirstAsync(c => c.ConnectionId == input.ConnectionId);
// if (user != null)
// {
// user.LastOfflineTime = DateTime.Now;
// await _onlineUserService.UpdateAsync(user);
// }
// await OfflineRoom(user.UserId.ToString());
// await _hubContext.Clients.Client(input.ConnectionId).ForceOffline("已强制退出");
// }
//}
/// <summary>
/// 监控用户在线时长并提醒此处后面可将用户vip信息写入redis缓存
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
private async Task MonitorUserOnlineAsync(SysOnlineUser user)
{
var connectionId = user.ConnectionId;
var isVip = _cache.Get<bool?>("WxVIP_" + user.UserId) ?? false;
var dbWriteKey = "DbWrite_" + user.UserId;
var writeDbInterval = TimeSpan.FromMinutes(1); // 最小数据库写入间隔
var lastDbWrite = DateTime.Now;
var pingKey = $"{CACHE_PREFIX}{user.UserId}_ping";
DingDingHook.DingTalkHookMessage("weminHub", $"CACHE_PREFIX:{CACHE_PREFIX + user.UserId};ExistKey:{_cache.ExistKey(CACHE_PREFIX + user.UserId)}");
while (_cache.ExistKey(CACHE_PREFIX + user.UserId))
{
var cachedUser = _cache.Get<SysOnlineUser>(CACHE_PREFIX + user.UserId);
DingDingHook.DingTalkHookMessage("weminHub0", $"cachedUser:" + cachedUser.ToJson());
if (cachedUser == null) break;
using (var scope = _scopeFactory.CreateScope())
{
var _onlineUserService = scope.ServiceProvider.GetRequiredService<SqlSugarRepository<SysOnlineUser>>();
var userInDb = await _onlineUserService.GetFirstAsync(c => c.UserId == user.UserId);
if (userInDb == null) break;
// 检查心跳时间
var lastPing = _cache.Get<DateTime>(pingKey);
DingDingHook.DingTalkHookMessage("weminHub0", $"TotalSeconds:" + (DateTime.Now - lastPing).TotalSeconds);
if (lastPing != DateTime.MinValue && (DateTime.Now - lastPing).TotalSeconds > 10)
{
// 掉线处理
await _hubContext.Clients.Client(connectionId).ForceOffline("长时间未响应,系统自动下线");
_cache.Remove(CACHE_PREFIX + user.UserId);
_cache.Remove(pingKey);
// 计算本次在线时长
var currentOnlineSpan = (DateTime.Now - userInDb.Time.GetValueOrDefault()).TotalSeconds;
userInDb.LastOfflineTime = DateTime.Now;
// 更新总在线时长
userInDb.TotalOnlineSeconds += (int)currentOnlineSpan;
// 更新用户上线时间为当前时间,避免重复计算
userInDb.Time = DateTime.Now;
await _onlineUserService.UpdateAsync(userInDb);
await OfflineRoom(userInDb.UserId.ToString());
break;
}
// 计算本次新增的在线时长
var newOnlineSpan = (DateTime.Now - (userInDb.Time ?? DateTime.Now)).TotalSeconds;
cachedUser.TotalOnlineSeconds = userInDb.TotalOnlineSeconds + (int)newOnlineSpan;
cachedUser.Time = userInDb.Time;
// 先判断缓存项是否存在,不存在时才设置
if (!_cache.ExistKey(CACHE_PREFIX + user.UserId))
{
_cache.Set(CACHE_PREFIX + user.UserId, cachedUser, TimeSpan.FromHours(1));
}
var totalMinutes = cachedUser.TotalOnlineSeconds / 60.0;
int MAX_MINUTES = isVip ? 30 : 1;
if (totalMinutes >= MAX_MINUTES)
{
// DingDingHook.DingTalkHookMessage("weminHub", $"totalMinutes:" + totalMinutes);
await _hubContext.Clients.Client(connectionId).ForceOffline("您的体验时间已到,系统自动下线");
_cache.Remove(CACHE_PREFIX + user.UserId);
_cache.Remove(pingKey);
userInDb.LastOfflineTime = DateTime.Now;
userInDb.TotalOnlineSeconds = cachedUser.TotalOnlineSeconds;
userInDb.Time = DateTime.Now; // 更新时间
await _onlineUserService.UpdateAsync(userInDb);
break;
}
else if (totalMinutes >= WARN_MINUTES)
{
await _hubContext.Clients.Client(connectionId).SendWarn("您即将被系统下线!");
}
// 每5分钟写一次数据库
if ((DateTime.Now - lastDbWrite) >= writeDbInterval)
{
userInDb.TotalOnlineSeconds = cachedUser.TotalOnlineSeconds;
userInDb.Time = DateTime.Now; // 更新时间
await _onlineUserService.UpdateAsync(userInDb);
lastDbWrite = DateTime.Now;
}
}
// DingDingHook.DingTalkHookMessage("Ping", $"MonitorUserOnline:{Context.ConnectionId}|Ping:" + DateTime.Now.ToString("yyyyMMddHHmmssfff"));
await Task.Delay(TimeSpan.FromSeconds(5)); // 每5秒检查一次
}
}
/// <summary>
/// 心跳
/// 缓存中存在,则更新时间,否则删除
/// </summary>
/// <returns></returns>
public async Task Ping()
{
var context = Context.GetHttpContext();
var token = context.Request.Query["access_token"];
var claims = JWTEncryption.ReadJwtToken(token)?.Claims;
var userIdStr = claims?.FirstOrDefault(c => c.Type == ClaimConst.UserId)?.Value;
if (!long.TryParse(userIdStr, out var userId)) return;
// DingDingHook.DingTalkHookMessage("Ping", $"ConnectionId:{Context.ConnectionId}|Ping:" + DateTime.Now.ToString());
var key = $"{CACHE_PREFIX}{userId}_ping";
_cache.Set(key, DateTime.Now, TimeSpan.FromMinutes(2));
}
/// <summary>
/// 发送信息给小程序用户
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public async Task ClientsSendMessage(MinProMessageInput message)
{
await _hubContext.Clients.Client(message.ConnectionId).SendWarn(message.Content);
}
}
}