using System.Net.WebSockets; using JNPF.Common.Configuration; using JNPF.Common.Const; using JNPF.Common.Enums; using JNPF.Common.Extension; using JNPF.Common.Manager; using JNPF.Common.Models.User; using JNPF.Common.Options; using JNPF.Common.Security; using JNPF.DataEncryption; using JNPF.Extras.WebSockets.Models; using JNPF.Message.Entitys; using JNPF.Message.Entitys.Dto.IM; using JNPF.Message.Entitys.Entity; using JNPF.Message.Entitys.Enums; using JNPF.Message.Entitys.Model.IM; using JNPF.RemoteRequest.Extensions; using JNPF.Systems.Entitys.Permission; using JNPF.WebSockets; using Mapster; using SqlSugar; namespace JNPF.Common.Core.Handlers; /// /// IM 处理程序. /// public class IMHandler : WebSocketHandler { /// /// SqlSugarClient客户端. /// private static SqlSugarScope? _sqlSugarClient; /// /// 缓存管理. /// private readonly ICacheManager _cacheManager; private readonly MessageOptions _messageOptions = App.GetConfig("Message", true); /// /// 初始化一个类型的新实例. /// public IMHandler( WebSocketConnectionManager webSocketConnectionManager, ISqlSugarClient sqlSugarClient, ICacheManager cacheManager) : base(webSocketConnectionManager) { _sqlSugarClient = (SqlSugarScope)sqlSugarClient; _cacheManager = cacheManager; } /// /// 消息接收. /// /// /// /// /// public override async Task ReceiveAsync(WebSocketClient client, WebSocketReceiveResult result, string receivedMessage) { try { MessageInput? message = receivedMessage.ToObject(); var claims = JWTEncryption.ReadJwtToken(message.token.Replace("Bearer ", string.Empty).Replace("bearer ", string.Empty))?.Claims; client.UserId = claims.FirstOrDefault(e => e.Type == ClaimConst.CLAINMUSERID)?.Value; client.ConnectionConfig = claims.FirstOrDefault(e => e.Type == ClaimConst.CONNECTIONCONFIG)?.Value.ToObject(); client.Account = claims.FirstOrDefault(e => e.Type == ClaimConst.CLAINMACCOUNT)?.Value; client.UserName = claims.FirstOrDefault(e => e.Type == ClaimConst.CLAINMREALNAME)?.Value; client.SingleLogin = (LoginMethod)Enum.Parse(typeof(LoginMethod), claims.FirstOrDefault(e => e.Type == ClaimConst.SINGLELOGIN)?.Value); client.LoginTime = string.Format("{0:yyyy-MM-dd HH:mm}", string.Format("{0}000", claims.FirstOrDefault(e => e.Type == "iat")?.Value).TimeStampToDateTime()); client.LoginIpAddress = client.LoginIpAddress; client.Token = message.token; client.IsMobileDevice = message.mobileDevice; if (client.WebSocket.State != WebSocketState.Open) return; await OnConnected(client.ConnectionId, client); WebSocketConnectionManager.AddToTenant(client.ConnectionId, client.ConnectionConfig?.ConfigId); WebSocketConnectionManager.AddToUser(client.ConnectionId, string.Format("{0}-{1}", client.ConnectionConfig?.ConfigId, client.UserId)); message.sendClientId = client.ConnectionId; await MessageRoute(message); } catch (Exception e) { } } /// /// 消息通道. /// /// private async Task MessageRoute(MessageInput message) { WebSocketClient client = WebSocketConnectionManager.GetSocketById(message.sendClientId); if (string.IsNullOrEmpty(client.UserId)) { await SendMessageAsync(client.ConnectionId, new { method = "logout" }.ToJsonString()); return; } // 判断ORM内是否存在该连接 if (!_sqlSugarClient.IsAnyConnection(client.ConnectionConfig.ConfigId)) { _sqlSugarClient.AddConnection(JNPFTenantExtensions.GetConfig(client.ConnectionConfig)); _sqlSugarClient.Ado.CommandTimeOut = 10; } // 当前数据连接ConfigId是否等于目标库ConfigId if (_sqlSugarClient.CurrentConnectionConfig.ConfigId != client.ConnectionConfig.ConfigId) { _sqlSugarClient.ChangeDatabase(client.ConnectionConfig.ConfigId); } // 验证连接是否成功 if (!_sqlSugarClient.Ado.IsValidConnection()) { await OnDisconnected(client.WebSocket); return; } if (string.IsNullOrEmpty(client.HeadIcon)) { UserEntity userEntity = await _sqlSugarClient.Queryable().SingleAsync(it => it.Id == client.UserId); if (userEntity != null) { client.HeadIcon = "/api/file/Image/userAvatar/" + userEntity.HeadIcon; await OnConnected(client.ConnectionId, client); } } switch (message.method) { // 建立连接 case MothodType.OnConnection: { List list = await GetOnlineUserList(client.ConnectionConfig.ConfigId); if (list == null) { list = new List(); } switch (client.SingleLogin) { case LoginMethod.Single: { UserOnlineModel? user = list.Find(it => it.userId.Equals(client.UserId) && it.isMobileDevice.Equals(client.IsMobileDevice)); if (user == null) { list.Add(new UserOnlineModel() { connectionId = client.ConnectionId, userId = client.UserId, account = client.Account, userName = client.UserName, lastTime = DateTime.Now, lastLoginIp = client.LoginIpAddress, tenantId = client.ConnectionConfig.ConfigId, lastLoginPlatForm = client.LoginPlatForm, isMobileDevice = client.IsMobileDevice, token = message.token }); await SetOnlineUserList(client.ConnectionConfig.ConfigId, list); } // 不同浏览器 else if (user != null && !user.token.Equals(message.token)) { var onlineUser = WebSocketConnectionManager.GetSocketById(user.connectionId); if (onlineUser != null) await SendMessageAsync(onlineUser.ConnectionId, new { method = MessageSendType.logout.ToString(), msg = "此账号已在其他地方登陆" }.ToJsonString()); list.RemoveAll((x) => x.connectionId == user.connectionId); list.Add(new UserOnlineModel() { connectionId = client.ConnectionId, userId = client.UserId, account = client.Account, userName = client.UserName, lastTime = DateTime.Now, lastLoginIp = client.LoginIpAddress, tenantId = client.ConnectionConfig.ConfigId, lastLoginPlatForm = client.LoginPlatForm, isMobileDevice = client.IsMobileDevice, token = message.token }); await SetOnlineUserList(client.ConnectionConfig.ConfigId, list); } } break; case LoginMethod.SameTime: { UserOnlineModel? user = list.Find(it => it.token.Equals(message.token)); if (user != null) { WebSocketClient? onlineUser = WebSocketConnectionManager.GetSocketById(user.connectionId); if (onlineUser != null) await SendMessageAsync(onlineUser.ConnectionId, new { method = MessageSendType.closeSocket.ToString() }.ToJsonString()); list.RemoveAll((x) => x.connectionId == user.connectionId); } list.Add(new UserOnlineModel() { connectionId = client.ConnectionId, userId = client.UserId, account = client.Account, userName = client.UserName, lastTime = DateTime.Now, lastLoginIp = client.LoginIpAddress, tenantId = client.ConnectionConfig.ConfigId, lastLoginPlatForm = client.LoginPlatForm, isMobileDevice = client.IsMobileDevice, token = message.token }); await SetOnlineUserList(client.ConnectionConfig.ConfigId, list); } break; } var onlineUserList = GetAllUserIdFromTenant(client.ConnectionConfig.ConfigId); // 获取接收者为当前用户的聊天且未读的信息 var imContentList = _sqlSugarClient.Queryable().Where(x => x.ReceiveUserId.Equals(client.UserId) && x.State.Equals(0) && (SqlFunc.IsNullOrEmpty(x.SendDeleteMark) || x.SendDeleteMark != client.UserId)).GroupBy(x => new { x.SendUserId, x.ReceiveUserId }).Select(x => new IMContentEntity { State = SqlFunc.AggregateSum(SqlFunc.IIF(x.State == 0, 1, 0)), SendUserId = x.SendUserId, ReceiveUserId = x.ReceiveUserId }).ToList(); var receiveList = _sqlSugarClient.Queryable().Where(x => x.ReceiveUserId == client.UserId && (SqlFunc.IsNullOrEmpty(x.SendDeleteMark) || x.SendDeleteMark != client.UserId)).OrderBy(x => x.SendTime, OrderByType.Desc).ToList(); var unreadNums = imContentList.Adapt>(); foreach (var item in unreadNums) { var entity = receiveList.FirstOrDefault(x => x.SendUserId == item.sendUserId); item.defaultMessage = entity?.Content; item.defaultMessageType = entity?.ContentType; item.defaultMessageTime = entity?.SendTime.ToString(); } var unreadNoticeCount = await _sqlSugarClient.Queryable((m, mr) => new JoinQueryInfos(JoinType.Left, m.Id == mr.MessageId)).Where((m, mr) => m.Type == 1 && m.DeleteMark == null && mr.UserId == client.UserId && mr.IsRead == 0).Select((m, mr) => new { mr.Id, mr.UserId, mr.IsRead, m.Type, m.DeleteMark }).CountAsync(); var unreadMessageCount = await _sqlSugarClient.Queryable((m, mr) => new JoinQueryInfos(JoinType.Left, m.Id == mr.MessageId)).Select((m, mr) => new { mr.Id, mr.UserId, mr.IsRead, m.Type, m.DeleteMark }).MergeTable().Where(x => x.Type == 2 && x.DeleteMark == null && x.UserId == client.UserId && x.IsRead == 0).CountAsync(); var messageDefault = await _sqlSugarClient.Queryable().Where(x => x.DeleteMark == null && x.EnabledMark == 1).OrderBy(x => x.CreatorTime, OrderByType.Desc).FirstAsync(); var messageDefaultText = messageDefault == null ? string.Empty : messageDefault.Title; var messageDefaultTime = messageDefault == null ? DateTime.Now : messageDefault.CreatorTime; await SendMessageAsync(client.ConnectionId, new { method = MessageSendType.initMessage.ToString(), onlineUserList, unreadNums, unreadNoticeCount, unreadMessageCount, messageDefaultText, messageDefaultTime }.ToJsonString()); await SendMessageToTenantAsync(client.ConnectionConfig.ConfigId, new { method = MessageSendType.online.ToString(), userId = client.UserId }.ToJsonString(), client.ConnectionId); } break; // 发送消息 case MothodType.SendMessage: { string toUserId = message.toUserId; MessageReceiveType messageType = message.messageType; object messageContent = message.messageContent; string fileName = string.Empty; var toUserEntity = await _sqlSugarClient.Queryable().SingleAsync(it => it.Id == toUserId); // 将发送消息对象信息补全 var toAccount = toUserEntity.Account; var toHeadIcon = toUserEntity.HeadIcon; var toRealName = toUserEntity.RealName; var entity = new IMContentEntity(); var toMessage = new object(); switch (messageType) { case MessageReceiveType.text: entity = CreateIMContent(client.UserId, toUserId, messageContent.ToString(), messageType.ToString()); break; case MessageReceiveType.image: { var directoryPath = FileVariable.IMContentFilePath; if (!Directory.Exists(directoryPath)) Directory.CreateDirectory(directoryPath); var imageInput = messageContent.ToObject(); fileName = fileName = imageInput.name; toMessage = new { path = "/api/file/Image/IM/" + fileName, width = imageInput.width, height = imageInput.height }; entity = CreateIMContent(client.UserId, toUserId, toMessage.ToJsonString(), messageType.ToString()); } break; case MessageReceiveType.voice: var voiceInput = messageContent.ToObject(); toMessage = new { path = "/api/file/Image/IM/" + voiceInput.name, length = voiceInput.length }; entity = CreateIMContent(client.UserId, toUserId, toMessage.ToJsonString(), messageType.ToString()); break; } // 写入到会话表中 if (await _sqlSugarClient.Queryable().AnyAsync(it => it.UserId == client.UserId && it.ReceiveUserId == toUserId)) { var imReplyEntity = await _sqlSugarClient.Queryable().SingleAsync(it => it.UserId == client.UserId && it.ReceiveUserId == toUserId); imReplyEntity.ReceiveTime = entity.SendTime; await _sqlSugarClient.Updateable(imReplyEntity).ExecuteCommandAsync(); } else { var imReplyEntity = new ImReplyEntity() { Id = SnowflakeIdHelper.NextId(), UserId = client.UserId, ReceiveUserId = toUserId, ReceiveTime = entity.SendTime }; await _sqlSugarClient.Insertable(imReplyEntity).ExecuteCommandAsync(); } await _sqlSugarClient.Insertable(entity).ExecuteCommandAsync(); switch (messageType) { case MessageReceiveType.text: await SendMessageAsync(client.ConnectionId, new { method = MessageSendType.sendMessage.ToString(), client.UserId, account = client.Account, headIcon = client.HeadIcon, realName = client.UserName, toAccount, toHeadIcon, messageType = messageType.ToString(), toUserId, toRealName, toMessage = messageContent, dateTime = DateTime.Now, latestDate = DateTime.Now }.ToJsonString()); break; case MessageReceiveType.image: var imageInput = messageContent.ToObject(); toMessage = new { path = "/api/file/Image/IM/" + fileName, width = imageInput.width, height = imageInput.height }; await SendMessageAsync(client.ConnectionId, new { method = MessageSendType.sendMessage.ToString(), client.UserId, account = client.Account, headIcon = client.HeadIcon, realName = client.UserName, toAccount, toHeadIcon, messageType = messageType.ToString(), toUserId, toMessage, dateTime = DateTime.Now, latestDate = DateTime.Now }.ToJsonString()); break; case MessageReceiveType.voice: var voiceInput = messageContent.ToObject(); toMessage = new { path = "/api/file/Image/IM/" + voiceInput.name, length = voiceInput.length }; await SendMessageAsync(client.ConnectionId, new { method = MessageSendType.sendMessage.ToString(), client.UserId, account = client.Account, headIcon = client.HeadIcon, realName = client.UserName, toAccount, toHeadIcon, messageType = messageType.ToString(), toUserId, toMessage, dateTime = DateTime.Now }.ToJsonString()); break; } if (WebSocketConnectionManager.GetSocketClientToUserCount(string.Format("{0}-{1}", client.ConnectionConfig.ConfigId, toUserId)) > 0) { switch (messageType) { case MessageReceiveType.text: await SendMessageToUserAsync(string.Format("{0}-{1}", client.ConnectionConfig.ConfigId, toUserId), new { method = MessageSendType.receiveMessage.ToString(), messageType = messageType.ToString(), formUserId = client.UserId, formMessage = messageContent, dateTime = DateTime.Now, latestDate = DateTime.Now, headIcon = client.HeadIcon, realName = client.UserName, account = client.Account }.ToJsonString()); break; case MessageReceiveType.image: var imageInput = messageContent.ToObject(); var formMessage = new { path = "/api/file/Image/IM/" + fileName, width = imageInput.width, height = imageInput.height }; await SendMessageToUserAsync(string.Format("{0}-{1}", client.ConnectionConfig.ConfigId, toUserId), new { method = MessageSendType.receiveMessage.ToString(), messageType = messageType.ToString(), formUserId = client.UserId, formMessage, dateTime = DateTime.Now, latestDate = DateTime.Now, headIcon = client.HeadIcon, realName = client.UserName, account = client.Account }.ToJsonString()); break; case MessageReceiveType.voice: var voiceInput = messageContent.ToObject(); toMessage = new { path = "/api/file/Image/IM/" + voiceInput.name, length = voiceInput.length }; await SendMessageToUserAsync(string.Format("{0}-{1}", client.ConnectionConfig.ConfigId, toUserId), new { method = MessageSendType.receiveMessage.ToString(), messageType = messageType.ToString(), formUserId = client.UserId, formMessage = toMessage, dateTime = DateTime.Now, latestDate = DateTime.Now, headIcon = client.HeadIcon, realName = client.UserName, account = client.Account }.ToJsonString()); break; } } await GeTuiMessage(toUserId, client); } break; case MothodType.UpdateReadMessage: var fromUserId = message.formUserId; await _sqlSugarClient.Updateable() .SetColumns(x => new IMContentEntity() { State = 1, ReceiveTime = DateTime.Now }).Where(x => x.State == 0 && x.SendUserId == fromUserId && x.ReceiveUserId == client.UserId).ExecuteCommandAsync(); break; case MothodType.MessageList: var sendUserId = message.toUserId; // 发送者 var receiveUserId = message.formUserId; // 接收者 var data = await _sqlSugarClient.Queryable().WhereIF(!string.IsNullOrEmpty(message.keyword), it => it.Content.Contains(message.keyword)) .Where(i => (i.SendUserId == message.toUserId && i.ReceiveUserId == message.formUserId) || (i.SendUserId == message.formUserId && i.ReceiveUserId == message.toUserId) && (SqlFunc.IsNullOrEmpty(i.SendDeleteMark) || i.SendDeleteMark != client.UserId)).OrderBy(it => it.SendTime, message.sord == "asc" ? OrderByType.Asc : OrderByType.Desc) .Select(it => new IMContentListOutput { id = it.Id, sendUserId = it.SendUserId, sendTime = it.SendTime, receiveUserId = it.ReceiveUserId, receiveTime = it.ReceiveTime, content = it.Content, contentType = it.ContentType, state = it.State }).ToPagedListAsync(message.currentPage, message.pageSize); await SendMessageAsync(client.ConnectionId, new { method = MessageSendType.messageList.ToString(), list = data.list.OrderBy(x => x.sendTime), pagination = data.pagination }.ToJsonString()); break; case MothodType.HeartCheck: break; } } /// /// 获取在线用户列表. /// /// 租户ID. /// public async Task> GetOnlineUserList(string tenantId) { string cacheKey = string.Format("{0}{1}", CommonConst.CACHEKEYONLINEUSER, tenantId); return await _cacheManager.GetAsync>(cacheKey); } /// /// 保存在线用户列表. /// /// 租户ID. /// 在线用户列表. /// public async Task SetOnlineUserList(string tenantId, List onlineList) { return await _cacheManager.SetAsync(string.Format("{0}{1}", CommonConst.CACHEKEYONLINEUSER, tenantId), onlineList); } /// /// 创建IM内容. /// /// private IMContentEntity CreateIMContent(string sendUserId, string receiveUserId, string message, string messageType) { return new IMContentEntity() { Id = SnowflakeIdHelper.NextId(), SendUserId = sendUserId, SendTime = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")), ReceiveUserId = receiveUserId, State = 0, Content = message, ContentType = messageType }; } private async Task GeTuiMessage(string toUserIds, WebSocketClient client) { var getuiUrl = "{0}?clientId={1}&title={2}&content={3}1&text={4}&create=true"; if (toUserIds.Any()) { var clientIdList = await _sqlSugarClient.Queryable().Where(x => toUserIds==x.UserId && x.DeleteMark == null).Select(x => x.ClientId).ToListAsync(); if (clientIdList.Any()) { var clientId = string.Join(",", clientIdList); var textDic = new Dictionary(); textDic.Add("type", "3"); textDic.Add("name", client.UserName+"/"+ client.Account); textDic.Add("formUserId", client.UserId); textDic.Add("headIcon", "/api/File/Image/userAvatar/" + client.HeadIcon); getuiUrl = string.Format(getuiUrl, _messageOptions.AppPushUrl, clientId, client.UserName + "/" + client.Account, "您有一条新消息", textDic.ToJsonString()); await getuiUrl.GetAsStringAsync(); } } } }