Files
bl/common/utils/qqwry/qqwry.go
昔念 4d0464c76b ```
feat(rpc): 设置默认RPC地址为本地回环

修复RPC客户端连接问题,将默认服务器地址设置为127.0.0.1以确保本地连接正常

refactor(qqwry): 优化IP地址查询功能

移除不必要的正则表达式依赖,重构IP地址查询逻辑,提高代码性能和可维护性

fix(server): 保存确定的端口到配置中

确保服务器端口在确定后正确保存到配置中,避免端口配置丢失
2026-01-08 05:15:10 +08:00

337 lines
7.3 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 qqwry
import (
"bytes"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"strings"
"sync"
"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
ipdbCity *ipdb.City
dataType = dataTypeDat
locationCache = &sync.Map{}
)
const (
dataTypeDat = 0
dataTypeIpdb = 1
)
const (
indexLen = 7
redirectMode1 = 0x01
redirectMode2 = 0x02
)
type Location struct {
Country string // 国家
Province string // 省份
City string // 城市
District string // 区县
ISP string // 运营商
IP string // IP地址
}
func byte3ToUInt32(data []byte) uint32 {
i := uint32(data[0]) & 0xff
i |= (uint32(data[1]) << 8) & 0xff00
i |= (uint32(data[2]) << 16) & 0xff0000
return i
}
func gb18030Decode(src []byte) string {
in := bytes.NewReader(src)
out := transform.NewReader(in, simplifiedchinese.GB18030.NewDecoder())
d, _ := io.ReadAll(out)
return string(d)
}
// QueryIP 从内存或缓存查询IP
func QueryIP(ip string) (location *Location, err error) {
if v, ok := locationCache.Load(ip); ok {
return v.(*Location), nil
}
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 {
return nil, errors.New("ip is not ipv4")
}
if ip.IsLoopback() {
return &Location{ISP: "局域网", IP: ipv4}, nil
}
privateNets := []string{
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
}
for _, cidr := range privateNets {
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
continue
}
if ipnet.Contains(ip) {
return &Location{ISP: "私有地址", IP: ipv4}, nil
}
}
ip32 := binary.BigEndian.Uint32(ip)
posA := binary.LittleEndian.Uint32(data[:4])
posZ := binary.LittleEndian.Uint32(data[4:8])
var offset uint32 = 0
for {
mid := posA + (((posZ-posA)/indexLen)>>1)*indexLen
if int(mid+indexLen) > len(data) {
// 当数据中找不到IP时尝试使用网络查询
r, _ := LookupIP(ipv4, "json")
if r != nil {
location := &Location{
IP: r.IP,
Country: r.CountryName,
ISP: r.ISP,
}
locationCache.Store(ipv4, location)
return location, nil
} else {
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
} else {
offset = 0
break
}
}
if _ip > ip32 {
posZ = mid
} else if _ip < ip32 {
posA = mid
} else if _ip == ip32 {
offset = byte3ToUInt32(buf[4:])
break
}
}
if offset <= 0 {
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])
mode = data[posC]
posCA := posC
if mode == redirectMode2 {
posCA = byte3ToUInt32(data[posC+1 : posC+4])
posC += 4
}
for i := posCA; i < dataLen; i++ {
if data[i] == 0 {
addr = string(data[posCA:i])
break
}
}
if mode != redirectMode2 {
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 {
addr = string(data[posCA:i])
break
}
}
ispPos = offset + 8
default:
posCA := offset + 4
for i := posCA; i < dataLen; i++ {
if data[i] == 0 {
addr = string(data[posCA:i])
break
}
}
ispPos = offset + uint32(5+len(addr))
}
if addr != "" {
addr = strings.TrimSpace(gb18030Decode([]byte(addr)))
}
ispMode := data[ispPos]
if ispMode == redirectMode1 || ispMode == redirectMode2 {
ispPos = byte3ToUInt32(data[ispPos+1 : ispPos+4])
}
if ispPos > 0 {
for i := ispPos; i < dataLen; i++ {
if data[i] == 0 {
isp = string(data[ispPos:i])
if isp != "" {
if strings.Contains(isp, "CZ88.NET") {
isp = ""
} else {
isp = strings.TrimSpace(gb18030Decode([]byte(isp)))
}
}
break
}
}
}
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
}
// IPLocationResponse 定义IP定位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"`
}
// LookupIP 调用外部API查询IP地理位置
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 响应
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
}
// 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) {
body, err := os.ReadFile(filepath)
if err != nil {
return
}
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
}