最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

golang實(shí)現(xiàn)通過ip地址解析城市信息

 更新時(shí)間:2026年03月04日 08:55:00   作者:子玖  
這篇文章主要為大家詳細(xì)介紹了如果使用golang實(shí)現(xiàn)通過ip地址解析城市信息,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

通過ip解析城市

環(huán)境:

  • Go 1.24.4+
  • ip2region 數(shù)據(jù)庫文件

功能特性

  • 自動(dòng)從 IP 解析城市信息
  • 支持 IPv4 和 IPv6 地址
  • 內(nèi)網(wǎng) IP 自動(dòng)跳過(不報(bào)錯(cuò))
  • 數(shù)據(jù)庫文件缺失時(shí)優(yōu)雅降級(jí)(服務(wù)可正常啟動(dòng))
  • 請(qǐng)求中可手動(dòng)指定 city,優(yōu)先級(jí)高于 IP 解析

準(zhǔn)備

下載ip2region數(shù)據(jù)庫文件

將下載的 ip2region_v4.xdb 文件放到項(xiàng)目目錄:

your-project/
├── data/
│   └── ip2region_v4.xdb    <-- 放在這里
├── config/
├── internal/
└── ...

配置說明

config/config.yaml:

ip:
  resolver:
    enabled: true              # 是否啟用 IP 解析
    db_path: "data/ip2region_v4.xdb"  # 數(shù)據(jù)文件路徑(服務(wù)器要在根目錄下例如:/f服務(wù)器根目錄/your-project/data/ip2region_v4.xdb)
    mode: "memory"             # 查詢模式:memory(內(nèi)存緩存) / file(文件讀取)

核心組件設(shè)計(jì)

整體架構(gòu)

┌─────────────────────────────────────────────────────────────┐
│                        API Layer                            │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  POST /api/v1/test(需要解析ip為city的接口)                      │   │
│  │  Handler: CreateLog                                 │   │
│  │  - 從 gin.Context 獲取 IP                           │   │
│  │  - 調(diào)用 Service.CreateDeviceLog                     │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────┬───────────────────────────────────┘
                          │
┌─────────────────────────▼───────────────────────────────────┐
│                      Service Layer                          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  UserDeviceLogService.CreateDeviceLog               │   │
│  │  - 接收 IP 字符串參數(shù)                               │   │
│  │  - 調(diào)用 IPResolver.Resolve(ip) 獲取城市           │   │
│  │  - 組裝數(shù)據(jù),調(diào)用 Repository 保存                   │   │
│  └─────────────────────────────────────────────────────┘   │
│                          │                                  │
│  ┌───────────────────────▼─────────────────────────────┐   │
│  │  IPResolver (internal/pkg/ip/resolver.go)           │   │
│  │  - 加載 ip2region.xdb 數(shù)據(jù)文件                      │   │
│  │  - 提供 Resolve(ip string) (city string, err error) │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────┬───────────────────────────────────┘
                          │
┌─────────────────────────▼───────────────────────────────────┐
│                    Repository Layer                         │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  UserDeviceLogRepository.Create                     │   │
│  │  - 保存設(shè)備日志到數(shù)據(jù)庫                             │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

模塊劃分

internal/
├── infra/
│   └── ip/
│       ├── resolver.go                 # IP 解析器接口和實(shí)現(xiàn)
│       ├── util.go                     # IP 工具函數(shù)
│       └── provider.go                 # Wire Provider
└── modules/
    └── test/
        ├── api/v1/
        │   └── test.go      # Handler 層
        ├── service/
        │   └── test_service.go  # Service 層
        └── repository/
            └── test_repository.go # Repository 層

核心代碼設(shè)計(jì)

調(diào)用時(shí)序圖

infra/ip 模塊

// internal\infra\ip\provider.go
package ip

import "github.com/google/wire"

// ProviderSet IP解析器依賴注入集合
var ProviderSet = wire.NewSet(
	NewResolver,
)

// internal\infra\ip\resolver.go

// Package ip 提供IP地址解析功能
package ip

import (
	"context"
	"net"
	"strings"

	"go-api/config"
	"go-api/pkg/logx"

	"github.com/lionsoul2014/ip2region/binding/golang/xdb"
)

// Resolver IP解析器接口
type Resolver interface {
	// Resolve 根據(jù)IP地址解析城市信息
	// 返回空字符串表示無法解析(內(nèi)網(wǎng)IP、IPv6、查詢失?。?
	Resolve(ctx context.Context, ip string) string

	// Close 關(guān)閉解析器,釋放資源
	Close()
}

