hotgo/server/internal/library/location/location.go

237 lines
5.4 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 location
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package location
import (
"context"
"fmt"
"github.com/gogf/gf/v2/container/gmap"
"github.com/gogf/gf/v2/encoding/gcharset"
"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"
"io"
"net"
"net/http"
"strings"
"time"
)
const (
whoisApi = "https://whois.pconline.com.cn/ipJson.jsp?json=true&ip="
dyndns = "http://members.3322.org/dyndns/getip"
)
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"`
}
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"`
}
var cacheMap *gmap.Map
func init() {
cacheMap = gmap.New(true)
}
// WhoisLocation 通过Whois接口查询IP归属地
func WhoisLocation(ctx context.Context, ip string) (*IpLocationData, error) {
response, err := g.Client().Timeout(10*time.Second).Get(ctx, whoisApi+ip)
if err != nil {
return nil, err
}
defer response.Close()
str, err := gcharset.ToUTF8("GBK", response.ReadAllString())
if err != nil {
return nil, err
}
var who *WhoisRegionData
if err = gconv.Struct([]byte(str), &who); err != nil {
return nil, err
}
return &IpLocationData{
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),
}, nil
}
// Cz88Find 通过Cz88的IP库查询IP归属地
func Cz88Find(ctx context.Context, ip string) (*IpLocationData, error) {
loc, err := iploc.OpenWithoutIndexes("./resource/ip/qqwry-utf8.dat")
if err != nil {
return nil, fmt.Errorf("%v for help, please go to: https://github.com/kayon/iploc", err.Error())
}
detail := loc.Find(ip)
if detail == nil {
return nil, fmt.Errorf("no ip data is queried. procedure:%v", ip)
}
locationData := &IpLocationData{
Ip: ip,
Country: detail.Country,
Region: detail.Region,
Province: detail.Province,
City: detail.City,
Area: detail.County,
}
return locationData, nil
}
// IsJurisByIpTitle 判断地区名称是否为直辖市
func IsJurisByIpTitle(title string) bool {
lists := []string{"北京市", "天津市", "重庆市", "上海市"}
for i := 0; i < len(lists); i++ {
if gstr.Contains(lists[i], title) {
return true
}
}
return false
}
// GetLocation 获取IP归属地信息
func GetLocation(ctx context.Context, ip string) (data *IpLocationData, err error) {
if !validate.IsIp(ip) {
return nil, fmt.Errorf("invalid input ip:%v", ip)
}
if validate.IsLocalIPAddr(ip) {
return // nil, fmt.Errorf("must be a public ip:%v", ip)
}
if cacheMap.Contains(ip) {
value := cacheMap.Get(ip)
data1, ok := value.(*IpLocationData)
if !ok {
cacheMap.Remove(ip)
err = fmt.Errorf("data assertion failed in the cache ip:%v", ip)
return
}
return data1, nil
}
mode := g.Cfg().MustGet(ctx, "hotgo.ipMethod", "cz88").String()
switch mode {
case "whois":
data, err = WhoisLocation(ctx, ip)
default:
data, err = Cz88Find(ctx, ip)
}
if err == nil && data != nil {
if cacheMap.Size() > 20000 {
cacheMap.Clear()
}
cacheMap.Set(ip, data)
}
return
}
// GetPublicIP 获取公网IP
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 {
g.Log().Info(ctx, "GetPublicIP fail, alternatives are being tried.")
return GetPublicIP2()
}
if data == nil {
g.Log().Info(ctx, "publicIP address Parsing failure, check the network and firewall blocking.")
return "0.0.0.0", nil
}
return data.Ip, nil
}
func GetPublicIP2() (ip string, err error) {
response, err := http.Get(dyndns)
if err != nil {
return
}
defer response.Body.Close()
body, err := io.ReadAll(response.Body)
if err != nil {
return
}
ip = strings.ReplaceAll(string(body), "\n", "")
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 {
if r == nil {
return ""
}
ip := r.Header.Get("X-Forwarded-For")
if ip == "" {
ip = r.GetClientIp()
}
// 兼容部分云厂商CDN如果存在多个默认取第一个
if gstr.Contains(ip, ",") {
ip = gstr.StrTillEx(ip, ",")
}
if gstr.Contains(ip, ", ") {
ip = gstr.StrTillEx(ip, ", ")
}
return ip
}