当前位置: 首页 > news >正文

Go语言开发通过本地数据xdb文件​查询获取IP地址的归属地区及运营商名称

说明:

用本地数据,离线识别ip属地,用于显示用户ip属地,不依赖第三方的api接口,本地数据包解析,解析速度快10微秒级别的查询效率。返回数据固定格式:国家|区域|省份|城市|ISP,例如:中国|0|云南省|昆明市|移动。不同

特性:

1、IP 数据管理框架

​xdb​​ 支持亿级别的 IP 数据段行数,默认的 region 信息都固定了格式:​​国家|区域|省份|城市|ISP​​,缺省的地域信息默认是0。 region 信息支持完全自定义,例如:你可以在 region 中追加特定业务需求的数据,例如:GPS信息/国际统一地域信息编码/邮编等。也就是你完全可以使用 ipregion 来管理你自己的 IP 定位数据。

2、数据去重和压缩

​xdb​​ 格式生成程序会自动去重和压缩部分数据,默认的全部 IP 数据,生成的 ipregion.xdb 数据库是 11MiB,随着数据的详细度增加数据库的大小也慢慢增大。

3、极速查询响应

即使是完全基于 ​​xdb​​ 文件的查询,单次查询响应时间在十微秒级别,可通过如下两种方式开启内存加速查询:

  • ​vIndex​​ 索引缓存 :使用固定的 ​​512KiB​​ 的内存空间缓存 vector index 数据,减少一次 IO 磁盘操作,保持平均查询效率稳定在10-20微秒之间。
  • ​xdb​​​ 整个文件缓存:将整个 ​​xdb​​​ 文件全部加载到内存,内存占用等同于 ​​xdb​​ 文件大小,无磁盘 IO 操作,保持微秒级别的查询效率。

使用方法:

1.下封装解析插件和

  到这:下载代码地址,在右边的“代码下载”下载插件代码,并在下面的代码附件下载 下载“xdb数据文件”解压后,复制ipregion.xdb文件到resource/static目录下。

下载到的插件代码目录

├── plugin                       # 扩展插件目录
│   ├── ipregion                # “ip地址解析属地区”目录文件夹
│   │    ├── searcher.go       # 编写业务
│   │    └── util.go           # 工具函数        
│   └── ipregion.go             # “ip地址解析属地区”文件 用于app开发时调用

其中代码插件源码:

searcher.go文件的源码如:

// ---
// ipregion database v2.0 searcher.
// @Note this is a Not thread safe implementation.
//
// @Author gofly
// @Date   2024/08/19
package ipregionimport ("encoding/binary""fmt""os""path/filepath"
)const (HeaderInfoLength      = 256VectorIndexRows       = 256VectorIndexCols       = 256VectorIndexSize       = 8SegmentIndexBlockSize = 14
)// --- Index policy definetype IndexPolicy intconst (VectorIndexPolicy IndexPolicy = 1BTreeIndexPolicy  IndexPolicy = 2
)func (i IndexPolicy) String() string {switch i {case VectorIndexPolicy:return "VectorIndex"case BTreeIndexPolicy:return "BtreeIndex"default:return "unknown"}
}// --- Header definetype Header struct {// data []byteVersion       uint16IndexPolicy   IndexPolicyCreatedAt     uint32StartIndexPtr uint32EndIndexPtr   uint32
}func NewHeader(input []byte) (*Header, error) {if len(input) < 16 {return nil, fmt.Errorf("invalid input buffer")}return &Header{Version:       binary.LittleEndian.Uint16(input),IndexPolicy:   IndexPolicy(binary.LittleEndian.Uint16(input[2:])),CreatedAt:     binary.LittleEndian.Uint32(input[4:]),StartIndexPtr: binary.LittleEndian.Uint32(input[8:]),EndIndexPtr:   binary.LittleEndian.Uint32(input[12:]),}, nil
}// --- searcher implementationtype Searcher struct {handle *os.File// header infoheader  *HeaderioCount int// use it only when this feature enabled.// Preload the vector index will reduce the number of IO operations// thus speedup the search processvectorIndex []byte// content buffer.// running with the whole xdb file cachedcontentBuff []byte
}func baseNew(vIndex []byte, cBuff []byte) (*Searcher, error) {var err errorpath, _ := os.Getwd()dbFile := filepath.Join(path, "/resource/static/ipregion.xdb")// content buff firstif cBuff != nil {return &Searcher{vectorIndex: nil,contentBuff: cBuff,}, nil}// open the xdb binary filehandle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600)if err != nil {return nil, err}return &Searcher{handle:      handle,vectorIndex: vIndex,}, nil
}func NewWithFileOnly() (*Searcher, error) {return baseNew(nil, nil)
}func NewWithVectorIndex(vIndex []byte) (*Searcher, error) {return baseNew(vIndex, nil)
}func NewWithBuffer(cBuff []byte) (*Searcher, error) {return baseNew(nil, cBuff)
}func (s *Searcher) Close() {if s.handle != nil {err := s.handle.Close()if err != nil {return}}
}// GetIOCount return the global io count for the last search
func (s *Searcher) GetIOCount() int {return s.ioCount
}// SearchByStr find the region for the specified ip string
func (s *Searcher) SearchByStr(str string) (string, error) {ip, err := CheckIP(str)if err != nil {return "", err}return s.Search(ip)
}// Search find the region for the specified long ip
func (s *Searcher) Search(ip uint32) (string, error) {// reset the global ioCounts.ioCount = 0// locate the segment index block based on the vector indexvar il0 = (ip >> 24) & 0xFFvar il1 = (ip >> 16) & 0xFFvar idx = il0*VectorIndexCols*VectorIndexSize + il1*VectorIndexSizevar sPtr, ePtr = uint32(0), uint32(0)if s.vectorIndex != nil {sPtr = binary.LittleEndian.Uint32(s.vectorIndex[idx:])ePtr = binary.LittleEndian.Uint32(s.vectorIndex[idx+4:])} else if s.contentBuff != nil {sPtr = binary.LittleEndian.Uint32(s.contentBuff[HeaderInfoLength+idx:])ePtr = binary.LittleEndian.Uint32(s.contentBuff[HeaderInfoLength+idx+4:])} else {// read the vector index blockvar buff = make([]byte, VectorIndexSize)err := s.read(int64(HeaderInfoLength+idx), buff)if err != nil {return "", fmt.Errorf("read vector index block at %d: %w", HeaderInfoLength+idx, err)}sPtr = binary.LittleEndian.Uint32(buff)ePtr = binary.LittleEndian.Uint32(buff[4:])}// fmt.Printf("sPtr=%d, ePtr=%d", sPtr, ePtr)// binary search the segment index to get the regionvar dataLen, dataPtr = 0, uint32(0)var buff = make([]byte, SegmentIndexBlockSize)var l, h = 0, int((ePtr - sPtr) / SegmentIndexBlockSize)for l <= h {m := (l + h) >> 1p := sPtr + uint32(m*SegmentIndexBlockSize)err := s.read(int64(p), buff)if err != nil {return "", fmt.Errorf("read segment index at %d: %w", p, err)}// decode the data step by step to reduce the unnecessary operationssip := binary.LittleEndian.Uint32(buff)if ip < sip {h = m - 1} else {eip := binary.LittleEndian.Uint32(buff[4:])if ip > eip {l = m + 1} else {dataLen = int(binary.LittleEndian.Uint16(buff[8:]))dataPtr = binary.LittleEndian.Uint32(buff[10:])break}}}//fmt.Printf("dataLen: %d, dataPtr: %d", dataLen, dataPtr)if dataLen == 0 {return "", nil}// load and return the region datavar regionBuff = make([]byte, dataLen)err := s.read(int64(dataPtr), regionBuff)if err != nil {return "", fmt.Errorf("read region at %d: %w", dataPtr, err)}return string(regionBuff), nil
}// do the data read operation based on the setting.
// content buffer first or will read from the file.
// this operation will invoke the Seek for file based read.
func (s *Searcher) read(offset int64, buff []byte) error {if s.contentBuff != nil {cLen := copy(buff, s.contentBuff[offset:])if cLen != len(buff) {return fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))}} else {_, err := s.handle.Seek(offset, 0)if err != nil {return fmt.Errorf("seek to %d: %w", offset, err)}s.ioCount++rLen, err := s.handle.Read(buff)if err != nil {return fmt.Errorf("handle read: %w", err)}if rLen != len(buff) {return fmt.Errorf("incomplete read: readed bytes should be %d", len(buff))}}return nil
}