// ip2regionResolver 基于ip2region的實(shí)現(xiàn)
type ip2regionResolver struct {
	searcher *xdb.Searcher
}

// NewResolver 創(chuàng)建IP解析器
// 如果配置文件未啟用或數(shù)據(jù)文件不存在,返回nil(不報(bào)錯(cuò),服務(wù)可正常啟動(dòng))
func NewResolver(cfg *config.Config) (Resolver, error) {
	if !cfg.IP.Resolver.Enabled {
		logx.Default.Info("IP解析器已禁用")
		return nil, nil
	}

	dbPath := cfg.IP.Resolver.DBPath
	if dbPath == "" {
		logx.Default.Warn("IP解析器數(shù)據(jù)文件路徑未配置,IP解析功能將不可用")
		return nil, nil
	}

	// 從數(shù)據(jù)文件加載頭部信息,獲取版本
	header, err := xdb.LoadHeaderFromFile(dbPath)
	if err != nil {
		logx.Default.Warn("加載IP數(shù)據(jù)庫頭部失敗,IP解析功能將不可用",
			"db_path", dbPath,
			"error", err,
		)
		return nil, nil
	}

	// 從頭部獲取版本信息
	version, err := xdb.VersionFromHeader(header)
	if err != nil {
		logx.Default.Warn("獲取IP數(shù)據(jù)庫版本失敗,IP解析功能將不可用",
			"db_path", dbPath,
			"error", err,
		)
		return nil, nil
	}

	// 創(chuàng)建 searcher(使用文件模式,根據(jù)數(shù)據(jù)文件版本)
	searcher, err := xdb.NewWithFileOnly(version, dbPath)
	if err != nil {
		logx.Default.Warn("加載IP數(shù)據(jù)庫失敗,IP解析功能將不可用",
			"db_path", dbPath,
			"error", err,
		)
		return nil, nil
	}

	logx.Default.Info("IP解析器初始化成功",
		"db_path", dbPath,
		"mode", cfg.IP.Resolver.Mode,
		"version", version.Name,
	)

	return &ip2regionResolver{
		searcher: searcher,
	}, nil
}

// Resolve 解析IP獲取城市信息
func (r *ip2regionResolver) Resolve(ctx context.Context, ip string) string {
	if r == nil || r.searcher == nil {
		return ""
	}

	// 清洗IP地址
	ip = cleanIP(ip)
	if ip == "" {
		return ""
	}

	// 檢查是否為內(nèi)網(wǎng)IP
	if isPrivateIP(ip) {
		logx.G(ctx).Debug("內(nèi)網(wǎng)IP,跳過解析", "ip", ip)
		return ""
	}

	// 將IP轉(zhuǎn)換為字節(jié)數(shù)組
	ipBytes, err := xdb.ParseIP(ip)
	if err != nil {
		logx.G(ctx).Warn("IP地址解析失敗", "ip", ip, "error", err)
		return ""
	}

	// 查詢ip2region
	region, err := r.searcher.Search(ipBytes)
	if err != nil {
		logx.G(ctx).Warn("IP解析失敗", "ip", ip, "error", err)
		return ""
	}

	// 解析結(jié)果格式:國(guó)家|區(qū)域|省份|城市|ISP
	city := extractCity(region)
	logx.G(ctx).Debug("IP解析成功", "ip", ip, "city", city, "region", region)

	return city
}

// Close 關(guān)閉解析器
func (r *ip2regionResolver) Close() {
	if r != nil && r.searcher != nil {
		r.searcher.Close()
	}
}

// cleanIP 清洗IP地址
// - 去除端口信息 [::1]:8080 -> ::1, 192.168.1.1:8080 -> 192.168.1.1
// - 處理IPv6映射地址 ::ffff:192.168.1.1 -> 192.168.1.1
func cleanIP(ip string) string {
	if ip == "" {
		return ""
	}

	// 使用 net.SplitHostPort 分離 IP 和端口
	host, _, err := net.SplitHostPort(ip)
	if err == nil {
		// 成功分離,使用 host 部分
		ip = host
	}
	// 如果分離失?。]有端口),保持原樣

	// 處理IPv6映射的IPv4地址 ::ffff:192.168.1.1
	if strings.HasPrefix(ip, "::ffff:") {
		ip = ip[7:]
	}

	return ip
}

