``feat(ip): 新增IP定位查询功能,支持通过API和网页解析获取归属地信息``
This commit is contained in:
@@ -3,19 +3,34 @@ package qqwry
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"github.com/ipipdotnet/ipdb-go"
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
//go:embed qqwry.ipdb
|
||||
var qqwry []byte
|
||||
|
||||
func init() {
|
||||
LoadData(qqwry)
|
||||
|
||||
}
|
||||
|
||||
var (
|
||||
data []byte
|
||||
dataLen uint32
|
||||
@@ -73,6 +88,103 @@ func QueryIP(ip string) (location *Location, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetIpAddress 获取 IP 归属地和运营商
|
||||
func GetIpAddress(ip string) (string, error) {
|
||||
html, err := doGet("https://www.ipshudi.com/" + ip + "/")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
div := getAddress(html, `<td>\n<span>.*</span>\n`)
|
||||
div1 := getAddress(html, `<td class="th">运营商</td><td><span>.*</span>`)
|
||||
|
||||
location := "未知归属地"
|
||||
if div != "" {
|
||||
location = strings.Split(div, "<span>")[1]
|
||||
location = strings.Split(location, "</span>")[0]
|
||||
}
|
||||
|
||||
isp := ""
|
||||
if div1 != "" {
|
||||
isp = strings.Split(div1, "<span>")[1]
|
||||
isp = strings.Split(isp, "</span>")[0]
|
||||
}
|
||||
|
||||
return location + " " + isp, nil
|
||||
}
|
||||
|
||||
// 定义响应数据结构(与 API 返回字段对应)
|
||||
type IPLocationResponse struct {
|
||||
IP string `json:"ip"`
|
||||
IPNumber string `json:"ip_number"`
|
||||
IPVersion int `json:"ip_version"`
|
||||
CountryName string `json:"country_name"`
|
||||
CountryCode2 string `json:"country_code2"`
|
||||
ISP string `json:"isp"`
|
||||
ResponseCode string `json:"response_code"`
|
||||
ResponseMessage string `json:"response_message"`
|
||||
}
|
||||
|
||||
// 调用 IP 定位 API
|
||||
func LookupIP(ip string, format string) (*IPLocationResponse, error) {
|
||||
// 构建请求 URL
|
||||
baseURL := "https://api.iplocation.net/"
|
||||
params := url.Values{}
|
||||
params.Add("ip", ip)
|
||||
if format != "" {
|
||||
params.Add("format", format)
|
||||
}
|
||||
|
||||
fullURL := baseURL + "?" + params.Encode()
|
||||
|
||||
// 发送 GET 请求
|
||||
resp, err := http.Get(fullURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应体
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取响应失败: %v", err)
|
||||
}
|
||||
|
||||
// 解析 JSON 响应(默认格式为 json)
|
||||
var result IPLocationResponse
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return nil, fmt.Errorf("解析响应失败: %v (响应内容: %s)", err, string(body))
|
||||
}
|
||||
|
||||
// 检查 API 响应状态
|
||||
if result.ResponseCode != "200" {
|
||||
return nil, fmt.Errorf("API 调用失败: %s (%s)", result.ResponseMessage, result.ResponseCode)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// doGet 发送 HTTP GET 请求,返回响应内容
|
||||
func doGet(url string) ([]byte, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// getAddress 使用正则表达式从 HTML 中提取内容
|
||||
func getAddress(html []byte, pattern string) string {
|
||||
re := regexp.MustCompile(pattern)
|
||||
return re.FindString(string(html))
|
||||
}
|
||||
|
||||
// 判断IP是否为私有地址(包括IPv4和IPv6)
|
||||
func isPrivateIP(ipStr string) (bool, error) {
|
||||
ip := net.ParseIP(ipStr)
|
||||
@@ -132,10 +244,14 @@ func QueryIPDat(ipv4 string) (location *Location, err error) {
|
||||
var offset uint32 = 0
|
||||
for {
|
||||
mid := posA + (((posZ-posA)/indexLen)>>1)*indexLen
|
||||
// if int(mid+indexLen) > len(data) {
|
||||
// return nil, errors.New("ip not found")
|
||||
// }
|
||||
buf := data[mid : mid+indexLen]
|
||||
_ip := binary.LittleEndian.Uint32(buf[:4])
|
||||
if posZ-posA == indexLen {
|
||||
offset = byte3ToUInt32(buf[4:])
|
||||
|
||||
buf = data[mid+indexLen : mid+indexLen+indexLen]
|
||||
if ip32 < binary.LittleEndian.Uint32(buf[:4]) {
|
||||
break
|
||||
|
||||
BIN
common/utils/qqwry/qqwry.ipdb
Normal file
BIN
common/utils/qqwry/qqwry.ipdb
Normal file
Binary file not shown.
Reference in New Issue
Block a user