| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162 |
- /**
- * 取本机对外 IP —— 用于 DMS 的**访客归属**(用户 2026-09-22 要求)。
- *
- * 接口:`GET {VITE_IP_API}/proxy/verify/getIPByBrowser`
- * 返回**纯文本 IP**(响应头 Content-Type 标的是 `application/json`,但正文就是裸 IP,
- * 实测 `116.232.248.128`)—— 所以**按 text 读、不要 JSON.parse**。
- *
- * ⚠️ **必须走同源代理**:接口是 `http://121.43.55.7:10011`,而页面是 HTTPS。
- * 接口本身带 `Access-Control-Allow-Origin: *`(跨域不成问题),但**混合内容**会被浏览器
- * 直接拦掉 —— 与聊天后端同一类问题。所以 dev 走 vite 代理(`vite.config.ts` 的 `/ip-api/`),
- * 生产需要 nginx 等价转发。
- *
- * 拿不到(没配代理 / 超时 / 返回不是 IP)一律返回空串、**不抛异常**:
- * 调用方会退回旧的「访客_<浏览器随机 id>」——**绝不能因为取不到 IP 就写不进 DMS**。
- */
- import { getClientIpApiBaseUrl } from "@/utils/runtime-config";
- /** 进程内缓存:一次会话里 IP 不会变,没必要每轮都问 */
- let cachedIp = "";
- /** 并发去重:多个写入同时要 IP 时只发一次请求 */
- let inflight: Promise<string> | null = null;
- /** 形如 `1.2.3.4` 才算数(IPv6 也放行:含冒号的裸地址) */
- const isIpLike = (text: string): boolean =>
- /^\d{1,3}(\.\d{1,3}){3}$/.test(text) || /^[0-9a-f:]{6,}$/i.test(text);
- /** 已经拿到的 IP(没拿到就是空串)。同步可用,给不方便 await 的地方读缓存。 */
- export const getCachedClientIp = (): string => cachedIp;
- /** 取 IP(并发调用只发一次请求;失败返回空串,下次调用会重试) */
- export const ensureClientIp = (): Promise<string> => {
- if (cachedIp) return Promise.resolve(cachedIp);
- if (inflight) return inflight;
- const base = getClientIpApiBaseUrl();
- if (!base) {
- return Promise.resolve(""); // 没配代理:静默退回随机 id,不打扰用户
- }
- inflight = (async () => {
- const controller = new AbortController();
- const timer = setTimeout(() => controller.abort(), 3000);
- try {
- const res = await fetch(`${base}/proxy/verify/getIPByBrowser`, { signal: controller.signal });
- const text = (await res.text()).trim();
- if (isIpLike(text)) {
- cachedIp = text;
- } else {
- console.warn("[client-ip] 返回的内容不像 IP,已忽略:", JSON.stringify(text.slice(0, 40)));
- }
- } catch (err) {
- console.warn("[client-ip] 取 IP 失败(DMS 访客归属退回浏览器随机 id):", (err as Error)?.message || err);
- } finally {
- clearTimeout(timer);
- inflight = null;
- }
- return cachedIp;
- })();
- return inflight;
- };
|