修复了一些问题
This commit is contained in:
28
internal/utils/get_client_ip.go
Normal file
28
internal/utils/get_client_ip.go
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* package utils
|
||||
* 作用:从 HTTP 请求中提取客户端真实 IP
|
||||
*/
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
/**
|
||||
* GetClientIP
|
||||
* 功能:优先从代理头获取,否则使用 Gin ClientIP
|
||||
*/
|
||||
func GetClientIP(c *gin.Context) string {
|
||||
ip := c.GetHeader("X-Forwarded-For")
|
||||
if ip != "" {
|
||||
parts := strings.Split(ip, ",")
|
||||
return strings.TrimSpace(parts[0])
|
||||
}
|
||||
ip = c.GetHeader("X-Real-IP")
|
||||
if ip != "" {
|
||||
return ip
|
||||
}
|
||||
return c.ClientIP()
|
||||
}
|
||||
@@ -1,163 +1,102 @@
|
||||
/**
|
||||
* package utils
|
||||
* 作用:IP归属地查询工具
|
||||
* 说明:使用第三方API查询IP归属地信息
|
||||
* 作用:IP 归属地查询(基于本地 ip2region.xdb 离线库)
|
||||
*/
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
"sync"
|
||||
|
||||
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||
)
|
||||
|
||||
// IPLocationInfo IP归属地信息
|
||||
type IPLocationInfo struct {
|
||||
Country string `json:"country"` // 国家
|
||||
Region string `json:"region"` // 省份
|
||||
City string `json:"city"` // 城市
|
||||
ISP string `json:"isp"` // 运营商
|
||||
FullLocation string `json:"full_location"` // 完整归属地
|
||||
}
|
||||
|
||||
// 缓存结构(简单内存缓存)
|
||||
var (
|
||||
ipCache = make(map[string]*IPLocationInfo)
|
||||
cacheTimeout = 24 * time.Hour
|
||||
ipSearcher *xdb.Searcher
|
||||
ipSearcherOnce sync.Once
|
||||
ipSearcherErr error
|
||||
ipCache sync.Map
|
||||
)
|
||||
|
||||
/**
|
||||
* InitIP2Region
|
||||
* 功能:加载 ip2region.xdb,服务启动时调用一次
|
||||
*/
|
||||
func InitIP2Region(dbPath string) error {
|
||||
ipSearcherOnce.Do(func() {
|
||||
ipSearcher, ipSearcherErr = xdb.NewWithFileOnly(xdb.IPv4, dbPath)
|
||||
if ipSearcherErr != nil {
|
||||
log.Printf("❌ [IP2Region] 加载失败: %v", ipSearcherErr)
|
||||
return
|
||||
}
|
||||
log.Printf("✅ [IP2Region] 已加载: %s", dbPath)
|
||||
})
|
||||
return ipSearcherErr
|
||||
}
|
||||
|
||||
/**
|
||||
* GetIPLocation
|
||||
* 功能:获取IP归属地信息
|
||||
* @param ip IP地址
|
||||
* @returns 归属地信息字符串
|
||||
* 功能:查询 IP 归属地,内网/本地返回「本地」
|
||||
*/
|
||||
func GetIPLocation(ip string) string {
|
||||
// 排除本地IP
|
||||
if isLocalIP(ip) {
|
||||
return "本地"
|
||||
}
|
||||
|
||||
// 检查缓存
|
||||
if info, ok := ipCache[ip]; ok {
|
||||
return info.FullLocation
|
||||
if ip == "" {
|
||||
return "未知"
|
||||
}
|
||||
|
||||
// 查询IP归属地
|
||||
info := queryIPLocation(ip)
|
||||
if info != nil {
|
||||
// 构建完整归属地字符串
|
||||
parts := []string{}
|
||||
if info.Country != "" {
|
||||
parts = append(parts, info.Country)
|
||||
}
|
||||
if info.Region != "" {
|
||||
parts = append(parts, info.Region)
|
||||
}
|
||||
if info.City != "" {
|
||||
parts = append(parts, info.City)
|
||||
}
|
||||
if info.ISP != "" {
|
||||
parts = append(parts, info.ISP)
|
||||
}
|
||||
|
||||
if len(parts) > 0 {
|
||||
info.FullLocation = strings.Join(parts, " ")
|
||||
} else {
|
||||
info.FullLocation = "未知"
|
||||
}
|
||||
|
||||
// 存入缓存
|
||||
ipCache[ip] = info
|
||||
return info.FullLocation
|
||||
if cached, ok := ipCache.Load(ip); ok {
|
||||
return cached.(string)
|
||||
}
|
||||
|
||||
return "未知"
|
||||
if ipSearcher == nil {
|
||||
return "未知"
|
||||
}
|
||||
|
||||
region, err := ipSearcher.Search(ip)
|
||||
if err != nil || region == "" {
|
||||
return "未知"
|
||||
}
|
||||
|
||||
// 格式: 国家|区域|省份|城市|ISP
|
||||
parts := strings.Split(region, "|")
|
||||
labels := []string{}
|
||||
for _, p := range parts {
|
||||
p = strings.TrimSpace(p)
|
||||
if p != "" && p != "0" {
|
||||
labels = append(labels, p)
|
||||
}
|
||||
}
|
||||
result := "未知"
|
||||
if len(labels) > 0 {
|
||||
result = strings.Join(labels, " ")
|
||||
}
|
||||
ipCache.Store(ip, result)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* isLocalIP
|
||||
* 功能:判断是否为本地IP
|
||||
* 功能:判断是否为本地/内网 IP
|
||||
*/
|
||||
func isLocalIP(ip string) bool {
|
||||
// 本地回环地址
|
||||
if ip == "127.0.0.1" || ip == "localhost" || ip == "::1" {
|
||||
return true
|
||||
}
|
||||
|
||||
// 内网地址
|
||||
if strings.HasPrefix(ip, "192.168.") ||
|
||||
strings.HasPrefix(ip, "10.") ||
|
||||
strings.HasPrefix(ip, "172.16.") ||
|
||||
strings.HasPrefix(ip, "172.17.") ||
|
||||
strings.HasPrefix(ip, "172.18.") ||
|
||||
strings.HasPrefix(ip, "172.19.") ||
|
||||
strings.HasPrefix(ip, "172.20.") ||
|
||||
strings.HasPrefix(ip, "172.21.") ||
|
||||
strings.HasPrefix(ip, "172.22.") ||
|
||||
strings.HasPrefix(ip, "172.23.") ||
|
||||
strings.HasPrefix(ip, "172.24.") ||
|
||||
strings.HasPrefix(ip, "172.25.") ||
|
||||
strings.HasPrefix(ip, "172.26.") ||
|
||||
strings.HasPrefix(ip, "172.27.") ||
|
||||
strings.HasPrefix(ip, "172.28.") ||
|
||||
strings.HasPrefix(ip, "172.29.") ||
|
||||
strings.HasPrefix(ip, "172.30.") ||
|
||||
strings.HasPrefix(ip, "172.31.") {
|
||||
if strings.HasPrefix(ip, "192.168.") || strings.HasPrefix(ip, "10.") {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(ip, "172.16.") || strings.HasPrefix(ip, "172.17.") ||
|
||||
strings.HasPrefix(ip, "172.18.") || strings.HasPrefix(ip, "172.19.") ||
|
||||
strings.HasPrefix(ip, "172.20.") || strings.HasPrefix(ip, "172.21.") ||
|
||||
strings.HasPrefix(ip, "172.22.") || strings.HasPrefix(ip, "172.23.") ||
|
||||
strings.HasPrefix(ip, "172.24.") || strings.HasPrefix(ip, "172.25.") ||
|
||||
strings.HasPrefix(ip, "172.26.") || strings.HasPrefix(ip, "172.27.") ||
|
||||
strings.HasPrefix(ip, "172.28.") || strings.HasPrefix(ip, "172.29.") ||
|
||||
strings.HasPrefix(ip, "172.30.") || strings.HasPrefix(ip, "172.31.") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* queryIPLocation
|
||||
* 功能:查询IP归属地(使用ip-api.com免费API)
|
||||
*/
|
||||
func queryIPLocation(ip string) *IPLocationInfo {
|
||||
// 使用ip-api.com免费API(限制:每分钟45次请求)
|
||||
url := fmt.Sprintf("http://ip-api.com/json/%s?lang=zh-CN&fields=status,message,country,regionName,city,isp", ip)
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 3 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Country string `json:"country"`
|
||||
Region string `json:"regionName"`
|
||||
City string `json:"city"`
|
||||
ISP string `json:"isp"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if result.Status != "success" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &IPLocationInfo{
|
||||
Country: result.Country,
|
||||
Region: result.Region,
|
||||
City: result.City,
|
||||
ISP: result.ISP,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user