目录

Go+Gin实现获取本机IP与访问IP

之前开发系统时,既要绑定本机指定IP启动服务,又要记录每个请求的访问来源IP,踩了不少坑——比如本机多网卡时拿到无效的回环地址,部署到服务器后因反向代理拿到的是代理IP而非真实访问IP。

今天把Go+Gin环境下的解决方案整理出来,覆盖各种场景和问题处理。

先明确两个核心场景的区别:本机IP是服务所在机器的网络地址,用于服务绑定、跨服务通信等;访问IP是发起请求的客户端地址,用于日志记录、权限校验等。

下面分别讲实现方法。

一、获取本机IP:处理多网卡+无效地址

直接调用系统方法可能拿到127.0.0.1(回环地址)或IPv6地址,实际开发中更需要“能被其他机器访问的IPv4地址”。

核心思路是:遍历所有网卡的网络地址,过滤掉回环地址、IPv6地址和无效地址,筛选出可用的本机IPv4。

完整代码如下:

package main

import (
    "fmt"
    "net"
    "strings"
)

// GetLocalIP 获取本机可用IPv4地址(非回环)
func GetLocalIP() (string, error) {
    // 获取所有网卡接口信息
    interfaces, err := net.Interfaces()
    if err != nil {
        return "", fmt.Errorf("获取网卡信息失败:%v", err)
    }

    // 遍历每个网卡
    for _, iface := range interfaces {
        // 跳过禁用的网卡
        if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
            continue // 网卡未启用或为回环网卡,跳过
        }

        // 获取网卡对应的所有地址
        addrs, err := iface.Addrs()
        if err != nil {
            return "", fmt.Errorf("获取网卡地址失败:%v", err)
        }

        // 遍历每个地址,筛选IPv4
        for _, addr := range addrs {
            // 转换为IP网络地址格式
            ipNet, ok := addr.(*net.IPNet)
            if !ok || ipNet.IP.IsLoopback() {
                continue // 不是IP网络地址或为回环地址,跳过
            }

            // 筛选IPv4地址(排除IPv6)
            if ip4 := ipNet.IP.To4(); ip4 != nil {
                return ip4.String(), nil
            }
        }
    }

    return "", fmt.Errorf("未找到可用的本机IPv4地址")
}

// GetAllLocalIP 获取所有本机IPv4地址(含多网卡场景)
func GetAllLocalIP() ([]string, error) {
    var ips []string
    interfaces, err := net.Interfaces()
    if err != nil {
        return nil, err
    }

    for _, iface := range interfaces {
        if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
            continue
        }

        addrs, err := iface.Addrs()
        if err != nil {
            return nil, err
        }

        for _, addr := range addrs {
            ipNet, ok := addr.(*net.IPNet)
            if !ok || ipNet.IP.IsLoopback() {
                continue
            }

            if ip4 := ipNet.IP.To4(); ip4 != nil {
                ips = append(ips, ip4.String())
            }
        }
    }

    if len(ips) == 0 {
        return nil, fmt.Errorf("未找到本机IPv4地址")
    }
    return ips, nil
}

func main() {
    // 测试获取单个可用IP
    localIP, err := GetLocalIP()
    if err != nil {
        fmt.Printf("获取单个本机IP失败:%v\n", err)
    } else {
        fmt.Printf("本机可用IP:%s\n", localIP)
    }

    // 测试获取所有IP(多网卡场景)
    allIPs, err := GetAllLocalIP()
    if err != nil {
        fmt.Printf("获取所有本机IP失败:%v\n", err)
    } else {
        fmt.Printf("本机所有IPv4地址:%v\n", allIPs)
    }
}
  1. 单IP场景:用GetLocalIP(),适合服务绑定指定IP启动(如Gin绑定非0.0.0.0的地址)
  2. 多网卡场景:用GetAllLocalIP(),比如服务器有内网和外网双网卡,需要展示所有可用IP时使用
  3. 过滤逻辑:代码中通过net.FlagUp判断网卡是否启用,通过To4()筛选IPv4,避免拿到无效地址

二、Gin获取访问IP:适配直连与反向代理

Gin获取访问IP的核心是解析请求的RemoteAddr字段,但线上环境通常有Nginx等反向代理,直接获取会拿到代理服务器IP,需通过代理头信息解析真实访问IP。

下面分两种场景实现。

(一)直连场景(无代理):简单获取

无反向代理时,请求直接打到Gin服务,通过c.ClientIP()或解析RemoteAddr即可获取,前者是Gin封装的便捷方法。

package main