// isPrivateIP 檢查是否為內(nèi)網(wǎng)IP
func isPrivateIP(ip string) bool {
	parsedIP := net.ParseIP(ip)
	if parsedIP == nil {
		return false
	}

	// 檢查IPv4私有地址段
	privateRanges := []string{
		"10.0.0.0/8",     // 10.0.0.0 - 10.255.255.255
		"172.16.0.0/12",  // 172.16.0.0 - 172.31.255.255
		"192.168.0.0/16", // 192.168.0.0 - 192.168.255.255
		"127.0.0.0/8",    // 127.0.0.0 - 127.255.255.255
		"169.254.0.0/16", // 鏈路本地地址
	}

	for _, cidr := range privateRanges {
		_, ipNet, err := net.ParseCIDR(cidr)
		if err != nil {
			continue
		}
		if ipNet.Contains(parsedIP) {
			return true
		}
	}

	return false
}

// extractCity 從ip2region結(jié)果中提取省份
// 格式:國(guó)家|省份|城市|ISP|國(guó)家代碼
// 示例:中國(guó)|廣東省|深圳市|電信|CN
func extractCity(region string) string {
	logx.Default.Info("ip2region返回的region", "region", region)
	if region == "" {
		return ""
	}

	parts := strings.Split(region, "|")
	if len(parts) < 2 {
		return ""
	}

	// 省份是第2個(gè)字段(索引1)
	province := parts[1]

	return province
}

// internal\infra\ip\util.go

package ip

