YingXingAI/docs/WebSocket实施计划.md

15 KiB
Raw Blame History

WebSocket 实施计划

🎯 项目信息

  • 项目名称英星AI - 浙江科技大学招生咨询系统
  • 技术栈uni-app + Vue 2
  • 后端ASP.NET Core推测
  • 方案选择WebSocket 原生方案

📋 实施步骤

阶段一:前端准备(已完成

1. WebSocket 管理器

  • 文件:utils/websocket-manager.js
  • 功能:自动重连、心跳检测、消息队列、事件监听

2. WebSocket 配置

  • 文件:config/websocket.config.js
  • 功能:集中管理 WebSocket 配置

3. 使用文档

  • 文件:docs/WebSocket使用示例.md
  • 文件:docs/实时通讯方案对比.md
  • 文件:docs/实时通讯快速选型.md

阶段二:后端开发(待实施

选项 AASP.NET Core WebSocket 服务(推荐)

时间3-5 天

工作量:

  1. 创建 WebSocket 中间件1 天)
  2. 实现消息路由1 天)
  3. 集成用户认证1 天)
  4. 消息持久化1 天)
  5. 测试调试1 天)

文件清单:

  • WebSocketMiddleware.cs - WebSocket 中间件
  • WebSocketConnectionManager.cs - 连接管理器
  • WebSocketMessageHandler.cs - 消息处理器
  • ChatController.cs - HTTP API获取历史消息
  • MessageRepository.cs - 消息存储

后端代码:见下文 .NET Core WebSocket 服务端代码

选项 B使用现有 SignalR备选

你的项目已经有 SignalR

// main.js
var connection = new HubConnectionBuilder()
  .withUrl("http://sl.vrgon.com:8003/ChatHub")
  .build();

建议:

  • 如果 SignalR 服务正常,可以继续使用
  • 如果需要切换到原生 WebSocket按选项 A 实施

阶段三前端集成2-3 天)

1. 在 App.vue 初始化 WebSocket

文件App.vue

import wsManager from '@/utils/websocket-manager.js'
import wsConfig from '@/config/websocket.config.js'

export default {
  onLaunch() {
    // 登录后连接 WebSocket
    if (this.vuex_token) {
      this.connectWebSocket()
    }
  },
  
  methods: {
    connectWebSocket() {
      // 构建 WebSocket URL带 token
      const wsUrl = `${wsConfig.url}?token=${this.vuex_token}`
      
      // 连接
      wsManager.connect(wsUrl, {
        reconnectInterval: wsConfig.reconnect.interval,
        maxReconnectAttempts: wsConfig.reconnect.maxAttempts,
        heartbeatInterval: wsConfig.heartbeat.interval
      })
      
      // 监听事件
      this.setupWebSocketListeners()
    },
    
    setupWebSocketListeners() {
      // 连接成功
      wsManager.on('open', () => {
        console.log('[App] WebSocket 已连接')
      })
      
      // 收到消息
      wsManager.on('message', (data) => {
        this.handleWebSocketMessage(data)
      })
      
      // 连接关闭
      wsManager.on('close', () => {
        console.log('[App] WebSocket 已断开')
      })
    },
    
    handleWebSocketMessage(data) {
      // 根据消息类型处理
      switch (data.type) {
        case 'message':
          // 新消息提醒
          this.$store.commit('addMessage', data)
          break
        case 'system':
          // 系统通知
          uni.showToast({ title: data.content, icon: 'none' })
          break
      }
    }
  }
}

2. 修改聊天页面

文件pages/message/dialogBox/dialogBox.vue

需要:

  1. 导入 WebSocket 管理器
  2. 监听实时消息
  3. 发送消息通过 WebSocket
  4. 加载历史消息通过 HTTP API

代码示例:见 docs/WebSocket使用示例.md

3. 添加聊天相关 API

文件common/http.api.js

// 获取聊天历史记录
let getChatHistory = (params = {}) => vm.$u.get('api/Chat/GetHistory', params);

// 发送离线消息WebSocket 断开时使用)
let sendOfflineMessage = (params = {}) => vm.$u.post('api/Chat/SendOffline', params);

// 标记消息已读
let markMessageRead = (params = {}) => vm.$u.post('api/Chat/MarkRead', params);

阶段四测试验证2-3 天)

1. 功能测试

  • 连接建立
  • 发送文字消息
  • 接收文字消息
  • 心跳保持
  • 自动重连
  • 离线消息推送
  • 消息已读回执

2. 场景测试

  • 单人聊天
  • 多人并发
  • 网络断开
  • 网络恢复
  • App 切换后台
  • App 恢复前台
  • 登录/登出

