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
{
///
/// 小程序进入房间时间统计
///
[MapHub("/hubs/weminpro")]
public class WeMinProHub : Hub
{
private readonly IHubContext _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 hubContext,
SysCacheService cache,
IServiceScopeFactory scopeFactory)
{
_hubContext = hubContext;
_cache = cache;
_scopeFactory = scopeFactory;
}
///
/// 用户连接时,首次上线或重连
///
///
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(cacheKey);
if (cachedUser == null)
{
using var scope = _scopeFactory.CreateScope();
var _onlineUserService = scope.ServiceProvider.GetRequiredService>();
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>();
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();
}
///
/// 退出房间
///
///
///
[NonAction]
private async Task OfflineRoom(string userId)
{
var roomId = _cache.Get(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>();
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);
}
///
/// 强制下线用户
///
///
///
//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>();
// 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("已强制退出");
// }
//}
///
/// 监控用户在线时长并提醒(此处后面可将用户vip信息写入redis缓存)
///
///
///
private async Task MonitorUserOnlineAsync(SysOnlineUser user)
{
var connectionId = user.ConnectionId;
var isVip = _cache.Get("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(CACHE_PREFIX + user.UserId);
DingDingHook.DingTalkHookMessage("weminHub0", $"cachedUser:" + cachedUser.ToJson());
if (cachedUser == null) break;
using (var scope = _scopeFactory.CreateScope())
{
var _onlineUserService = scope.ServiceProvider.GetRequiredService>();
var userInDb = await _onlineUserService.GetFirstAsync(c => c.UserId == user.UserId);
if (userInDb == null) break;
// 检查心跳时间
var lastPing = _cache.Get(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秒检查一次
}
}
///
/// 心跳
/// 缓存中存在,则更新时间,否则删除
///
///
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));
}
///
/// 发送信息给小程序用户
///
///
///
public async Task ClientsSendMessage(MinProMessageInput message)
{
await _hubContext.Clients.Client(message.ConnectionId).SendWarn(message.Content);
}
}
}