import (
	"strings"

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

// GetClientIP 從gin.Context獲取客戶端真實(shí)IP
// 優(yōu)先級(jí):X-Forwarded-For → X-Real-IP → RemoteAddr
func GetClientIP(c *gin.Context) string {
	// 1. 嘗試從 X-Forwarded-For 獲取
	if xff := c.GetHeader("X-Forwarded-For"); xff != "" {
		if ip := parseXForwardedFor(xff); ip != "" {
			return ip
		}
	}

	// 2. 嘗試從 X-Real-IP 獲取
	if xri := c.GetHeader("X-Real-IP"); xri != "" {
		if ip := cleanIP(xri); ip != "" {
			return ip
		}
	}

	// 3. 從 RemoteAddr 獲取
	if ip := cleanIP(c.Request.RemoteAddr); ip != "" {
		return ip
	}

	return ""
}

// parseXForwardedFor 解析X-Forwarded-For頭
// 格式:client, proxy1, proxy2,取第一個(gè)有效IP
func parseXForwardedFor(header string) string {
	if header == "" {
		return ""
	}

	// 按逗號(hào)分割,取第一個(gè)IP
	parts := strings.Split(header, ",")
	for _, part := range parts {
		ip := strings.TrimSpace(part)
		if ip = cleanIP(ip); ip != "" {
			return ip
		}
	}

	return ""
}

handler和service中使用 獲得ip

// api\test\v1\test_handler.go
package v1

import (
	"go-api/internal/infra/ip"
)

// 從gin.Context獲取客戶端真實(shí)IP
func (h *UserDeviceLogHandler) GetClientIP(c *gin.Context) string {
	// 獲取客戶端真實(shí)IP
	clientIP := ip.GetClientIP(c)
	resp, err := h.logService.CreateLog(c, userID, clientIP, &req)
	if err != nil {
		logx.G(c).Error("創(chuàng)建設(shè)備日志失敗", "error", err)
		return nil
	}
	return resp
}
// internal\modules\test\service\test_service.go
func (s *userDeviceLogService) CreateLog(ctx context.Context, userID int64, clientIP string, req *v1.CreateUserDeviceLogReq) (*v1.UserDeviceLogResp, error) {

    // 解析IP獲取城市
    city := s.ipResolver.Resolve(clientIP)
    if city == "" {
        // 如果解析失敗,保持?jǐn)?shù)據(jù)庫字段為空
        city = nil
    }
    // 后面的處理我省略了
}

本地測(cè)試

  • 下載 ip2region_v4.xdb 文件到 data/ 目錄
  • 確保配置 enabled: true
  • 啟動(dòng)服務(wù):go run main.go
  • 使用 Postman 測(cè)試,注意:
    • 本地 ::1127.0.0.1 無法解析城市(內(nèi)網(wǎng) IP)
    • 需要公網(wǎng) IP 才能解析出城市

到此這篇關(guān)于golang實(shí)現(xiàn)通過ip地址解析城市信息的文章就介紹到這了,更多相關(guān)golang解析ip地址內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang 類型轉(zhuǎn)換的實(shí)現(xiàn)(斷言、強(qiáng)制、顯式類型)

    Golang 類型轉(zhuǎn)換的實(shí)現(xiàn)(斷言、強(qiáng)制、顯式類型)

    將一個(gè)值從一種類型轉(zhuǎn)換到另一種類型,便發(fā)生了類型轉(zhuǎn)換,在go可以分為斷言、強(qiáng)制、顯式類型轉(zhuǎn)換,本文就詳細(xì)的介紹一下這就幾種轉(zhuǎn)換方式,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-09-09
  • golang實(shí)現(xiàn)java uuid的序列化方法

    golang實(shí)現(xiàn)java uuid的序列化方法

    這篇文章主要介紹了golang實(shí)現(xiàn)java uuid的序列化方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • 如何在VScode 中編譯多個(gè)Go文件

    如何在VScode 中編譯多個(gè)Go文件

    這篇文章主要介紹了VScode 中編譯多個(gè)Go文件的實(shí)現(xiàn)方法,本文通過實(shí)例圖文并茂的形式給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2021-08-08
  • golang中struct和[]byte的相互轉(zhuǎn)換示例

    golang中struct和[]byte的相互轉(zhuǎn)換示例

    這篇文章主要介紹了golang中struct和[]byte的相互轉(zhuǎn)換示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-07-07
  • GoLang sync.Pool簡(jiǎn)介與用法

    GoLang sync.Pool簡(jiǎn)介與用法

    這篇文章主要介紹了GoLang sync.Pool簡(jiǎn)介與用法,Pool是可伸縮、并發(fā)安全的臨時(shí)對(duì)象池,用來存放已經(jīng)分配但暫時(shí)不用的臨時(shí)對(duì)象,通過對(duì)象重用機(jī)制,緩解GC壓力,提高程序性能
    2023-01-01
  • GO語言實(shí)現(xiàn)TCP服務(wù)器的示例代碼

    GO語言實(shí)現(xiàn)TCP服務(wù)器的示例代碼

    這篇文章主要為大家詳細(xì)介紹了如何通過GO語言實(shí)現(xiàn)TCP服務(wù)器,文中的示例代碼講解詳細(xì),對(duì)我們深入了解Go語言有一定的幫助,需要的可以參考一下
    2023-03-03
  • golang優(yōu)化目錄遍歷的實(shí)現(xiàn)方法

    golang優(yōu)化目錄遍歷的實(shí)現(xiàn)方法

    對(duì)于go1.16的新變化,大家印象最深的可能是io包的大規(guī)模重構(gòu),但這個(gè)重構(gòu)實(shí)際上還引進(jìn)了一個(gè)優(yōu)化,這篇文章要說的就是這個(gè)優(yōu)化,所以本將給大家介紹golang是如何優(yōu)化目錄遍歷的,需要的朋友可以參考下
    2024-08-08
  • Go語言中defer語句的用法

    Go語言中defer語句的用法

    這篇文章介紹了Go語言中defer語句的用法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-07-07
  • Go和RabbitMQ構(gòu)建高效的消息隊(duì)列系統(tǒng)

    Go和RabbitMQ構(gòu)建高效的消息隊(duì)列系統(tǒng)

    本文主要介紹了使用Go語言和RabbitMQ搭建一個(gè)簡(jiǎn)單的消息隊(duì)列系統(tǒng),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-01-01
  • Go語言編譯程序從后臺(tái)運(yùn)行,不出現(xiàn)dos窗口的操作

    Go語言編譯程序從后臺(tái)運(yùn)行,不出現(xiàn)dos窗口的操作

    這篇文章主要介紹了Go語言編譯程序從后臺(tái)運(yùn)行,不出現(xiàn)dos窗口的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04

最新評(píng)論

建昌县| 南康市| 同心县| 博湖县| 榆社县| 武鸣县| 夏河县| 勃利县| 大冶市| 丰镇市| 东兰县| 介休市| 资源县| 郑州市| 尼木县| 昔阳县| 衡南县| 子长县| 孙吴县| 商丘市| 安龙县| 阿克苏市| 修水县| 新巴尔虎左旗| 瑞安市| 金溪县| 蕲春县| 岑巩县| 于都县| 原平市| 集贤县| 论坛| 封开县| 赞皇县| 和硕县| 雅江县| 洞口县| 密山市| 清镇市| 安达市| 潍坊市|