YingXingAI/docs/WebSocket实施计划.md

617 lines
15 KiB
Markdown
Raw Permalink Normal View 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
```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. 兼容性测试
- [ ] H5Chrome、Safari
- [ ] 微信小程序
- [ ] 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. ✅ 调试和测试支持
随时告诉我!🚀