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[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的更多相关文章
- ABP vNext 审计日志获取真实客户端IP
背景 在使用ABP vNext时,当需要记录审计日志时,我们按照https://docs.abp.io/zh-Hans/abp/latest/Audit-Logging配置即可开箱即用,然而在实际生产 ...
- nginx+tomcat集群配置(3)---获取真实客户端IP
前言: 在初步构建的nginx+tomcat服务集群时, 发现webserver获取到的客户端ip都是同一个, 皆为作为反向代理服务的nginx所在的机器IP. 这不太符合我们的基本需求, 为将来的数 ...
- nginx做反向负载均衡,后端服务器获取真实客户端ip(转)
首先,在前端nginx上需要做如下配置: location / proxy_set_hearder host $host; proxy_set_header X-forw ...
- 根据Request获取真实客户端IP
转载:http://www.cnblogs.com/icerainsoft/p/3584532.html 在JSP里,获取客户端的IP地址的方法是:request.getRemoteAddr() ,这 ...
- tornado 反向代理后 获取真实客户端IP
首先,nginx必定会设置一个Header传送过来真实的IP nginx.conf server { proxy_set_header X-Real-IP $remote_addr; location ...
- nginx 部署 .net core 获取的客户端ip为127.0.0.1
采用nginx和.net core 部署一套api接口到服务器上,发现获取到的ip地址为127.0.0.1 经过检查发现,需要在nginx配置上以下参数 proxy_set_header Host $ ...
- nginx设置反向代理,获取真实客户端ip
upstream这个模块提供一个简单方法来实现在轮询和客户端IP之间的后端服务器负荷平衡. upstream abc.com { server 127.0.0.1:8080; server 127.0 ...
- Vue实战041:获取当前客户端IP地址详解(内网和外网)
前言 我们经常会有需求,希望能获取的到当前用户的IP地址,而IP又分为公网ip(也称外网)和私网IP(也称内网IP),IP地址是IP协议提供的一种统一的地址格式,每台设备都设定了一个唯一的IP地址”, ...
- gin框架中设置信任代理IP并获取远程客户端IP
package main import ( "fmt" "github.com/gin-gonic/gin" ) func main() { gin.SetMo ...
- 使用electron开发指静脉客户端遇到的问题总结
使用electron 使用nodejs 的ffi模块调用dll文件 总结1.electron 与nodejs版本不需要一致,甚至nodejs版本应该高于electron的node版本2.要安装 Vis ...
随机推荐
- 深入理解第二范式(2NF):提升数据库设计的有效性与灵活性
title: 深入理解第二范式(2NF):提升数据库设计的有效性与灵活性 date: 2025/1/16 updated: 2025/1/16 author: cmdragon excerpt: 数据 ...
- 欧拉积分(Genshin)
\(\Gamma\) 函数 引入.定义 在计算组合数式子的时候,我们时常会看到这样的式子: \[\frac{(-2n)!((-n/2)!)^2}{((-n)!)^3} \] 然而,我们不知道什么是负数 ...
- 数据同步-同步mysql到iceberg后如何确定数据一致性
一.数据打快照做数据比较 1.mysql创建快照 优点:可以选择时间做快照,然后对比 缺点:需要额外的存储空间和处理时间,不好自动化,大表做快照成本高 2.实现方式 create database 快 ...
- 接口响应指标的p99、p95、p50到底是什么?
一.简介 我们对服务响应时间的衡量指标有Min(最小响应时间).Max(最大响应时间).Avg(平均响应时间)等,P99.P90也是衡量指标 二.指标简介 1.平均值Avg 其中比较常用的值就是平均值 ...
- Linux目录管理命令
1. pwd :显示当前所在目录的路径 1.1 语法格式 pwd #直接按回车键 1.2 实践案例 案例:查看当前所在目录路径 [root@yyds ~]# pwd /root --->显示的是 ...
- 并发编程 - 线程同步(八)之自旋锁SpinLock
前面对互斥锁Monitor进行了详细学习,今天我们将继续学习,一种更轻量级的锁--自旋锁SpinLock. 在 C# 中,SpinLock是一个高效的自旋锁实现,用于提供一种轻量级的锁机制.SpinL ...
- [BZOJ3786] 星系探索 题解
题目链接:\(BZOJ\) 本题通过 \(dyf\_DYF\) 的题解理解 \(ETT\),代码则借鉴 \(lcyfrog\) 的题解,图片则使用了何太狼的题解.在此笔者感谢这三位神犇. 声明变量: ...
- cJSON解析器总结[转载]
一. 简介 cJson 是c语言编写的一个解析器. 是一个超轻巧,携带方便,单文件,简单的可以作为ANSI-C标准的JSON解析器.主要两个文件cJSON.c 和cJSON.h . 主要用来编码和解析 ...
- 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
你好呀,我是歪歪. 事情是这样的,前几天有一个读者给我发消息,说他面试的时候遇到一个奇形怪状的面试题. 歪师傅纵横面试界多年,最喜欢的是奇形怪状的面试题. 可以说是见过大场面的人,所以让他描述一下具体 ...
- 13. MySQL 事务基础知识(详细说明实操剖析)
13. MySQL 事务基础知识(详细说明实操剖析) @ 目录 13. MySQL 事务基础知识(详细说明实操剖析) 1. 数据库事务概述 1.1 存储引擎支持情况 1.2 事务基本概念 1.3 事务 ...