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 = 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[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()
}

Electron 开发:获取当前客户端 IP的更多相关文章

  1. ABP vNext 审计日志获取真实客户端IP

    背景 在使用ABP vNext时,当需要记录审计日志时,我们按照https://docs.abp.io/zh-Hans/abp/latest/Audit-Logging配置即可开箱即用,然而在实际生产 ...

  2. nginx+tomcat集群配置(3)---获取真实客户端IP

    前言: 在初步构建的nginx+tomcat服务集群时, 发现webserver获取到的客户端ip都是同一个, 皆为作为反向代理服务的nginx所在的机器IP. 这不太符合我们的基本需求, 为将来的数 ...

  3. nginx做反向负载均衡,后端服务器获取真实客户端ip(转)

    首先,在前端nginx上需要做如下配置: location / proxy_set_hearder host                $host; proxy_set_header X-forw ...

  4. 根据Request获取真实客户端IP

    转载:http://www.cnblogs.com/icerainsoft/p/3584532.html 在JSP里,获取客户端的IP地址的方法是:request.getRemoteAddr() ,这 ...

  5. tornado 反向代理后 获取真实客户端IP

    首先,nginx必定会设置一个Header传送过来真实的IP nginx.conf server { proxy_set_header X-Real-IP $remote_addr; location ...

  6. nginx 部署 .net core 获取的客户端ip为127.0.0.1

    采用nginx和.net core 部署一套api接口到服务器上,发现获取到的ip地址为127.0.0.1 经过检查发现,需要在nginx配置上以下参数 proxy_set_header Host $ ...

  7. nginx设置反向代理,获取真实客户端ip

    upstream这个模块提供一个简单方法来实现在轮询和客户端IP之间的后端服务器负荷平衡. upstream abc.com { server 127.0.0.1:8080; server 127.0 ...

  8. Vue实战041:获取当前客户端IP地址详解(内网和外网)

    前言 我们经常会有需求,希望能获取的到当前用户的IP地址,而IP又分为公网ip(也称外网)和私网IP(也称内网IP),IP地址是IP协议提供的一种统一的地址格式,每台设备都设定了一个唯一的IP地址”, ...

  9. gin框架中设置信任代理IP并获取远程客户端IP

    package main import ( "fmt" "github.com/gin-gonic/gin" ) func main() { gin.SetMo ...

  10. 使用electron开发指静脉客户端遇到的问题总结

    使用electron 使用nodejs 的ffi模块调用dll文件 总结1.electron 与nodejs版本不需要一致,甚至nodejs版本应该高于electron的node版本2.要安装 Vis ...

随机推荐

  1. 利用bash脚本函数执行创建用户和组,并设置sudo权限等

    示例:利用bash脚本函数执行创建用户和组,并设置sudo权限等: Linux服务器设置历史命令记录,及命令执行的时间: sudo echo 'HISTTIMEFORMAT="%F %T w ...

  2. DeepSeek,你是懂.NET的!

    这两天火爆出圈的话题,除了过年,那一定是DeepSeek!你是否也被刷屏了? DeepSeek 是什么 DeepSeek是一款由国内人工智能公司研发的大型语言模型,拥有强大的自然语言处理能力,能够理解 ...

  3. RAW镜像格式介绍

    本文分享自天翼云开发者社区<RAW镜像格式介绍>,作者:z****n RAW(Raw Disk Image)是一种简单而基本的虚拟化镜像格式,用于存储虚拟机的磁盘内容.它是一种原始的二进制 ...

  4. 问一下,利用在线 DeepSeek 等 API 服务实现一个答题 APP

    简介 这是一个利用 Android 无障碍功能 + 悬浮窗 + 大模型的搜题应用 原理就是利用无障碍读取屏幕内容,然后通过悬浮窗来显示答案 众所周知我是一个学渣,所以在搜答案方面颇有成就 大概是在 4 ...

  5. linux安装spark

    1.首先在官网下载http://spark.apache.org/downloads.html, 选择与hadoop对应的版本,对应关系为:spark1.6.2--scala2.10:spark2.0 ...

  6. datax从mysql迁移数据到OceanBase

    datax部署 下载datax datax下载地址 安装datax tar -zxvf datax.tar.gz 使用datax 使用配置文件 { "job": { "s ...

  7. Docker容器访问挂载文件权限问题

    问题描述 在使用docker-compose部署项目时,yaml文件如下: version: '3' services: purchasing-contract-consumer: image: my ...

  8. vue+elementUI当渲染文本超出一定字数时显示省略号

    如图,当渲染的文字超出30字后显示省略号 1.设置过滤器 filters: { ellipsis(value) { if (!value) return ""; if (value ...

  9. 【软件开发】C++使用笔记

    [软件开发]C++使用笔记 数据类型 值类型 存放在栈空间中的一段内存. T:左值,最普通的变量,是具有变量名且可取地址的值. \(~\) :右值,常量或不具备名称的值,无变量名不可取地址.通常都是一 ...

  10. 绝对荧光定量pcr结果计算

    绘制标准曲线 首先根据公式,计算出标准品曲线的x值(拷贝数),需要修改的数值是标准品质粒浓度(图上的321)和4565(基因片段长度),得出的结果单位是拷贝数/μl. 根据计算结果,可以列出表格,样品 ...