王妍芳 发表于 2025-6-2 22:29:40

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

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

一、背景与需求

1. 项目背景

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

2.1. 初始方案:通过代码获取本机 IP

/**
* 获取局域网 IP
* @returns {string} 局域网 IP
*/
export function getLocalIP(): string {
const interfaces = os.networkInterfaces()
for (const name of Object.keys(interfaces)) {
    for (const iface of interfaces || []) {
      if (iface.family === 'IPv4' && !iface.internal) {
      log.info('获取局域网 IP:', iface.address)
      return iface.address
      }
    }
}
log.warn('无法获取局域网 IP,使用默认 IP: 127.0.0.1')
return '127.0.0.1'
}2.2. 遇到的问题

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

1. 总体思路


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

使用 Node.js 的 os.networkInterfaces() 获取所有可用 IP
private getAllPossibleIPs(): string[] {
const interfaces = os.networkInterfaces()
const result: string[] = []

for (const name of Object.keys(interfaces)) {
    const lowerName = name.toLowerCase()
    if (lowerName.includes('vmware')
      || lowerName.includes('virtual')
      || lowerName.includes('vpn')
      || lowerName.includes('docker')
      || lowerName.includes('vethernet')) {
      continue
    }

    for (const iface of interfaces || []) {
      if (iface.family === 'IPv4' && !iface.internal) {
      result.push(iface.address)
      }
    }
}

return result
}3. 遍历 IP 请求验证

轮询所有 IP,尝试访问客户端服务,验证是否可用
private async testIPsParallel(ips: string[]): Promise<string | null> {
if (ips.length === 0)
    return null
return new Promise((resolve) => {
    const globalTimeout = setTimeout(() => {
      resolve(null)
    }, this.TIMEOUT * 1.5)

    const controllers = ips.map(() => new AbortController())
    let hasResolved = false
    let completedCount = 0

    const testIP = (ip: string, index: number) => {
      const controller = controllers
      axios.get(`http://${ip}:${PORT}/api/task-server/ip`, {
      timeout: this.TIMEOUT,
      signal: controller.signal,
      })
      .then(() => {
          if (!hasResolved) {
            hasResolved = true
            clearTimeout(globalTimeout)
            controllers.forEach((c, i) => {
            if (i !== index)
                c.abort()
            })
            resolve(ip)
          }
      })
      .catch(() => {
          if (!hasResolved) {
            completedCount++
            if (completedCount >= ips.length) {
            clearTimeout(globalTimeout)
            resolve(null)
            }
          }
      })
    }
    ips.forEach(testIP)
})
}4. 添加缓存策略

对成功的 IP 进行缓存,设定缓存有效时间,避免重复请求
private cachedValidIP: string | null = null
private lastValidationTime = 0
private readonly CACHE_VALID_DURATION = 24 * 60 * 60 * 1000三、完整代码

import os from 'node:os'
import axios from 'axios'
import { PORT } from '../../enum/env'

/**
* IP管理器单例类
* 用于获取并缓存本地有效IP地址
*/
export class IPManager {
private static instance: IPManager
private cachedValidIP: string | null = null
private lastValidationTime = 0
private readonly CACHE_VALID_DURATION = 24 * 60 * 60 * 1000
private readonly TIMEOUT = 200
private isTestingIPs = false

private constructor() {}

static getInstance(): IPManager {
    if (!IPManager.instance) {
      IPManager.instance = new IPManager()
    }
    return IPManager.instance
}

async getLocalIP(): Promise<string> {
    const now = Date.now()
    if (this.cachedValidIP && now - this.lastValidationTime < this.CACHE_VALID_DURATION) {
      console.log('从缓存中获取 IP', this.cachedValidIP)
      return this.cachedValidIP
    }

    if (this.isTestingIPs) {
      const allIPs = this.getAllPossibleIPs()
      return allIPs.length > 0 ? allIPs : '127.0.0.1'
    }
    this.isTestingIPs = true

    try {
      const allIPs = this.getAllPossibleIPs()
      if (allIPs.length === 0) {
      return '127.0.0.1'
      }

      const validIP = await this.testIPsParallel(allIPs)
      if (validIP) {
      this.cachedValidIP = validIP
      this.lastValidationTime = now
      return validIP
      }
      return allIPs
    }
    catch (error) {
      const allIPs = this.getAllPossibleIPs()
      return allIPs.length > 0 ? allIPs : '127.0.0.1'
    }
    finally {
      this.isTestingIPs = false
    }
}

private getAllPossibleIPs(): string[] {
    const interfaces = os.networkInterfaces()
    const result: string[] = []

    for (const name of Object.keys(interfaces)) {
      const lowerName = name.toLowerCase()
      if (lowerName.includes('vmware')
      || lowerName.includes('virtual')
      || lowerName.includes('vpn')
      || lowerName.includes('docker')
      || lowerName.includes('vethernet')) {
      continue
      }

      for (const iface of interfaces || []) {
      if (iface.family === 'IPv4' && !iface.internal) {
          result.push(iface.address)
      }
      }
    }

    return result
}

private async testIPsParallel(ips: string[]): Promise<string | null> {
    if (ips.length === 0)
      return null
    return new Promise((resolve) => {
      const globalTimeout = setTimeout(() => {
      resolve(null)
      }, this.TIMEOUT * 1.5)

      const controllers = ips.map(() => new AbortController())
      let hasResolved = false
      let completedCount = 0

      const testIP = (ip: string, index: number) => {
      const controller = controllers
      axios.get(`http://${ip}:${PORT}/api/task-server/ip`, {
          timeout: this.TIMEOUT,
          signal: controller.signal,
          // validateStatus: status => status === 200,
      })
          .then(() => {
            if (!hasResolved) {
            hasResolved = true
            clearTimeout(globalTimeout)
            controllers.forEach((c, i) => {
                if (i !== index)
                  c.abort()
            })
            resolve(ip)
            }
          })
          .catch(() => {
            if (!hasResolved) {
            completedCount++
            if (completedCount >= ips.length) {
                clearTimeout(globalTimeout)
                resolve(null)
            }
            }
          })
      }
      ips.forEach(testIP)
    })
}
}

/**
* 获取本地有效IP地址
*/
export async function getLocalIP(): Promise<string> {
return IPManager.getInstance().getLocalIP()
}
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: Electron 开发:获取当前客户端 IP