English 简体中文 繁體中文 한국 사람 日本語 Deutsch русский بالعربية TÜRKÇE português คนไทย french
查看: 9|回复: 0

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

[复制链接]
查看: 9|回复: 0

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

[复制链接]
查看: 9|回复: 0

245

主题

0

回帖

745

积分

高级会员

积分
745
fGklZUdWXn5r

245

主题

0

回帖

745

积分

高级会员

积分
745
2025-4-11 11:45:10 | 显示全部楼层 |阅读模式
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[name] || []) {      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[name] || []) {      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[index]      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 = nullprivate lastValidationTime = 0private 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[0] : '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[0]    }    catch (error) {      const allIPs = this.getAllPossibleIPs()      return allIPs.length > 0 ? allIPs[0] : '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[name] || []) {        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[index]        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()}
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

245

主题

0

回帖

745

积分

高级会员

积分
745

QQ|智能设备 | 粤ICP备2024353841号-1

GMT+8, 2025-5-2 13:40 , Processed in 1.663621 second(s), 21 queries .

Powered by 智能设备

©2025