golang实现IP地址转归属地国家、省份、城市、获取网络运营商-在线客服系统获取访客地址功能【唯一客服】
现在很多网络应用已经都在展示网友的IP归属地,通过golang以及qqzengIP地址库,可以很方便的实现这个功能
package tools import ( "io/ioutil" "log" "strconv" "strings" ) /** * @author xiao.luo * @description This is the go version for IpSearch */ type CityInfo struct { CountryName string `json:"country_name"` RegionName string `json:"region_name"` CityName string `json:"city_name"` AreaName string `json:"area_name"` } type ipIndex struct { startip, endip uint32 local_offset, local_length uint32 } type prefixIndex struct { start_index, end_index uint32 } type ipSearch struct { data []byte prefixMap map[uint32]prefixIndex firstStartIpOffset uint32 prefixStartOffset uint32 prefixEndOffset uint32 prefixCount uint32 } var ips *ipSearch = nil func NewIpdb(ipPath string) (ipSearch, error) { if ips == nil { var err error ips, err = loadIpDat(ipPath) if err != nil { log.Fatal("the IP Dat loaded failed!") return *ips, err } } return *ips, nil } func loadIpDat(ipPath string) (*ipSearch, error) { p := ipSearch{} //加载ip地址库信息 data, err := ioutil.ReadFile(ipPath) if err != nil { log.Fatal(err) } p.data = data p.prefixMap = make(map[uint32]prefixIndex) p.firstStartIpOffset = bytesToLong(data[0], data[1], data[2], data[3]) p.prefixStartOffset = bytesToLong(data[8], data[9], data[10], data[11]) p.prefixEndOffset = bytesToLong(data[12], data[13], data[14], data[15]) p.prefixCount = (p.prefixEndOffset-p.prefixStartOffset)/9 + 1 // 前缀区块每组 // 初始化前缀对应索引区区间 indexBuffer := p.data[p.prefixStartOffset:(p.prefixEndOffset + 9)] for k := uint32(0); k < p.prefixCount; k++ { i := k * 9 prefix := uint32(indexBuffer[i] & 0xFF) pf := prefixIndex{} pf.start_index = bytesToLong(indexBuffer[i+1], indexBuffer[i+2], indexBuffer[i+3], indexBuffer[i+4]) pf.end_index = bytesToLong(indexBuffer[i+5], indexBuffer[i+6], indexBuffer[i+7], indexBuffer[i+8]) p.prefixMap[prefix] = pf } return &p, nil } func (p ipSearch) Get(ip string) string { ips := strings.Split(ip, ".") x, _ := strconv.Atoi(ips[0]) prefix := uint32(x) intIP := ipToLong(ip) var high uint32 = 0 var low uint32 = 0 if _, ok := p.prefixMap[prefix]; ok { low = p.prefixMap[prefix].start_index high = p.prefixMap[prefix].end_index } else { return "" } var my_index uint32 if low == high { my_index = low } else { my_index = p.binarySearch(low, high, intIP) } ipindex := ipIndex{} ipindex.getIndex(my_index, &p) if ipindex.startip <= intIP && ipindex.endip >= intIP { return ipindex.getLocal(&p) } else { return "" } } // 二分逼近算法 func (p ipSearch) binarySearch(low uint32, high uint32, k uint32) uint32 { var M uint32 = 0 for low <= high { mid := (low + high) / 2 endipNum := p.getEndIp(mid) if endipNum >= k { M = mid if mid == 0 { break // 防止溢出 } high = mid - 1 } else { low = mid + 1 } } return M } // 只获取结束ip的数值 // 索引区第left个索引 // 返回结束ip的数值 func (p ipSearch) getEndIp(left uint32) uint32 { left_offset := p.firstStartIpOffset + left*12 return bytesToLong(p.data[4+left_offset], p.data[5+left_offset], p.data[6+left_offset], p.data[7+left_offset]) } func (p *ipIndex) getIndex(left uint32, ips *ipSearch) { left_offset := ips.firstStartIpOffset + left*12 p.startip = bytesToLong(ips.data[left_offset], ips.data[1+left_offset], ips.data[2+left_offset], ips.data[3+left_offset]) p.endip = bytesToLong(ips.data[4+left_offset], ips.data[5+left_offset], ips.data[6+left_offset], ips.data[7+left_offset]) p.local_offset = bytesToLong3(ips.data[8+left_offset], ips.data[9+left_offset], ips.data[10+left_offset]) p.local_length = uint32(ips.data[11+left_offset]) } // / 返回地址信息 // / 地址信息的流位置 // / 地址信息的流长度 func (p *ipIndex) getLocal(ips *ipSearch) string { bytes := ips.data[p.local_offset : p.local_offset+p.local_length] return string(bytes) } func ipToLong(ip string) uint32 { quads := strings.Split(ip, ".") if len(quads) < 4 { return 0 } var result uint32 = 0 a, _ := strconv.Atoi(quads[3]) result += uint32(a) b, _ := strconv.Atoi(quads[2]) result += uint32(b) << 8 c, _ := strconv.Atoi(quads[1]) result += uint32(c) << 16 d, _ := strconv.Atoi(quads[0]) result += uint32(d) << 24 return result } //字节转整形 func bytesToLong(a, b, c, d byte) uint32 { a1 := uint32(a) b1 := uint32(b) c1 := uint32(c) d1 := uint32(d) return (a1 & 0xFF) | ((b1 << 8) & 0xFF00) | ((c1 << 16) & 0xFF0000) | ((d1 << 24) & 0xFF000000) } func bytesToLong3(a, b, c byte) uint32 { a1 := uint32(a) b1 := uint32(b) c1 := uint32(c) return (a1 & 0xFF) | ((b1 << 8) & 0xFF00) | ((c1 << 16) & 0xFF0000) }
针对上面的代码进行单元测试
package tools import ( "log" "strings" "testing" ) func TestNewIpdb(t *testing.T) { var ip, ipstr string var infos []string p, _ := NewIpdb("../config/qqzeng-ip-utf8.dat") ip = "113.104.209.240" ipstr = p.Get(ip) infos = strings.Split(ipstr, "|") log.Println(infos) ip = "39.155.215.54" ipstr = p.Get(ip) infos = strings.Split(ipstr, "|") log.Println(infos) ip = "127.0.0.1" ipstr = p.Get(ip) infos = strings.Split(ipstr, "|") log.Println(infos) ip = "192.168.1.1" ipstr = p.Get(ip) infos = strings.Split(ipstr, "|") log.Println(infos) }
2023/03/03 15:49:48 [亚洲 中国 广东 深圳 宝安 电信 440306 China CN 113.88311 22.55371] 2023/03/03 15:49:48 [亚洲 中国 北京 北京 移动 110100 China CN 116.405285 39.904989] 2023/03/03 15:49:48 [ 保留 ] 2023/03/03 15:49:48 [ 保留 ]
另外,有客服系统需求的同学,可以访问下面地址研究测试
十年开发经验程序员,离职全心创业中,历时三年开发出的产品《唯一客服系统》
一款基于Golang+Vue开发的在线客服系统,软件著作权编号:2021SR1462600。一套可私有化部署的网站在线客服系统,编译后的二进制文件可直接使用无需搭开发环境,下载zip解压即可,仅依赖MySQL数据库,是一个开箱即用的全渠道在线客服系统,致力于帮助广大开发者/公司快速部署整合私有化客服功能。
开源地址:唯一客服(开源学习版)
官网地址:唯一客服官网
标签:
Go
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
2022-03-03 [CSS] media query媒体查询中的min-width和max-width
2022-03-03 [Linux] 使用lsof 查看解决too many open file问题
2021-03-03 [Go] go mod下载的依赖包位置
2021-03-03 [Go] go mod 设置GOPROXY环境变量中的direct意义
2020-03-03 [PHP] PHP5中的写时复制change on write
2018-03-03 [日常] DNS的迭代查询过程
2018-03-03 [日常] Redis基本使用测试