一个自由职业独立开发者,在线客服零碎的开发日志
客服零碎里须要展现出访客的 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)
}
下面的 IP 库文件能够在我网站上加微信,我独自发送
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 [保留]
我的网站
惟一客服零碎