update:对接个人训练改版

This commit is contained in:
2026-07-23 09:44:58 +08:00
parent a78ab1daeb
commit 3c5754b3fd
64 changed files with 1769 additions and 488 deletions
+82 -17
View File
@@ -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) => {