一、核心功能设计

  1. 消息模型:定义消息结构(发送者、接收者、内容、时间、状态等)
  2. 通讯服务:负责消息的发送、接收、存储
  3. 实时通知:使用 WebSocket 或 SignalR 实现实时推送
  4. 消息存储:基于数据库保存消息记录

二、技术选型

  • 实时通讯ASP.NET Core SignalR(简化实时通讯开发)
  • 数据库:SQL Server(存储消息和用户关系)
  • 后端框架ASP.NET Core(提供 API 和 SignalR 服务)
  • 前端:可搭配 Blazor 或 JavaScript 调用 SignalR 客户端

三、代码实现

1. 消息模型定义

// 消息实体
public class Message
{
    public int Id { get; set; }
    public string SenderId { get; set; } // 发送者ID
    public string ReceiverId { get; set; } // 接收者ID
    public string Content { get; set; } // 消息内容
    public DateTime SendTime { get; set; } = DateTime.Now;
    public MessageStatus Status { get; set; } = MessageStatus.Sent; // 状态:发送/已读/未读
}

// 消息状态枚举
public enum MessageStatus
{
    Sent, // 已发送
    Delivered, // 已送达
    Read // 已读
}

2. 数据库上下文(EF Core)

public class OaDbContext : DbContext
{
    public OaDbContext(DbContextOptions<OaDbContext> options) : base(options) { }

    public DbSet<Message> Messages { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // 配置索引(优化查询)
        modelBuilder.Entity<Message>()
            .HasIndex(m => new { m.SenderId, m.ReceiverId });
    }
}

3. SignalR 通讯集线器(核心)

using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;

// 对话通讯集线器
public class ChatHub : Hub
{
    private readonly OaDbContext _dbContext;

    public ChatHub(OaDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    // 连接时加入个人用户组(用于定向推送)
    public override async Task OnConnectedAsync()
    {
        var userId = Context.UserIdentifier; // 需要配置身份认证获取用户ID
        if (!string.IsNullOrEmpty(userId))
        {
            await Groups.AddToGroupAsync(Context.ConnectionId, $"user_{userId}");
        }
        await base.OnConnectedAsync();
    }

    // 发送消息
    public async Task SendMessage(string receiverId, string content)
    {
        var senderId = Context.UserIdentifier;
        if (string.IsNullOrEmpty(senderId) || string.IsNullOrEmpty(receiverId))
            return;

        // 1. 保存消息到数据库
        var message = new Message
        {
            SenderId = senderId,
            ReceiverId = receiverId,
            Content = content
        };
        _dbContext.Messages.Add(message);
        await _dbContext.SaveChangesAsync();

        // 2. 实时推送给接收者(通过用户组)
        await Clients.Group($"user_{receiverId}").SendAsync(
            "ReceiveMessage", 
            senderId, 
            content, 
            message.SendTime, 
            message.Id
        );

        // 3. 通知发送者消息已送达
        await Clients.Caller.SendAsync("MessageDelivered", message.Id);
    }

    // 标记消息为已读
    public async Task MarkAsRead(int messageId)
    {
        var message = await _dbContext.Messages.FindAsync(messageId);
        if (message != null && message.ReceiverId == Context.UserIdentifier)
        {
            message.Status = MessageStatus.Read;
            await _dbContext.SaveChangesAsync();

            // 通知发送者消息已读
            await Clients.Group($"user_{message.SenderId}").SendAsync("MessageRead", messageId);
        }
    }
}

4. 配置 SignalR 服务(Program.cs)

var builder = WebApplication.CreateBuilder(args);

// 添加数据库上下文
builder.Services.AddDbContext<OaDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("OaDb")));

// 添加SignalR服务
builder.Services.AddSignalR();

// 添加身份认证(根据实际需求配置,如JWT)
builder.Services.AddAuthentication();

var app = builder.Build();

// 配置中间件
app.UseAuthentication();
app.UseAuthorization();

// 映射SignalR集线器端点
app.MapHub<ChatHub>("/chathub");

app.Run();

5. 前端调用示例(JavaScript)

<!-- 引入SignalR客户端 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.min.js"></script>

<script>
    // 连接到SignalR集线器
    const connection = new signalR.HubConnectionBuilder()
        .withUrl("/chathub", {
            accessTokenFactory: () => "用户认证Token" // 需传入登录用户的Token
        })
        .build();

    // 接收消息事件
    connection.on("ReceiveMessage", (senderId, content, sendTime, messageId) => {
        console.log(`收到来自${senderId}的消息:${content}`);
        // 显示消息到界面
        // 调用标记已读
        connection.invoke("MarkAsRead", messageId);
    });

    // 连接启动
    connection.start().catch(err => console.error(err.toString()));

    // 发送消息函数
    function sendMessage(receiverId, content) {
        connection.invoke("SendMessage", receiverId, content)
            .catch(err => console.error(err.toString()));
    }
</script>

6. 消息历史查询 API

[ApiController]
[Route("api/messages")]
public class MessageController : ControllerBase
{
    private readonly OaDbContext _dbContext;

    public MessageController(OaDbContext dbContext) => _dbContext = dbContext;

    // 获取与指定用户的聊天历史
    [HttpGet("history/{targetUserId}")]
    public async Task<IActionResult> GetHistory(string targetUserId)
    {
        var currentUserId = User.FindFirstValue(ClaimTypes.NameIdentifier);
        var messages = await _dbContext.Messages
            .Where(m => (m.SenderId == currentUserId && m.ReceiverId == targetUserId) ||
                        (m.SenderId == targetUserId && m.ReceiverId == currentUserId))
            .OrderBy(m => m.SendTime)
            .ToListAsync();

        return Ok(messages);
    }
}