mirror of
https://github.com/xiaoqidun/qqwry.git
synced 2025-12-11 10:22:54 +08:00
Compare commits
22 Commits
01491ac5e0
...
cc141ac4f5
| Author | SHA1 | Date | |
|---|---|---|---|
| cc141ac4f5 | |||
| 1dd385f77d | |||
| 20c61b7efd | |||
| 8b26e3b115 | |||
| cbdd413fc4 | |||
| 9392bc022a | |||
| cf49741a97 | |||
| 7a1610fa02 | |||
| 90b4bdcfc3 | |||
| 149532dea2 | |||
| 345094d135 | |||
| 13ade58f06 | |||
| 672ffb4f51 | |||
| c60cca6bd2 | |||
| c3e92bf2ea | |||
| c666cbda28 | |||
| 698a12cf03 | |||
| 25daf60573 | |||
| 26b587798e | |||
| 79da17e7d1 | |||
| 1b27c45e2a | |||
| b4bbcc98b2 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
||||
.vscode/
|
||||
.devcontainer/
|
||||
assets/qqwry.dat
|
||||
assets/qqwry.ipdb
|
||||
33
README.md
33
README.md
@@ -4,8 +4,8 @@ Golang QQWry,高性能纯真IP查询库。
|
||||
|
||||
# 使用须知
|
||||
|
||||
1. 仅支持ipv4查询。
|
||||
2. city也可能是国家。
|
||||
1. dat格式仅支持ipv4查询。
|
||||
2. ipdb格式支持ipv4和ipv6查询。
|
||||
|
||||
# 使用说明
|
||||
|
||||
@@ -13,24 +13,35 @@ Golang QQWry,高性能纯真IP查询库。
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/xiaoqidun/qqwry"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 从文件加载IP数据库
|
||||
if err := qqwry.LoadFile("qqwry.dat"); err != nil {
|
||||
if err := qqwry.LoadFile("qqwry.ipdb"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// 从内存或缓存查询IP
|
||||
city, isp, err := qqwry.QueryIP("1.1.1.1")
|
||||
log.Printf("城市:%s,运营商:%s,错误:%v", city, isp, err)
|
||||
location, err := qqwry.QueryIP("119.29.29.29")
|
||||
if err != nil {
|
||||
fmt.Printf("错误:%v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("国家:%s,省份:%s,城市:%s,区县:%s,运营商:%s\n",
|
||||
location.Country,
|
||||
location.Province,
|
||||
location.City,
|
||||
location.District,
|
||||
location.ISP,
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
# IP数据库
|
||||
|
||||
- [https://aite.xyz/share-file/qqwry/qqwry.dat](https://aite.xyz/share-file/qqwry/qqwry.dat)
|
||||
- DAT格式:[https://aite.xyz/share-file/qqwry/qqwry.dat](https://aite.xyz/share-file/qqwry/qqwry.dat)
|
||||
- IPDB格式:[https://aite.xyz/share-file/qqwry/qqwry.ipdb](https://aite.xyz/share-file/qqwry/qqwry.ipdb)
|
||||
|
||||
# 编译说明
|
||||
|
||||
@@ -38,16 +49,22 @@ func main() {
|
||||
2. client和server需要go1.16的内嵌资源特性。
|
||||
3. 作为库使用,请直接引包,并不需要go1.16+才能编译。
|
||||
|
||||
# 数据更新
|
||||
|
||||
- 由于qqwry.dat缺乏更新,官方czdb格式又难以获得和分发,建议使用ipdb格式。
|
||||
- 这里的ipdb格式指metowolf提供的官方czdb格式转换而来的ipdb格式(纯真格式原版)。
|
||||
|
||||
# 服务接口
|
||||
|
||||
1. 自行根据需要调整server下源码。
|
||||
2. 可以通过-listen参数指定http服务地址。
|
||||
3. json api:curl http://127.0.0.1/ip/1.1.1.1
|
||||
3. json api:curl http://127.0.0.1/ip/119.29.29.29
|
||||
|
||||
# 特别感谢
|
||||
|
||||
- 感谢[纯真IP库](https://www.cz88.net/)一直坚持为大家提供免费IP数据库。
|
||||
- 感谢[yinheli](https://github.com/yinheli)的[qqwry](https://github.com/yinheli/qqwry)项目,为我提供纯真ip库解析算法参考。
|
||||
- 感谢[metowolf](https://github.com/metowolf)的[qqwry.ipdb](https://github.com/metowolf/qqwry.ipdb)项目,提供纯真czdb转ipdb数据库。
|
||||
|
||||
# 授权说明
|
||||
|
||||
|
||||
@@ -4,3 +4,6 @@ import _ "embed"
|
||||
|
||||
//go:embed qqwry.dat
|
||||
var QQWryDat []byte
|
||||
|
||||
//go:embed qqwry.ipdb
|
||||
var QQWryIpdb []byte
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
qqwry.LoadData(assets.QQWryDat)
|
||||
qqwry.LoadData(assets.QQWryIpdb)
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -16,10 +16,22 @@ func main() {
|
||||
return
|
||||
}
|
||||
queryIp := os.Args[1]
|
||||
city, isp, err := qqwry.QueryIP(queryIp)
|
||||
location, err := qqwry.QueryIP(queryIp)
|
||||
if err != nil {
|
||||
fmt.Printf("错误:%v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("城市:%s,运营商:%s\n", city, isp)
|
||||
emptyVal := func(val string) string {
|
||||
if val != "" {
|
||||
return val
|
||||
}
|
||||
return "未知"
|
||||
}
|
||||
fmt.Printf("国家:%s,省份:%s,城市:%s,区县:%s,运营商:%s\n",
|
||||
emptyVal(location.Country),
|
||||
emptyVal(location.Province),
|
||||
emptyVal(location.City),
|
||||
emptyVal(location.District),
|
||||
emptyVal(location.ISP),
|
||||
)
|
||||
}
|
||||
|
||||
5
go.mod
5
go.mod
@@ -2,4 +2,7 @@ module github.com/xiaoqidun/qqwry
|
||||
|
||||
go 1.20
|
||||
|
||||
require golang.org/x/text v0.9.0
|
||||
require (
|
||||
github.com/ipipdotnet/ipdb-go v1.3.3
|
||||
golang.org/x/text v0.31.0
|
||||
)
|
||||
|
||||
6
go.sum
6
go.sum
@@ -1,2 +1,4 @@
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
github.com/ipipdotnet/ipdb-go v1.3.3 h1:GLSAW9ypLUd6EF9QNK2Uhxew9Jzs4XMJ9gOZEFnJm7U=
|
||||
github.com/ipipdotnet/ipdb-go v1.3.3/go.mod h1:yZ+8puwe3R37a/3qRftXo40nZVQbxYDLqls9o5foexs=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
|
||||
118
qqwry.go
118
qqwry.go
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"github.com/ipipdotnet/ipdb-go"
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/transform"
|
||||
"io"
|
||||
@@ -14,9 +15,16 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
data []byte
|
||||
dataLen uint32
|
||||
ipCache = &sync.Map{}
|
||||
data []byte
|
||||
dataLen uint32
|
||||
ipdbCity *ipdb.City
|
||||
dataType = dataTypeDat
|
||||
locationCache = &sync.Map{}
|
||||
)
|
||||
|
||||
const (
|
||||
dataTypeDat = 0
|
||||
dataTypeIpdb = 1
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -25,9 +33,13 @@ const (
|
||||
redirectMode2 = 0x02
|
||||
)
|
||||
|
||||
type cache struct {
|
||||
City string
|
||||
Isp string
|
||||
type Location struct {
|
||||
Country string // 国家
|
||||
Province string // 省份
|
||||
City string // 城市
|
||||
District string // 区县
|
||||
ISP string // 运营商
|
||||
IP string // IP地址
|
||||
}
|
||||
|
||||
func byte3ToUInt32(data []byte) uint32 {
|
||||
@@ -45,16 +57,25 @@ func gb18030Decode(src []byte) string {
|
||||
}
|
||||
|
||||
// QueryIP 从内存或缓存查询IP
|
||||
func QueryIP(queryIp string) (city string, isp string, err error) {
|
||||
if v, ok := ipCache.Load(queryIp); ok {
|
||||
city = v.(cache).City
|
||||
isp = v.(cache).Isp
|
||||
return
|
||||
func QueryIP(ip string) (location *Location, err error) {
|
||||
if v, ok := locationCache.Load(ip); ok {
|
||||
return v.(*Location), nil
|
||||
}
|
||||
ip := net.ParseIP(queryIp).To4()
|
||||
switch dataType {
|
||||
case dataTypeDat:
|
||||
return QueryIPDat(ip)
|
||||
case dataTypeIpdb:
|
||||
return QueryIPIpdb(ip)
|
||||
default:
|
||||
return nil, errors.New("data type not support")
|
||||
}
|
||||
}
|
||||
|
||||
// QueryIPDat 从dat查询IP,仅加载dat格式数据库时使用
|
||||
func QueryIPDat(ipv4 string) (location *Location, err error) {
|
||||
ip := net.ParseIP(ipv4).To4()
|
||||
if ip == nil {
|
||||
err = errors.New("ip is not ipv4")
|
||||
return
|
||||
return nil, errors.New("ip is not ipv4")
|
||||
}
|
||||
ip32 := binary.BigEndian.Uint32(ip)
|
||||
posA := binary.LittleEndian.Uint32(data[:4])
|
||||
@@ -84,12 +105,12 @@ func QueryIP(queryIp string) (city string, isp string, err error) {
|
||||
}
|
||||
}
|
||||
if offset <= 0 {
|
||||
err = errors.New("ip not found")
|
||||
return
|
||||
return nil, errors.New("ip not found")
|
||||
}
|
||||
posM := offset + 4
|
||||
mode := data[posM]
|
||||
var ispPos uint32
|
||||
var addr, isp string
|
||||
switch mode {
|
||||
case redirectMode1:
|
||||
posC := byte3ToUInt32(data[posM+1 : posM+4])
|
||||
@@ -101,19 +122,19 @@ func QueryIP(queryIp string) (city string, isp string, err error) {
|
||||
}
|
||||
for i := posCA; i < dataLen; i++ {
|
||||
if data[i] == 0 {
|
||||
city = string(data[posCA:i])
|
||||
addr = string(data[posCA:i])
|
||||
break
|
||||
}
|
||||
}
|
||||
if mode != redirectMode2 {
|
||||
posC += uint32(len(city) + 1)
|
||||
posC += uint32(len(addr) + 1)
|
||||
}
|
||||
ispPos = posC
|
||||
case redirectMode2:
|
||||
posCA := byte3ToUInt32(data[posM+1 : posM+4])
|
||||
for i := posCA; i < dataLen; i++ {
|
||||
if data[i] == 0 {
|
||||
city = string(data[posCA:i])
|
||||
addr = string(data[posCA:i])
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -122,14 +143,14 @@ func QueryIP(queryIp string) (city string, isp string, err error) {
|
||||
posCA := offset + 4
|
||||
for i := posCA; i < dataLen; i++ {
|
||||
if data[i] == 0 {
|
||||
city = string(data[posCA:i])
|
||||
addr = string(data[posCA:i])
|
||||
break
|
||||
}
|
||||
}
|
||||
ispPos = offset + uint32(5+len(city))
|
||||
ispPos = offset + uint32(5+len(addr))
|
||||
}
|
||||
if city != "" {
|
||||
city = strings.TrimSpace(gb18030Decode([]byte(city)))
|
||||
if addr != "" {
|
||||
addr = strings.TrimSpace(gb18030Decode([]byte(addr)))
|
||||
}
|
||||
ispMode := data[ispPos]
|
||||
if ispMode == redirectMode1 || ispMode == redirectMode2 {
|
||||
@@ -150,22 +171,65 @@ func QueryIP(queryIp string) (city string, isp string, err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
ipCache.Store(queryIp, cache{City: city, Isp: isp})
|
||||
return
|
||||
location = SplitResult(addr, isp, ipv4)
|
||||
locationCache.Store(ipv4, location)
|
||||
return location, nil
|
||||
}
|
||||
|
||||
// QueryIPIpdb 从ipdb查询IP,仅加载ipdb格式数据库时使用
|
||||
func QueryIPIpdb(ip string) (location *Location, err error) {
|
||||
ret, err := ipdbCity.Find(ip, "CN")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
location = SplitResult(ret[0], ret[1], ip)
|
||||
locationCache.Store(ip, location)
|
||||
return location, nil
|
||||
}
|
||||
|
||||
// LoadData 从内存加载IP数据库
|
||||
func LoadData(database []byte) {
|
||||
if string(database[6:11]) == "build" {
|
||||
dataType = dataTypeIpdb
|
||||
loadCity, err := ipdb.NewCityFromBytes(database)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ipdbCity = loadCity
|
||||
return
|
||||
}
|
||||
data = database
|
||||
dataLen = uint32(len(data))
|
||||
}
|
||||
|
||||
// LoadFile 从文件加载IP数据库
|
||||
func LoadFile(filepath string) (err error) {
|
||||
data, err = os.ReadFile(filepath)
|
||||
body, err := os.ReadFile(filepath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dataLen = uint32(len(data))
|
||||
LoadData(body)
|
||||
return
|
||||
}
|
||||
|
||||
// SplitResult 按照调整后的纯真社区版IP库地理位置格式返回结果
|
||||
func SplitResult(addr string, isp string, ipv4 string) (location *Location) {
|
||||
location = &Location{ISP: isp, IP: ipv4}
|
||||
splitList := strings.Split(addr, "–")
|
||||
for i := 0; i < len(splitList); i++ {
|
||||
switch i {
|
||||
case 0:
|
||||
location.Country = splitList[i]
|
||||
case 1:
|
||||
location.Province = splitList[i]
|
||||
case 2:
|
||||
location.City = splitList[i]
|
||||
case 3:
|
||||
location.District = splitList[i]
|
||||
}
|
||||
}
|
||||
if location.Country == "局域网" {
|
||||
location.ISP = location.Country
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -5,16 +5,28 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := LoadFile("assets/qqwry.dat"); err != nil {
|
||||
if err := LoadFile("assets/qqwry.ipdb"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryIP(t *testing.T) {
|
||||
queryIp := "1.1.1.1"
|
||||
city, isp, err := QueryIP(queryIp)
|
||||
queryIp := "119.29.29.29"
|
||||
location, err := QueryIP(queryIp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("城市:%s,运营商:%s", city, isp)
|
||||
emptyVal := func(val string) string {
|
||||
if val != "" {
|
||||
return val
|
||||
}
|
||||
return "未知"
|
||||
}
|
||||
t.Logf("国家:%s,省份:%s,城市:%s,区县:%s,运营商:%s",
|
||||
emptyVal(location.Country),
|
||||
emptyVal(location.Province),
|
||||
emptyVal(location.City),
|
||||
emptyVal(location.District),
|
||||
emptyVal(location.ISP),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,14 +10,13 @@ import (
|
||||
)
|
||||
|
||||
type resp struct {
|
||||
IP string `json:"ip"`
|
||||
Err string `json:"err"`
|
||||
City string `json:"city"`
|
||||
Isp string `json:"isp"`
|
||||
Data *qqwry.Location `json:"data"`
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
qqwry.LoadData(assets.QQWryDat)
|
||||
qqwry.LoadData(assets.QQWryIpdb)
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -34,14 +33,14 @@ func IpAPI(writer http.ResponseWriter, request *http.Request) {
|
||||
if ip == "" {
|
||||
ip, _, _ = net.SplitHostPort(request.RemoteAddr)
|
||||
}
|
||||
rw := &resp{IP: ip}
|
||||
city, isp, err := qqwry.QueryIP(ip)
|
||||
response := &resp{}
|
||||
location, err := qqwry.QueryIP(ip)
|
||||
if err != nil {
|
||||
rw.Err = err.Error()
|
||||
response.Message = err.Error()
|
||||
} else {
|
||||
rw.City = city
|
||||
rw.Isp = isp
|
||||
response.Data = location
|
||||
response.Success = true
|
||||
}
|
||||
b, _ := json.MarshalIndent(rw, "", " ")
|
||||
b, _ := json.MarshalIndent(response, "", " ")
|
||||
_, _ = writer.Write(b)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user