修复了一些问题

This commit is contained in:
李琦
2026-07-08 08:18:58 +08:00
parent bb7891706b
commit 3417856607
19 changed files with 615 additions and 778 deletions

View 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()
}

View File

@@ -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,
}
}