407 lines
9.6 KiB
Go
407 lines
9.6 KiB
Go
package utils
|
||
|
||
import (
|
||
"encoding/binary"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"golang.org/x/text/encoding/simplifiedchinese"
|
||
"io/ioutil"
|
||
"net"
|
||
"net/http"
|
||
"os"
|
||
"path/filepath"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
const (
|
||
// DefaultDict 默认字典
|
||
DefaultDict = "./conf/data.dat"
|
||
// IndexLen 索引长度
|
||
IndexLen = 7
|
||
// RedirectMode1 国家的类型, 指向另一个指向
|
||
RedirectMode1 = 0x01
|
||
// RedirectMode2 国家的类型, 指向一个指向
|
||
RedirectMode2 = 0x02
|
||
)
|
||
|
||
type Result struct {
|
||
IP string `json:"ip"`
|
||
Location Location `json:"location"`
|
||
AdInfo AdInfo `json:"ad_info"`
|
||
}
|
||
|
||
type ResultV2 struct {
|
||
IP string `json:"ip"`
|
||
City string `json:"city"`
|
||
Country string `json:"country"`
|
||
}
|
||
|
||
type Location struct {
|
||
Lat float64 `json:"lat"`
|
||
Lng float64 `json:"lng"`
|
||
}
|
||
|
||
type AdInfo struct {
|
||
Nation string `json:"nation"`
|
||
Province string `json:"province"`
|
||
City string `json:"city"`
|
||
District string `json:"district"`
|
||
AdcodE int `json:"adcode"`
|
||
}
|
||
|
||
type IpResult struct {
|
||
Status int `json:"status"`
|
||
Message string `json:"message"`
|
||
RequestID string `json:"request_id"`
|
||
Result Result `json:"result"`
|
||
}
|
||
|
||
type IpResultV2 struct {
|
||
Ret int `json:"ret"`
|
||
Msg string `json:"Msg"`
|
||
LogId string `json:"log_id"`
|
||
Result ResultV2 `json:"data"`
|
||
}
|
||
|
||
type IPDict struct {
|
||
fileData []byte //文件数据
|
||
offset uint32 //当前下标定位
|
||
firstOffset uint32 //第一条IP记录的偏移地址
|
||
lastOffset uint32 //最后一条IP记录的偏移地址
|
||
totalIPNum uint32 //IP记录的总条数(不包含版本信息记录)
|
||
}
|
||
|
||
type IPLocation struct {
|
||
IP string `json:"ip"`
|
||
BeginIP string `json:"begin_ip"`
|
||
EndIP string `json:"end_ip"`
|
||
Country string `json:"country"`
|
||
Area string `json:"area"`
|
||
}
|
||
|
||
func NewIPDict() *IPDict {
|
||
return &IPDict{}
|
||
}
|
||
|
||
func (q *IPDict) Load(fileName string) error {
|
||
filePath, err := dictPath(fileName)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
dictFile, err := os.OpenFile(filePath, os.O_RDONLY, 0400)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer dictFile.Close()
|
||
q.fileData, err = ioutil.ReadAll(dictFile)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
buf := q.readBuf(8)
|
||
q.firstOffset = binary.LittleEndian.Uint32(buf[:4])
|
||
q.lastOffset = binary.LittleEndian.Uint32(buf[4:])
|
||
q.totalIPNum = (q.lastOffset - q.firstOffset) / IndexLen
|
||
return nil
|
||
}
|
||
|
||
func (q *IPDict) FindIP(ip string) (*IPLocation, error) {
|
||
if false == checkIPv4(ip) {
|
||
return nil, errors.New("IP format error")
|
||
}
|
||
res := IPLocation{IP: ip}
|
||
if nil == q.fileData {
|
||
err := q.Load(DefaultDict)
|
||
if nil != err {
|
||
return nil, err
|
||
}
|
||
}
|
||
q.seekOffset(0)
|
||
index := q.findIndex(ip)
|
||
if index <= 0 {
|
||
return nil, errors.New("IP not fount")
|
||
}
|
||
q.seekOffset(index)
|
||
res.BeginIP = long2ip(q.getIPLong4()) //endIPOffset
|
||
endIPOffset := q.getRedirectOffset()
|
||
q.seekOffset(endIPOffset)
|
||
res.EndIP = long2ip(q.getIPLong4()) //endIPOffset
|
||
mode := q.readMode() // 标志字节
|
||
var country, area []byte
|
||
enc := simplifiedchinese.GBK.NewDecoder()
|
||
switch mode {
|
||
case RedirectMode1: // 标志字节为1,表示国家和区域信息都被同时重定向
|
||
countryOffset := q.getRedirectOffset() // 重定向地址
|
||
q.seekOffset(countryOffset)
|
||
mode2 := q.readMode() // 标志字节
|
||
switch mode2 {
|
||
case RedirectMode2: // 标志字节为2,表示国家信息又被重定向
|
||
q.seekOffset(q.getRedirectOffset()) // 重定向地址
|
||
country = q.readString(0)
|
||
q.seekOffset(countryOffset + 4) // 重定向地址
|
||
area = q.readArea()
|
||
default: // 否则,表示国家信息没有被重定向
|
||
country = q.readString(mode2)
|
||
area = q.readArea()
|
||
}
|
||
case RedirectMode2: // 标志字节为2,表示国家信息被重定向
|
||
q.seekOffset(q.getRedirectOffset()) // 重定向地址
|
||
country = q.readString(0)
|
||
q.seekOffset(endIPOffset + 8)
|
||
area = q.readArea()
|
||
default:
|
||
country = q.readString(mode)
|
||
area = q.readArea()
|
||
}
|
||
countryUTF8, _ := enc.String(string(country))
|
||
if strings.Trim(countryUTF8, " ") == "CZ88.NET" {
|
||
res.Country = ""
|
||
} else {
|
||
res.Country = countryUTF8
|
||
}
|
||
areaUTF8, _ := enc.String(string(area))
|
||
if strings.Trim(areaUTF8, " ") == "CZ88.NET" {
|
||
res.Area = ""
|
||
} else {
|
||
res.Area = areaUTF8
|
||
}
|
||
return &res, nil
|
||
}
|
||
|
||
func (q *IPDict) findIndex(ip string) uint32 {
|
||
if false == checkIPv4(ip) {
|
||
return 0
|
||
}
|
||
if nil == q.fileData {
|
||
err := q.Load(DefaultDict)
|
||
if nil != err {
|
||
return 0
|
||
}
|
||
}
|
||
uIP := ip2long(ip) // 将输入的IP地址转化为可比较的IP地址
|
||
min := uint32(0) // 搜索的下边界
|
||
max := q.totalIPNum // 搜索的上边界
|
||
findIndex := q.lastOffset // 如果没有找到就返回最后一条IP记录(IPDict.Dat的版本信息)
|
||
for min <= max {
|
||
// 当上边界小于下边界时,查找失败
|
||
mid := (min + max) / 2 // 计算近似中间记录
|
||
q.seekOffset(q.firstOffset + mid*IndexLen)
|
||
cBeginIP := q.getIPLong4() // 获取中间记录的开始IP地址
|
||
if uIP < cBeginIP { // 用户的IP小于中间记录的开始IP地址时
|
||
max = mid - 1 // 将搜索的上边界修改为中间记录减一
|
||
} else {
|
||
q.seekOffset(q.getRedirectOffset())
|
||
cEndIP := q.getIPLong4() // 获取中间记录的开始IP地址
|
||
if uIP > cEndIP { // 用户的IP大于中间记录的结束IP地址时
|
||
min = mid + 1 // 将搜索的下边界修改为中间记录加一
|
||
} else {
|
||
// 用户的IP在中间记录的IP范围内时
|
||
findIndex = q.firstOffset + mid*IndexLen
|
||
break // 则表示找到结果,退出循环
|
||
}
|
||
}
|
||
}
|
||
return findIndex
|
||
}
|
||
|
||
//模拟文件读取Seek
|
||
func (q *IPDict) seekOffset(offset uint32) {
|
||
q.offset = offset
|
||
}
|
||
|
||
//模拟文件读取Read
|
||
func (q *IPDict) readBuf(length uint32) []byte {
|
||
q.offset = q.offset + length
|
||
return q.fileData[q.offset-length : q.offset] // 标志字节
|
||
}
|
||
|
||
//返回读取的长整型数
|
||
func (q *IPDict) getIPLong4() uint32 {
|
||
buf := q.readBuf(4)
|
||
return binary.LittleEndian.Uint32(buf)
|
||
}
|
||
|
||
//返回读取的3个字节的长整型数
|
||
func (q *IPDict) getRedirectOffset() uint32 {
|
||
buf := q.readBuf(3)
|
||
return binary.LittleEndian.Uint32([]byte{buf[0], buf[1], buf[2], 0})
|
||
}
|
||
|
||
// readString 获取字符
|
||
func (q *IPDict) readMode() byte {
|
||
return q.readBuf(1)[0] // 标志字节
|
||
}
|
||
|
||
// readString 获取字符串
|
||
func (q *IPDict) readString(char byte) []byte {
|
||
data := make([]byte, 0, 30)
|
||
if char != 0 {
|
||
data = append(data, char)
|
||
}
|
||
buf := q.readBuf(1)
|
||
for buf[0] != 0 {
|
||
data = append(data, buf[0])
|
||
buf = q.readBuf(1)
|
||
}
|
||
return data
|
||
}
|
||
|
||
// readArea 获取地区字符串
|
||
func (q *IPDict) readArea() []byte {
|
||
mode := q.readMode()
|
||
switch mode { // 标志字节
|
||
case 0: // 结束标识
|
||
return []byte{}
|
||
case RedirectMode1:
|
||
case RedirectMode2: // 标志字节为1或2,表示区域信息被重定向
|
||
q.seekOffset(q.getRedirectOffset()) // 重定向地址
|
||
return q.readString(0)
|
||
}
|
||
return q.readString(mode)
|
||
}
|
||
|
||
//数值IP转换字符串IP
|
||
func long2ip(ipInt uint32) string {
|
||
// need to do two bit shifting and “0xff” masking
|
||
ipInt64 := int64(ipInt)
|
||
b0 := strconv.FormatInt((ipInt64>>24)&0xff, 10)
|
||
b1 := strconv.FormatInt((ipInt64>>16)&0xff, 10)
|
||
b2 := strconv.FormatInt((ipInt64>>8)&0xff, 10)
|
||
b3 := strconv.FormatInt(ipInt64&0xff, 10)
|
||
return b0 + "." + b1 + "." + b2 + "." + b3
|
||
}
|
||
|
||
//字符串IP转换数值IP
|
||
func ip2long(ip string) uint32 {
|
||
bIP := net.ParseIP(ip).To4()
|
||
if nil == bIP {
|
||
return 0
|
||
}
|
||
return binary.BigEndian.Uint32(bIP)
|
||
}
|
||
|
||
//返回字典绝对路径
|
||
func dictPath(dictFileName string) (string, error) {
|
||
if filepath.IsAbs(dictFileName) {
|
||
return dictFileName, nil
|
||
}
|
||
var dictFilePath string
|
||
cwd, err := os.Getwd()
|
||
if err != nil {
|
||
return dictFilePath, err
|
||
}
|
||
dictFilePath = filepath.Clean(filepath.Join(cwd, dictFileName))
|
||
return dictFilePath, nil
|
||
}
|
||
|
||
//检查ip地址
|
||
func checkIPv4(IP string) bool {
|
||
// 字符串这样切割
|
||
strList := strings.Split(IP, ".")
|
||
if len(strList) != 4 {
|
||
return false
|
||
}
|
||
for _, s := range strList {
|
||
if len(s) == 0 || (len(s) > 1 && s[0] == '0') {
|
||
return false
|
||
}
|
||
// 直接访问字符串的值
|
||
if s[0] < '0' || s[0] > '9' {
|
||
return false
|
||
}
|
||
// 字符串转数字
|
||
n, err := strconv.Atoi(s)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
if n < 0 || n > 255 {
|
||
return false
|
||
}
|
||
}
|
||
return true
|
||
}
|
||
|
||
func GetIpAddress(ip string) (string, error) {
|
||
IPDict := NewIPDict()
|
||
//载入IP字典
|
||
err := IPDict.Load("../conf/qqwry.dat")
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
//查询IP
|
||
res, err := IPDict.FindIP(ip)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
return res.Country, nil
|
||
}
|
||
|
||
func GetIpOnlyAddress(ip string) string {
|
||
IPDict := NewIPDict()
|
||
//载入IP字典
|
||
err := IPDict.Load("../conf/qqwry.dat")
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
//查询IP
|
||
res, err := IPDict.FindIP(ip)
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
return res.Country
|
||
}
|
||
|
||
func AliIpAddress(ip string) (string, error) {
|
||
|
||
url := "https://c2ba.api.huachen.cn/ip?ip=" + ip
|
||
|
||
client := &http.Client{
|
||
Timeout: 1 * time.Second,
|
||
}
|
||
//提交请求
|
||
reqest, err := http.NewRequest("GET", url, nil)
|
||
|
||
//增加header选项
|
||
reqest.Header.Add("Authorization", "APPCODE 89aaef07254149aabac798c911647673")
|
||
|
||
if err != nil {
|
||
fmt.Println(err)
|
||
return "", err
|
||
//panic(err)
|
||
}
|
||
//处理返回结果
|
||
response, err := client.Do(reqest)
|
||
if err != nil {
|
||
fmt.Println(err)
|
||
return "", err
|
||
}
|
||
|
||
resByte, err := ioutil.ReadAll(response.Body)
|
||
|
||
fmt.Println("1-------------", string(resByte))
|
||
var result IpResultV2
|
||
err = json.Unmarshal(resByte, &result)
|
||
fmt.Printf("1-------------%+v\n", result)
|
||
|
||
defer response.Body.Close()
|
||
|
||
return result.Result.Country + result.Result.City, nil
|
||
}
|
||
|
||
func GetIpOnlyAddressFromAli(ip string) string {
|
||
IPDict := NewIPDict()
|
||
//载入IP字典
|
||
err := IPDict.Load("../conf/qqwry.dat")
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
//查询IP
|
||
res, err := IPDict.FindIP(ip)
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
return res.Country
|
||
}
|