|
@@ -3292,8 +3292,9 @@ const messageInit = () => {
|
|
|
// 必须使用 messageV3:服务器上 messageV2 累积了旧的 delete('latest') 标记,
|
|
// 必须使用 messageV3:服务器上 messageV2 累积了旧的 delete('latest') 标记,
|
|
|
// 导致 get('latest') 永远返回 undefined(Yjs 的 typeMapGet 在最后一个 item 被 deleted 时返回 undefined)
|
|
// 导致 get('latest') 永远返回 undefined(Yjs 的 typeMapGet 在最后一个 item 被 deleted 时返回 undefined)
|
|
|
yMessage.value = doc.getMap('messageV3')
|
|
yMessage.value = doc.getMap('messageV3')
|
|
|
|
|
+ // ★ 消息处理已全部移到 raw-message handler(parseYjsMessage 直接从二进制解析)
|
|
|
|
|
+ // observe 因服务器累积 DELETE 标记不触发,仅保留绑定用于 catch-up 读取
|
|
|
yMessage.value.observe((e: any) => {
|
|
yMessage.value.observe((e: any) => {
|
|
|
- // 同时兼容 keysChanged 和 changes.keys(不同 Yjs 版本/事件结构)
|
|
|
|
|
const changed = e.keysChanged || e.changes?.keys
|
|
const changed = e.keysChanged || e.changes?.keys
|
|
|
const keysMap = e.keys || e.changes?.keys
|
|
const keysMap = e.keys || e.changes?.keys
|
|
|
if (keysMap && keysMap.get) {
|
|
if (keysMap && keysMap.get) {
|
|
@@ -3316,7 +3317,7 @@ const messageInit = () => {
|
|
|
const message = yMessage.value!.get('latest')
|
|
const message = yMessage.value!.get('latest')
|
|
|
console.log('🔗 yMessage latest:', message, '当前 mId:', mId.value)
|
|
console.log('🔗 yMessage latest:', message, '当前 mId:', mId.value)
|
|
|
if (message && message.mId !== mId.value) {
|
|
if (message && message.mId !== mId.value) {
|
|
|
- getMessages(message)
|
|
|
|
|
|
|
+ // getMessages(message)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
@@ -4162,7 +4163,7 @@ onMounted(() => {
|
|
|
if (document.hidden && isCreator.value) {
|
|
if (document.hidden && isCreator.value) {
|
|
|
// 页面被隐藏时,清空所有同步状态
|
|
// 页面被隐藏时,清空所有同步状态
|
|
|
clearAllSyncStates()
|
|
clearAllSyncStates()
|
|
|
- if (timerIndicator.value.visible) {
|
|
|
|
|
|
|
+ if (timerIndicator.value.visible && isCreator.value) {
|
|
|
sendMessage({ type: 'timer_stop', courseid: props.courseid })
|
|
sendMessage({ type: 'timer_stop', courseid: props.courseid })
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -4428,7 +4429,7 @@ const createWebSocketConnection = async (type = 1) => {
|
|
|
for (const val of arr) {
|
|
for (const val of arr) {
|
|
|
if (val && typeof val === 'object' && val.type && val.mId) {
|
|
if (val && typeof val === 'object' && val.type && val.mId) {
|
|
|
console.log(`📦 提取: type=${val.type}, mId=${val.mId}`)
|
|
console.log(`📦 提取: type=${val.type}, mId=${val.mId}`)
|
|
|
- if (val.mId !== mId.value) getMessages(val)
|
|
|
|
|
|
|
+ // if (val.mId !== mId.value) getMessages(val)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -4510,95 +4511,41 @@ const createWebSocketConnection = async (type = 1) => {
|
|
|
console.log('👉 WebSocket 连接关闭(将自动重连):', event)
|
|
console.log('👉 WebSocket 连接关闭(将自动重连):', event)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- // 每条消息到达时触发(用于调试批量消息问题)
|
|
|
|
|
- // ★ 从原始消息里直接解析出用户数据
|
|
|
|
|
|
|
+ // ★ 所有消息处理走 raw-message,不再依赖 observe(observe 因服务器累积 DELETE 标记不触发)
|
|
|
providerSocket.value.on('raw-message', (data: Uint8Array) => {
|
|
providerSocket.value.on('raw-message', (data: Uint8Array) => {
|
|
|
- const obj = parseYjsMessage(data)
|
|
|
|
|
- if (!obj) return
|
|
|
|
|
- // 过滤掉不带业务字段的(比如只含 slideIndex 也可能有效,看你的需要)
|
|
|
|
|
- if (!obj.type && obj.slideIndex === undefined) return
|
|
|
|
|
- // mId 过滤,跳过自己发的
|
|
|
|
|
- if (obj.mId && obj.mId === mId.value) return
|
|
|
|
|
-
|
|
|
|
|
- console.log('📨 解析业务消息:', obj)
|
|
|
|
|
- // obj = { slideIndex: 1, courseid: 'dd004650-...', type: 'slideIndex', timestamp: '...', mId: '...' }
|
|
|
|
|
-
|
|
|
|
|
- // ★ 如果需要走原有的 getMessages 逻辑(推荐保留原有 observe,两路互不干扰):
|
|
|
|
|
- getMessages(obj)
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ const objs = parseYjsMessage(data)
|
|
|
|
|
+ if (!objs || objs.length === 0) return
|
|
|
|
|
|
|
|
- // ★ synced 事件:在服务器状态同步完成(sync step 2 已应用到 Y.Doc)后触发
|
|
|
|
|
- // 'connected' 在 onopen 时就触发,此时 sync 尚未完成,messageInit 的 catch-up 读不到数据
|
|
|
|
|
- // 'synced' 才是做 catch-up 的正确时机——确保服务器上的 latest 已写入本地 Y.Doc
|
|
|
|
|
- providerSocket.value.on('sync', (isSynced: boolean) => {
|
|
|
|
|
- if (!isSynced) return
|
|
|
|
|
- console.log('🔗 Yjs sync 完成,开始 catch-up')
|
|
|
|
|
-
|
|
|
|
|
- // ★ 诊断:枚举 doc.share 里所有根类型,给每个都加 observe
|
|
|
|
|
- const share = (docSocket.value as any).share
|
|
|
|
|
- if (share) {
|
|
|
|
|
- console.log('🔗 根类型列表:')
|
|
|
|
|
- for (const [name, type] of share.entries()) {
|
|
|
|
|
- console.log(` ${name}: ${type.constructor.name}, size: ${type.size ?? type.length ?? '?'}`)
|
|
|
|
|
- // 给每个根类型加 observe,看哪个在动
|
|
|
|
|
- if (type.observe && !type.__diagObserved) {
|
|
|
|
|
- type.__diagObserved = true
|
|
|
|
|
- type.observe((e: any) => {
|
|
|
|
|
- const changed = e.keysChanged || e.changes?.keys
|
|
|
|
|
- console.log(`🔗 [${name}] observe 触发! changed:`, changed ? Array.from(changed) : e)
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ for (const obj of objs) {
|
|
|
|
|
+ // 过滤掉不带业务字段的
|
|
|
|
|
+ if (!obj.type && obj.slideIndex === undefined) continue
|
|
|
|
|
+ // mId 过滤,跳过自己发的
|
|
|
|
|
+ if (obj.mId && obj.mId === mId.value) continue
|
|
|
|
|
+
|
|
|
|
|
+ console.log('📨 解析业务消息:', obj)
|
|
|
|
|
+ getMessages(obj)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // catch-up:读取服务器同步过来的 latest 和 slideIndex 消息
|
|
|
|
|
- if (yMessage.value) {
|
|
|
|
|
- const latest = yMessage.value.get('latest')
|
|
|
|
|
- console.log('🔗 synced catch-up latest:', latest)
|
|
|
|
|
- if (latest && latest.mId !== mId.value) {
|
|
|
|
|
- console.log('🔗 synced catch-up: 处理 latest 消息', latest.type)
|
|
|
|
|
- getMessages(latest)
|
|
|
|
|
|
|
+ // ★ 消息处理后检查特殊消息数组长度,超出则清理(替代 observe 里的清理逻辑)
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (yTimerMessages.value && yTimerMessages.value.length > 500 && isCreator.value) {
|
|
|
|
|
+ smartCleanupMessages(yTimerMessages.value, 500, 'timer', docSocket.value!)
|
|
|
}
|
|
}
|
|
|
- const slideIdx = yMessage.value.get('slideIndex')
|
|
|
|
|
- console.log('🔗 synced catch-up slideIndex:', slideIdx)
|
|
|
|
|
- if (slideIdx && slideIdx.mId !== mId.value) {
|
|
|
|
|
- console.log('🔗 synced catch-up: 处理 slideIndex 消息', slideIdx.slideIndex)
|
|
|
|
|
- getMessages(slideIdx)
|
|
|
|
|
|
|
+ if (yLaserMessages.value && yLaserMessages.value.length > 500 && isCreator.value) {
|
|
|
|
|
+ smartCleanupMessages(yLaserMessages.value, 500, 'laser', docSocket.value!)
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // catch-up:读取计时器、激光笔、画图状态
|
|
|
|
|
- if (yTimerState.value) {
|
|
|
|
|
- const snapshot = yTimerState.value.toJSON()
|
|
|
|
|
- applyTimerStateSnapshot(snapshot)
|
|
|
|
|
- }
|
|
|
|
|
- if (yLaserState.value) {
|
|
|
|
|
- const laserSnap = yLaserState.value.toJSON()
|
|
|
|
|
- if (laserSnap.visible !== undefined) {
|
|
|
|
|
- laserPenOverlay.value.visible = !!laserSnap.visible
|
|
|
|
|
|
|
+ if (yWritingBoardMessages.value && yWritingBoardMessages.value.length > 500 && isCreator.value) {
|
|
|
|
|
+ smartCleanupMessages(yWritingBoardMessages.value, 500, 'writingBoard', docSocket.value!)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (yIsResultArrayMessages.value && yIsResultArrayMessages.value.length > 1 && isCreator.value) {
|
|
|
|
|
+ smartCleanupMessages(yIsResultArrayMessages.value, 1, 'isResultArray', docSocket.value!)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- // 学生端补发上线消息,让老师知道学生已就绪
|
|
|
|
|
- if (props.type === '2' && opRes.value.length === 0) {
|
|
|
|
|
- console.log('🔗 sync handler: yMessage.value =', yMessage.value ? '已绑定' : 'null', 'doc.guid=', docSocket.value?.guid)
|
|
|
|
|
- sendMessage({ type: 'class_begin', courseid: props.courseid })
|
|
|
|
|
- // ★ 确认 class_begin 是否真的写入了 messageV3
|
|
|
|
|
- const msgV3 = (docSocket.value as any)?.share?.get?.('messageV3')
|
|
|
|
|
- console.log('🔗 学生发送 class_begin 后, messageV3.size:', msgV3?.size, 'latest:', msgV3?.get?.('latest')?.type)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // ★ 老师端:sync 完成后补发当前幻灯片,确保 messageV3 的 latest 有值
|
|
|
|
|
- // 解决:服务器上 latest 被 clearAllSyncStates 删除后,新连入学生拿到空 Map 无法跟随
|
|
|
|
|
- if (props.type === '1') {
|
|
|
|
|
- const curIndex = slidesStore.slideIndex ?? 0
|
|
|
|
|
- sendMessage({ slideIndex: curIndex, courseid: props.courseid, type: 'slideIndex' })
|
|
|
|
|
- console.log('🔗 老师补发当前幻灯片 slideIndex:', curIndex)
|
|
|
|
|
|
|
+ catch (cleanupErr) {
|
|
|
|
|
+ // 清理失败不影响消息处理
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- // 自动运行 Yjs 诊断
|
|
|
|
|
- (window as any).PPTistStudent?.diagnoseYjs?.()
|
|
|
|
|
})
|
|
})
|
|
|
|
|
+
|
|
|
// 监听连接错误
|
|
// 监听连接错误
|
|
|
// providerSocket.value.on('connection-error', (error: any) => {
|
|
// providerSocket.value.on('connection-error', (error: any) => {
|
|
|
// console.error('👉 WebSocket连接错误:', error)
|
|
// console.error('👉 WebSocket连接错误:', error)
|
|
@@ -5696,8 +5643,8 @@ const clearTimerState = () => {
|
|
|
.upBtn{
|
|
.upBtn{
|
|
|
// border-bottom: 3px solid #fff;
|
|
// border-bottom: 3px solid #fff;
|
|
|
// padding-bottom: 3px;
|
|
// padding-bottom: 3px;
|
|
|
- width: 24px;
|
|
|
|
|
- height: 24px;
|
|
|
|
|
|
|
+ width: 35px;
|
|
|
|
|
+ height: 35px;
|
|
|
|
|
|
|
|
&:hover,
|
|
&:hover,
|
|
|
&.active {
|
|
&.active {
|