util.go源码:

package ipregionimport ("fmt""strconv""strings"
)// 工具函数
var shiftIndex = []int{24, 16, 8, 0}func CheckIP(ip string) (uint32, error) {var ps = strings.Split(strings.TrimSpace(ip), ".")if len(ps) != 4 {return 0, fmt.Errorf("invalid ip address `%s`", ip)}var val = uint32(0)for i, s := range ps {d, err := strconv.Atoi(s)if err != nil {return 0, fmt.Errorf("the %dth part `%s` is not an integer", i, s)}if d < 0 || d > 255 {return 0, fmt.Errorf("the %dth part `%s` should be an integer bettween 0 and 255", i, s)}val |= uint32(d) << shiftIndex[i]}// convert the ip to integerreturn val, nil
}

3.调用ip解析方法

import引入插件包

import ("gofly/utils/plugin"
)

在接口业务中使用, data, err := plugin.NewIpRegion(ip),调用NewIpRegion()方法即可解析出IP归属地信息,代码如下:

// get请求获取ip属地
func (api *Iptest) GetIpRegion(c *gf.GinCtx) {ip := c.DefaultQuery("ip", "")data, err := plugin.NewIpRegion(ip)gf.Success().SetMsg("获取ip属地").SetData(data).SetExdata(err).Regin(c)
}

这里给大家补充一个gofly框架获取用户请求IP地址函数  ip := gf.GetIp(c),其中c是gin框架请求上下文*gf.GinCtx

插件调用测试:

我们添加一个接口测试调用data, err := plugin.NewIpRegion(ip) 返回数据为:中国|0|云南省|昆明市|移动 ,如下图:


http://www.mrgr.cn/news/13219.html

相关文章:

  • CSS中的Flexbox布局和Grid布局有什么区别?适用场景
  • WPF—画刷(使用画刷实现背景颜色渐变效果)
  • C语言—字符函数和字符串函数
  • 基于SpringBoot+Vue+MySQL的小区物业管理系统
  • YarnClient发送和接收请求源码解析
  • 如何使用ssm实现基于SSM的在线教育网站的设计与实现+vue
  • 基于SSM+小程序的垃圾分类管理系统(垃圾2)(源码+sql脚本+视频导入教程+文档)
  • Java:Calendar类
  • 【区块链 + 司法存证】区块链电子数据存证平台 | FISCO BCOS应用案例
  • 【NO.11】LeetCode经典150题-274. H 指数
  • C++/Qt 多媒体(续二)
  • C++_CH09_循环
  • 免杀笔记 ---> CS特性角度看Veh免杀
  • Ubuntu美化为类Windows风格
  • DataWhale AI夏令营-《李宏毅深度学习教程》笔记-task2
  • Qt:玩转QPainter序列三
  • 内存管理篇-14kmalloc机制实现分析
  • 如何在 Vim 中显示行号
  • 分数规划问题
  • numpy 中的降维与升维