micro-account/pkg/common/utils/ip.go
2025-02-20 16:18:23 +08:00

407 lines
9.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}