From 8dfc9189443d7baf2e73d99a73e1b20eefba366e Mon Sep 17 00:00:00 2001
From: WXL (wul) <wl_5969728@163.com>
Date: 星期二, 06 一月 2026 17:38:49 +0800
Subject: [PATCH] 测试完成

---
 src/utils/sipService.js |  354 ++++++++++++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 290 insertions(+), 64 deletions(-)

diff --git a/src/utils/sipService.js b/src/utils/sipService.js
index b93e184..5ce3b1a 100644
--- a/src/utils/sipService.js
+++ b/src/utils/sipService.js
@@ -1,5 +1,21 @@
 import JsSIP from "jssip";
-
+import { Notification, MessageBox, Message, Loading } from "element-ui";
+// 鍖婚櫌鏈烘瀯涓嶴IP鏈嶅姟鍣ㄦ槧灏勯厤缃�
+const HOSPITAL_CONFIG = {
+  涓芥按甯備腑鍖婚櫌: {
+    wsUrl: "wss://192.168.10.124:7443",
+    domain: "192.168.10.124",
+  },
+  榫欐硥甯備汉姘戝尰闄�: {
+    wsUrl: "wss://10.10.0.220:7443",
+    domain: "10.10.0.220",
+  },
+  // 鍙互缁х画娣诲姞鍏朵粬鍖婚櫌閰嶇疆
+  default: {
+    wsUrl: "wss://192.168.10.124:7443",
+    domain: "192.168.10.124",
+  },
+};
 class SipService {
   constructor() {
     this.ua = null;
@@ -7,53 +23,113 @@
     this.onStatusChange = null;
     this.onCallStatusChange = null;
     this.onIncomingCall = null;
+    this.isRegistered = false; // 鏂板娉ㄥ唽鐘舵�佹爣蹇�
+    this.registrationTime = null; // 鏂板娉ㄥ唽鎴愬姛鏃堕棿鎴�
+    this.currentConfig = null; // 瀛樺偍褰撳墠閰嶇疆
   }
-
-  init(config) {
+  // 鑾峰彇鍖婚櫌閰嶇疆鏂规硶
+  getHospitalConfig() {
+    const orgName = localStorage.getItem("orgname");
+    return HOSPITAL_CONFIG[orgName] || HOSPITAL_CONFIG.default;
+  }
+  init(baseConfig) {
     try {
+      // 鑾峰彇鏈烘瀯鍚嶇О锛屽鏋滄病鏈変紶鍏ュ垯浠巐ocalStorage璇诲彇
+      const orgName = baseConfig.orgName || localStorage.getItem("orgname");
+
+      // 鏍规嵁鏈烘瀯鍚嶇О鑾峰彇瀵瑰簲鐨勬湇鍔″櫒閰嶇疆
+      const hospitalConfig = this.getHospitalConfig(orgName);
+      console.log(hospitalConfig, "88");
+
+      // 鍚堝苟閰嶇疆
+      this.currentConfig = {
+        ...baseConfig,
+        ...hospitalConfig,
+      };
+
+      console.log(
+        `褰撳墠鏈烘瀯: ${orgName}, 浣跨敤鏈嶅姟鍣�: ${this.currentConfig.domain}`
+      );
+
       this.updateStatus("connecting", "杩炴帴涓�...");
+      console.log(baseConfig.sipUri, "baseConfig.sipUri");
 
       this.ua = new JsSIP.UA({
-        sockets: [new JsSIP.WebSocketInterface(config.wsUrl)],
-        uri: config.sipUri,
-        password: config.password,
-        display_name: config.displayName,
-        iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
+        sockets: [new JsSIP.WebSocketInterface(this.currentConfig.wsUrl)],
+        uri: baseConfig.sipUri, // 杩欓噷浣跨敤鍩虹鐨剆ipUri锛宒omain閮ㄥ垎浼氳鍔ㄦ�佹浛鎹�
+        password: baseConfig.password,
+        display_name: baseConfig.displayName,
+        iceServers: [],
         register: true,
-        session_expires: 900,
-        sessionTimersExpires: 600,
-        extraHeaders: ["Min-SE: 120"],
+        sessionExpires: 1800,
+        minSessionExpires: 90,
         register_expires: 300,
-        connection_recovery_min_interval: 2,
-        connection_recovery_max_interval: 30,
-        pcConfig: {
-          iceTransportPolicy: "all",
-          rtcpMuxPolicy: "require",
-          bundlePolicy: "max-bundle"
-        }
       });
 
       this.ua.start();
 
       // 浜嬩欢鐩戝惉
-      this.ua.on("registered", () => this.updateStatus("registered", "宸叉敞鍐�"));
-      this.ua.on("registrationFailed", (e) =>
-        this.updateStatus("failed", `娉ㄥ唽澶辫触: ${e.cause}`));
-      this.ua.on("disconnected", () =>
-        this.updateStatus("disconnected", "杩炴帴鏂紑"));
-      this.ua.on("connected", () =>
-        this.updateStatus("connecting", "閲嶆柊杩炴帴涓�..."));
-      this.ua.on("newRTCSession", (data) =>
-        this.handleIncomingCall(data.session));
+      this.ua.on("registered", () => {
+        this.isRegistered = true;
+        this.registrationTime = Date.now(); // 璁板綍娉ㄥ唽鎴愬姛鏃堕棿
+        console.log(this.registrationTime, "娉ㄥ唽鏃堕棿");
 
+        this.updateStatus("registered", "宸叉敞鍐�");
+      });
+
+      this.ua.on("registrationFailed", (e) => {
+        this.isRegistered = false;
+        this.updateStatus("failed", `娉ㄥ唽澶辫触: ${e.cause}`);
+      });
+
+      this.ua.on("unregistered", () => {
+        this.isRegistered = false;
+        let registrationTime = Date.now(); // 璁板綍娉ㄩ攢鎴愬姛鏃堕棿
+        console.log(registrationTime, "娉ㄩ攢鏃堕棿");
+        this.updateStatus("disconnected", "宸叉敞閿�");
+      });
+      this.ua.on("disconnected", () =>
+        this.updateStatus("disconnected", "杩炴帴鏂紑")
+      );
+      this.ua.on("connected", () =>
+        this.updateStatus("connecting", "閲嶆柊杩炴帴涓�...")
+      );
+      this.ua.on("newRTCSession", (data) =>
+        this.handleIncomingCall(data.session)
+      );
     } catch (error) {
       this.updateStatus("failed", `鍒濆鍖栧け璐�: ${error.message}`);
       console.error("SIP鍒濆鍖栧け璐�:", error);
       throw error;
     }
   }
+  // 鏂板鏂规硶锛氭鏌ユ槸鍚﹀彲浠ュ懠鍙�
+  canMakeCall(minDelay = 2000) {
+    if (!this.isRegistered) {
+      return { canCall: false, reason: "SIP鏈敞鍐岋紝鏃犳硶鍛煎彨" };
+    }
 
+    const now = Date.now();
+    const timeSinceRegistration = now - this.registrationTime;
+
+    if (timeSinceRegistration < minDelay) {
+      const remaining = minDelay - timeSinceRegistration;
+      return {
+        canCall: false,
+        reason: `娉ㄥ唽鎴愬姛锛岃祫婧愬姞杞戒腑璇风瓑寰� ${Math.ceil(
+          remaining / 1000
+        )} 绉掑悗鍐嶅懠鍙玚,
+      };
+    }
+
+    return { canCall: true, reason: "" };
+  }
   makeCall(targetNumber) {
+    const { canCall, reason } = this.canMakeCall();
+    if (!canCall) {
+      Message.error(reason);
+      return Promise.reject(new Error(reason));
+    }
     return new Promise((resolve, reject) => {
       try {
         if (!this.ua) {
@@ -63,18 +139,16 @@
         if (!this.ua.isRegistered()) {
           throw new Error("SIP鏈敞鍐岋紝鏃犳硶鍛煎彨");
         }
-
+        const targetUri = `sip:${targetNumber}@${this.currentConfig.domain}`;
+        console.log(`鍛煎彨鐩爣: ${targetUri}`);
         const options = {
-          sessionTimers: false, // 鏆傛椂绂佺敤浠ュ噺灏戝吋瀹规�ч棶棰�
-          extraHeaders: [
-            "Min-SE: 120",
-            "Accept: application/sdp",
-            "Supported: outbound"
-          ],
+          sessionTimers: true, // 鍚敤浼氳瘽璁℃椂鍣�
+          sessionTimersExpires: 150,
+          extraHeaders: ["Accept: application/sdp"],
           mediaConstraints: { audio: true, video: false },
           rtcOfferConstraints: {
             offerToReceiveAudio: true,
-            offerToReceiveVideo: false
+            offerToReceiveVideo: false,
           },
           eventHandlers: {
             progress: () => this.updateCallStatus("calling", "鍛煎彨涓�..."),
@@ -85,20 +159,16 @@
             confirmed: () => {
               this.updateCallStatus("connected", "閫氳瘽宸叉帴閫�");
               resolve();
-            }
-          }
+            },
+          },
         };
 
-        this.currentSession = this.ua.call(
-          `sip:${targetNumber}@192.168.10.124`,
-          options
-        );
+        this.currentSession = this.ua.call(targetUri, options);
 
         this.setupPeerConnection(this.currentSession);
         this.setupAudio(this.currentSession);
-
       } catch (error) {
-        this.updateCallStatus("failed", `鍛煎彨澶辫触1: ${error.message}`);
+        this.updateCallStatus("failed", `鍛煎彨澶辫触22: ${error.message}`);
         reject(error);
       }
     });
@@ -120,44 +190,197 @@
     });
   }
 
+  //   normalizeSDP(offer) {
+  //     let sdp = offer.sdp;
+  //  console.log("鍘熷SDP:", sdp); // 璋冭瘯鐢紝鎹曡幏鍘熷SDP
+  //     // 鏍囧噯鍖朣DP
+  //     sdp = sdp.replace(/c=IN IP4.*\r\n/, "c=IN IP4 0.0.0.0\r\n");
+  //     sdp = sdp.replace(
+  //       /m=audio \d+.*\r\n/,
+  //       "m=audio 9 UDP/TLS/RTP/SAVPF 0 8\r\n"
+  //     );
+
+  //     // 纭繚鍖呭惈鍩烘湰缂栬В鐮佸櫒
+  //     if (!sdp.includes("PCMU/8000")) sdp += "a=rtpmap:0 PCMU/8000\r\n";
+  //     if (!sdp.includes("PCMA/8000")) sdp += "a=rtpmap:8 PCMA/8000\r\n";
+
+  //     // 娣诲姞蹇呰灞炴��
+  //     sdp += "a=rtcp-mux\r\n";
+  //     sdp += "a=sendrecv\r\n";
+
+  //     console.log("鏍囧噯鍖栧悗鐨凷DP:", sdp);
+  //     return new RTCSessionDescription({
+  //       type: offer.type,
+  //       sdp: sdp,
+  //     });
+  //   }
+  // 鍦� SipService 绫讳腑鏂板鏂规硶锛岀敤浜庤幏鍙栭拡瀵圭壒瀹氭湇鍔″櫒鐨凷DP澶勭悊绛栫暐
+  getSDPNormalizationStrategy(orgName) {
+    const strategies = {
+      榫欐硥甯備汉姘戝尰闄�: "conservative", // 淇濆畧绛栫暐锛氭渶灏忓寲淇敼锛屼紭鍏堝吋瀹�
+      涓芥按甯備腑鍖婚櫌: "aggressive", // 婵�杩涚瓥鐣ワ細淇濇寔鍘熸湁寮烘爣鍑嗗寲閫昏緫
+      // 鍙互涓哄叾浠栨満鏋勬坊鍔犳洿澶氱瓥鐣�
+    };
+    return strategies[orgName] || "moderate"; // 榛樿绛栫暐
+  }
+
+  /**
+   * 鏍囧噯鍖朣DP Offer - 淇榫欐硥甯備汉姘戝尰闄�488閿欒
+   * 鏍稿績鎬濊矾锛氫粠鈥滃己鍒惰鐩栤�濇敼涓衡�滄櫤鑳戒慨琛モ�濓紝閽堝涓嶅悓鏈嶅姟鍣ㄤ娇鐢ㄥ樊寮傚寲绛栫暐
+   */
   normalizeSDP(offer) {
+    const orgName = localStorage.getItem("orgname");
+    const strategy = this.getSDPNormalizationStrategy(orgName);
     let sdp = offer.sdp;
 
-    // 1. 鏍囧噯鍖栬繛鎺ヨ
-    sdp = sdp.replace(/c=IN IP4.*\r\n/, "c=IN IP4 0.0.0.0\r\n");
+    console.log(`[SDP鏍囧噯鍖朷 鏈烘瀯: ${orgName}, 绛栫暐: ${strategy}`);
+    console.log("[SDP鏍囧噯鍖朷 鍘熷SDP:", sdp);
 
-    // 2. 鏍囧噯鍖栭煶棰戝獟浣撹
-    sdp = sdp.replace(/m=audio \d+.*\r\n/,
-      "m=audio 9 UDP/TLS/RTP/SAVPF 0 8\r\n");
+    if (strategy === "conservative") {
+      // ==================== 淇濆畧绛栫暐锛氶拡瀵归緳娉夊競浜烘皯鍖婚櫌绛変弗鏍兼湇鍔″櫒 ====================
+      // 鍘熷垯锛氶櫎闈炲繀瑕侊紝鍚﹀垯涓嶄慨鏀瑰師鏈塖DP缁撴瀯锛屼粎娣诲姞缂哄け鐨勫叧閿睘鎬�
 
-    // 3. 纭繚鍖呭惈鍩烘湰缂栬В鐮佸櫒
-    if (!sdp.includes("PCMU/8000")) {
-      sdp += "a=rtpmap:0 PCMU/8000\r\n";
-    }
-    if (!sdp.includes("PCMA/8000")) {
-      sdp += "a=rtpmap:8 PCMA/8000\r\n";
+      // 1. 璋ㄦ厧澶勭悊杩炴帴鍦板潃锛氫粎鍦ㄥ湴鍧�鏄槑鏄惧唴缃戝湴鍧�鏃舵墠淇敼
+      const privateIPRegex =
+        /c=IN IP4 (192\.168|10\.|172\.(1[6-9]|2[0-9]|3[0-1]))/;
+      if (privateIPRegex.test(sdp)) {
+        sdp = sdp.replace(/c=IN IP4.*\r\n/, "c=IN IP4 0.0.0.0\r\n");
+        console.log("[SDP鏍囧噯鍖朷 宸蹭慨鏀硅繛鎺ュ湴鍧�涓� 0.0.0.0");
+      }
+
+      // 2. 淇濇寔濯掍綋琛屽師鏍凤紝涓嶅己鍒朵慨鏀圭鍙e拰鍗忚
+      // sdp = sdp.replace(/m=audio \d+.*\r\n/, "m=audio 9 UDP/TLS/RTP/SAVPF 0 8\r\n");
+
+      // 3. 鏅鸿兘娣诲姞鍩虹缂栬В鐮佸櫒鏄犲皠锛堜粎鍦ㄧ己澶辨椂娣诲姞锛�
+      const codecMappings = [
+        { pt: 0, name: "PCMU/8000" },
+        { pt: 8, name: "PCMA/8000" },
+      ];
+
+      codecMappings.forEach((codec) => {
+        const rtpmapPattern = `a=rtpmap:${codec.pt} ${codec.name}`;
+        const payloadPattern = ` ${codec.pt} `;
+
+        // 鍙湁褰揝DP涓寘鍚璐熻浇绫诲瀷浣嗙己灏戣缁嗘槧灏勬椂鎵嶆坊鍔�
+        if (sdp.includes(payloadPattern) && !sdp.includes(rtpmapPattern)) {
+          sdp += `${rtpmapPattern}\r\n`;
+          console.log(`[SDP鏍囧噯鍖朷 宸叉坊鍔犵紪瑙g爜鍣ㄦ槧灏�: ${rtpmapPattern}`);
+        }
+      });
+
+      // 4. 鏉′欢鎬ф坊鍔犲繀瑕佸睘鎬э紙閬垮厤閲嶅锛�
+      const essentialAttributes = [
+        { attr: "a=rtcp-mux", desc: "RTCP澶嶇敤" },
+        { attr: "a=sendrecv", desc: "鍙屽悜濯掍綋娴�" },
+      ];
+
+      essentialAttributes.forEach((item) => {
+        if (!sdp.includes(item.attr)) {
+          sdp += `${item.attr}\r\n`;
+          console.log(`[SDP鏍囧噯鍖朷 宸叉坊鍔犲睘鎬�: ${item.desc}`);
+        }
+      });
+    } else if (strategy === "aggressive") {
+      // ==================== 婵�杩涚瓥鐣ワ細淇濇寔鍘熸湁閫昏緫 ====================
+      sdp = sdp.replace(/c=IN IP4.*\r\n/, "c=IN IP4 0.0.0.0\r\n");
+      sdp = sdp.replace(
+        /m=audio \d+.*\r\n/,
+        "m=audio 9 UDP/TLS/RTP/SAVPF 0 8\r\n"
+      );
+
+      // 纭繚鍖呭惈鍩虹缂栬В鐮佸櫒
+      if (!sdp.includes("PCMU/8000")) sdp += "a=rtpmap:0 PCMU/8000\r\n";
+      if (!sdp.includes("PCMA/8000")) sdp += "a=rtpmap:8 PCMA/8000\r\n";
+
+      // 娣诲姞閫氱敤灞炴��
+      sdp += "a=rtcp-mux\r\n";
+      sdp += "a=sendrecv\r\n";
+    } else {
+      // ==================== 榛樿绛栫暐锛氬钩琛℃柟妗� ====================
+      // 閫傚害淇敼锛屽吋椤惧吋瀹规�у拰鍔熻兘鎬�
+      sdp = sdp.replace(/c=IN IP4.*\r\n/, "c=IN IP4 0.0.0.0\r\n");
+
+      // 浠呭湪濯掍綋琛屾牸寮忔槑鏄惧紓甯告椂淇敼
+      if (!sdp.match(/m=audio \d+ RTP\/AVP/)) {
+        sdp = sdp.replace(
+          /m=audio \d+.*\r\n/,
+          "m=audio 9 UDP/TLS/RTP/SAVPF 0 8\r\n"
+        );
+      }
+
+      // 鏅鸿兘娣诲姞缂哄け鐨勫睘鎬�
+      if (!sdp.includes("a=rtcp-mux")) sdp += "a=rtcp-mux\r\n";
+      if (!sdp.includes("a=sendrecv")) sdp += "a=sendrecv\r\n";
     }
 
-    // 4. 娣诲姞蹇呰灞炴��
-    sdp += "a=rtcp-mux\r\n";
-    sdp += "a=sendrecv\r\n";
-
-    console.log("鏍囧噯鍖栧悗鐨凷DP:", sdp);
+    console.log("[SDP鏍囧噯鍖朷 鏍囧噯鍖栧悗SDP:", sdp);
     return new RTCSessionDescription({
       type: offer.type,
-      sdp: sdp
+      sdp: sdp,
     });
   }
 
+  /**
+   * 澧炲己鐨凷DP璋冭瘯鏂规硶 - 鐢ㄤ簬瀵规瘮鍒嗘瀽
+   */
+  debugSDPComparison(originalOffer, normalizedOffer, context) {
+    console.group(`[SDP璋冭瘯] ${context}`);
+    console.log(
+      "鍘熷SDP濯掍綋琛�:",
+      originalOffer.sdp.match(/m=audio.*\r\n/)?.[0] || "鏈壘鍒�"
+    );
+    console.log(
+      "鏍囧噯鍖栧悗濯掍綋琛�:",
+      normalizedOffer.sdp.match(/m=audio.*\r\n/)?.[0] || "鏈壘鍒�"
+    );
+    console.log(
+      "鍘熷缂栬В鐮佸櫒鍒楄〃:",
+      originalOffer.sdp.match(/a=rtpmap:\d+.*\r\n/g) || []
+    );
+    console.log(
+      "鏍囧噯鍖栧悗缂栬В鐮佸櫒鍒楄〃:",
+      normalizedOffer.sdp.match(/a=rtpmap:\d+.*\r\n/g) || []
+    );
+    console.groupEnd();
+  }
+
+  // 鍦� setupPeerConnection 鏂规硶涓泦鎴愯皟璇曞姛鑳�
+  setupPeerConnection(session) {
+    session.on("peerconnection", (pc) => {
+      const originalCreateOffer = pc.createOffer.bind(pc);
+
+      pc.createOffer = async (offerOptions) => {
+        try {
+          const offer = await originalCreateOffer(offerOptions);
+          const normalizedOffer = this.normalizeSDP(offer);
+
+          // 璋冭瘯淇℃伅杈撳嚭
+          this.debugSDPComparison(offer, normalizedOffer, "Offer鍒涘缓闃舵");
+
+          return normalizedOffer;
+        } catch (error) {
+          console.error("鍒涘缓Offer澶辫触:", error);
+          throw error;
+        }
+      };
+    });
+  }
   handleCallFailure(e, reject) {
+    if (e.response?.status_code === 422) {
+      const serverMinSE = e.response.headers["Min-SE"]?.[0]?.raw || "鏈煡";
+      console.error(`鏈嶅姟鍣ㄨ姹� Min-SE 鈮� ${serverMinSE}锛屽綋鍓嶈缃�: 120`);
+    }
     console.error("鍛煎彨澶辫触璇︽儏:", {
       cause: e.cause,
       message: e.message,
-      response: e.response && e.response.status_code
+      response: e.response && {
+        status: e.response.status_code,
+        reason: e.response.reason_phrase,
+      },
     });
 
     let errorMessage = "鍛煎彨澶辫触";
-    switch(e.cause) {
+    switch (e.cause) {
       case "Incompatible SDP":
         errorMessage = "濯掍綋鍗忓晢澶辫触锛岃妫�鏌ョ紪瑙g爜鍣ㄩ厤缃�";
         break;
@@ -165,11 +388,14 @@
       case "606":
         errorMessage = "瀵规柟璁惧涓嶆敮鎸佸綋鍓嶅獟浣撻厤缃�";
         break;
+      case "422":
+        errorMessage = "浼氳瘽鍙傛暟涓嶆弧瓒虫湇鍔″櫒瑕佹眰";
+        break;
       default:
-        errorMessage = `鍛煎彨澶辫触: ${e.cause || e.message}`;
+        errorMessage = `鍛煎彨澶辫触3: ${e.cause || e.message}`;
     }
 
-    this.updateCallStatus("failed2", errorMessage);
+    this.updateCallStatus("failed55", errorMessage);
     reject(new Error(errorMessage));
   }
 

--
Gitblit v1.9.3