/// <summary> /// 获取历史聊天记录 /// </summary> /// <param name="roomHashid">房间哈希ID</param> /// <param name="entryTime">进入房间的时间</param> /// <param name="startIndex">此次获取的聊天历史记录的开始序号</param> /// <returns>表示获取聊天记录的任务</returns> public async Task <List <ChatHistoryDto> > GetChatHistoryAsync(string roomHashid, long entryTime, int startIndex) { var roomId = HashidsHelper.Decode(roomHashid); var query = await _dbContext.ChatHistory .Where(msg => msg.RoomId == roomId && msg.UnixTimeMilliseconds < entryTime) .OrderByDescending(msg => msg.UnixTimeMilliseconds) .Skip(startIndex) .Take(20) .ToListAsync(); var history = new List <ChatHistoryDto>(); foreach (var msg in query) { var data = msg.Message; if (msg.IsPicture.Value) { var path = Path.Combine(_picturesDirectory, msg.RoomId.ToString(), "thumbnails", $"{msg.UserId}_{msg.UnixTimeMilliseconds}.jpg"); data = Convert.ToBase64String(await File.ReadAllBytesAsync(path)); } history.Add(new ChatHistoryDto { UserId = HashidsHelper.Encode(msg.UserId), Username = msg.Username, Data = data, Timestamp = msg.UnixTimeMilliseconds, IsPicture = msg.IsPicture.Value }); } return(history); }
/// <summary> /// 获取用户上一次登录时所在的房间ID /// </summary> /// <param name="uid">用户ID</param> /// <returns>表示获取用户上一次登录时所在的房间ID的任务</returns> public async Task <string> GetPreviousRoomIdAsync(int uid) { var connection = await _dbContext.Connection .Where(conn => conn.UserId == uid && !conn.IsDeleted.Value) .FirstOrDefaultAsync(); // 连接信息没找到 if (connection == null) { return(null); } var room = await _dbContext.ChatRoom.FindAsync(connection.RoomId); if (room == null) { // 如果房间没找到,但连接信息却找到了,说明之前的数据有问题 // 删除连接信息 _dbContext.Remove(connection); await _dbContext.SaveChangesAsync(); return(null); } return(HashidsHelper.Encode(connection.RoomId)); }
/// <summary> /// 创建房间 /// </summary> /// <param name="uid">用户ID</param> /// <param name="roomDto">用户输入的用于创建房间的信息</param> /// <returns>表示异步创建房间的任务,如果创建失败则返回错误信息</returns> public async Task <ChatRoomCreateResponseDto> CreateRoomAsync(int uid, ChatRoomDto roomDto) { // 防止用户打开多个窗口创建房间 var error = await ApplyForCreatingRoomAsync(uid); if (!string.IsNullOrEmpty(error)) { return(new ChatRoomCreateResponseDto { Error = error, CloseModalIfError = true }); } try { var room = new ChatRoom { OwnerId = uid, Name = roomDto.Name, MaxUsers = roomDto.MaxUsers, IsEncrypted = roomDto.IsEncrypted, IsPermanent = roomDto.IsPermanent, IsHidden = roomDto.IsHidden, AllowGuest = roomDto.AllowGuest }; // 如果房间被加密 if (roomDto.IsEncrypted) { Guid salt = Guid.NewGuid(); room.Salt = salt.ToString(); room.PasswordHash = PasswordHelper.GeneratePasswordHash(roomDto.Password, room.Salt); } _dbContext.ChatRoom.Add(room); await _dbContext.SaveChangesAsync(); return(new ChatRoomCreateResponseDto { RoomId = HashidsHelper.Encode(room.Id) }); } catch (Exception) { // 因为是多线程,任然可能发生异常 // 房间名重复 return(new ChatRoomCreateResponseDto { Error = _msg.GetMessage("E003", "房间名"), CloseModalIfError = false }); } }
/// <summary> /// 作为游客登录 /// </summary> /// <returns>异步获取Token的任务</returns> public async Task <AccessTokenResponseDto> LoginAsGuestAsync() { var guestId = Convert.ToInt32(DateTime.Now.ToString("ddHHmmss") + new Random().Next(0, 9)); var username = $"游客{HttpUtility.UrlDecode(HashidsHelper.Encode(guestId))}"; var user = new User { Id = guestId, Username = username, RoleId = (int)Roles.Guest }; return(new AccessTokenResponseDto { AccessToken = await _tokenAuthService.GenerateAccessTokenAsync(user), RefreshToken = await _tokenAuthService.GenerateRefreshTokenAsync(user) }); }
/// <summary> /// 刷新房间成员列表信息 /// </summary> /// <param name="roomHashid">房间哈希ID</param> /// <param name="roomId">房间ID</param> /// <returns>表示刷新房间成员列表信息的任务</returns> private async Task RefreshMemberListAsync(string roomHashid, int roomId) { var ownerId = await _dbContext.ChatRoom .Where(room => room.Id == roomId) .Select(room => room.OwnerId).FirstOrDefaultAsync(); // order by is_owner desc, is_online desc, create_time var list = await _dbContext.Connection .Where(conn => conn.RoomId == roomId) .OrderBy(conn => conn.CreateTime) .Select(conn => new ChatRoomMemberDto { UserId = HashidsHelper.Encode(conn.UserId), Username = conn.Username, IsOnline = conn.IsOnline.Value, IsOwner = conn.UserId == ownerId }) .OrderByDescending(x => x.IsOwner) .ThenByDescending(x => x.IsOnline) .ToListAsync(); await Clients.Group(roomHashid).InvokeAsync("refreshMemberList", list); }
/// <summary> /// 失去连接 /// </summary> /// <param name="exception">异常信息</param> /// <returns>表示处理失去连接的任务</returns> public async override Task OnDisconnectedAsync(Exception exception) { var connenction = await _dbContext .Connection.FirstOrDefaultAsync(conn => conn.ConnectionId == Context.ConnectionId); // 如果用户开了多个窗口的话,这边可能会出问题 // 或者用户被移出房间 if (connenction == null) { return; } var roomHashid = HashidsHelper.Encode(connenction.RoomId); // 检索房主ID var ownerId = await _dbContext.ChatRoom .Where(r => r.Id == connenction.RoomId) .Select(r => r.OwnerId) .FirstOrDefaultAsync(); // 如果当前房间已经被删除,ownerId会得到0 // 或者该连接被标记为删除,即用户退出聊天室 if (ownerId == 0 || connenction.IsDeleted.Value) { // 删除连接信息 _dbContext.Connection.Remove(connenction); } else { // 以下为用户失去连接的情况 // 消息ID string msgId; Roles userRole = (Roles)Convert.ToInt32(Context.User.FindFirst(ClaimTypes.Role).Value); // 如果是游客,直接删除链接信息 if (userRole == Roles.Guest) { // 游客直接通知离开房间 msgId = "I004"; _dbContext.Connection.Remove(connenction); } else { // 通知同一房间里其他人该用户已经离线的ID msgId = "I002"; connenction.IsOnline = false; _dbContext.Update(connenction); } // 通知同一房间里其他人该用户已经离线或游客离开房间 await Clients.Group(roomHashid).InvokeAsync( "receiveSystemMessage", _msg.GetMessage(msgId, HttpUtility.UrlDecode(Context.User.Identity.Name))); } await _dbContext.SaveChangesAsync(); // 刷新用户列表 await RefreshMemberListAsync(roomHashid, connenction.RoomId); // 从分组中删除当前连接 // 注意 移除必须是在最后做,否则会报错 await Groups.RemoveAsync(Context.ConnectionId, roomHashid); await base.OnDisconnectedAsync(exception); }
/// <summary> /// 加入房间 /// </summary> /// <param name="roomHashid">房间哈希ID</param> /// <returns>表示加入房间的任务,返回房间名和加入该房间的时间</returns> public async Task <ChatRoomInitialDisplayDto> JoinRoomAsync(string roomHashid) { var roomId = HashidsHelper.Decode(roomHashid); var userId = HashidsHelper.Decode(Context.User.FindFirst("uid").Value); var username = HttpUtility.UrlDecode(Context.User.Identity.Name); // 因为用户可能会尝试用一个账号同时登陆两个房间 // 这里查询条件不加上房间ID,前一个房间内的用户会被提示在别处登录 var connection = await _dbContext.Connection .FirstOrDefaultAsync(x => x.UserId == userId); string msgId = null; string connIdToBeRemoved = null; if (connection == null) { // 第一次进入该房间 msgId = "I001"; _dbContext.Add(new Connection { RoomId = roomId, UserId = userId, Username = username, ConnectionId = Context.ConnectionId }); } else { // 如果用户同时打开两个窗口 if (connection.IsOnline.Value) { connIdToBeRemoved = connection.ConnectionId; } // 重连的情况下 msgId = "I003"; connection.IsOnline = true; connection.ConnectionId = Context.ConnectionId; // 只保留一条记录 _dbContext.Update(connection); } await _dbContext.SaveChangesAsync(); // 将用户添加到分组 await Groups.AddAsync(Context.ConnectionId, roomHashid); // 只加载加入房间前的消息,避免消息重复显示 long unixTimeMilliseconds = new DateTimeOffset(DateTime.Now).ToUnixTimeMilliseconds(); // 显示欢迎用户加入房间的消息 await Clients.Group(roomHashid).InvokeAsync( "receiveSystemMessage", _msg.GetMessage(msgId, username)); if (!string.IsNullOrEmpty(connIdToBeRemoved)) { // 前一个窗口显示消息,告知账号已经在其他地方登陆 await Clients.Client(connIdToBeRemoved).InvokeAsync("onDuplicateLogin"); await Groups.RemoveAsync(connIdToBeRemoved, roomHashid); } // 显示用户列表 await RefreshMemberListAsync(roomHashid, roomId); var room = await _dbContext.ChatRoom .Where(r => r.Id == roomId) .Select(r => new { r.Name, r.OwnerId }) .FirstOrDefaultAsync(); return(new ChatRoomInitialDisplayDto { OwnerId = HashidsHelper.Encode(room.OwnerId), RoomName = room.Name, EntryTime = unixTimeMilliseconds }); }
/// <summary> /// 获取房间列表 /// </summary> /// <param name="keyword">关键词</param> /// <param name="page">页码</param> /// <param name="uid">用户ID</param> /// <param name="role">用户角色</param> /// <returns>表示异步获取房间列表的任务,如果创建失败则返回错误信息</returns> public async Task <ChatRoomSearchResponseDto> GetRoomList(string keyword, int page, int uid, Roles role) { // 房主和管理员可以像正常房间一样检索隐藏房间 IQueryable <ChatRoom> query = null; if (string.IsNullOrEmpty(keyword)) { query = from room in _dbContext.ChatRoom where !room.IsHidden.Value || room.OwnerId == uid || role == Roles.Admin select room; } else if (role == Roles.Admin) { query = from room in _dbContext.ChatRoom where room.Name.Contains(keyword) || room.Owner.Username.Contains(keyword) select room; } else { // 因为用三元表达式会报错,所以这里暂时这么写 query = from room in _dbContext.ChatRoom where ((room.IsHidden.Value && room.OwnerId != uid) && room.Name == keyword) || ((!room.IsHidden.Value || room.OwnerId == uid) && (room.Name.Contains(keyword) || room.Owner.Username.Contains(keyword))) select room; } int count = await query.CountAsync(); int totalPages = (int)Math.Ceiling(((decimal)count / 10)); page = Math.Min(page, totalPages); ChatRoomSearchResponseDto chatRoomListDto = new ChatRoomSearchResponseDto(); // 小于0的判断是为了防止不正当数据 if (page <= 0) { return(chatRoomListDto); } chatRoomListDto.ChatRoomList = await query .OrderByDescending(room => room.CreateTime) .Skip((page - 1) * 10) .Take(10) .Select(room => new ChatRoomDto { Id = HashidsHelper.Encode(room.Id), Name = room.Name, MaxUsers = room.MaxUsers, CurrentUsers = room.CurrentUsers, OwnerName = room.Owner.Username, IsEncrypted = room.IsEncrypted.Value, IsHidden = room.IsHidden.Value, AllowGuest = room.AllowGuest.Value, CreateTime = new DateTimeOffset(room.CreateTime).ToUnixTimeMilliseconds() }).ToListAsync(); chatRoomListDto.Pagination = new PaginationDto { CurrentPage = page, TotalPages = totalPages, TotalItems = count }; return(chatRoomListDto); }