165 lines
3.9 KiB
Go
165 lines
3.9 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// IPDetectionService IP 检测服务
|
|
type IPDetectionService struct {
|
|
httpClient *http.Client
|
|
}
|
|
|
|
// NewIPDetectionService 创建 IP 检测服务
|
|
func NewIPDetectionService() *IPDetectionService {
|
|
return &IPDetectionService{
|
|
httpClient: &http.Client{
|
|
Timeout: 10 * time.Second,
|
|
},
|
|
}
|
|
}
|
|
|
|
// GetPublicIPv4 获取公网 IPv4 地址
|
|
func (s *IPDetectionService) GetPublicIPv4() (string, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.ipify.org?format=json", nil)
|
|
if err != nil {
|
|
return "", fmt.Errorf("创建请求失败:%w", err)
|
|
}
|
|
|
|
resp, err := s.httpClient.Do(req)
|
|
if err != nil {
|
|
return "", fmt.Errorf("请求失败:%w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", fmt.Errorf("读取响应失败:%w", err)
|
|
}
|
|
|
|
var result struct {
|
|
IP string `json:"ip"`
|
|
}
|
|
|
|
if err := json.Unmarshal(body, &result); err != nil {
|
|
return "", fmt.Errorf("解析 JSON 失败:%w", err)
|
|
}
|
|
|
|
if result.IP == "" {
|
|
return "", fmt.Errorf("未获取到 IPv4 地址")
|
|
}
|
|
|
|
return result.IP, nil
|
|
}
|
|
|
|
// GetPublicIPv6 获取公网 IPv6 地址
|
|
func (s *IPDetectionService) GetPublicIPv6() (string, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", "https://api64.ipify.org?format=json", nil)
|
|
if err != nil {
|
|
return "", fmt.Errorf("创建请求失败:%w", err)
|
|
}
|
|
|
|
resp, err := s.httpClient.Do(req)
|
|
if err != nil {
|
|
return "", fmt.Errorf("请求失败:%w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", fmt.Errorf("读取响应失败:%w", err)
|
|
}
|
|
|
|
var result struct {
|
|
IP string `json:"ip"`
|
|
}
|
|
|
|
if err := json.Unmarshal(body, &result); err != nil {
|
|
return "", fmt.Errorf("解析 JSON 失败:%w", err)
|
|
}
|
|
|
|
if result.IP == "" {
|
|
return "", fmt.Errorf("未获取到 IPv6 地址")
|
|
}
|
|
|
|
// 检查是否是 IPv6 地址(包含冒号)
|
|
if !strings.Contains(result.IP, ":") {
|
|
return "", fmt.Errorf("获取到的不是有效的 IPv6 地址:%s", result.IP)
|
|
}
|
|
|
|
return result.IP, nil
|
|
}
|
|
|
|
// GetLocalIPv4 获取本地 IPv4 地址(第一个非回环接口)
|
|
func (s *IPDetectionService) GetLocalIPv4() (string, error) {
|
|
addrs, err := net.InterfaceAddrs()
|
|
if err != nil {
|
|
return "", fmt.Errorf("获取网络接口失败:%w", err)
|
|
}
|
|
|
|
for _, addr := range addrs {
|
|
// 检查是否为 IP 地址且为 IPv4
|
|
if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
|
|
if ipNet.IP.To4() != nil {
|
|
return ipNet.IP.String(), nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return "", fmt.Errorf("未找到 IPv4 地址")
|
|
}
|
|
|
|
// GetLocalIPv6 获取本地 IPv6 地址(第一个非回环接口)
|
|
func (s *IPDetectionService) GetLocalIPv6() (string, error) {
|
|
addrs, err := net.InterfaceAddrs()
|
|
if err != nil {
|
|
return "", fmt.Errorf("获取网络接口失败:%w", err)
|
|
}
|
|
|
|
for _, addr := range addrs {
|
|
if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
|
|
if ipNet.IP.To4() == nil && ipNet.IP.To16() != nil {
|
|
return ipNet.IP.String(), nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return "", fmt.Errorf("未找到 IPv6 地址")
|
|
}
|
|
|
|
// DetectIP 检测 IP 地址(根据记录类型返回对应的 IP)
|
|
func (s *IPDetectionService) DetectIP(recordType string) (string, error) {
|
|
switch recordType {
|
|
case "A":
|
|
// A 记录优先使用公网 IPv4
|
|
ip, err := s.GetPublicIPv4()
|
|
if err != nil {
|
|
// 降级到本地 IPv4
|
|
return s.GetLocalIPv4()
|
|
}
|
|
return ip, nil
|
|
case "AAAA":
|
|
// AAAA 记录优先使用公网 IPv6
|
|
ip, err := s.GetPublicIPv6()
|
|
if err != nil {
|
|
// 降级到本地 IPv6
|
|
return s.GetLocalIPv6()
|
|
}
|
|
return ip, nil
|
|
default:
|
|
return "", fmt.Errorf("不支持的记录类型:%s", recordType)
|
|
}
|
|
}
|