import (
    "fmt"
    "net"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    // 接口1:用Gin封装方法获取
    r.GET("/get-client-ip1", func(c *gin.Context) {
        clientIP := c.ClientIP() // Gin封装,直接获取
        c.JSON(200, gin.H{
            "client_ip": clientIP,
            "method":    "Gin ClientIP()",
        })
    })

    // 接口2:手动解析RemoteAddr(备用方案)
    r.GET("/get-client-ip2", func(c *gin.Context) {
        // RemoteAddr格式:"IP:端口",需拆分出IP
        remoteAddr := c.Request.RemoteAddr
        ip, _, err := net.SplitHostPort(remoteAddr)
        if err != nil {
            c.JSON(500, gin.H{"error": fmt.Sprintf("解析IP失败:%v", err)})
            return
        }
        c.JSON(200, gin.H{
            "client_ip": ip,
            "method":    "解析RemoteAddr",
        })
    })

    _ = r.Run(":8080")
}

测试:本地用Postman访问http://localhost:8080/get-client-ip1,返回"client_ip":"127.0.0.1",符合预期。

(二)反向代理场景(Nginx等):解析真实IP

线上部署时,Gin通常放在Nginx等反向代理后,此时RemoteAddr是代理服务器IP,真实访问IP会放在X-Forwarded-ForX-Real-IP等请求头中。需先配置代理传递头信息,再在Gin中解析。

步骤1:配置反向代理(以Nginx为例)

在Nginx配置中添加请求头,将真实IP传递给Gin服务:

server {
    listen 80;
    server_name your-domain.com;

    location / {
        # 转发请求到Gin服务
        proxy_pass http://127.0.0.1:8080;
        # 传递真实访问IP到请求头
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr; # 真实访问IP
        # 多个代理时,记录所有代理IP(第一个为真实IP)
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

步骤2:Gin解析真实IP

Gin的ClientIP()默认会解析X-Forwarded-ForX-Real-IP,但需配置“信任的代理网段”,避免被伪造IP攻击。

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
    "net"
)

func main() {
    // 方式1:全局配置信任的代理网段(推荐)
    // 假设Nginx代理的IP是127.0.0.1,添加到信任列表
    gin.ForwardedByClientIP = true
    // 配置信任的代理IP段(支持单个IP或CIDR网段)
    trustedProxies := []string{"127.0.0.1", "192.168.1.0/24"}
    err := gin.SetTrustedProxies(trustedProxies)
    if err != nil {
        panic(fmt.Sprintf("配置信任代理失败:%v", err))
    }

    r := gin.Default()

    // 此时c.ClientIP()会自动解析真实IP
    r.GET("/get-real-ip", func(c *gin.Context) {
        realIP := c.ClientIP()
        // 也可手动解析X-Forwarded-For(适合自定义场景)
        xff := c.Request.Header.Get("X-Forwarded-For")
        c.JSON(200, gin.H{
            "real_ip":         realIP,
            "x_forwarded_for": xff,
            "note":            "X-Forwarded-For第一个IP为真实IP",
        })
    })

    // 方式2:局部中间件配置(适合单接口需求)
    r.GET("/get-real-ip-local", func(c *gin.Context) {
        // 手动获取X-Real-IP
        xRealIP := c.Request.Header.Get("X-Real-IP")
        if xRealIP != "" {
            c.Set("client_ip", xRealIP)
            c.Next()
            return
        }
        // 若X-Real-IP为空,解析X-Forwarded-For
        xff := c.Request.Header.Get("X-Forwarded-For")
        if xff != "" {
            // X-Forwarded-For格式:"真实IP,代理IP1,代理IP2"
            realIP := strings.Split(xff, ",")[0]
            c.Set("client_ip", realIP)
            c.Next()
            return
        }
        // 都为空时用默认方法
        c.Set("client_ip", c.ClientIP())
        c.Next()
    }, func(c *gin.Context) {
        clientIP, _ := c.Get("client_ip")
        c.JSON(200, gin.H{"real_ip": clientIP})
    })

    _ = r.Run(":8080")
}

测试:通过Nginx访问域名,返回的real_ip会是客户端的真实公网IP,而非127.0.0.1。

常见问题

Q1. 无论是否反向代理,Gin都拿不到请求IP?

这种情况可以使用自定义 header,前端在发起请求时设置一个预定的header,比如x-request-ip,将实际请求 ip 设置在该 header,后端 gin 通过该 header 进行获取

总结

Go+Gin获取IP的核心是“场景适配”:

获取本机IP要处理多网卡和无效地址,推荐用遍历筛选法;

获取访问IP要区分直连和反向代理场景,反向代理需配置代理头和信任列表。

关键避坑点:本机IP务必过滤回环地址,反向代理必须配置信任代理网段,多场景下可通过网卡名称或IP网段精准筛选。

如果大家遇到云服务器(阿里云、腾讯云)等特殊环境的IP获取问题,欢迎在评论区交流~

版权声明

未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!

本文原文链接: https://fiveyoboy.com/articles/go-gin-get-ip/

备用原文链接: https://blog.fiveyoboy.com/articles/go-gin-get-ip/