update:对接个人训练改版
This commit is contained in:
+82
-17
@@ -4,6 +4,7 @@ import {
|
||||
createAckMessage,
|
||||
createHeartbeatAckMessage,
|
||||
createLeaveMessage,
|
||||
createSyncPracticeInfoMessage,
|
||||
decodeServerMessage,
|
||||
getServerMessageTypeName,
|
||||
} from "@/utils/matchProtocol";
|
||||
@@ -57,6 +58,7 @@ const BUSINESS_TYPE_BY_SERVER_TYPE = {
|
||||
const MATCH_READY_SNAPSHOT_PREFIX = "match-ready-snapshot:";
|
||||
export const MATCH_WS_AUDIO_ACK_EVENT = "match-ws-audio-ack";
|
||||
export const MATCH_WS_STATE_EVENT = "match-ws-state";
|
||||
export const MATCH_WS_PRACTICE_SYNC_EVENT = "match-ws-practice-sync";
|
||||
|
||||
function normalizeShootData(shootData) {
|
||||
if (!shootData || typeof shootData !== "object") return shootData;
|
||||
@@ -184,6 +186,22 @@ function buildBusinessMessage(message) {
|
||||
};
|
||||
}
|
||||
|
||||
function buildPracticeSyncMessage(message) {
|
||||
const practiceInfo = normalizePracticeInfo(message.practice_info);
|
||||
const matchId = normalizeId(
|
||||
pickField(message, "matchId", "match_id") ||
|
||||
practiceInfo.id ||
|
||||
currentContext?.matchId
|
||||
);
|
||||
|
||||
return {
|
||||
matchId,
|
||||
timestamp: message.timestamp,
|
||||
sequence: message.sequence,
|
||||
practiceInfo,
|
||||
};
|
||||
}
|
||||
|
||||
function getReadySnapshotKey(matchId) {
|
||||
return `${MATCH_READY_SNAPSHOT_PREFIX}${normalizeId(matchId)}`;
|
||||
}
|
||||
@@ -611,6 +629,24 @@ function sendHeartbeatAck() {
|
||||
);
|
||||
}
|
||||
|
||||
function sendPracticeInfoSync() {
|
||||
if (!socket || !currentContext?.matchId || !currentContext?.userId) return;
|
||||
|
||||
const clientMessage = {
|
||||
type: ClientMessageType.CLIENT_MSG_SYNC_PRACTICE_INFO,
|
||||
match_id: currentContext.matchId,
|
||||
user_id: currentContext.userId,
|
||||
};
|
||||
sendBuffer(
|
||||
createSyncPracticeInfoMessage({
|
||||
matchId: clientMessage.match_id,
|
||||
userId: clientMessage.user_id,
|
||||
}),
|
||||
"CLIENT_MSG_SYNC_PRACTICE_INFO",
|
||||
clientMessage
|
||||
);
|
||||
}
|
||||
|
||||
function sendAck({ matchId, sequence }) {
|
||||
// sequence 由后端处理,前端只原样带回,不做重排和断线补发。
|
||||
if (sequence === undefined || sequence === null || sequence === "") return;
|
||||
@@ -682,26 +718,27 @@ function removeAudioAckListener() {
|
||||
}
|
||||
|
||||
function queueAckAfterAudio(message, businessMessage) {
|
||||
// 除心跳外,只要服务端带了 sequence,都需要走 ACK;没有语音的消息立即 ACK。
|
||||
if (
|
||||
message.sequence === undefined ||
|
||||
message.sequence === null ||
|
||||
message.sequence === ""
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// 终止消息即使没有 sequence,也要等结束语音完成后主动关闭连接。
|
||||
const leaveAfterAck = shouldCloseAfterAck(message, businessMessage);
|
||||
const hasSequence =
|
||||
message.sequence !== undefined &&
|
||||
message.sequence !== null &&
|
||||
message.sequence !== "";
|
||||
if (!hasSequence && !leaveAfterAck) return;
|
||||
|
||||
const task = {
|
||||
matchId: normalizeId(
|
||||
pickField(message, "matchId", "match_id") || currentContext?.matchId
|
||||
),
|
||||
sequence: message.sequence,
|
||||
leaveAfterAck: shouldCloseAfterAck(message, businessMessage),
|
||||
leaveAfterAck,
|
||||
};
|
||||
const actionLabel = hasSequence ? "ack" : "terminal close";
|
||||
|
||||
const audioKeys = getAckAudioKeys(message, businessMessage).filter(Boolean);
|
||||
if (!audioKeys.length) {
|
||||
console.log(
|
||||
"[match-ws] ack immediately without audio",
|
||||
`[match-ws] ${actionLabel} immediately without audio`,
|
||||
getServerMessageTypeName(message.type),
|
||||
message.sequence
|
||||
);
|
||||
@@ -715,7 +752,7 @@ function queueAckAfterAudio(message, businessMessage) {
|
||||
if (index === -1) return;
|
||||
pendingAcks.splice(index, 1);
|
||||
console.log(
|
||||
"[match-ws] ack audio wait timeout",
|
||||
`[match-ws] ${actionLabel} audio wait timeout`,
|
||||
getServerMessageTypeName(message.type),
|
||||
message.sequence,
|
||||
task.expectedAudioKey
|
||||
@@ -724,7 +761,7 @@ function queueAckAfterAudio(message, businessMessage) {
|
||||
}, ACK_AUDIO_TIMEOUT_MS);
|
||||
pendingAcks.push(task);
|
||||
console.log(
|
||||
"[match-ws] ack queued until audioEnded",
|
||||
`[match-ws] ${actionLabel} queued until audioEnded`,
|
||||
getServerMessageTypeName(message.type),
|
||||
message.sequence,
|
||||
task.expectedAudioKey
|
||||
@@ -741,11 +778,6 @@ function handleMessage(data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const decodedMatchId = normalizeId(pickField(message, "matchId", "match_id"));
|
||||
if (decodedMatchId && currentContext) {
|
||||
currentContext.matchId = decodedMatchId;
|
||||
}
|
||||
|
||||
if (message.type === ServerMessageType.SERVER_MSG_HEARTBEAT) {
|
||||
sendHeartbeatAck();
|
||||
return;
|
||||
@@ -754,6 +786,34 @@ function handleMessage(data) {
|
||||
const typeName = getServerMessageTypeName(message.type);
|
||||
console.log("收到比赛服 WebSocket 消息", typeName, message);
|
||||
|
||||
const decodedMatchId = normalizeId(pickField(message, "matchId", "match_id"));
|
||||
if (message.type === ServerMessageType.SERVER_MSG_SYNC_PRACTICE_INFO) {
|
||||
// 同步响应是完整快照,不映射成开始/报靶等实时事件,避免重放页面副作用。
|
||||
queueAckAfterAudio(message, null);
|
||||
if (
|
||||
decodedMatchId &&
|
||||
currentContext?.matchId &&
|
||||
decodedMatchId !== currentContext.matchId
|
||||
) {
|
||||
console.log("[match-ws] ignore mismatched practice sync", {
|
||||
expectedMatchId: currentContext.matchId,
|
||||
receivedMatchId: decodedMatchId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const syncMessage = buildPracticeSyncMessage(message);
|
||||
if (syncMessage.matchId && currentContext) {
|
||||
currentContext.matchId = syncMessage.matchId;
|
||||
}
|
||||
uni.$emit(MATCH_WS_PRACTICE_SYNC_EVENT, syncMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (decodedMatchId && currentContext) {
|
||||
currentContext.matchId = decodedMatchId;
|
||||
}
|
||||
|
||||
const businessMessage = buildBusinessMessage(message);
|
||||
if (businessMessage?.matchId && currentContext) {
|
||||
currentContext.matchId = businessMessage.matchId;
|
||||
@@ -788,6 +848,7 @@ export function connectMatchWebSocket(options = {}) {
|
||||
userId,
|
||||
token,
|
||||
mode,
|
||||
requestPracticeInfoOnOpen = false,
|
||||
force = false,
|
||||
reconnecting = false,
|
||||
reconnectReason = "",
|
||||
@@ -858,6 +919,7 @@ export function connectMatchWebSocket(options = {}) {
|
||||
mode: Number.isFinite(normalizedMode) ? normalizedMode : undefined,
|
||||
isMelee:
|
||||
Number.isFinite(normalizedMode) ? normalizedMode > 3 : undefined,
|
||||
requestPracticeInfoOnOpen: requestPracticeInfoOnOpen === true,
|
||||
meleeHalfRest: isSameContext
|
||||
? currentContext?.meleeHalfRest === true
|
||||
: false,
|
||||
@@ -911,6 +973,9 @@ export function connectMatchWebSocket(options = {}) {
|
||||
reason: reconnectReason,
|
||||
reconnected: wasReconnected,
|
||||
});
|
||||
if (currentContext?.requestPracticeInfoOnOpen) {
|
||||
sendPracticeInfoSync();
|
||||
}
|
||||
});
|
||||
|
||||
socketTask.onMessage((res) => {
|
||||
|
||||
Reference in New Issue
Block a user