3. 性能测试

  • 消息延迟 < 200ms
  • 重连时间 < 5s
  • 内存占用正常
  • CPU 占用正常
  • 100+ 并发用户

4. 兼容性测试

  • H5Chrome、Safari
  • 微信小程序
  • Android App
  • iOS App

🔧 后端实现

.NET Core WebSocket 服务端代码

1. WebSocket 中间件

文件WebSocketMiddleware.cs

using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;

public class WebSocketMiddleware
{
    private readonly RequestDelegate _next;
    private readonly WebSocketConnectionManager _connectionManager;

    public WebSocketMiddleware(RequestDelegate next, WebSocketConnectionManager connectionManager)
    {
        _next = next;
        _connectionManager = connectionManager;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // 检查是否是 WebSocket 请求
        if (!context.WebSockets.IsWebSocketRequest)
        {
            await _next(context);
            return;
        }

        // 验证 token
        var token = context.Request.Query["token"].ToString();
        var userId = ValidateToken(token);
        
        if (string.IsNullOrEmpty(userId))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("Unauthorized");
            return;
        }

        // 接受 WebSocket 连接
        var webSocket = await context.WebSockets.AcceptWebSocketAsync();
        
        // 保存连接
        _connectionManager.AddConnection(userId, webSocket);
        
        Console.WriteLine($"[WebSocket] 用户 {userId} 已连接");

        try
        {
            await HandleWebSocketAsync(userId, webSocket);
        }
        finally
        {
            // 移除连接
            _connectionManager.RemoveConnection(userId);
            Console.WriteLine($"[WebSocket] 用户 {userId} 已断开");
        }
    }

    private async Task HandleWebSocketAsync(string userId, WebSocket webSocket)
    {
        var buffer = new byte[1024 * 4];

        while (webSocket.State == WebSocketState.Open)
        {
            var result = await webSocket.ReceiveAsync(
                new ArraySegment<byte>(buffer), 
                CancellationToken.None
            );

            if (result.MessageType == WebSocketMessageType.Close)
            {
                await webSocket.CloseAsync(
                    WebSocketCloseStatus.NormalClosure,
                    "关闭连接",
                    CancellationToken.None
                );
                break;
            }

            // 解析消息
            var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
            await HandleMessageAsync(userId, message);
        }
    }

    private async Task HandleMessageAsync(string userId, string message)
    {
        try
        {
            var data = JsonConvert.DeserializeObject<WebSocketMessage>(message);

            switch (data.Type)
            {
                case "message":
                    // 处理聊天消息
                    await HandleChatMessageAsync(userId, data);
                    break;

                case "ping":
                    // 处理心跳
                    await SendToUserAsync(userId, new
                    {
                        type = "pong",
                        timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds()
                    });
                    break;

                case "read":
                    // 处理已读回执
                    await HandleReadReceiptAsync(userId, data);
                    break;

                default:
                    Console.WriteLine($"未知消息类型: {data.Type}");
                    break;
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"消息处理失败: {ex.Message}");
        }
    }

    private async Task HandleChatMessageAsync(string fromUserId, WebSocketMessage data)
    {
        var toUserId = data.ToUserId;
        var content = data.Content;

        // 保存消息到数据库
        var messageId = await SaveMessageAsync(fromUserId, toUserId, content);

        // 构建消息对象
        var messageObj = new
        {
            type = "message",
            messageId = messageId,
            fromUserId = fromUserId,
            toUserId = toUserId,
            content = content,
            timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds()
        };

        // 发送给接收者
        await SendToUserAsync(toUserId, messageObj);

        // 发送给发送者(确认消息已发送)
        await SendToUserAsync(fromUserId, messageObj);
    }

    private async Task<string> SaveMessageAsync(string fromUserId, string toUserId, string content)
    {
        // TODO: 保存消息到数据库
        // 返回消息 ID
        return Guid.NewGuid().ToString();
    }

    private async Task HandleReadReceiptAsync(string userId, WebSocketMessage data)
    {
        // TODO: 更新消息已读状态
        Console.WriteLine($"用户 {userId} 已读消息 {data.MessageId}");
    }

    private async Task SendToUserAsync(string userId, object message)
    {
        var webSocket = _connectionManager.GetConnection(userId);
        if (webSocket != null && webSocket.State == WebSocketState.Open)
        {
            var json = JsonConvert.SerializeObject(message);
            var bytes = Encoding.UTF8.GetBytes(json);
            await webSocket.SendAsync(
                new ArraySegment<byte>(bytes),
                WebSocketMessageType.Text,
                true,
                CancellationToken.None
            );
        }
    }

    private string ValidateToken(string token)
    {
        // TODO: 验证 JWT token返回用户 ID
        // 这里简化处理,直接返回一个用户 ID
        return "user123";
    }
}

