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. 项目PMP之十一项目风险管理

    项目PMP之十一--项目风险管理   一.定义:削弱负面风险,增强正面风险,将风险敞口保持在可接受的范围,扩大项目实现的概率 非事件类风险:变异性风险,已规划的不确定性(通过蒙特卡洛分析,缩小结果区间 ...

  2. MySQL---锁、变量、存储过程、游标、自定义函数

    一概述 数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则.对于任何一种数据库来说都需要有相应的锁定机制. MySQL各存储引擎使用了三种类型 ...

  3. 基于FATE的可验证秘密分享算法详解及应用场景分享:学习

    内容来自"光大科技-基于FATE的可验证秘密分享算法详解及应用场景分享" 理论 基于Shamir的秘密共享方案,通过多项式插值实现. 加入可验证功能,即发送多项式系数的模数给对方作 ...

  4. kNN(K- Nearest Neighbor)基本原理

  5. ORACLE事物隔离级别和脏读、幻读、不可重复读区别

    一.事务和隔离级别 事务的概念:事务是把对数据库的一系列操作都看做一个整体,要么全部成功,要么全部失败,利用事务我们可以保证数据库的完整性,事务具有原子性. 隔离级别:隔离级别定义了事务与事务之间的隔 ...

  6. 一个WPF下的虚拟键盘实现

    给上位机触摸屏做一个虚拟键盘,这玩意儿不就是一排的网格里面放满button嘛 .说归这样说 依然还是有一堆细节需要你去处理的.不论如何 先画个键盘吧. 简单的从网上找个键盘位图做参照使用 4行Grid ...

  7. .NET 进程 stackoverflow异常后,还可以接收 TCP 连接请求吗?

    昨天线上有几个进程因为 StackOverFlowException 导致进程 Crash 了,但是 TCP 请求还是可以连接,具体可不可以连接一个出现StackOverFlowException的微 ...

  8. Iceberg问题记录-数据湖问题记录跟进

    一.问题追踪 问题 详细描述 提出问题时间 是否完成 计划完成时间 备注 了解Iceberg数据存储方式 了解元数据存储信息.数据组织方式.查询时处理流程等 20231013 是 20231019 ! ...

  9. Hetao P1156 最大战力 题解 [ 绿 ][ 二分 ][ 最大子段和 ]

    最大战力 Vjudge 原题 题解 形式化题意 给定两个数组 \(a[n]\) 和 \(b[n]\) ,需要在数组 \(b\) 中选择一个区间 \(b[l,r]\) ,替换掉区间 \(a[l,r]\) ...

  10. FANUC发那科工业机器人减速器维修小细节

    在现代工业生产中,FANUC发那科机器人已成为不可或缺的一部分.然而,随着时间的推移,发那科机械手减速器可能会出现故障,影响机器人的正常工作. 一.了解减速器的结构与工作原理 在开始FANUC发那科机 ...