YingXingAI/docs/WebSocket实施计划.md

617 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
```javascript
// 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`
```javascript
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`
```javascript
// 获取聊天历史记录
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. 兼容性测试
- [ ] H5ChromeSafari
- [ ] 微信小程序
- [ ] Android App
- [ ] iOS App
---
## 🔧 后端实现
### .NET Core WebSocket 服务端代码
#### 1. WebSocket 中间件
**文件**`WebSocketMiddleware.cs`
```csharp
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`
```csharp
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`
```csharp
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`
```csharp
[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. 调试和测试支持
随时告诉我!🚀