// 消息实体
public class WebSocketMessage
{
    public string Type { get; set; }
    public string FromUserId { get; set; }
    public string ToUserId { get; set; }
    public string Content { get; set; }
    public string MessageId { get; set; }
    public long Timestamp { get; set; }
}

2. 连接管理器

文件WebSocketConnectionManager.cs

using System.Collections.Concurrent;
using System.Net.WebSockets;

public class WebSocketConnectionManager
{
    // 存储所有连接(用户 ID -> WebSocket
    private readonly ConcurrentDictionary<string, WebSocket> _connections = 
        new ConcurrentDictionary<string, WebSocket>();

    // 添加连接
    public void AddConnection(string userId, WebSocket webSocket)
    {
        _connections.TryAdd(userId, webSocket);
    }

    // 移除连接
    public void RemoveConnection(string userId)
    {
        _connections.TryRemove(userId, out _);
    }

    // 获取连接
    public WebSocket GetConnection(string userId)
    {
        _connections.TryGetValue(userId, out var webSocket);
        return webSocket;
    }

    // 获取所有在线用户
    public IEnumerable<string> GetOnlineUsers()
    {
        return _connections.Keys;
    }

    // 获取在线用户数
    public int GetOnlineCount()
    {
        return _connections.Count;
    }
}

3. 注册中间件

文件Startup.cs

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // 注册连接管理器为单例
        services.AddSingleton<WebSocketConnectionManager>();
        
        // 其他服务...
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // 启用 WebSocket
        app.UseWebSockets(new WebSocketOptions
        {
            KeepAliveInterval = TimeSpan.FromSeconds(30)
        });

        // 注册 WebSocket 中间件
        app.Map("/ws/chat", builder =>
        {
            builder.UseMiddleware<WebSocketMiddleware>();
        });

        // 其他中间件...
        app.UseRouting();
        app.UseAuthentication();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

4. HTTP API获取历史消息

文件ChatController.cs

[ApiController]
[Route("api/[controller]")]
public class ChatController : ControllerBase
{
    // 获取聊天历史
    [HttpGet("GetHistory")]
    public async Task<IActionResult> GetHistory(string userId, int page = 1, int pageSize = 50)
    {
        // TODO: 从数据库查询历史消息
        var messages = new List<object>
        {
            new
            {
                messageId = "msg001",
                fromUserId = "user123",
                toUserId = userId,
                content = "你好",
                timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds()
            }
        };

        return Ok(new
        {
            success = true,
            data = messages,
            total = messages.Count
        });
    }

    // 发送离线消息
    [HttpPost("SendOffline")]
    public async Task<IActionResult> SendOffline([FromBody] SendMessageRequest request)
    {
        // TODO: 保存离线消息到数据库
        return Ok(new { success = true });
    }

    // 标记已读
    [HttpPost("MarkRead")]
    public async Task<IActionResult> MarkRead([FromBody] MarkReadRequest request)
    {
        // TODO: 更新消息已读状态
        return Ok(new { success = true });
    }
}

public class SendMessageRequest
{
    public string ToUserId { get; set; }
    public string Content { get; set; }
}

public class MarkReadRequest
{
    public string MessageId { get; set; }
}

📊 时间安排

阶段 工作内容 预计时间 负责人
阶段一 前端准备 已完成 前端
阶段二 后端开发 3-5 天 后端
阶段三 前端集成 2-3 天 前端
阶段四 测试验证 2-3 天 全员
总计 7-11 天

🎯 下一步行动

立即可做(前端)

  1. 在 App.vue 中初始化 WebSocket

    • 导入 websocket-manager.js
    • onLaunch 中调用 wsManager.connect()
    • 设置事件监听
  2. 测试 WebSocket 管理器

    • 使用在线 WebSocket 测试服务(如 wss://echo.websocket.org
    • 验证连接、发送、接收功能

需要后端配合

  1. 确认后端技术栈

    • 是否是 ASP.NET Core
    • 后端开发人员是谁?
  2. 部署 WebSocket 服务

    • 使用上面提供的 .NET Core 代码
    • 或者告诉我你的后端技术,我提供对应代码
  3. 配置服务器

    • 确保服务器支持 WebSocket
    • 配置 Nginx 反向代理(如果需要)

📞 联系与支持

如果你需要:

  1. 其他后端语言的 WebSocket 服务端代码Node.js/Java/Go/Python
  2. 帮助修改 dialogBox.vue 集成 WebSocket
  3. Nginx 配置 WebSocket 反向代理
  4. 调试和测试支持

随时告诉我!🚀