update:优化多端登录

This commit is contained in:
2026-05-11 14:11:52 +08:00
3 changed files with 132 additions and 54 deletions

View File

@@ -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();
});
</script>

View File

@@ -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});

View File

@@ -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);