Forráskód Böngészése

测试环境wss配置.

LuTong 2 napja
szülő
commit
98da59227c

+ 3 - 2
.env.production

@@ -37,8 +37,9 @@ VITE_PROXY = [["/alienStore","http://120.26.186.130:8000/alienStore"]]
 # AI接口
 VITE_PROXY_AI = [["/ai-api","http://124.93.18.180:9000"]]
 
-# WebSocket 基础地址
-VITE_WS_BASE = wss://test.ailien.shop/alienStore/socket
+# WebSocket:HTTPS 部署下「联系业主」等页面会使用 getWebSocketBase() → wss://当前访问域名/alienStore/socket
+# 可不配置 VITE_WS_BASE,避免写死域名与实际上线域名不一致。仅 HTTP/本地联调时再配 ws/wss。
+# VITE_WS_BASE = wss://test.ailien.shop/alienStore/socket
 
 # 上传请求:不配则走同源 /ai-upload(Nginx 反代示例)
 # location /ai-upload/ {

+ 3 - 3
src/layouts/components/Header/components/NotificationBell.vue

@@ -17,6 +17,7 @@ import { useDebounceFn } from "@vueuse/core";
 import { Bell } from "@element-plus/icons-vue";
 import { getMessageNoRead } from "@/api/modules/headerNotice";
 import { localGet } from "@/utils";
+import { getWebSocketBase } from "@/utils/wsBase";
 import { useWebSocketStore } from "@/stores/modules/websocket";
 import NotificationDrawerContent from "./NotificationDrawerContent.vue";
 
@@ -26,8 +27,6 @@ const drawerContentRef = ref<InstanceType<typeof NotificationDrawerContent> | nu
 const socketStore = useWebSocketStore();
 let cleanMessageFn: (() => void) | null = null;
 
