feat: geo ip.
This commit is contained in:
6
bank_card/assets/assets.go
Normal file
6
bank_card/assets/assets.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package assets
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
//go:embed bank_card.db
|
||||||
|
var BankCardDatabase []byte
|
||||||
@@ -3,17 +3,15 @@ package bank_card
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
_ "embed"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/mattn/go-sqlite3"
|
"github.com/mattn/go-sqlite3"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed bank_card.db
|
"github.com/tx7do/kratos-utils/bank_card/assets"
|
||||||
var embeddedDatabase []byte
|
)
|
||||||
|
|
||||||
type Database struct {
|
type Database struct {
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
@@ -33,7 +31,7 @@ func NewDatabase(openFile bool) *Database {
|
|||||||
|
|
||||||
// openFromFile 从文件打开数据库
|
// openFromFile 从文件打开数据库
|
||||||
func (d *Database) openFromFile() {
|
func (d *Database) openFromFile() {
|
||||||
db, err := sql.Open("sqlite3", "bank_card.db")
|
db, err := sql.Open("sqlite3", "assets/bank_card.db")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("failed to connect database")
|
panic("failed to connect database")
|
||||||
}
|
}
|
||||||
@@ -59,7 +57,7 @@ func (d *Database) openFromEmbed() error {
|
|||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
if err = conn.Raw(func(raw interface{}) error {
|
if err = conn.Raw(func(raw interface{}) error {
|
||||||
return raw.(*sqlite3.SQLiteConn).Deserialize(embeddedDatabase, "")
|
return raw.(*sqlite3.SQLiteConn).Deserialize(assets.BankCardDatabase, "")
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func TestImportBankName(t *testing.T) {
|
|||||||
|
|
||||||
//db.openFromFile()
|
//db.openFromFile()
|
||||||
|
|
||||||
file, err := os.Open("name.csv")
|
file, err := os.Open("assets/name.csv")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Read file err, err =", err)
|
fmt.Println("Read file err, err =", err)
|
||||||
return
|
return
|
||||||
@@ -65,7 +65,7 @@ func TestImportBankCard(t *testing.T) {
|
|||||||
|
|
||||||
//db.openFromFile()
|
//db.openFromFile()
|
||||||
|
|
||||||
file, err := os.Open("bin.csv")
|
file, err := os.Open("assets/bin.csv")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Read file err, err =", err)
|
fmt.Println("Read file err, err =", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
# GEOIP2(GeoLite2)
|
# IP地理位置查询
|
||||||
|
|
||||||
GeoLite2数据库是免费的IP地理位置数据库,它可以通过IP地址识别其所在的城市和国家。与MaxMind的GeoIP2数据库相比,准确性较差。
|
## 支持库
|
||||||
|
|
||||||
GeoLite2-*.mmdb文件是一个二进制数据库文件,包含了MaxMind GeoLite2所需的全部数据。
|
- [GeoLite2](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data)
|
||||||
|
- [纯真](https://update.cz88.net/geo-public)
|
||||||
GeoLite2的数据库分为:国家(Country),城市(City)和ASN三个数据库。每周两次更新。
|
|
||||||
|
|
||||||
## 数据库下载地址
|
|
||||||
|
|
||||||
1. <https://github.com/P3TERX/GeoLite.mmdb/releases>
|
|
||||||
2. <https://dev.maxmind.com/geoip/geolite2-free-geolocation-data>
|
|
||||||
|
|||||||
12
geoip/geolite/README.md
Normal file
12
geoip/geolite/README.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# GEOIP2(GeoLite2)
|
||||||
|
|
||||||
|
GeoLite2数据库是免费的IP地理位置数据库,它可以通过IP地址识别其所在的城市和国家。与MaxMind的GeoIP2数据库相比,准确性较差。
|
||||||
|
|
||||||
|
GeoLite2-*.mmdb文件是一个二进制数据库文件,包含了MaxMind GeoLite2所需的全部数据。
|
||||||
|
|
||||||
|
GeoLite2的数据库分为:国家(Country),城市(City)和ASN三个数据库。每周两次更新。
|
||||||
|
|
||||||
|
## 数据库下载地址
|
||||||
|
|
||||||
|
1. <https://github.com/P3TERX/GeoLite.mmdb/releases>
|
||||||
|
2. <https://dev.maxmind.com/geoip/geolite2-free-geolocation-data>
|
||||||
|
Before Width: | Height: | Size: 68 MiB After Width: | Height: | Size: 68 MiB |
6
geoip/geolite/assets/assets.go
Normal file
6
geoip/geolite/assets/assets.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package assets
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
//go:embed GeoLite2-City.mmdb
|
||||||
|
var GeoLite2CityData []byte
|
||||||
@@ -1,48 +1,52 @@
|
|||||||
package geoip
|
package geolite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/go-kratos/kratos/v2/log"
|
"github.com/go-kratos/kratos/v2/log"
|
||||||
"github.com/oschwald/geoip2-golang"
|
"github.com/oschwald/geoip2-golang"
|
||||||
|
"github.com/tx7do/kratos-utils/geoip/geolite/assets"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed GeoLite2-City.mmdb
|
|
||||||
var geoipMmdbByte []byte
|
|
||||||
|
|
||||||
type Result struct {
|
type Result struct {
|
||||||
Country string // 国家
|
Country string // 国家
|
||||||
Province string // 省
|
Province string // 省
|
||||||
City string // 城市
|
City string // 城市
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeoIp 地理位置解析结构体
|
const defaultOutputLanguage = "zh-CN"
|
||||||
type GeoIp struct {
|
|
||||||
|
// Client 地理位置解析结构体
|
||||||
|
type Client struct {
|
||||||
db *geoip2.Reader
|
db *geoip2.Reader
|
||||||
outputLanguage string
|
outputLanguage string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGeoIp .
|
// NewClient .
|
||||||
func NewGeoIp() (geoip *GeoIp, err error) {
|
func NewClient() (*Client, error) {
|
||||||
db, err := geoip2.FromBytes(geoipMmdbByte)
|
db, err := geoip2.FromBytes(assets.GeoLite2CityData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &GeoIp{db: db, outputLanguage: "zh-CN"}, nil
|
return &Client{db: db, outputLanguage: defaultOutputLanguage}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GeoIp) Close() (err error) {
|
// Close 关闭客户端
|
||||||
|
func (g *Client) Close() error {
|
||||||
|
if g.db == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return g.db.Close()
|
return g.db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLanguage 设置输出的语言,默认为:zh-CN
|
// SetLanguage 设置输出的语言,默认为:zh-CN
|
||||||
func (g *GeoIp) SetLanguage(code string) {
|
func (g *Client) SetLanguage(code string) {
|
||||||
g.outputLanguage = code
|
g.outputLanguage = code
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GeoIp) query(rawIP string) (city *geoip2.City, err error) {
|
// query 查询城市级别数据
|
||||||
|
func (g *Client) query(rawIP string) (city *geoip2.City, err error) {
|
||||||
ip := net.ParseIP(rawIP)
|
ip := net.ParseIP(rawIP)
|
||||||
if ip == nil {
|
if ip == nil {
|
||||||
return nil, errors.New("invalid ip")
|
return nil, errors.New("invalid ip")
|
||||||
@@ -51,8 +55,8 @@ func (g *GeoIp) query(rawIP string) (city *geoip2.City, err error) {
|
|||||||
return g.db.City(ip)
|
return g.db.City(ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAreaFromIP 通过IP获取地区
|
// Query 通过IP获取地区
|
||||||
func (g *GeoIp) GetAreaFromIP(rawIP string) (ret Result, err error) {
|
func (g *Client) Query(rawIP string) (ret Result, err error) {
|
||||||
record, err := g.query(rawIP)
|
record, err := g.query(rawIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
package geoip
|
package geolite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGeoIp(t *testing.T) {
|
func TestGeoLite(t *testing.T) {
|
||||||
g, err := NewGeoIp()
|
g, err := NewClient()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
ret, err := g.GetAreaFromIP("47.108.149.89")
|
ret, err := g.Query("47.108.149.89")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, ret.Country, "中国")
|
assert.Equal(t, ret.Country, "中国")
|
||||||
assert.Equal(t, ret.Province, "四川省")
|
assert.Equal(t, ret.Province, "四川省")
|
||||||
@@ -6,6 +6,7 @@ require (
|
|||||||
github.com/go-kratos/kratos/v2 v2.7.0
|
github.com/go-kratos/kratos/v2 v2.7.0
|
||||||
github.com/oschwald/geoip2-golang v1.9.0
|
github.com/oschwald/geoip2-golang v1.9.0
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
|
golang.org/x/text v0.11.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
|
|||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||||
|
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
8
geoip/qqwry/README.md
Normal file
8
geoip/qqwry/README.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# QQWry
|
||||||
|
|
||||||
|
## 数据库下载地址
|
||||||
|
|
||||||
|
- [纯真社区版IP库](https://update.cz88.net/geo-public)
|
||||||
|
- [qqwry.dat - out0fmemory - Github](https://github.com/out0fmemory/qqwry.dat/blob/master/qqwry_lastest.dat)
|
||||||
|
- [qqwry - xiaoqidun - Github](https://github.com/xiaoqidun/qqwry)
|
||||||
|
- [qqwry - freshcn - Github](https://github.com/freshcn/qqwry)
|
||||||
6
geoip/qqwry/assets/assets.go
Normal file
6
geoip/qqwry/assets/assets.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package assets
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
//go:embed qqwry.dat
|
||||||
|
var QQWryDat []byte
|
||||||
BIN
geoip/qqwry/assets/qqwry.dat
Normal file
BIN
geoip/qqwry/assets/qqwry.dat
Normal file
Binary file not shown.
20
geoip/qqwry/consts.go
Normal file
20
geoip/qqwry/consts.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package qqwry
|
||||||
|
|
||||||
|
const (
|
||||||
|
ipRecordLength = 7 // IndexLen 索引长度
|
||||||
|
|
||||||
|
redirectMode1 = 0x01 // RedirectMode1 国家的类型, 指向另一个指向
|
||||||
|
|
||||||
|
redirectMode2 = 0x02 // RedirectMode2 国家的类型, 指向一个指向
|
||||||
|
)
|
||||||
|
|
||||||
|
//var unCountry = []byte{"未知国家"}
|
||||||
|
//var unArea = []byte{"未知地区"}
|
||||||
|
|
||||||
|
// Result 归属地信息
|
||||||
|
type Result struct {
|
||||||
|
IP string `json:"ip"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
Area string `json:"area"`
|
||||||
|
ISP string `json:"isp"`
|
||||||
|
}
|
||||||
204
geoip/qqwry/qqwry.go
Normal file
204
geoip/qqwry/qqwry.go
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
package qqwry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/tx7do/kratos-utils/geoip/qqwry/assets"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
data []byte
|
||||||
|
dataLen uint32
|
||||||
|
ipCache sync.Map
|
||||||
|
|
||||||
|
IPNum int64
|
||||||
|
|
||||||
|
startPos uint32
|
||||||
|
endPos uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient() *Client {
|
||||||
|
cli := &Client{
|
||||||
|
ipCache: sync.Map{},
|
||||||
|
}
|
||||||
|
|
||||||
|
cli.init()
|
||||||
|
|
||||||
|
return cli
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) init() {
|
||||||
|
c.startPos, c.endPos = c.readHeader()
|
||||||
|
c.IPNum = int64((c.endPos-c.startPos)/ipRecordLength + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseIp 解析IP
|
||||||
|
func (c *Client) parseIp(queryIp string) (uint32, error) {
|
||||||
|
ip := net.ParseIP(queryIp).To4()
|
||||||
|
if ip == nil {
|
||||||
|
return 0, errors.New("ip is not ipv4")
|
||||||
|
}
|
||||||
|
ip32 := binary.BigEndian.Uint32(ip)
|
||||||
|
return ip32, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readHeader 读取文件头
|
||||||
|
func (c *Client) readHeader() (uint32, uint32) {
|
||||||
|
startPos := binary.LittleEndian.Uint32(assets.QQWryDat[:4])
|
||||||
|
endPos := binary.LittleEndian.Uint32(assets.QQWryDat[4:8])
|
||||||
|
return startPos, endPos
|
||||||
|
}
|
||||||
|
|
||||||
|
// readMode 获取偏移值类型
|
||||||
|
func (c *Client) readMode(offset uint32) byte {
|
||||||
|
return assets.QQWryDat[offset]
|
||||||
|
}
|
||||||
|
|
||||||
|
// readIpRecord 读取IP记录 前4字节:起始IP,后3字节:偏移量
|
||||||
|
func (c *Client) readIpRecord(offset uint32) (ip32 uint32, ipOffset uint32) {
|
||||||
|
buf := assets.QQWryDat[offset : offset+ipRecordLength]
|
||||||
|
ip32 = binary.LittleEndian.Uint32(buf[:4])
|
||||||
|
ipOffset = byte3ToUInt32(buf[4:])
|
||||||
|
return ip32, ipOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
// locateIP 定位IP
|
||||||
|
func (c *Client) locateIP(ip32 uint32) int32 {
|
||||||
|
var _ip32 uint32
|
||||||
|
var _ipOffset uint32
|
||||||
|
var offset uint32
|
||||||
|
|
||||||
|
var mid uint32
|
||||||
|
i := c.startPos
|
||||||
|
j := c.endPos
|
||||||
|
for {
|
||||||
|
mid = getMiddleOffset(i, j)
|
||||||
|
_ip32, _ipOffset = c.readIpRecord(mid)
|
||||||
|
|
||||||
|
if j-i == ipRecordLength {
|
||||||
|
offset = _ipOffset
|
||||||
|
_ip32, _ipOffset = c.readIpRecord(mid + ipRecordLength)
|
||||||
|
if ip32 < _ip32 {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
offset = 0
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _ip32 > ip32 {
|
||||||
|
j = mid
|
||||||
|
} else if _ip32 < ip32 {
|
||||||
|
i = mid
|
||||||
|
} else if _ip32 == ip32 {
|
||||||
|
offset = _ipOffset
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return int32(offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readArea 读取区域
|
||||||
|
func (c *Client) readArea(offset uint32) []byte {
|
||||||
|
mode := c.readMode(offset)
|
||||||
|
if mode == redirectMode1 || mode == redirectMode2 {
|
||||||
|
areaOffset := c.readUInt24(int32(offset) + 1)
|
||||||
|
if areaOffset == 0 {
|
||||||
|
return []byte{}
|
||||||
|
}
|
||||||
|
return c.readString(areaOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.readString(offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readString 获取字符串
|
||||||
|
func (c *Client) readString(offset uint32) []byte {
|
||||||
|
data := make([]byte, 0, 30)
|
||||||
|
for i := offset; i < uint32(len(assets.QQWryDat)); i++ {
|
||||||
|
if assets.QQWryDat[i] == 0 {
|
||||||
|
data = assets.QQWryDat[offset:i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) readUInt24(offset int32) uint32 {
|
||||||
|
i := uint32(assets.QQWryDat[offset+0]) & 0xFF
|
||||||
|
i |= (uint32(assets.QQWryDat[offset+1]) << 8) & 0xFF00
|
||||||
|
i |= (uint32(assets.QQWryDat[offset+2]) << 16) & 0xFF0000
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Query(queryIp string) (country, city, isp string, err error) {
|
||||||
|
ip32, err := c.parseIp(queryIp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := c.locateIP(ip32)
|
||||||
|
if offset <= 0 {
|
||||||
|
err = errors.New("ip not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//读取第一个字节判断是否是标志字节
|
||||||
|
offset += 4
|
||||||
|
mode := c.readMode(uint32(offset))
|
||||||
|
|
||||||
|
var _city []byte
|
||||||
|
|
||||||
|
var ispPos uint32
|
||||||
|
switch mode {
|
||||||
|
case redirectMode1:
|
||||||
|
posC := c.readUInt24(offset + 1)
|
||||||
|
mode = c.readMode(posC)
|
||||||
|
posCA := posC
|
||||||
|
if mode == redirectMode2 {
|
||||||
|
posCA = c.readUInt24(int32(posC) + 1)
|
||||||
|
posC += 4
|
||||||
|
}
|
||||||
|
_city = c.readString(posCA)
|
||||||
|
if mode != redirectMode2 {
|
||||||
|
posC += uint32(len(city) + 1)
|
||||||
|
}
|
||||||
|
ispPos = posC
|
||||||
|
|
||||||
|
case redirectMode2:
|
||||||
|
posCA := c.readUInt24(offset + 1)
|
||||||
|
_city = c.readString(posCA)
|
||||||
|
ispPos = uint32(offset) + 4
|
||||||
|
|
||||||
|
default:
|
||||||
|
posCA := offset + 0
|
||||||
|
_city = c.readString(uint32(posCA))
|
||||||
|
ispPos = uint32(offset) + uint32(len(city)) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(_city) != 0 {
|
||||||
|
city = strings.TrimSpace(gb18030Decode(_city))
|
||||||
|
}
|
||||||
|
|
||||||
|
ispMode := assets.QQWryDat[ispPos]
|
||||||
|
if ispMode == redirectMode1 || ispMode == redirectMode2 {
|
||||||
|
ispPos = c.readUInt24(int32(ispPos + 1))
|
||||||
|
}
|
||||||
|
if ispPos > 0 {
|
||||||
|
var _isp []byte
|
||||||
|
_isp = c.readString(ispPos)
|
||||||
|
isp = strings.TrimSpace(gb18030Decode(_isp))
|
||||||
|
if isp != "" {
|
||||||
|
if strings.Contains(isp, "CZ88.NET") {
|
||||||
|
isp = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
16
geoip/qqwry/qqwry_test.go
Normal file
16
geoip/qqwry/qqwry_test.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package qqwry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClient(t *testing.T) {
|
||||||
|
g := NewClient()
|
||||||
|
assert.NotNil(t, g)
|
||||||
|
|
||||||
|
country, city, isp, err := g.Query("47.108.149.89")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
fmt.Println("国家:", country, "城市:", city, "服务商:", isp)
|
||||||
|
}
|
||||||
28
geoip/qqwry/utils.go
Normal file
28
geoip/qqwry/utils.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package qqwry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"golang.org/x/text/encoding/simplifiedchinese"
|
||||||
|
"golang.org/x/text/transform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func gb18030Decode(src []byte) string {
|
||||||
|
in := bytes.NewReader(src)
|
||||||
|
out := transform.NewReader(in, simplifiedchinese.GB18030.NewDecoder())
|
||||||
|
d, _ := io.ReadAll(out)
|
||||||
|
return string(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMiddleOffset 取得begin和end之间的偏移量,用于二分搜索
|
||||||
|
func getMiddleOffset(start uint32, end uint32) uint32 {
|
||||||
|
return start + (((end-start)/ipRecordLength)>>1)*ipRecordLength
|
||||||
|
}
|
||||||
|
|
||||||
|
func byte3ToUInt32(data []byte) uint32 {
|
||||||
|
i := uint32(data[0]) & 0xff
|
||||||
|
i |= (uint32(data[1]) << 8) & 0xff00
|
||||||
|
i |= (uint32(data[2]) << 16) & 0xff0000
|
||||||
|
return i
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user