找回密码
 立即注册
首页 业界区 业界 Electron 开发:获取当前客户端 IP

Electron 开发:获取当前客户端 IP

王妍芳 5 天前
Electron 开发:获取当前客户端 IP

一、背景与需求

1. 项目背景

客户端会自启动一个服务,Web/后端服务通过 IP + port 请求以操作客户端接口
2. 初始方案与问题

2.1. 初始方案:通过代码获取本机 IP
  1. /**
  2. * 获取局域网 IP
  3. * @returns {string} 局域网 IP
  4. */
  5. export function getLocalIP(): string {
  6.   const interfaces = os.networkInterfaces()
  7.   for (const name of Object.keys(interfaces)) {
  8.     for (const iface of interfaces[name] || []) {
  9.       if (iface.family === 'IPv4' && !iface.internal) {
  10.         log.info('获取局域网 IP:', iface.address)
  11.         return iface.address
  12.       }
  13.     }
  14.   }
  15.   log.warn('无法获取局域网 IP,使用默认 IP: 127.0.0.1')
  16.   return '127.0.0.1'
  17. }
复制代码
2.2. 遇到的问题

如果设备开启了代理,可能获取的是代理 IP,导致后端请求失败
二、解决方案设计

1. 总体思路


  • 获取本机所有 IP
  • 遍历 IP + port 请求客户端服务接口
  • 成功响应即为目标 IP
  • 缓存有效 IP,避免频繁请求
2. 获取所有可能的 IP

使用 Node.js 的 os.networkInterfaces() 获取所有可用 IP
  1. private getAllPossibleIPs(): string[] {
  2.   const interfaces = os.networkInterfaces()
  3.   const result: string[] = []
  4.   for (const name of Object.keys(interfaces)) {
  5.     const lowerName = name.toLowerCase()
  6.     if (lowerName.includes('vmware')
  7.       || lowerName.includes('virtual')
  8.       || lowerName.includes('vpn')
  9.       || lowerName.includes('docker')
  10.       || lowerName.includes('vethernet')) {
  11.       continue
  12.     }
  13.     for (const iface of interfaces[name] || []) {
  14.       if (iface.family === 'IPv4' && !iface.internal) {
  15.         result.push(iface.address)
  16.       }
  17.     }
  18.   }
  19.   return result
  20. }
复制代码
3. 遍历 IP 请求验证

轮询所有 IP,尝试访问客户端服务,验证是否可用
  1. private async testIPsParallel(ips: string[]): Promise<string | null> {
  2.   if (ips.length === 0)
  3.     return null
  4.   return new Promise((resolve) => {
  5.     const globalTimeout = setTimeout(() => {
  6.       resolve(null)
  7.     }, this.TIMEOUT * 1.5)
  8.     const controllers = ips.map(() => new AbortController())
  9.     let hasResolved = false
  10.     let completedCount = 0
  11.     const testIP = (ip: string, index: number) => {
  12.       const controller = controllers[index]
  13.       axios.get(`http://${ip}:${PORT}/api/task-server/ip`, {
  14.         timeout: this.TIMEOUT,
  15.         signal: controller.signal,
  16.       })
  17.         .then(() => {
  18.           if (!hasResolved) {
  19.             hasResolved = true
  20.             clearTimeout(globalTimeout)
  21.             controllers.forEach((c, i) => {
  22.               if (i !== index)
  23.                 c.abort()
  24.             })
  25.             resolve(ip)
  26.           }
  27.         })
  28.         .catch(() => {
  29.           if (!hasResolved) {
  30.             completedCount++
  31.             if (completedCount >= ips.length) {
  32.               clearTimeout(globalTimeout)
  33.               resolve(null)
  34.             }
  35.           }
  36.         })
  37.     }
  38.     ips.forEach(testIP)
  39.   })
  40. }
复制代码
4. 添加缓存策略

对成功的 IP 进行缓存,设定缓存有效时间,避免重复请求
  1. private cachedValidIP: string | null = null
  2. private lastValidationTime = 0
  3. private readonly CACHE_VALID_DURATION = 24 * 60 * 60 * 1000
