Compare commits

...

22 Commits

Author SHA1 Message Date
cc141ac4f5 feat(更新依赖): 更新依赖 2025-11-27 16:55:33 +08:00
1dd385f77d feat(升级依赖): 升级依赖 2025-09-15 19:03:12 +08:00
20c61b7efd feat(升级依赖): 升级依赖 2025-07-11 09:37:19 +08:00
8b26e3b115 feat(升级依赖): 升级依赖 2025-05-14 00:06:06 +08:00
cbdd413fc4 feat(升级依赖): 升级依赖 2025-04-08 08:59:50 +08:00
9392bc022a feat(升级依赖): 升级依赖 2025-03-06 19:39:39 +08:00
cf49741a97 feat(升级依赖): 升级依赖 2025-02-06 13:02:15 +08:00
7a1610fa02 docs(更新文档): 更新文档 2025-01-07 16:35:38 +08:00
90b4bdcfc3 fix(优化查询): 统一局域网IP地址的ISP字段 2025-01-07 16:15:28 +08:00
149532dea2 fix(优化查询): 统一局域网IP地址的ISP字段 2025-01-07 16:13:13 +08:00
345094d135 docs(更新文档): 更新文档 2025-01-07 16:03:29 +08:00
13ade58f06 feat(更新功能): 兼容metowolf提供的qqwry.ipdb格式(原版) 2025-01-07 15:52:22 +08:00
672ffb4f51 feat(升级依赖): 更新依赖 2024-12-05 10:14:38 +08:00
c60cca6bd2 feat(更新依赖): 更新依赖 2024-11-20 20:15:17 +08:00
c3e92bf2ea feat(更新依赖): 更新依赖 2024-10-09 10:47:34 +08:00
c666cbda28 feat(更新依赖): 更新依赖 2024-09-19 23:26:40 +08:00
698a12cf03 style(代码风格): 调整代码风格 2024-06-20 09:45:00 +08:00
25daf60573 feat(适配特性): 适配纯真IP库社区版更新(2024-06-19)地理位置格式 2024-06-20 00:38:19 +08:00
26b587798e feat(升级依赖): 升级依赖 2024-06-05 20:57:12 +08:00
79da17e7d1 feat(更新依赖): 更新依赖 2024-05-07 15:22:36 +08:00
1b27c45e2a feat(更新依赖): 更新依赖 2023-11-14 14:36:55 +08:00
b4bbcc98b2 feat(更新依赖): 更新依赖 2023-09-29 18:14:16 +08:00
9 changed files with 169 additions and 56 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@
.vscode/
.devcontainer/
assets/qqwry.dat
assets/qqwry.ipdb

View File

@@ -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 apicurl http://127.0.0.1/ip/1.1.1.1
3. json apicurl 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数据库。
# 授权说明

View File

@@ -4,3 +4,6 @@ import _ "embed"
//go:embed qqwry.dat
var QQWryDat []byte
//go:embed qqwry.ipdb
var QQWryIpdb []byte

View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -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
}

View File

@@ -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),
)
}

View File

@@ -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)
}