// 房间行为助手 import { EVENT, CODEMEG, FROMTYPE } from "../../enum/index.js"; import { getCurrentUser, updateUser, removeRoomAllUsers, getAllRoomUsers, updateRoomUser, removeRoomUser } from "../../service/userService.js"; import { setRoomConfig, getRoomConfig, isRoomMaster } from "../../service/roomConfigService.js"; import { subClient } from "../../connection/redis.js"; const prefix = process.env.REDIS_PREFIX || "chat"; const getInKey = (realKey) => { return `${prefix}:${realKey}`; }; export class RoomAssistant { constructor(socket, redis, room) { this.socket = socket; this.redis = redis; this.roomId = null; this.hasCall = false; this.illegalMaster = false; this.room = room; this.roomMax = false; } /** * 准备房间 * @param {*} roomSessionId * @param {*} roomId * @returns */ async prepearRoom(roomSessionId, roomId) { // const uRoomId = await this.redis.get(getInKey(roomSessionId)); // const mergeRoomId = uRoomId || roomId; // this.roomId = mergeRoomId; this.room.logger.info("prepearRoom", roomSessionId, roomId); await this.redis.set(getInKey(roomSessionId), roomId); return Promise.resolve(roomId); } // async prepearRoom(roomSessionId, roomId) { // const uRoomId = await this.redis.get(getInKey(roomSessionId)); // const mergeRoomId = uRoomId || roomId; // this.roomId = mergeRoomId; // this.room.logger.info("prepearRoom", roomSessionId, this.roomId); // await this.redis.set(getInKey(roomSessionId), mergeRoomId); // return Promise.resolve(this.roomId); // } async destoryRoom(roomSessionId, roomConfigId) { this.room.logger.info("destoryRoom", roomSessionId, roomConfigId); await this.redis.del(getInKey(roomSessionId)); await this.redis.del(getInKey(roomConfigId)); this.disconnect(); return Promise.resolve(true); } async notifyRoomDismiss(roomId) { this.socket.broadcast.to(roomId).emit(EVENT.roomDisMiss); } /** * kickPersion LEADER or assistant 房主或助手 */ async kickPersion(roomId, userId) { console.log("kickPersion", roomId, userId); getInKey(roomId); try { const hasJoin = await this.redis.HVALS(getInKey(roomId), userId); // const blackListId = "" if (hasJoin.length > 0) { await this.redis.hDel(getInKey(roomId), userId); return Promise.resolve(true); } else { return Promise.resolve(false); } } catch (error) { return Promise.resolve(false); } } /** * 设置助手 LEADER(权限) 房主或助手 * @param {*} roomId * @param {*} userId */ async setAssistant(roomId, userId, cancel) { try { const userRes = await getCurrentUser(roomId, userId, FROMTYPE.MiniAPP); const user = JSON.parse(userRes); const roomConfigRes = await getRoomConfig(roomId); if (this.room.userId == userId) { console.log("不能设置自己为助理!"); return; } // const role = cancel ? "customer" : "assistant"; const isAssistant = cancel ? 0 : 1; // assistant是助手,customer是普通角色,操作role会好些 const userObj = Object.assign({}, user, { role: "customer", order: 1, isAssistant }); const roomObj = Object.assign({}, roomConfigRes, { assistantId: cancel ? "" : user.userId }); // console.log("setAssistant", userObj, roomObj); // console.error("roomObj", roomObj); await updateRoomUser(roomId, userId, userObj); // // 更新roomConfig 设置助手id await setRoomConfig(roomId, roomObj); const AllRoomUsers = await getAllRoomUsers(roomId); // 同房间的其他人重置 const resetOther = Array.from(AllRoomUsers) .filter((i) => i.role !== "leader" && i.userId !== userObj.userId) .map((roomer) => { const userKey = `user:${roomer.userId}`; const unsetUserObj = Object.assign({}, roomer, { isAssistant: 0, role: "customer", order: 2 }); // console.log("同房间的其他人重置", userKey, unsetUserObj); return updateRoomUser(roomId, userKey, unsetUserObj); }); //总处理完成 Promise.all(resetOther).then(() => { this.room.notify.notifyBeAssistant(roomId, userObj, this.room.userId); }); // console.log("AllRoomUsers", AllRoomUsers); // callback(user); } catch (error) { this.room.logger.error("setAssistant:error", error); } } async getRoomAssistant(roomId) { const roomConfig = await getRoomConfig(roomId); const assistantId = roomConfig.assistantId || ""; return Promise.resolve(assistantId); } /** * 设置MIC权 LEADER(权限) 房主或助手 * 主要 * @param {*} roomId * @param {*} userId */ async setMicRight(roomId, userId, isAllowMic) { try { const userRes = await getCurrentUser(roomId, userId, FROMTYPE.MiniAPP); const user = JSON.parse(userRes); const roomConfigRes = await getRoomConfig(roomId); // if (this.room.userId == userId && this.room.isHoster(this.room.user.role)) { // console.log("房主不用设置自己的MIC!"); // return; // } const reveseMic = Number(isAllowMic) === 0 ? 1 : 0; console.log("设置MIC权当前用户:: %s 新MIC权", user.userId, reveseMic); const userObj = Object.assign({}, user, { isAllowMic: reveseMic }); const roomObj = Object.assign({}, roomConfigRes, { allowMicId: user.userId }); await updateRoomUser(roomId, userId, userObj); await setRoomConfig(roomId, roomObj); const AllRoomUsers = await getAllRoomUsers(roomId); // 已存在的设置为false let preMicUser = null; const resetOther = Array.from(AllRoomUsers) .filter((i) => i.role !== "leader" && i.userId !== userObj.userId) .map((roomer) => { if (Number(roomer.isAllowMic) === 1) { preMicUser = roomer.userId; } const userKey = `user:${roomer.userId}`; const unsetUserObj = Object.assign({}, roomer, { isAllowMic: 0 }); return updateRoomUser(roomId, userKey, unsetUserObj); }); Promise.all(resetOther).then(() => { this.room.notify.notifyBeHasMic(roomId, userObj, this.room.userId, preMicUser); }); } catch (error) { this.room.logger.error("setMicRight::error", error); } } /** * 创建房间 LEADER or assistant 房主或助手 * @param {*string} roomId * @param {*string} userId * @param {*Object} user */ async buildRoom(roomId, userId, user) { const hasJoin = await this.redis.HVALS(getInKey(roomId), userId); if (hasJoin.length === 0) { await this.redis.hSet(getInKey(roomId), userId, JSON.stringify(user)); } } /** * 关闭房间 * @param {*} roomId */ async removeRoom(roomId) { this.room.logger.info("removeRoom", { roomId }); await this.redis.del(getInKey(roomId)); } /** * 加入房间 * @param {*} roomId * @param {*} userId * @param {*} user */ async joinRoom(roomId, userId, user) { const hasRoom = await this.redis.exists(getInKey(roomId)); const isJoinRoom = await this.redis.hExists(getInKey(roomId), userId); if (hasRoom) { await this.redis.hSet(getInKey(roomId), userId, JSON.stringify(user)); } else { await this.buildRoom(roomId, userId, user); this.room.logger.error("不存在房间", roomId); } this.socket.join(roomId); this.room.logger.info("加入房间 :", { userId, roomId, user }); // if (!isJoinRoom) { // this.room.logger.info("加入房间 :", { userId, roomId, user }); // const AllRoomUsers = await getAllRoomUsers(roomId); // const roomConfig = await getRoomConfig(roomId); // this.socket.emit(EVENT.roomIn, { // user, // roomsPerson: AllRoomUsers, // roomsConfig: roomConfig, // }); // this.socket.broadcast.to(roomId).emit(EVENT.someOneInRoom, { // user, // roomsPerson: AllRoomUsers, // roomsConfig: roomConfig, // }); // } else { // this.room.logger.info(`已加入房间 :`, { userId }); // } } /** * 退出房间 * @param {*} roomId * @param {*} userId * @param {*} user */ async leaveRoom(roomId, userId, user) { try { await this.redis.hDel(getInKey(roomId), userId); await removeRoomUser(roomId, userId); const AllRoomUsers = await getAllRoomUsers(roomId); const roomConfig = await getRoomConfig(roomId); this.room.logger.info("退出房间", userId, AllRoomUsers); this.socket.broadcast.to(roomId).emit(EVENT.roomOut, { user, roomsPerson: AllRoomUsers, roomsConfig: roomConfig, }); this.socket.broadcast.to(roomId).emit(EVENT.someOneLeaveRoom, { user, roomsPerson: AllRoomUsers, }); await this.socket.leave(roomId); } catch (error) { console.log("leaveRoom::error", error); } } /** * 房主关闭房间 * @param {*} clientRoom * @param {*} userUniqueId * @param {*} roomUniqueId */ async closeRoom(roomId, userId, user) { try { this.room.logger.info("房主关闭房间", userId); console.log("isInRoom", this.socket.rooms.has(roomId)); this.socket.broadcast.to(roomId).emit(EVENT.roomClose, { code: 3002, msg: CODEMEG[3002] }); await removeRoomAllUsers(roomId); this.socket.leave(roomId); } catch (error) { this.room.logger.error("RoomAssistant::closeRoom", error); } } /** * 呼叫房间 * @param {*} roomId * @param {*} userId * @param {*} user */ async startCall(roomId, userId, user) { try { if (!this.roomMax) { if (user.oid) { console.log("hasDuplicateUser-存在oid", user.oid); const hasDuplicateUser = await this.getOpenidInRoom(roomId, user.oid); if (hasDuplicateUser && hasDuplicateUser.length > 0) { const removeAll = []; Array.from(hasDuplicateUser).forEach((duplicateUser) => { if (duplicateUser.userId !== user.userId) { console.log("duplicateUser-去重用户", duplicateUser); const deleteUserKey = `user:${duplicateUser.userId}`; console.log("deleteUserKey", deleteUserKey); removeAll.push(removeRoomUser(roomId, deleteUserKey)); } else { user = Object.assign({}, user, { isAllowMic: Number(duplicateUser.isAllowMic), voiceStatus: Number(duplicateUser.voiceStatus), }); console.log("在房间内延续部分数据", user); if (Number(duplicateUser.isAllowMic) === 1) { console.log("在房间内延续强制开MIC voiceStatus", Number(duplicateUser.voiceStatus)); this.socket.broadcast.to(this.room.syncId).emit(EVENT.serverOnMic, { voiceStatus: Number(duplicateUser.voiceStatus), }); } } }); const res = await Promise.all(removeAll); console.log("去重完成", res); } } if (!this.room.isHoster(user.role)) { this.room.logger.info("不是房主", JSON.stringify(user)); await this.joinRoom(roomId, userId, user); } else { const hasRoom = await this.redis.hVals(getInKey(roomId)); if (hasRoom.length === 0) { this.illegalMaster = false; this.room.logger.info("房主主动创建房间 :", { roomId, userId }); await this.buildRoom(roomId, userId, user); } else { //TODO const checkIsRoomMaster = await isRoomMaster(roomId, userId); console.log("isRoomMaster", checkIsRoomMaster); if (checkIsRoomMaster) { this.illegalMaster = false; this.room.logger.info("房主已存在房间 :", { roomId, userId, from: user.from }); await this.joinRoom(roomId, userId, user); // this.notifyUserJitter(roomId); } else { this.room.logger.error("存在非法房主", roomId, userId); this.illegalMaster = true; this.socket.broadcast.to(this.room.syncId).emit(EVENT.unKnowError); } } } user.isInRoom = true; this.hasCall = true; const AllRoomUsers = await getAllRoomUsers(roomId); const roomConfig = await getRoomConfig(roomId); await updateRoomUser(roomId, userId, user); this.room.logger.info("roomId", roomId); this.room.logger.info("AllRoomUsers", AllRoomUsers.length); this.socket.emit(EVENT.roomIn, { user, roomsPerson: AllRoomUsers, roomsConfig: roomConfig, }); this.socket.emit(EVENT.someOneInRoom, { user, roomsPerson: AllRoomUsers, }); this.socket.broadcast.to(roomId).emit(EVENT.someOneInRoom, { user, roomsPerson: AllRoomUsers, }); } else { this.room.logger.warn("超出房间上限"); this.socket.emit(EVENT.roomMaximum, user); this.socket.broadcast.to(this.room.syncId).emit(EVENT.roomMaximum, user); } // await this.notifyUsersChange(roomId, user, true); } catch (error) { this.room.logger.error("assistant::startCall:", error); } } async notifyUserJitter(roomId, userId) { const AllRoomUsers = await getAllRoomUsers(roomId); const roomConfig = await getRoomConfig(roomId); const currentUser = await getCurrentUser(userId, FROMTYPE.MiniAPP); const user = JSON.parse(currentUser); await updateRoomUser(roomId, userId, user); this.room.logger.info("notifyUserJitter", roomId, AllRoomUsers.length); this.socket.emit(EVENT.roomIn, { user, roomsPerson: AllRoomUsers, roomsConfig: roomConfig, }); this.socket.broadcast.to(roomId).emit(EVENT.someOneInRoom, { user, roomsPerson: AllRoomUsers, }); } /** * 通知房间人员变动 */ async notifyUsersChange(roomId, user, inter = true) { const AllRoomUsers = await getAllRoomUsers(roomId); // const roomConfig = await getRoomConfig(roomId); this.room.logger.info("notifyUsersChange", roomId, AllRoomUsers.length); const actionName = inter ? "inRoom" : "outRoom"; this.socket.broadcast.to(roomId).emit(EVENT.roomPersonChange, { user: user, actionName: actionName, roomsPerson: AllRoomUsers, }); } /** * 关闭呼叫房间 * @param {*} roomId * @param {*} userId * @param {*} user */ stopCall(roomId, userId, user) { if (!this.room.isHoster(user.role)) { this.leaveRoom(roomId, userId, user); } else { this.closeRoom(roomId, userId, user); } this.removeRoomSession(this.room); } async removeRoomSession(roomSessionId) { await this.redis.del(getInKey(roomSessionId)); } async getOpenidInRoom(roomId, oid) { const AllRoomUsers = await getAllRoomUsers(roomId); if (AllRoomUsers.length > 0) { const users = AllRoomUsers.filter((item) => item.oid === oid); return Promise.resolve(users); } else { return Promise.resolve([]); } } // 下线用户强制上线 async setOnlineStatus(roomId, userId, user) { user.onlineStatus = 1; await updateRoomUser(roomId, userId, user); // this.silentUpdateRoom(roomId); await this.notifyUsersChange(roomId, user, true); } // 静默认更新房间状态 async silentUpdateRoom(roomId) { const AllRoomUsers = await getAllRoomUsers(roomId); this.socket.broadcast.to(roomId).emit(EVENT.silentUpdateRoom, { users: AllRoomUsers, }); } async checkRoomMaximum(roomId) { const roomConfigRes = await getRoomConfig(roomId); const userLimitNum = Number(roomConfigRes.userLimitNum) || 50; console.log(`${roomId} 上限人数:`, userLimitNum); const users = await getAllRoomUsers(roomId); console.log(`${roomId} 当前人数:`, users.length); if (users && users.length >= Number(userLimitNum)) { return Promise.resolve({ isMax: true, num: userLimitNum, }); } else { return Promise.resolve({ isMax: false, num: userLimitNum, }); } } // 主动断开 async disconnect() { try { const syncId = this.room.syncId; const roomId = this.room.roomId; const userId = this.room.userId; this.socket.leave(syncId); this.socket.leave(roomId); await removeRoomUser(roomId, userId); await this.redis.del(getInKey(syncId)); await this.redis.del(getInKey(userId)); this.notifyUsersChange(roomId, this.room.user, false); } catch (error) { console.log("disconnect::error", error); } } // RoomSessionId 房间有效时间 setRoomUnlimit(roomSessionId) { return this.redis.expire(getInKey(roomSessionId), -1); } setRoomAvailableBySeconds(roomSessionId, seconds) { return this.redis.expire(getInKey(roomSessionId), seconds); } setRoomAvailableByHours(roomSessionId, hours) { return this.redis.expire(getInKey(roomSessionId), 60 * 60 * hours); } watchRoomExpired(callback) { subClient.subscribe("__keyevent@0__:expired", this.watchRoomExpiredFn); } async watchRoomExpiredFn(key) { console.log("key=> ", key); } unWatchRoomExpired() { subClient.unsubscribe("__keyevent@0__:expired", this.watchRoomExpiredFn); } }