复制代码
三、完整代码
  1. import os from 'node:os'
  2. import axios from 'axios'
  3. import { PORT } from '../../enum/env'
  4. /**
  5. * IP管理器单例类
  6. * 用于获取并缓存本地有效IP地址
  7. */
  8. export class IPManager {
  9.   private static instance: IPManager
  10.   private cachedValidIP: string | null = null
  11.   private lastValidationTime = 0
  12.   private readonly CACHE_VALID_DURATION = 24 * 60 * 60 * 1000
  13.   private readonly TIMEOUT = 200
  14.   private isTestingIPs = false
  15.   private constructor() {}
  16.   static getInstance(): IPManager {
  17.     if (!IPManager.instance) {
  18.       IPManager.instance = new IPManager()
  19.     }
  20.     return IPManager.instance
  21.   }
  22.   async getLocalIP(): Promise<string> {
  23.     const now = Date.now()
  24.     if (this.cachedValidIP && now - this.lastValidationTime < this.CACHE_VALID_DURATION) {
  25.       console.log('从缓存中获取 IP', this.cachedValidIP)
  26.       return this.cachedValidIP
  27.     }
  28.     if (this.isTestingIPs) {
  29.       const allIPs = this.getAllPossibleIPs()
  30.       return allIPs.length > 0 ? allIPs[0] : '127.0.0.1'
  31.     }
  32.     this.isTestingIPs = true
  33.     try {
  34.       const allIPs = this.getAllPossibleIPs()
  35.       if (allIPs.length === 0) {
  36.         return '127.0.0.1'
  37.       }
  38.       const validIP = await this.testIPsParallel(allIPs)
  39.       if (validIP) {
  40.         this.cachedValidIP = validIP
  41.         this.lastValidationTime = now
  42.         return validIP
  43.       }
  44.       return allIPs[0]
  45.     }
  46.     catch (error) {
  47.       const allIPs = this.getAllPossibleIPs()
  48.       return allIPs.length > 0 ? allIPs[0] : '127.0.0.1'
  49.     }
  50.     finally {
  51.       this.isTestingIPs = false
  52.     }
  53.   }
  54.   private getAllPossibleIPs(): string[] {
  55.     const interfaces = os.networkInterfaces()
  56.     const result: string[] = []
  57.     for (const name of Object.keys(interfaces)) {
  58.       const lowerName = name.toLowerCase()
  59.       if (lowerName.includes('vmware')
  60.         || lowerName.includes('virtual')
  61.         || lowerName.includes('vpn')
  62.         || lowerName.includes('docker')
  63.         || lowerName.includes('vethernet')) {
  64.         continue
  65.       }
  66.       for (const iface of interfaces[name] || []) {
  67.         if (iface.family === 'IPv4' && !iface.internal) {
  68.           result.push(iface.address)
  69.         }
  70.       }
  71.     }
  72.     return result
  73.   }
  74.   private async testIPsParallel(ips: string[]): Promise<string | null> {
  75.     if (ips.length === 0)
  76.       return null
  77.     return new Promise((resolve) => {
  78.       const globalTimeout = setTimeout(() => {
  79.         resolve(null)
  80.       }, this.TIMEOUT * 1.5)
  81.       const controllers = ips.map(() => new AbortController())
  82.       let hasResolved = false
  83.       let completedCount = 0
  84.       const testIP = (ip: string, index: number) => {
  85.         const controller = controllers[index]
  86.         axios.get(`http://${ip}:${PORT}/api/task-server/ip`, {
  87.           timeout: this.TIMEOUT,
  88.           signal: controller.signal,
  89.           // validateStatus: status => status === 200,
  90.         })
  91.           .then(() => {
  92.             if (!hasResolved) {
  93.               hasResolved = true
  94.               clearTimeout(globalTimeout)
  95.               controllers.forEach((c, i) => {
  96.                 if (i !== index)
  97.                   c.abort()
  98.               })
  99.               resolve(ip)
  100.             }
  101.           })
  102.           .catch(() => {
  103.             if (!hasResolved) {
  104.               completedCount++
  105.               if (completedCount >= ips.length) {
  106.                 clearTimeout(globalTimeout)
  107.                 resolve(null)
  108.               }
  109.             }
  110.           })
  111.       }
  112.       ips.forEach(testIP)
  113.     })
  114.   }
  115. }
  116. /**
  117. * 获取本地有效IP地址
  118. */
  119. export async function getLocalIP(): Promise<string> {
  120.   return IPManager.getInstance().getLocalIP()
  121. }
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册