From a6becf67ffabd346a9574428219fb667cf5f10cb Mon Sep 17 00:00:00 2001 From: linyimin <18316471919@139.com> Date: Thu, 7 May 2026 18:30:46 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=E4=B8=AA=E4=BA=BA=E7=BB=83=E4=B9=A0?= =?UTF-8?q?=E6=8A=A5=E7=8E=AF=E5=AE=8C=E6=88=90=E4=B9=8B=E5=90=8E=E5=86=8D?= =?UTF-8?q?=E5=BC=B9=E7=BB=83=E4=B9=A0=E7=BB=93=E6=9E=9C=E5=BC=B9=E7=AA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ShootProgress.vue | 2 +- src/pages/practise-one.vue | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/ShootProgress.vue b/src/components/ShootProgress.vue index 350472e..6ad4d49 100644 --- a/src/components/ShootProgress.vue +++ b/src/components/ShootProgress.vue @@ -139,7 +139,7 @@ async function onReceiveMessage(msg) { halfTime.value = false; audioManager.play("比赛开始"); } else if (msg.type === MESSAGETYPESV2.BattleEnd) { - audioManager.play("比赛结束"); + audioManager.play("比赛结束", false); } else if (msg.type === MESSAGETYPESV2.ShootResult) { let arrow = {}; if (msg.details && Array.isArray(msg.details)) { diff --git a/src/pages/practise-one.vue b/src/pages/practise-one.vue index 27a2d21..9473cf6 100644 --- a/src/pages/practise-one.vue +++ b/src/pages/practise-one.vue @@ -59,7 +59,7 @@ async function onReceiveMessage(msg) { if (msg.type === MESSAGETYPESV2.ShootResult) { scores.value = msg.details; } else if (msg.type === MESSAGETYPESV2.BattleEnd) { - setTimeout(onOver, 1500); + // setTimeout(onOver, 1500); } } @@ -84,6 +84,12 @@ const onClickShare = debounce(async () => { await wxShare("shareCanvas"); }); +function onAudioEnded(s) { + if (s.indexOf("比赛结束") >= 0) { + onOver() + } +} + onMounted(async () => { // audioManager.play("第一轮"); uni.setKeepScreenOn({ @@ -91,6 +97,7 @@ onMounted(async () => { }); uni.$on("socket-inbox", onReceiveMessage); uni.$on("share-image", onClickShare); + uni.$on("audioEnded", onAudioEnded); const result = await createPractiseAPI(total, 120, targetType.value); if (result) practiseId.value = result.id; }); @@ -101,6 +108,7 @@ onBeforeUnmount(() => { }); uni.$off("socket-inbox", onReceiveMessage); uni.$off("share-image", onClickShare); + uni.$off("audioEnded", onAudioEnded); audioManager.stopAll(); endPractiseAPI(); }); From 4dcfdeda687fc9ebcb8aa548573af8b888653ac3 Mon Sep 17 00:00:00 2001 From: linyimin <18316471919@139.com> Date: Thu, 7 May 2026 21:03:31 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=88=86?= =?UTF-8?q?=E6=94=AF=E7=AE=A1=E7=90=86=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc.md | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 doc.md diff --git a/doc.md b/doc.md new file mode 100644 index 0000000..ca46096 --- /dev/null +++ b/doc.md @@ -0,0 +1,116 @@ +# 微信小程序多人协作分支管理规范 + +## 一、分支结构 + +``` +main (主分支/生产环境) + └── test (测试分支) + └── feature/xxx (个人开发分支) +``` + +| 分支 | 用途 | 稳定性 | +|------|------|--------| +| main | 生产环境代码 | 最高,仅接受测试通过的代码合并 | +| test | 测试环境,用于体验版发布 | 中,需验证后合并到 main | +| feature/xxx | 个人开发分支 | 低,按需命名,如 `feature/user-center` | + +--- + +## 二、开发流程 + +### 1. 开始开发 + +```bash +# 确保本地 main 最新 +git checkout main +git pull origin main + +# 从 main 创建自己的开发分支 +git checkout -b feature/your-name-work +``` + +### 2. 开发阶段 + +- 在个人分支上开发功能 +- 频繁提交,保持原子性提交 +- 定期 `git pull origin main` 同步主线变更,避免合并冲突累积 + +```bash +git add . +git commit -m "feat: 完成xxx功能" +``` + +### 3. 合并到 test 分支 + +```bash +# 切换到 test +git checkout test +git pull origin test + +# 合并个人分支 +git merge feature/your-name-work + +# 推送 test 分支 +git push origin test +``` + +### 4. 打包上传体验版 + +```bash +# 执行打包 +npm run build +``` + +打包完成后: + +1. 打开 **微信开发者工具** +2. 导入项目,选择 `dist/build/mp-weixin` 目录 +3. 在开发者工具中点击 **上传** +4. 登录 [微信公众平台](https://mp.weixin.qq.com) +5. 进入 **管理->版本管理** +6. 找到刚上传的版本,点击 **选为体验版** + +--- + +## 三、合并到 main 分支 + +当 test 分支验证通过后,将其合并到 main: + +```bash +git checkout main +git pull origin main + +git merge origin/test + +git push origin main +``` + +--- + +## 四、冲突处理 + +合并时如有冲突,在个人分支解决后再合并: + +```bash +git checkout feature/your-name-work +git merge main +# 解决冲突后 +git add . +git commit -m "merge: 解决与main的冲突" +git push origin feature/your-name-work + +# 重新合并到 test +git checkout test +git merge feature/your-name-work +git push origin test +``` + +--- + +## 五、注意事项 + +1. **禁止直接向 main 和 test 分支提交代码**,必须通过合并 +2. **每次合并前先拉取最新代码**,避免覆盖他人改动 +3. **体验版发布前确认代码已提交**,避免遗漏 +4. **开发分支命名建议**:`feature/姓名-功能名`,如 `feature/zhangsan-login` +5. **删除已合并的开发分支**:`git branch -d feature/your-name-work` \ No newline at end of file From 8b9c862b967f1c9476990f56237d5b03e6568dea Mon Sep 17 00:00:00 2001 From: zhangyibo95 <690096405@qq.com> Date: Mon, 11 May 2026 14:11:25 +0800 Subject: [PATCH 3/3] =?UTF-8?q?update:=E4=BC=98=E5=8C=96=E5=A4=9A=E7=AB=AF?= =?UTF-8?q?=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.vue | 25 +++++++- src/apis.js | 2 + src/websocket.js | 159 +++++++++++++++++++++++++++++++---------------- 3 files changed, 132 insertions(+), 54 deletions(-) diff --git a/src/App.vue b/src/App.vue index 751b9d6..3a630ac 100644 --- a/src/App.vue +++ b/src/App.vue @@ -21,7 +21,10 @@ } = storeToRefs(store); const { updateUser, - updateOnline + updateOnline, + updateDevice, + updateGame, + updateRoomNumber } = store; watch( @@ -46,6 +49,22 @@ updateUser(value); } + function onSessionKickedOut() { + uni.removeStorageSync( + `${uni.getAccountInfoSync().miniProgram.envVersion}_token` + ); + updateUser(); + updateDevice("", ""); + updateOnline(false); + updateGame(false, ""); + updateRoomNumber(""); + uni.showModal({ + title: "提示", + content: "账号已在其他设备登录", + showCancel: false, + }); + } + async function emitUpdateOnline() { const data = await getDeviceBatteryAPI(); updateOnline(data.online); @@ -65,6 +84,7 @@ onShow(() => { uni.$on("update-user", emitUpdateUser); uni.$on("update-online", emitUpdateOnline); + uni.$on("session-kicked-out", onSessionKickedOut); const token = uni.getStorageSync( `${uni.getAccountInfoSync().miniProgram.envVersion}_token` ); @@ -77,6 +97,7 @@ onHide(() => { uni.$off("update-user", emitUpdateUser); uni.$off("update-online", emitUpdateOnline); + uni.$off("session-kicked-out", onSessionKickedOut); websocket.closeWebSocket(); }); @@ -289,4 +310,4 @@ font-style: normal; font-display: swap; } - \ No newline at end of file + diff --git a/src/apis.js b/src/apis.js index 0259878..242172d 100644 --- a/src/apis.js +++ b/src/apis.js @@ -46,6 +46,8 @@ function request(method, url, data = {}) { `${uni.getAccountInfoSync().miniProgram.envVersion}_token` ); uni.$emit("update-user"); + reject({ type: "AUTH_INVALID", message }); + return; } if (message === "ROOM_FULL") { resolve({full: true}); diff --git a/src/websocket.js b/src/websocket.js index ba202b3..639da84 100644 --- a/src/websocket.js +++ b/src/websocket.js @@ -1,61 +1,91 @@ import { MESSAGETYPES, getMessageTypeName } from "@/constants"; +import { getUserGameState } from "@/apis"; + let socket = null; let heartbeatInterval = null; let reconnectTimer = null; +let manualClose = false; +let checkingSession = false; +let kickedOut = false; +let isConnecting = false; -/** - * 建立 WebSocket 连接 - */ function createWebSocket(token, onMessage) { + if (!token) return; + if (kickedOut) kickedOut = false; + if (socket || isConnecting) return; + + manualClose = false; + isConnecting = true; + let url = "wss://api.shelingxingqiu.com/socket"; try { const accountInfo = uni.getAccountInfoSync(); const envVersion = accountInfo.miniProgram.envVersion; switch (envVersion) { - case "develop": // 开发版 + case "develop": // url = "ws://localhost:8000/socket"; url = "wss://apitest.shelingxingqiu.com/socket"; break; - case "trial": // 体验版 + case "trial": url = "wss://apitest.shelingxingqiu.com/socket"; break; - case "release": // 正式版 + case "release": url = "wss://api.shelingxingqiu.com/socket"; break; default: - // 保持默认值 break; } } catch (e) { - console.error("获取环境信息失败,使用默认正式环境", e); + console.error("获取 WebSocket 环境信息失败,使用默认正式环境", e); } + url += `?authorization=${token}`; - socket = uni.connectSocket({ + const socketTask = uni.connectSocket({ url, success: () => { - console.log("websocket 连接成功"); - // 启动心跳 - startHeartbeat(onMessage); + console.log("WebSocket 已发起连接"); }, - fail: () => { + fail: (err) => { + if (socket !== socketTask) return; + console.error("WebSocket 连接失败", err); + socket = null; + isConnecting = false; reconnect(onMessage); }, }); - // 接收消息 - uni.onSocketMessage((res) => { + socket = socketTask; + + socketTask.onOpen(() => { + if (socket !== socketTask) return; + console.log("WebSocket 连接成功"); + isConnecting = false; + startHeartbeat(onMessage); + }); + + socketTask.onMessage((res) => { + if (socket !== socketTask) return; + const { data, event } = JSON.parse(res.data); if (event === "pong") return; if (data.type) { - console.log("收到消息:", getMessageTypeName(data.type), data.data); + console.log( + "收到 WebSocket 消息", + getMessageTypeName(data.type), + data.data + ); if (onMessage) onMessage({ ...(data.data || {}), type: data.type }); return; } if (onMessage && data.updates) onMessage(data.updates); const msg = data.updates[0]; if (msg) { - console.log("收到消息:", getMessageTypeName(msg.constructor), msg); + console.log( + "收到 WebSocket 更新", + getMessageTypeName(msg.constructor), + msg + ); if (msg.constructor === MESSAGETYPES.RankUpdate) { uni.setStorageSync("latestRank", msg.lvl); } else if (msg.constructor === MESSAGETYPES.LvlUpdate) { @@ -68,84 +98,109 @@ function createWebSocket(token, onMessage) { } }); - // 错误处理 - uni.onSocketError((err) => { + socketTask.onError((err) => { + if (socket !== socketTask) return; console.error("WebSocket 错误", err); - reconnect(onMessage); }); - uni.onSocketClose((result) => { + socketTask.onClose(async (result) => { + if (socket !== socketTask) return; console.log("WebSocket 已关闭", result); stopHeartbeat(); - reconnect(onMessage); + socket = null; + isConnecting = false; + + if (manualClose || kickedOut) return; + await handleUnexpectedClose(onMessage); }); } -/** - * 重连机制 - */ -function reconnect(onMessage) { - reconnectTimer && clearTimeout(reconnectTimer); - closeWebSocket(); // 确保关闭旧连接 +async function handleUnexpectedClose(onMessage) { + if (checkingSession || manualClose || kickedOut) return; const token = uni.getStorageSync( `${uni.getAccountInfoSync().miniProgram.envVersion}_token` ); if (!token) return; + checkingSession = true; + try { + await getUserGameState(); + if (!manualClose && !kickedOut) reconnect(onMessage); + } catch (err) { + if (err?.type === "AUTH_INVALID") { + kickedOut = true; + manualClose = true; + reconnectTimer && clearTimeout(reconnectTimer); + uni.$emit("session-kicked-out"); + return; + } + if (!manualClose && !kickedOut) reconnect(onMessage); + } finally { + checkingSession = false; + } +} + +function reconnect(onMessage) { + reconnectTimer && clearTimeout(reconnectTimer); + + const token = uni.getStorageSync( + `${uni.getAccountInfoSync().miniProgram.envVersion}_token` + ); + if (!token || manualClose || kickedOut || socket || isConnecting) return; + reconnectTimer = setTimeout(() => { - console.log("reconnecting..."); + if (manualClose || kickedOut || socket || isConnecting) return; + console.log("WebSocket 正在重连..."); createWebSocket(token, onMessage); }, 1000); } -function closeWebSocket() { +function closeWebSocket(isManual = true) { + manualClose = isManual; + reconnectTimer && clearTimeout(reconnectTimer); + stopHeartbeat(); + isConnecting = false; + if (socket) { - reconnectTimer && clearTimeout(reconnectTimer); - stopHeartbeat(); + const currentSocket = socket; + socket = null; try { - socket.close(); + currentSocket.close(); } catch (err) { - console.error("关闭WebSocket连接失败", err); + console.error("关闭 WebSocket 失败", err); } - - socket = null; // 清除socket引用 } } function sendHeartbeat(onMessage) { - uni.sendSocketMessage({ + if (!socket) return; + + const currentSocket = socket; + currentSocket.send({ data: JSON.stringify({ event: "ping", data: {} }), - success: () => { - // console.log("发送心跳成功"); - }, + success: () => {}, fail: (err) => { - console.error("发送心跳失败", err); + if (socket !== currentSocket) return; + console.error("心跳发送失败", err); stopHeartbeat(); - closeWebSocket(); // 关闭失效的连接 - reconnect(onMessage); // 触发重连 + closeWebSocket(false); + reconnect(onMessage); }, }); } -/** - * 启动心跳 - */ function startHeartbeat(onMessage) { - stopHeartbeat(); // 防止重复启动 + stopHeartbeat(); heartbeatInterval = setInterval(() => { - if (socket && socket.readyState === 1) { - // 检查连接状态 + if (socket) { sendHeartbeat(onMessage); } }, 10000); } -/** - * 停止心跳 - */ function stopHeartbeat() { if (heartbeatInterval) { clearInterval(heartbeatInterval);