2022-11-24 23:37:34 +08:00
|
|
|
|
// Package location
|
|
|
|
|
// @Link https://github.com/bufanyun/hotgo
|
2023-02-23 17:53:04 +08:00
|
|
|
|
// @Copyright Copyright (c) 2023 HotGo CLI
|
2022-11-24 23:37:34 +08:00
|
|
|
|
// @Author Ms <133814250@qq.com>
|
|
|
|
|
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
|
|
|
|
package location
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2023-01-18 16:23:39 +08:00
|
|
|
|
"fmt"
|
2023-05-12 16:20:22 +08:00
|
|
|
|
"github.com/gogf/gf/v2/encoding/gcharset"
|
2024-04-22 23:08:40 +08:00
|
|
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
2022-11-24 23:37:34 +08:00
|
|
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
|
|
|
"github.com/gogf/gf/v2/net/ghttp"
|
|
|
|
|
"github.com/gogf/gf/v2/text/gstr"
|
|
|
|
|
"github.com/gogf/gf/v2/util/gconv"
|
|
|
|
|
"github.com/kayon/iploc"
|
|
|
|
|
"hotgo/utility/validate"
|
2023-05-10 23:54:50 +08:00
|
|
|
|
"io"
|
2022-11-24 23:37:34 +08:00
|
|
|
|
"net"
|
|
|
|
|
"net/http"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
2023-01-18 16:23:39 +08:00
|
|
|
|
const (
|
|
|
|
|
whoisApi = "https://whois.pconline.com.cn/ipJson.jsp?json=true&ip="
|
2024-03-07 20:08:56 +08:00
|
|
|
|
dyndns = "http://members.3322.org/dyndns/getip" // 备用:"https://ifconfig.co/ip"
|
2023-01-18 16:23:39 +08:00
|
|
|
|
)
|
|
|
|
|
|
2022-11-24 23:37:34 +08:00
|
|
|
|
type IpLocationData struct {
|
|
|
|
|
Ip string `json:"ip"`
|
|
|
|
|
Country string `json:"country"`
|
|
|
|
|
Region string `json:"region"`
|
|
|
|
|
Province string `json:"province"`
|
|
|
|
|
ProvinceCode int64 `json:"province_code"`
|
|
|
|
|
City string `json:"city"`
|
|
|
|
|
CityCode int64 `json:"city_code"`
|
|
|
|
|
Area string `json:"area"`
|
|
|
|
|
AreaCode int64 `json:"area_code"`
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-18 16:23:39 +08:00
|
|
|
|
type WhoisRegionData struct {
|
|
|
|
|
Ip string `json:"ip"`
|
|
|
|
|
Pro string `json:"pro" `
|
|
|
|
|
ProCode string `json:"proCode" `
|
|
|
|
|
City string `json:"city" `
|
|
|
|
|
CityCode string `json:"cityCode"`
|
|
|
|
|
Region string `json:"region"`
|
|
|
|
|
RegionCode string `json:"regionCode"`
|
|
|
|
|
Addr string `json:"addr"`
|
|
|
|
|
Err string `json:"err"`
|
|
|
|
|
}
|
2022-11-24 23:37:34 +08:00
|
|
|
|
|
2024-04-22 23:08:40 +08:00
|
|
|
|
var (
|
|
|
|
|
defaultRetry int64 = 3 // 默认重试次数
|
|
|
|
|
)
|
2023-04-10 15:31:08 +08:00
|
|
|
|
|
2023-01-18 16:23:39 +08:00
|
|
|
|
// WhoisLocation 通过Whois接口查询IP归属地
|
2024-04-22 23:08:40 +08:00
|
|
|
|
func WhoisLocation(ctx context.Context, ip string, retry ...int64) (*IpLocationData, error) {
|
2023-01-18 16:23:39 +08:00
|
|
|
|
response, err := g.Client().Timeout(10*time.Second).Get(ctx, whoisApi+ip)
|
2022-11-24 23:37:34 +08:00
|
|
|
|
if err != nil {
|
2023-01-18 16:23:39 +08:00
|
|
|
|
return nil, err
|
2022-11-24 23:37:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defer response.Close()
|
|
|
|
|
|
2023-05-12 16:20:22 +08:00
|
|
|
|
str, err := gcharset.ToUTF8("GBK", response.ReadAllString())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2022-11-24 23:37:34 +08:00
|
|
|
|
|
2024-04-22 23:08:40 +08:00
|
|
|
|
// 利用重试机制缓解高并发情况下限流的影响
|
|
|
|
|
// 毕竟这是一个免费的接口,如果你对IP归属地定位要求毕竟高,可以考虑换个付费接口
|
|
|
|
|
if response.StatusCode != 200 {
|
|
|
|
|
retryCount := defaultRetry
|
|
|
|
|
if len(retry) > 0 {
|
|
|
|
|
retryCount = retry[0]
|
|
|
|
|
}
|
|
|
|
|
if retryCount > 0 {
|
|
|
|
|
retryCount--
|
|
|
|
|
return WhoisLocation(ctx, ip, retryCount)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-02 20:29:08 +08:00
|
|
|
|
var who *WhoisRegionData
|
2024-04-22 23:08:40 +08:00
|
|
|
|
if err = gconv.Scan([]byte(str), &who); err != nil {
|
|
|
|
|
err = gerror.Newf("WhoisLocation Scan err:%v, str:%v", err, str)
|
2023-01-18 16:23:39 +08:00
|
|
|
|
return nil, err
|
2022-11-24 23:37:34 +08:00
|
|
|
|
}
|
2023-01-18 16:23:39 +08:00
|
|
|
|
return &IpLocationData{
|
2023-06-02 20:29:08 +08:00
|
|
|
|
Ip: who.Ip,
|
|
|
|
|
Region: who.Addr,
|
|
|
|
|
Province: who.Pro,
|
|
|
|
|
ProvinceCode: gconv.Int64(who.ProCode),
|
|
|
|
|
City: who.City,
|
|
|
|
|
CityCode: gconv.Int64(who.CityCode),
|
|
|
|
|
Area: who.Region,
|
|
|
|
|
AreaCode: gconv.Int64(who.RegionCode),
|
2023-01-18 16:23:39 +08:00
|
|
|
|
}, nil
|
2022-11-24 23:37:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cz88Find 通过Cz88的IP库查询IP归属地
|
2023-01-18 16:23:39 +08:00
|
|
|
|
func Cz88Find(ctx context.Context, ip string) (*IpLocationData, error) {
|
|
|
|
|
loc, err := iploc.OpenWithoutIndexes("./resource/ip/qqwry-utf8.dat")
|
2022-11-24 23:37:34 +08:00
|
|
|
|
if err != nil {
|
2023-01-18 16:23:39 +08:00
|
|
|
|
return nil, fmt.Errorf("%v for help, please go to: https://github.com/kayon/iploc", err.Error())
|
2022-11-24 23:37:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
detail := loc.Find(ip)
|
|
|
|
|
if detail == nil {
|
2023-01-18 16:23:39 +08:00
|
|
|
|
return nil, fmt.Errorf("no ip data is queried. procedure:%v", ip)
|
2022-11-24 23:37:34 +08:00
|
|
|
|
}
|
2023-06-05 20:14:57 +08:00
|
|
|
|
return &IpLocationData{
|
2022-11-24 23:37:34 +08:00
|
|
|
|
Ip: ip,
|
|
|
|
|
Country: detail.Country,
|
|
|
|
|
Region: detail.Region,
|
|
|
|
|
Province: detail.Province,
|
|
|
|
|
City: detail.City,
|
|
|
|
|
Area: detail.County,
|
2023-06-05 20:14:57 +08:00
|
|
|
|
}, nil
|
2022-11-24 23:37:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetLocation 获取IP归属地信息
|
2023-04-10 15:31:08 +08:00
|
|
|
|
func GetLocation(ctx context.Context, ip string) (data *IpLocationData, err error) {
|
2023-03-16 15:35:02 +08:00
|
|
|
|
if !validate.IsIp(ip) {
|
|
|
|
|
return nil, fmt.Errorf("invalid input ip:%v", ip)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if validate.IsLocalIPAddr(ip) {
|
2023-05-29 11:54:51 +08:00
|
|
|
|
return // nil, fmt.Errorf("must be a public ip:%v", ip)
|
2023-03-16 15:35:02 +08:00
|
|
|
|
}
|
2023-04-10 15:31:08 +08:00
|
|
|
|
|
2024-04-22 23:08:40 +08:00
|
|
|
|
if cache.Contains(ip) {
|
|
|
|
|
return cache.GetIpCache(ip)
|
2022-11-24 23:37:34 +08:00
|
|
|
|
}
|
2023-04-10 15:31:08 +08:00
|
|
|
|
|
2024-04-22 23:08:40 +08:00
|
|
|
|
cache.Lock()
|
|
|
|
|
defer cache.Unlock()
|
|
|
|
|
|
|
|
|
|
if cache.Contains(ip) {
|
|
|
|
|
return cache.GetIpCache(ip)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mode := g.Cfg().MustGet(ctx, "system.ipMethod", "cz88").String()
|
2023-04-10 15:31:08 +08:00
|
|
|
|
switch mode {
|
|
|
|
|
case "whois":
|
|
|
|
|
data, err = WhoisLocation(ctx, ip)
|
|
|
|
|
default:
|
|
|
|
|
data, err = Cz88Find(ctx, ip)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err == nil && data != nil {
|
2024-04-22 23:08:40 +08:00
|
|
|
|
cache.SetIpCache(ip, data)
|
2023-04-10 15:31:08 +08:00
|
|
|
|
}
|
|
|
|
|
return
|
2022-11-24 23:37:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetPublicIP 获取公网IP
|
2023-01-18 16:23:39 +08:00
|
|
|
|
func GetPublicIP(ctx context.Context) (ip string, err error) {
|
|
|
|
|
var data *WhoisRegionData
|
|
|
|
|
err = g.Client().Timeout(10*time.Second).GetVar(ctx, whoisApi).Scan(&data)
|
|
|
|
|
if err != nil {
|
2023-05-10 23:54:50 +08:00
|
|
|
|
g.Log().Info(ctx, "GetPublicIP fail, alternatives are being tried.")
|
2023-01-18 16:23:39 +08:00
|
|
|
|
return GetPublicIP2()
|
|
|
|
|
}
|
2023-01-25 11:49:21 +08:00
|
|
|
|
|
|
|
|
|
if data == nil {
|
2023-05-07 22:35:29 +08:00
|
|
|
|
g.Log().Info(ctx, "publicIP address Parsing failure, check the network and firewall blocking.")
|
2023-01-25 11:49:21 +08:00
|
|
|
|
return "0.0.0.0", nil
|
|
|
|
|
}
|
2023-01-18 16:23:39 +08:00
|
|
|
|
return data.Ip, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GetPublicIP2() (ip string, err error) {
|
|
|
|
|
response, err := http.Get(dyndns)
|
2022-11-24 23:37:34 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
defer response.Body.Close()
|
|
|
|
|
|
2023-05-10 23:54:50 +08:00
|
|
|
|
body, err := io.ReadAll(response.Body)
|
2023-01-18 16:23:39 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
ip = strings.ReplaceAll(string(body), "\n", "")
|
2022-11-24 23:37:34 +08:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetLocalIP 获取服务器内网IP
|
|
|
|
|
func GetLocalIP() (ip string, err error) {
|
|
|
|
|
addrs, err := net.InterfaceAddrs()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
for _, addr := range addrs {
|
|
|
|
|
ipAddr, ok := addr.(*net.IPNet)
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if ipAddr.IP.IsLoopback() {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if !ipAddr.IP.IsGlobalUnicast() {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
return ipAddr.IP.String(), nil
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetClientIp 获取客户端IP
|
|
|
|
|
func GetClientIp(r *ghttp.Request) string {
|
2023-05-10 23:54:50 +08:00
|
|
|
|
if r == nil {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
2022-11-24 23:37:34 +08:00
|
|
|
|
ip := r.Header.Get("X-Forwarded-For")
|
|
|
|
|
if ip == "" {
|
|
|
|
|
ip = r.GetClientIp()
|
|
|
|
|
}
|
2023-01-25 11:49:21 +08:00
|
|
|
|
|
2023-04-10 15:31:08 +08:00
|
|
|
|
// 兼容部分云厂商CDN,如果存在多个,默认取第一个
|
2023-01-25 11:49:21 +08:00
|
|
|
|
if gstr.Contains(ip, ",") {
|
2023-02-23 17:53:04 +08:00
|
|
|
|
ip = gstr.StrTillEx(ip, ",")
|
2023-01-25 11:49:21 +08:00
|
|
|
|
}
|
2023-02-09 14:49:51 +08:00
|
|
|
|
|
|
|
|
|
if gstr.Contains(ip, ", ") {
|
2023-02-23 17:53:04 +08:00
|
|
|
|
ip = gstr.StrTillEx(ip, ", ")
|
2023-02-09 14:49:51 +08:00
|
|
|
|
}
|
2022-11-24 23:37:34 +08:00
|
|
|
|
return ip
|
|
|
|
|
}
|