优化信息

This commit is contained in:
2026-05-25 16:58:41 +08:00
parent cc734790ef
commit 254d9337bf
4 changed files with 196 additions and 38 deletions

View File

@@ -7,7 +7,7 @@ import Offer from './offer';
import Answer from './answer';
import Candidate from './candidate';
import { v4 as uuid } from 'uuid';
import { onGetAllConnectionIds, onGetOnlineUsers as onGetWsOnlineUsers } from './websockethandler';
import { onGetAllConnectionIds, onGetOnlineUsers as onGetWsOnlineUsers, onGetRooms as onGetWsRooms } from './websockethandler';
import { log, LogLevel } from '../log';
/**
* 断开连接记录类
@@ -1039,40 +1039,13 @@ function postCandidate(req: Request, res: Response): void {
* description: 总房间数
*/
function onGetConnections(req: Request, res: Response): void {
const connectionId = typeof req.query.connectionId === 'string' ? req.query.connectionId : undefined;
const wsRooms = onGetWsRooms(connectionId).map((room) => ({
...room,
users: room.members
}));
// 收集所有房间ID和链接用户信息
const rooms = [];
// 遍历所有连接对
for (const [connectionId, pair] of Array.from(connectionPair.entries())) {
// 收集房间中的用户信息
const users = [];
// 添加第一个用户
if (pair[0] && clients.has(pair[0])) {
users.push({
sessionId: pair[0],
connected: true
});
}
// 添加第二个用户
if (pair[1] && clients.has(pair[1])) {
users.push({
sessionId: pair[1],
connected: true
});
}
// 添加房间信息
rooms.push({
roomId: connectionId,
users: users,
userCount: users.length
});
}
res.json({ rooms: rooms, totalRooms: rooms.length });
res.json({ rooms: wsRooms, totalRooms: wsRooms.length });
}
/**

View File

@@ -53,12 +53,37 @@ interface OnlineUser {
avatar: string;
}
interface RoomMemberInfo extends OnlineUser {
joinedAt: number;
updatedAt: number;
}
interface RoomSnapshot {
roomId: string;
connectionId: string;
hostSocketId: string;
createdAt: number;
updatedAt: number;
members: RoomMemberInfo[];
userCount: number;
}
interface StoredRoom {
roomId: string;
connectionId: string;
hostSocketId: string;
createdAt: number;
updatedAt: number;
members: Map<string, RoomMemberInfo>;
}
/**
* 连接组映射
* 键: connectionId
* 值: ConnectionGroup1个host + 多个participants
*/
const connectionGroup: Map<string, ConnectionGroup> = new Map<string, ConnectionGroup>();
const rooms: Map<string, StoredRoom> = new Map<string, StoredRoom>();
function asAppWebSocket(ws: WebSocket): AppWebSocket {
return ws as AppWebSocket;
@@ -89,7 +114,12 @@ function getUserInfo(ws: WebSocket): UserInfo {
}
function setUserInfo(ws: WebSocket, userInfo: UserInfo): void {
asAppWebSocket(ws).userInfo = userInfo;
asAppWebSocket(ws).userInfo = {
id: userInfo.id || '',
name: userInfo.name || '',
avatar: userInfo.avatar || ''
};
updateRoomMembersForSocket(ws);
}
function safeSend(ws: WebSocket, payload: unknown): boolean {
@@ -111,6 +141,106 @@ function findParticipantSocket(group: ConnectionGroup, participantId: string): W
return null;
}
function getSocketRoleInRoom(ws: WebSocket, connectionId: string): 'host' | 'participant' | 'idle' {
const group = connectionGroup.get(connectionId);
if (group) {
if (group.host === ws) {
return 'host';
}
if (group.participants.has(ws)) {
return 'participant';
}
}
return isPrivate ? 'idle' : 'participant';
}
function toRoomMember(ws: WebSocket, connectionId: string, existing?: RoomMemberInfo): RoomMemberInfo {
const userInfo = getUserInfo(ws);
const now = Date.now();
return {
socketId: ensureSocketId(ws),
connectionId,
participantId: ensureParticipantId(ws),
role: getSocketRoleInRoom(ws, connectionId),
userId: userInfo.id || '',
name: userInfo.name || '',
avatar: userInfo.avatar || '',
joinedAt: existing ? existing.joinedAt : now,
updatedAt: now
};
}
function getOrCreateRoom(connectionId: string, ws: WebSocket): StoredRoom {
let room = rooms.get(connectionId);
const now = Date.now();
const socketId = ensureSocketId(ws);
if (!room) {
room = {
roomId: connectionId,
connectionId,
hostSocketId: '',
createdAt: now,
updatedAt: now,
members: new Map<string, RoomMemberInfo>()
};
rooms.set(connectionId, room);
}
if (!room.hostSocketId || getSocketRoleInRoom(ws, connectionId) === 'host') {
room.hostSocketId = socketId;
}
room.updatedAt = now;
return room;
}
function saveRoomMember(ws: WebSocket, connectionId: string): void {
const room = getOrCreateRoom(connectionId, ws);
const socketId = ensureSocketId(ws);
const existing = room.members.get(socketId);
room.members.set(socketId, toRoomMember(ws, connectionId, existing));
room.updatedAt = Date.now();
}
function updateRoomMembersForSocket(ws: WebSocket): void {
const connectionIds = clients.get(ws);
if (!connectionIds) {
return;
}
connectionIds.forEach(connectionId => {
if (rooms.has(connectionId)) {
saveRoomMember(ws, connectionId);
}
});
}
function removeRoomMember(ws: WebSocket, connectionId: string): void {
const room = rooms.get(connectionId);
if (!room) {
return;
}
room.members.delete(getSocketId(ws));
room.updatedAt = Date.now();
if (room.members.size === 0 || room.hostSocketId === getSocketId(ws)) {
rooms.delete(connectionId);
}
}
function toRoomSnapshot(room: StoredRoom): RoomSnapshot {
const members = Array.from(room.members.values());
return {
roomId: room.roomId,
connectionId: room.connectionId,
hostSocketId: room.hostSocketId,
createdAt: room.createdAt,
updatedAt: room.updatedAt,
members,
userCount: members.length
};
}
/**
* 获取或创建WebSocket会话的连接ID集合
* @param session WebSocket会话实例
@@ -140,6 +270,7 @@ function reset(mode: string): void {
isPrivate = mode == "private";
clients.clear();
connectionGroup.clear();
rooms.clear();
}
/**
@@ -201,12 +332,16 @@ function remove(ws: WebSocket): void {
group.participants.forEach(participantWs => {
safeSend(participantWs, { type: "disconnect", connectionId: connectionId, reason: "host-left" });
});
rooms.delete(connectionId);
connectionGroup.delete(connectionId);
} else {
group.participants.delete(ws);
removeRoomMember(ws, connectionId);
// 包含participantId让host能识别是哪个participant离开
safeSend(group.host, { type: "participant-left", connectionId: connectionId, participantId: getParticipantId(ws) });
}
} else {
removeRoomMember(ws, connectionId);
}
log(LogLevel.log, `Remove connectionId: ${connectionId}`);
});
@@ -242,6 +377,7 @@ function onConnect(ws: WebSocket, connectionId: string): void {
const connectionIds = getOrCreateConnectionIds(ws);
connectionIds.add(connectionId);
const role = polite ? 'participant' : 'host';
saveRoomMember(ws, connectionId);
safeSend(ws, { type: "connect", connectionId: connectionId, polite: polite, role: role, participantId: participantId });
}
@@ -266,14 +402,18 @@ function onDisconnect(ws: WebSocket, connectionId: string): void {
group.participants.forEach(participantWs => {
safeSend(participantWs, { type: "disconnect", connectionId: connectionId, reason: "host-left" });
});
rooms.delete(connectionId);
connectionGroup.delete(connectionId);
log(LogLevel.log, `Host disconnected, room ${connectionId} deleted, notified ${group.participants.size} participants`);
} else {
// participant断开连接从组中移除并通知host使用participant-left类型host不会关闭房间
group.participants.delete(ws);
removeRoomMember(ws, connectionId);
safeSend(group.host, { type: "participant-left", connectionId: connectionId, participantId: getParticipantId(ws) });
log(LogLevel.log, `Participant left connectionId: ${connectionId}, remaining participants: ${group.participants.size}`);
}
} else {
removeRoomMember(ws, connectionId);
}
// 向当前连接发送断开连接消息
@@ -328,6 +468,7 @@ function onOffer(ws: WebSocket, message: any): void {
if (!connectionGroup.has(connectionId)) {
connectionGroup.set(connectionId, { host: ws, participants: new Set<WebSocket>() });
}
saveRoomMember(ws, connectionId);
// 向所有其他客户端广播offer
clients.forEach((_v, k) => {
if (k == ws) {
@@ -570,8 +711,11 @@ function RemoveHeartbeat(ws: WebSocket) {
*/
function onGetAllConnectionIds(): string[] {
// 获取所有connectionId
const connectionIds = Array.from(connectionGroup.keys());
return connectionIds;
const connectionIds = new Set<string>(Array.from(connectionGroup.keys()));
rooms.forEach((_room, connectionId) => {
connectionIds.add(connectionId);
});
return Array.from(connectionIds);
}
/**
@@ -629,6 +773,17 @@ function onGetOnlineUsers(connectionId?: string): OnlineUser[] {
return onlineUsers;
}
function onGetRooms(connectionId?: string): RoomSnapshot[] {
const roomSnapshots: RoomSnapshot[] = [];
rooms.forEach((room, roomConnectionId) => {
if (connectionId && roomConnectionId !== connectionId) {
return;
}
roomSnapshots.push(toRoomSnapshot(room));
});
return roomSnapshots;
}
/**
* 处理chat-message信令1对多模式
* host的消息转发给所有participantsparticipant的消息转发给host
@@ -677,5 +832,5 @@ function onMessage(ws: WebSocket, message: any): void {
* 导出WebSocket处理器函数
*/
export { reset, add, remove, onConnect, onDisconnect, onOffer, onAnswer, onCandidate, onCallConnectionId,
onBroadcast, onGetAllConnectionIds, onGetOnlineUsers, AddHeartbeat, RemoveHeartbeat, onMessage, isHost,
onBroadcast, onGetAllConnectionIds, onGetOnlineUsers, onGetRooms, AddHeartbeat, RemoveHeartbeat, onMessage, isHost,
broadcastToGroup, connectionGroup, onHostUserInfo, onInviteCall };

View File

@@ -6,6 +6,7 @@ const router: express.Router = express.Router();
// 不需要会话ID的路由
router.get('/connection-ids', handler.getAllConnectionIds);
router.get('/users', handler.getOnlineUsers);
router.get('/rooms', handler.onGetConnections);
// 需要会话ID的路由
router.use(handler.checkSessionId);

View File

@@ -161,6 +161,35 @@ describe('websocket signaling test in private mode', () => {
});
});
test('save room and member info', async () => {
wsHandler.onHostUserInfo(client, { id: 'host-user', name: 'Host User', avatar: '/host.png' });
wsHandler.onHostUserInfo(client2, { id: 'guest-user', name: 'Guest User', avatar: '/guest.png' });
expect(wsHandler.onGetRooms()).toEqual([
expect.objectContaining({
roomId: connectionId,
connectionId: connectionId,
userCount: 2,
members: expect.arrayContaining([
expect.objectContaining({
connectionId: connectionId,
role: 'host',
userId: 'host-user',
name: 'Host User',
avatar: '/host.png'
}),
expect.objectContaining({
connectionId: connectionId,
role: 'participant',
userId: 'guest-user',
name: 'Guest User',
avatar: '/guest.png'
})
])
})
]);
});
test('send offer from session1', async () => {
await wsHandler.onOffer(client, { connectionId: connectionId, sdp: testsdp });
const receiveOffer = new Offer(testsdp, Date.now(), true);