441 lines
17 KiB
C#
441 lines
17 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Identity.Data;
|
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
using Microsoft.AspNetCore.SignalR;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Caching.Memory;
|
|
using StackExchange.Redis;
|
|
using System.Diagnostics;
|
|
using System.Security.Cryptography.Xml;
|
|
using WebAppServer1.ApplicationDbContext;
|
|
using WebAppServer1.Authentication;
|
|
using WebAppServer1.Models;
|
|
using WebAppServer1.Tool;
|
|
|
|
namespace WebAppServer1.Chat
|
|
{
|
|
public class Chat : Hub
|
|
{
|
|
|
|
private readonly ILogger<Chat> logger;
|
|
private readonly IConnectionMultiplexer _redis;
|
|
private readonly AppDbContext pgSql;
|
|
private readonly TokenService jwtService;
|
|
|
|
|
|
public Chat(ILogger<Chat> _logger, IConnectionMultiplexer redis, AppDbContext _pgsql,TokenService _jwtService)
|
|
{
|
|
logger = _logger;
|
|
_redis = redis;
|
|
pgSql = _pgsql;
|
|
jwtService = _jwtService;
|
|
}
|
|
|
|
//wss子端点API
|
|
|
|
//在线事件
|
|
public override async Task OnConnectedAsync()
|
|
{
|
|
var db = _redis.GetDatabase();
|
|
await db.SetAddAsync("AllConnections", Context.ConnectionId);
|
|
await BroadcastOnlineCount();
|
|
Console.WriteLine("在线id:"+Context.UserIdentifier);
|
|
if (Context.UserIdentifier != null)
|
|
{
|
|
int userId = int.Parse(Context.UserIdentifier!);
|
|
var update = await pgSql.Users.FindAsync(userId);
|
|
if (update != null)
|
|
{
|
|
update.IsOnline = true;
|
|
pgSql.Update(update);
|
|
await pgSql.SaveChangesAsync();
|
|
}
|
|
List<int> friends = await pgSql.Friends.Where(f => f.Status == FriendStatus.Accepted &&
|
|
(f.UserId == userId || f.FriendId == userId)).Select
|
|
(f => f.UserId == userId ? f.FriendId : f.UserId).ToListAsync();
|
|
var friendIds = friends.Select(id => id.ToString()).ToList();
|
|
await Clients.Users(friendIds).SendAsync("UserStatusChanged", userId, true);
|
|
}
|
|
await base.OnConnectedAsync();
|
|
}
|
|
|
|
|
|
//掉线事件
|
|
public override async Task OnDisconnectedAsync(Exception? exception)
|
|
{
|
|
var db = _redis.GetDatabase();
|
|
await db.SetRemoveAsync("AllConnections", Context.ConnectionId);
|
|
// 遍历所有群组,移除该连接
|
|
var server = _redis.GetServer(_redis.GetEndPoints().First());
|
|
foreach (var key in server.Keys(pattern: "Group:*"))
|
|
{
|
|
await db.SetRemoveAsync(key, Context.ConnectionId);
|
|
var groupName = key.ToString().Replace("Group:", "");
|
|
await BroadcastGroupCount(groupName);
|
|
}
|
|
await BroadcastOnlineCount();
|
|
|
|
Console.WriteLine("掉线id:" + Context.UserIdentifier);
|
|
if (Context.UserIdentifier != null)
|
|
{
|
|
int userId = int.Parse(Context.UserIdentifier!);
|
|
var update = await pgSql.Users.FindAsync(userId);
|
|
if (update != null)
|
|
{
|
|
update.IsOnline = false;
|
|
pgSql.Update(update);
|
|
await pgSql.SaveChangesAsync();
|
|
}
|
|
List<int> friends = await pgSql.Friends.Where(f => f.Status == FriendStatus.Accepted &&
|
|
(f.UserId == userId || f.FriendId == userId)).Select
|
|
(f => f.UserId == userId ? f.FriendId : f.UserId).ToListAsync();
|
|
var friendIds = friends.Select(id => id.ToString()).ToList();
|
|
await Clients.Users(friendIds).SendAsync("UserStatusChanged", userId, false);
|
|
}
|
|
|
|
|
|
await base.OnDisconnectedAsync(exception);
|
|
}
|
|
|
|
//简单广播消息
|
|
[Authorize]
|
|
public async Task SendMessage(string user, string message)
|
|
{
|
|
logger.LogWarning("广播消息");
|
|
//测试发送
|
|
var exuser = Context.User?.Identity?.Name ?? "匿名";
|
|
await Clients.All.SendAsync("ReceiveMessage", exuser, message);
|
|
}
|
|
|
|
//群聊分组消息
|
|
public async Task JoinGroup(string groupName)
|
|
{
|
|
var db = _redis.GetDatabase();
|
|
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
|
|
await db.SetAddAsync($"Group:{groupName}", Context.ConnectionId);
|
|
await BroadcastGroupCount(groupName);
|
|
|
|
logger.LogWarning($"用户: {Context.ConnectionId}加入了{groupName}群组!");
|
|
await Clients.Group(groupName).SendAsync("ReceiveMessage", groupName, "系统", $"{Context.ConnectionId} 加入了群组");
|
|
}
|
|
|
|
public async Task LeaveGroup(string groupName)
|
|
{
|
|
var db = _redis.GetDatabase();
|
|
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
|
|
await db.SetRemoveAsync($"Group:{groupName}", Context.ConnectionId);
|
|
await BroadcastGroupCount(groupName);
|
|
logger.LogInformation($"用户{Context.ConnectionId}---离开了---{groupName}群组");
|
|
await Clients.Group(groupName).SendAsync("ReceiveMessage",groupName, "系统", $"{Context.ConnectionId} 离开了群聊 {groupName}");
|
|
}
|
|
[Authorize]
|
|
public async Task SendMessageToGroup(string groupName, string user, string message)
|
|
{
|
|
logger.LogInformation($"用户{user}发来的的消息:{message}");
|
|
await Clients.Group(groupName).SendAsync("ReceiveMessage", groupName, user, message);
|
|
}
|
|
|
|
//获取聊天记录
|
|
[Authorize]
|
|
public async Task<List<ChatMessageDto>> GetChatRecord(int friendId)
|
|
{
|
|
var exfriendId = await pgSql.Users.FindAsync(friendId);
|
|
if (exfriendId == null) return new List<ChatMessageDto>();
|
|
//if (Context.UserIdentifier == null)
|
|
var userId = int.Parse(Context.UserIdentifier!);
|
|
return await pgSql.Messages
|
|
.Where(a => (a.SenderId == userId && a.ReceiverId == friendId) ||
|
|
(a.SenderId == friendId && a.ReceiverId == userId))
|
|
.OrderBy(f => f.Id)
|
|
.Select(f => new ChatMessageDto
|
|
{
|
|
MessageId = f.Id,
|
|
Message = f.Content,
|
|
IsRead = f.IsRead,
|
|
SenderId = f.SenderId,
|
|
ReceiverId = f.ReceiverId,
|
|
IsMine = f.SenderId == userId
|
|
})
|
|
.ToListAsync();
|
|
}
|
|
|
|
|
|
//发送已读回执
|
|
public async Task SendReadReceipt(int messageId)
|
|
{
|
|
var update = await pgSql.Messages.FindAsync(messageId);
|
|
if (update == null) return;
|
|
|
|
update.IsRead = true;
|
|
pgSql.Messages.Update(update);
|
|
await pgSql.SaveChangesAsync();
|
|
|
|
// 通知消息发送者该消息已读
|
|
await Clients.User(update.SenderId.ToString())
|
|
.SendAsync("SendReadReceipt", messageId);
|
|
}
|
|
|
|
|
|
//发送私聊消息
|
|
[Authorize]
|
|
public async Task SendPrivateMessage(string friendId, string message)
|
|
{
|
|
|
|
int intFriendId = int.Parse(friendId);
|
|
int intUserId = int.Parse(Context.UserIdentifier!);
|
|
var receiver = await pgSql.Users.FindAsync(intFriendId);
|
|
if (receiver == null) return;
|
|
var sender = await pgSql.Users.FindAsync(intUserId);
|
|
if (sender == null) return;
|
|
bool isfriend = await pgSql.Friends.Where
|
|
(a => (a.UserId == intUserId && a.FriendId == intFriendId &&a.Status == FriendStatus.Accepted) ||
|
|
(a.UserId == intFriendId && a.FriendId == intUserId && a.Status == FriendStatus.Accepted)).AnyAsync();
|
|
if (!isfriend)
|
|
{
|
|
await Clients.Caller.SendAsync("SendPrivateMessage", new ChatMessageDto
|
|
{
|
|
IsMine = true,
|
|
Message = "双方不是朋友关系,暂时无法发送消息,请添加好友后再发送消息!",
|
|
SenderId = intUserId,
|
|
ReceiverId = receiver.Id
|
|
});
|
|
return;
|
|
}
|
|
|
|
var newMessage = new Message
|
|
{
|
|
SenderId = intUserId,
|
|
ReceiverId = receiver.Id,
|
|
Content = message,
|
|
CreatedAt = DateTime.UtcNow,
|
|
MessageType = MessageType.Text,
|
|
IsDeleted = false,
|
|
IsRead = false
|
|
};
|
|
pgSql.Messages.Add(newMessage);
|
|
await pgSql.SaveChangesAsync();
|
|
|
|
// 发给接收者
|
|
await Clients.User(friendId).SendAsync("SendPrivateMessage",new ChatMessageDto
|
|
{
|
|
IsMine = false,
|
|
Message = message,
|
|
SenderId = intUserId,
|
|
ReceiverId = receiver.Id,
|
|
MessageId = newMessage.Id,
|
|
Nickname = sender.Nickname,
|
|
AvatarUrl = sender.AvatarUrl,
|
|
});
|
|
|
|
// 同时回给发送者,保证前端立即显示
|
|
await Clients.Caller.SendAsync("SendPrivateMessage", new ChatMessageDto
|
|
{
|
|
IsMine = true,
|
|
Message = message,
|
|
SenderId = int.Parse(Context.UserIdentifier!),
|
|
ReceiverId = receiver.Id,
|
|
MessageId = newMessage.Id,
|
|
Nickname = receiver.Nickname,
|
|
AvatarUrl = receiver.AvatarUrl
|
|
});
|
|
}
|
|
|
|
|
|
//添加好友申请
|
|
public async Task<bool> AddFriend(int userId)
|
|
{
|
|
var senderId = int.Parse(Context.UserIdentifier!);
|
|
var exuser = await pgSql.Friends.Where(a => a.UserId == userId && a.FriendId == senderId ||
|
|
(a.FriendId == userId && a.UserId == senderId))
|
|
.AnyAsync();
|
|
Console.WriteLine("测试结果:"+exuser);
|
|
if (exuser) return false;
|
|
pgSql.Friends.Add(new Friend
|
|
{
|
|
UserId = senderId,
|
|
FriendId = userId,
|
|
CreatedAt = DateTime.UtcNow,
|
|
Status = FriendStatus.Accepted
|
|
});
|
|
await pgSql.SaveChangesAsync();
|
|
return true;
|
|
}
|
|
|
|
//获取好友列表
|
|
public async Task<List<FriendsResultResponse>> GetFriendsAsync(int userId)
|
|
{
|
|
var friends = await pgSql.Friends
|
|
.Where(f => f.Status == FriendStatus.Accepted &&
|
|
(f.UserId == userId || f.FriendId == userId))
|
|
.Select(f => f.UserId == userId
|
|
? new FriendsResultResponse
|
|
{
|
|
Id = f.FriendUser.Id,
|
|
Nickname = f.FriendUser.Nickname,
|
|
Username = f.FriendUser.Username,
|
|
Signature = f.FriendUser.Signature,
|
|
AvatarUrl = f.FriendUser.AvatarUrl,
|
|
IsOnline = f.FriendUser.IsOnline
|
|
}
|
|
: new FriendsResultResponse
|
|
{
|
|
Id = f.User.Id,
|
|
Nickname = f.User.Nickname,
|
|
Username = f.User.Username,
|
|
Signature = f.User.Signature,
|
|
AvatarUrl = f.User.AvatarUrl,
|
|
IsOnline = f.User.IsOnline
|
|
})
|
|
.ToListAsync();
|
|
|
|
return friends;
|
|
}
|
|
|
|
//搜索好友
|
|
public async Task<object> SearchFriends(string username)
|
|
{
|
|
User? user = null;
|
|
|
|
if (int.TryParse(username, out int userId))
|
|
{
|
|
user = await pgSql.Users.FindAsync(userId);
|
|
Console.WriteLine("按 ID 查询: " + userId);
|
|
}
|
|
if (user == null)
|
|
{
|
|
user = await pgSql.Users.FirstOrDefaultAsync(a => a.Username == username);
|
|
}
|
|
return user == null
|
|
? new { success = false }
|
|
: new
|
|
{
|
|
success = true,
|
|
userid = user.Id,
|
|
username = user.Username,
|
|
nickname = user.Nickname,
|
|
avatarurl = user.AvatarUrl,
|
|
signature = user.Signature,
|
|
isonline = user.IsOnline
|
|
};
|
|
}
|
|
|
|
//获取用户表单测试
|
|
public async Task<List<object>> GetFormData()
|
|
{
|
|
var users = await pgSql.Users
|
|
.Select(u => new {
|
|
Id = u.Id,
|
|
Name = u.Username,
|
|
Password = u.PasswordHash,
|
|
CreatedDate = u.CreatedAt,
|
|
LastModifiedDate = u.LastActive
|
|
})
|
|
.ToListAsync();
|
|
|
|
return users.Cast<object>().ToList();
|
|
}
|
|
|
|
|
|
//刷新Token
|
|
// 刷新 Token
|
|
public async Task RefreshAccessToken(string refreshToken)
|
|
{
|
|
var token = await pgSql.Tokens.FirstOrDefaultAsync(t => t.RefreshToken == refreshToken);
|
|
|
|
if (token == null || token.IsRevoked || token.ExpiresAt < DateTime.UtcNow)
|
|
{
|
|
await Clients.Caller.SendAsync("RefreshFailed", "Refresh token invalid or expired");
|
|
return;
|
|
}
|
|
|
|
var newAccessToken = jwtService.GenerateAccessToken(token.UserName, token.UserId);
|
|
var newRefreshToken = await jwtService.GenerateRefreshToken(token.UserId, token.UserName);
|
|
|
|
token.IsRevoked = true;
|
|
await pgSql.SaveChangesAsync();
|
|
|
|
await Clients.Caller.SendAsync("ReceiveNewAccessToken", newAccessToken, newRefreshToken);
|
|
}
|
|
|
|
//登录认证
|
|
public async Task<LoginResultResponse> LoginAuthentication(string username, string password, string ua)
|
|
{
|
|
var user = await pgSql.Users.FirstOrDefaultAsync(a => a.Username == username && a.PasswordHash == password);
|
|
if (user == null)
|
|
{
|
|
//await Clients.Client(Context.ConnectionId).SendAsync("LoginResult",false,"用户名或密码错误!");
|
|
return new LoginResultResponse { Success = false };
|
|
}
|
|
if (user == null) { return new LoginResultResponse { Success = false }; }
|
|
var accessToken = jwtService.GenerateAccessToken(user.Username,user.Id);
|
|
var refreshToken = await jwtService.GenerateRefreshToken(user.Id, username);
|
|
//await Clients.Client(Context.ConnectionId).SendAsync("LoginResult", true, accessToken, refreshToken);
|
|
logger.LogInformation($"用户:{username}正在进行登录认证!密码:{password}===accessToken:{accessToken}");
|
|
var ip = Context.GetHttpContext()?.Connection?.RemoteIpAddress?.ToString();
|
|
if (ip == null ) { return new LoginResultResponse { Success = false }; }
|
|
pgSql.LoginRecords.Add(new LoginRecord
|
|
{
|
|
UserId = user.Id,
|
|
LoginTime = DateTime.UtcNow,
|
|
IpAddress = ip,
|
|
DeviceInfo = ua,
|
|
IsSuccess = true
|
|
});
|
|
await pgSql.SaveChangesAsync();
|
|
return new LoginResultResponse
|
|
{
|
|
Success = true,
|
|
AccessToken = accessToken,
|
|
RefreshToken = refreshToken,
|
|
Nickname = user.Nickname,
|
|
Id = user.Id,
|
|
IsOnline = true,
|
|
AvatarUrl = user.AvatarUrl,
|
|
Signature = user.Signature
|
|
};
|
|
}
|
|
//新用户注册
|
|
public async Task<object> Register()
|
|
{
|
|
var newUser = new User
|
|
{
|
|
Username = "user_" + Guid.NewGuid().ToString("N").Substring(0, 8),
|
|
Nickname = GenerateTool.GenerateRandomNickname(),
|
|
PasswordHash = GenerateTool.GeneratePassword(),
|
|
Signature = GenerateTool.GenerateRandomSignature(),
|
|
AvatarUrl = GenerateTool.GenerateRandomAvatarUrl(),
|
|
CreatedAt = DateTime.UtcNow,
|
|
LastActive = DateTime.UtcNow
|
|
};
|
|
pgSql.Add(newUser);
|
|
await pgSql.SaveChangesAsync();
|
|
logger.LogInformation($"新用户:{newUser.Username}正在进行登录注册操作!密码:{newUser.Nickname}");
|
|
return new
|
|
{
|
|
Username = newUser.Username,
|
|
Password = newUser.PasswordHash,
|
|
Success = true
|
|
};
|
|
|
|
}
|
|
|
|
//统计在线人数
|
|
private async Task BroadcastOnlineCount()
|
|
{
|
|
var db = _redis.GetDatabase();
|
|
var count = await db.SetLengthAsync("AllConnections");
|
|
await Clients.All.SendAsync("OnlineCount", count);
|
|
}
|
|
|
|
//统计群组在线人数
|
|
private async Task BroadcastGroupCount(string groupName)
|
|
{
|
|
var db = _redis.GetDatabase();
|
|
var count = await db.SetLengthAsync($"Group:{groupName}");
|
|
await Clients.Group(groupName).SendAsync("GroupCount", groupName, count);
|
|
}
|
|
|
|
|
|
}
|
|
}
|