diff --git a/src/js/event/index.js b/src/js/event/index.js index 304d8c4..b5e7ec9 100644 --- a/src/js/event/index.js +++ b/src/js/event/index.js @@ -1,2 +1,3 @@ export * from './receiveChatMsg' export * from './receiveChatReadMsg' +export * from './receiveStatusRes' diff --git a/src/js/event/receiveStatusRes.js b/src/js/event/receiveStatusRes.js new file mode 100644 index 0000000..9c46d91 --- /dev/null +++ b/src/js/event/receiveStatusRes.js @@ -0,0 +1,27 @@ +import { userStore, messageStore } from '@/stores' +import { combineId } from '@/utils/common' + +export const onReceiveStatusResMsg = () => { + return async (msg) => { + const userData = userStore() + const messageData = messageStore() + const content = JSON.parse(msg.body.content) + if (content[userData.user.account]) { + userData.updateUserStatus(content[userData.user.account]) + } + + //遍历content的每个属性,属性key是account + Object.keys(content).forEach((key) => { + const sessionId = combineId(userData.user.account, key) + if (messageData.sessionList[sessionId]) { + messageData.updateSession({ + sessionId: sessionId, + objectInfo: { + ...messageData.sessionList[sessionId].objectInfo, + status: content[key] + } + }) + } + }) + } +} diff --git a/src/js/websocket/constructor.js b/src/js/websocket/constructor.js index 8cfc4a1..8ccccd5 100644 --- a/src/js/websocket/constructor.js +++ b/src/js/websocket/constructor.js @@ -77,6 +77,27 @@ export const chatReadConstructor = (toId, content) => { return data } +export const statusReqConstructor = (accounts) => { + const header = Header.create({ + magic: proto.magic, + version: proto.version, + msgType: MsgType.STATUS_REQ, + isExtension: false + }) + + const userData = userStore() + const body = Body.create({ + fromId: userData.user.account, + fromClient: userData.clientId, + content: accounts + }) + + const msg = Msg.create({ header: header, body: body }) + const payload = Msg.encode(msg).finish() + const data = encodePayload(payload) + return data +} + /** * 发送前对长度编码,配合服务端解决半包黏包问题 * @param {*} payload diff --git a/src/js/websocket/wsConnect.js b/src/js/websocket/wsConnect.js index 76e28cd..d0ef724 100644 --- a/src/js/websocket/wsConnect.js +++ b/src/js/websocket/wsConnect.js @@ -6,8 +6,10 @@ import { chatConstructor, heartBeatConstructor, helloConstructor, - chatReadConstructor + chatReadConstructor, + statusReqConstructor } from './constructor' +import { onReceiveStatusResMsg } from '@/js/event' class WsConnect { /** @@ -98,7 +100,8 @@ class WsConnect { [MsgType.CHAT]: () => {}, [MsgType.HEART_BEAT]: () => { if (this.heartBeat.healthPoint > 0) this.heartBeat.healthPoint-- - } + }, + [MsgType.STATUS_RES]: onReceiveStatusResMsg() } /** @@ -108,7 +111,8 @@ class WsConnect { [MsgType.HELLO]: helloConstructor, [MsgType.HEART_BEAT]: heartBeatConstructor, [MsgType.CHAT]: chatConstructor, - [MsgType.CHAT_READ]: chatReadConstructor + [MsgType.CHAT_READ]: chatReadConstructor, + [MsgType.STATUS_REQ]: statusReqConstructor } /** @@ -345,6 +349,11 @@ class WsConnect { this.reconnect.taskObj && clearInterval(this.reconnect.taskObj) this.reconnect.taskObj = null } + + statusReq(accounts) { + const data = this.dataConstructor[MsgType.STATUS_REQ](accounts) + this.connect.send(data) + } } export default WsConnect.getInstance() diff --git a/src/proto/msg.js b/src/proto/msg.js index 3e264b0..77a8f41 100644 --- a/src/proto/msg.js +++ b/src/proto/msg.js @@ -290,6 +290,8 @@ export const Msg = ($root.Msg = (() => { * @property {number} GROUP_CHAT_READ=5 GROUP_CHAT_READ value * @property {number} DELIVERED=6 DELIVERED value * @property {number} SENDER_SYNC=7 SENDER_SYNC value + * @property {number} STATUS_REQ=8 STATUS_REQ value + * @property {number} STATUS_RES=9 STATUS_RES value * @property {number} CLOSE_BY_READ_IDLE=10 CLOSE_BY_READ_IDLE value * @property {number} CLOSE_BY_ERROR_MAGIC=11 CLOSE_BY_ERROR_MAGIC value * @property {number} DEFAULT=99 DEFAULT value @@ -305,6 +307,8 @@ export const MsgType = ($root.MsgType = (() => { values[(valuesById[5] = 'GROUP_CHAT_READ')] = 5 values[(valuesById[6] = 'DELIVERED')] = 6 values[(valuesById[7] = 'SENDER_SYNC')] = 7 + values[(valuesById[8] = 'STATUS_REQ')] = 8 + values[(valuesById[9] = 'STATUS_RES')] = 9 values[(valuesById[10] = 'CLOSE_BY_READ_IDLE')] = 10 values[(valuesById[11] = 'CLOSE_BY_ERROR_MAGIC')] = 11 values[(valuesById[99] = 'DEFAULT')] = 99 @@ -498,6 +502,8 @@ export const Header = ($root.Header = (() => { case 5: case 6: case 7: + case 8: + case 9: case 10: case 11: case 99: @@ -560,6 +566,14 @@ export const Header = ($root.Header = (() => { case 7: message.msgType = 7 break + case 'STATUS_REQ': + case 8: + message.msgType = 8 + break + case 'STATUS_RES': + case 9: + message.msgType = 9 + break case 'CLOSE_BY_READ_IDLE': case 10: message.msgType = 10 @@ -660,7 +674,35 @@ export const Body = ($root.Body = (() => { /** * Constructs a new Body. * @exports Body - * @classdesc Represents a Body. + * @classdesc 每种消息需要携带的字段规定:M必须,o非必须,-不带 + * NO filed HELLO HEART_BEAT CHAT GROUP_CHAT CHAT_READ GROUP_CHAT_READ DELIVERED SENDER_SYNC CLOSE_BY_READ_IDLE CLOSE_BY_ERROR_MAGIC + * +----+--------------+------+-----------+-----+-----------+----------+----------------+----------+------------+-------------------+---------------------+ + * | 1 | fromId | - | - | M | M | M | M | - | M | todo | todo | + * | 2 | fromClient | - | - | M | M | M | M | - | M | todo | todo | + * | 3 | toId | - | - | M | O | M | O | - | M | todo | todo | + * | 4 | toClient | - | - | O | O | O | O | - | M | todo | todo | + * | 5 | groupId | - | - | - | M | - | M | - | O | todo | todo | + * | 6 | msgId | - | - | O | O | O | O | M | M | todo | todo | + * | 7 | seq(todo) | - | - | - | - | - | - | - | - | todo | todo | + * | 8 | ack(todo) | - | - | - | - | - | - | - | - | todo | todo | + * | 9 | content | - | - | M | M | M | M | - | M | todo | todo | + * | 10 | tempMsgId | - | - | O | O | O | O | M | O | todo | todo | + * | 11 | sessionId | - | - | - | - | - | - | M | M | todo | todo | + * +----+--------------+------+-----------+-----+-----------+----------+----------------+----------+------------+-------------------+---------------------+ + * NO filed STATUS_REQ STATUS_RES + * +----+--------------+------------+------------+ + * | 1 | fromId | M | M | + * | 2 | fromClient | M | M | + * | 3 | toId | - | - | + * | 4 | toClient | - | - | + * | 5 | groupId | - | - | + * | 6 | msgId | - | - | + * | 7 | seq(todo) | - | - | + * | 8 | ack(todo) | - | - | + * | 9 | content | M | M | + * | 10 | tempMsgId | - | - | + * | 11 | sessionId | O | O | + * +----+--------------+------------+------------+ * @implements IBody * @constructor * @param {IBody=} [properties] Properties to set diff --git a/src/proto/msg.proto b/src/proto/msg.proto index eae8583..7d869ad 100644 --- a/src/proto/msg.proto +++ b/src/proto/msg.proto @@ -15,7 +15,8 @@ enum MsgType { GROUP_CHAT_READ = 5; // 群聊已读 DELIVERED = 6; //已发送 SENDER_SYNC = 7; //发送端多设备之间同步的消息 - + STATUS_REQ = 8; //连接状态查询请求 + STATUS_RES = 9; //连接状态响应 CLOSE_BY_READ_IDLE = 10; //超时关闭 CLOSE_BY_ERROR_MAGIC = 11; //magic不对关闭 @@ -45,7 +46,21 @@ message Header { | 10 | tempMsgId | - | - | O | O | O | O | M | O | todo | todo | | 11 | sessionId | - | - | - | - | - | - | M | M | todo | todo | +----+--------------+------+-----------+-----+-----------+----------+----------------+----------+------------+-------------------+---------------------+ - */ + NO filed STATUS_REQ STATUS_RES ++----+--------------+------------+------------+ +| 1 | fromId | M | M | +| 2 | fromClient | M | M | +| 3 | toId | - | - | +| 4 | toClient | - | - | +| 5 | groupId | - | - | +| 6 | msgId | - | - | +| 7 | seq(todo) | - | - | +| 8 | ack(todo) | - | - | +| 9 | content | M | M | +| 10 | tempMsgId | - | - | +| 11 | sessionId | O | O | ++----+--------------+------------+------------+ +*/ message Body { optional string fromId = 1; optional string fromClient = 2; diff --git a/src/stores/user.js b/src/stores/user.js index b3e55b3..1ff5121 100644 --- a/src/stores/user.js +++ b/src/stores/user.js @@ -104,6 +104,10 @@ export const userStore = defineStore( user.value = obj } + const updateUserStatus = (status) => { + user.value.status = status + } + const isRemenberMe = ref(false) const setIsRemenberMe = (flag) => { @@ -129,6 +133,7 @@ export const userStore = defineStore( user, updateUser, setUser, + updateUserStatus, isRemenberMe, setIsRemenberMe, clientId, diff --git a/src/views/layout/LayoutContainer.vue b/src/views/layout/LayoutContainer.vue index 1377729..822db73 100644 --- a/src/views/layout/LayoutContainer.vue +++ b/src/views/layout/LayoutContainer.vue @@ -21,16 +21,25 @@ import AvatarIcon from '@/components/common/AvatarIcon.vue' const myCardDialog = ref() const myAvatar = ref() const userData = userStore() +let statusReqTask onMounted(async () => { setTimeout(() => { wsConnect.createWs() }, 1500) // 延迟启动,防止token刷新碰撞 document.addEventListener('click', clickListener) + + // 定时查询自己的状态(多端设备场景,比如其他设备正在忙碌,要同步过来) + let accounts = [] + accounts.push(userData.user.account) + statusReqTask = setInterval(() => { + wsConnect.statusReq(JSON.stringify(accounts)) + }, 5000) }) onUnmounted(() => { document.removeEventListener('click', clickListener) + clearInterval(statusReqTask) }) const clickListener = (e) => { diff --git a/src/views/message/MessageLayout.vue b/src/views/message/MessageLayout.vue index e3f759c..4515a26 100644 --- a/src/views/message/MessageLayout.vue +++ b/src/views/message/MessageLayout.vue @@ -111,6 +111,7 @@ const selectedSession = computed(() => { return messageData.sessionList[selectedSessionId.value] }) +let statusReqTask onMounted(async () => { asideWidth.value = settingData.sessionListDrag[userData.user.account] || 300 inputBoxHeight.value = settingData.inputBoxDrag[userData.user.account] || 300 @@ -119,10 +120,25 @@ onMounted(async () => { messageData.setSessionList(res.data.data) //入缓存 wsConnect.bindEvent(MsgType.CHAT, onReceiveChatMsg(msgListDiv, capacity)) //绑定接收Chat消息的事件 wsConnect.bindEvent(MsgType.CHAT_READ, onReceiveChatReadMsg()) //绑定接收Chat已读消息的事件 + + // 定时更新单聊对象的状态 + const accounts = [] + Object.keys(messageData.sessionList).forEach(key => { + const session = messageData.sessionList[key] + const sessionType = session.sessionType + if (sessionType === MsgType.CHAT) { //只看单聊的,群里在打开聊天窗时触发查询 + accounts.push(session.objectInfo.account) + } + }) + + statusReqTask = setInterval(() => { + wsConnect.statusReq(JSON.stringify(accounts)) + }, 5000) }) onUnmounted(() => { messageData.clear() + clearInterval(statusReqTask) }) const handleMsgListWheel = async () => {