-const WS_BASE = (import.meta.env.VITE_WS_BASE || "ws://120.26.186.130:8000/alienStore/socket/").replace(/\/$/, "");
-
 /** 与商家端 tabbar getMessage 一致:获取未读数量 */
 async function getMessage() {
   const phone = localGet("iphone") || localGet("geeker-user")?.userInfo?.phone;
@@ -59,12 +58,13 @@ async function connectWebSocket() {
   const phone = localGet("iphone") || localGet("geeker-user")?.userInfo?.phone;
   if (!phone) return;
 
+  const WS_BASE = getWebSocketBase();
   const wsUrl =
     WS_BASE.startsWith("wss") || WS_BASE.startsWith("ws")
       ? `${WS_BASE}/store_${phone}`
       : WS_BASE.replace("https", "wss").replace("http", "ws") + `/store_${phone}`;
 
-  if (!socketStore.isConnected || socketStore.lastConnectedUrl !== wsUrl) {
+  if (!socketStore.isSocketOpen() || socketStore.lastConnectedUrl !== wsUrl) {
     await socketStore.connect(wsUrl);
   }
 

+ 9 - 2
src/stores/modules/websocket.ts

@@ -14,8 +14,12 @@ export const useWebSocketStore = defineStore("websocket", () => {
   // 消息订阅(用于聊天等)
   const messageHandlers = new Map<string, Array<(msg: any) => void>>();
 
+  /** 以底层 readyState 为准,避免 isConnected 与真实连接短暂不一致 */
+  const isSocketOpen = () => !!socket.value && socket.value.readyState === WebSocket.OPEN;
+
   const connect = (url: string): Promise<boolean> => {
-    if (isConnected.value && lastConnectedUrl.value === url) {
+    if (isSocketOpen() && lastConnectedUrl.value === url) {
+      isConnected.value = true;
       return Promise.resolve(true);
     }
     if (socket.value) {
@@ -74,6 +78,7 @@ export const useWebSocketStore = defineStore("websocket", () => {
         ws.onerror = () => {
           isConnected.value = false;
           isConnecting.value = false;
+          socket.value = null;
           resolve(false);
         };
       } catch (e) {
@@ -101,7 +106,8 @@ export const useWebSocketStore = defineStore("websocket", () => {
 
   const sendMessage = (data: Record<string, unknown>): Promise<boolean> => {
     return new Promise(resolve => {
-      if (!isConnected.value || !socket.value || socket.value.readyState !== WebSocket.OPEN) {
+      if (!isSocketOpen()) {
+        if (isConnected.value) isConnected.value = false;
         console.warn("WebSocket 未连接,无法发送消息");
         resolve(false);
         return;
@@ -135,6 +141,7 @@ export const useWebSocketStore = defineStore("websocket", () => {
     isConnected,
     isConnecting,
     lastConnectedUrl,
+    isSocketOpen,
     connect,
     sendMessage,
     disconnect,

+ 39 - 0
src/utils/wsBase.ts

@@ -0,0 +1,39 @@
+/**
+ * WebSocket 基础地址
+ * - 优先使用 .env 中的 VITE_WS_BASE(若为 https 页面且配的是 ws://,会自动改为 wss + 当前 host)
+ * - 若当前页面为 HTTPS,强制使用 wss + 当前 host(走 Nginx 443),避免混合内容被拦截
+ * - 若页面是 HTTP 且访问 IP/与 VITE_WS_BASE 中 wss 域名不一致,改用 ws://当前主机:8000(避免在 http://IP 下仍连 wss://其它域导致握手失败)
+ */
+export function getWebSocketBase(): string {
+  const envBase = (import.meta.env.VITE_WS_BASE as string | undefined)?.trim();
+  if (typeof window !== "undefined" && window.location) {
+    const isHttps = window.location.protocol === "https:";
+    const host = window.location.host; // hostname + port(如 120.26.186.130 或 uat.ailien.shop)
+    // 页面是 HTTPS 时,必须用 wss,且用当前 host(不写死端口),由 Nginx 443 代理到后端
+    if (isHttps) {
+      return `wss://${host}/alienStore/socket`;
+    }
+    if (envBase && envBase.trim()) {
+      const e = envBase.replace(/\/$/, "");
+      const locHost = window.location.hostname;
+      const isLocal = locHost === "localhost" || locHost === "127.0.0.1";
+      // 例如:浏览器打开 http://120.26.186.130/... 但 .env 写死 wss://test.ailien.shop → 跨域 wss 常失败;同机 store 多监听 8000
+      if (!isHttps && e.startsWith("wss://") && !isLocal) {
+        try {
+          const envHost = new URL(e.replace(/^wss:/, "https:")).hostname;
+          if (envHost !== locHost) {
+            return `ws://${locHost}:8000/alienStore/socket`;
+          }
+        } catch (_) {
+          /* ignore */
+        }
+      }
+      return e;
+    }
+    return "ws://120.26.186.130:8000/alienStore/socket";
+  }
+  if (envBase) {
+    return envBase.replace(/\/$/, "");
+  }
+  return "ws://120.26.186.130:8000/alienStore/socket";
+}

+ 12 - 7
src/views/storeDecoration/decorationChat.vue

@@ -116,13 +116,13 @@ import { ArrowLeft, Avatar, Loading, Picture, VideoPlay } from "@element-plus/ic
 import { useWebSocketStore } from "@/stores/modules/websocket";
 import { getChatRecord, messageRead, socketStatus, uploadChatFile } from "@/api/modules/storeDecoration";
 import { localGet } from "@/utils";
+import { getWebSocketBase } from "@/utils/wsBase";
 
 const route = useRoute();
 const router = useRouter();
 const socketStore = useWebSocketStore();
 
-// WebSocket 基础地址(与商家端一致)
-const WS_BASE = (import.meta.env.VITE_WS_BASE || "ws://120.26.186.130:8000/alienStore/socket/").replace(/\/$/, "");
+const isWsReady = () => socketStore.isSocketOpen();
 
 // 会话信息
 const sendId = ref("");
@@ -176,7 +176,7 @@ const scrollToBottom = () => {
 const handleSend = async () => {
   const text = inputText.value?.trim();
   if (!text || !receiverId.value) return;
-  if (!socketStore.isConnected) {
+  if (!isWsReady()) {
     ElMessage.warning("连接已断开,请稍后重试");
     return;
   }
@@ -228,7 +228,7 @@ const handleImageSelect = async (e: Event) => {
   const file = target.files?.[0];
   target.value = "";
   if (!file || !receiverId.value) return;
-  if (!socketStore.isConnected) {
+  if (!isWsReady()) {
     ElMessage.warning("连接已断开,请稍后重试");
     return;
   }
@@ -278,7 +278,7 @@ const handleVideoSelect = async (e: Event) => {
   const file = target.files?.[0];
   target.value = "";
   if (!file || !receiverId.value) return;
-  if (!socketStore.isConnected) {
+  if (!isWsReady()) {
     ElMessage.warning("连接已断开,请稍后重试");
     return;
   }
@@ -369,13 +369,18 @@ const initWebSocket = async () => {
     return;
   }
   sendId.value = `store_${phone}`;
+  // 与打包 mode 无关:HTTPS 走 wss://当前域名/alienStore/socket,避免 .env.production 仍为 ws 导致混合内容拦截
+  const WS_BASE = getWebSocketBase();
   const wsUrl =
     WS_BASE.startsWith("wss") || WS_BASE.startsWith("ws")
       ? `${WS_BASE}/store_${phone}`
       : WS_BASE.replace("https", "wss").replace("http", "ws") + `/store_${phone}`;
 
-  if (!socketStore.isConnected || socketStore.lastConnectedUrl !== wsUrl) {
-    await socketStore.connect(wsUrl);
+  if (!isWsReady() || socketStore.lastConnectedUrl !== wsUrl) {
+    const ok = await socketStore.connect(wsUrl);
+    if (!ok || !isWsReady()) {
+      ElMessage.error("即时通讯连接失败,请确认 Nginx 已代理 /alienStore/socket/ 后刷新重试");
+    }
   }
   if (cleanMessageFn) {
     cleanMessageFn();