71 lines
1.7 KiB
Go
71 lines
1.7 KiB
Go
package clientgeo
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"net/http"
|
||
"net/url"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
const DefaultLookupURL = "https://cf-ip-geo.smyhub.com/api"
|
||
|
||
type apiPayload struct {
|
||
Geo *struct {
|
||
CountryName string `json:"countryName"`
|
||
RegionName string `json:"regionName"`
|
||
CityName string `json:"cityName"`
|
||
} `json:"geo"`
|
||
}
|
||
|
||
func FormatDisplay(countryName, regionName, cityName string) string {
|
||
parts := make([]string, 0, 3)
|
||
for _, s := range []string{countryName, regionName, cityName} {
|
||
s = strings.TrimSpace(s)
|
||
if s != "" {
|
||
parts = append(parts, s)
|
||
}
|
||
}
|
||
return strings.Join(parts, " ")
|
||
}
|
||
|
||
// FetchDisplayLocation 使用 cf-ip-geo 的 ?ip= 查询展示用位置(服务端调用,避免浏览器 CORS)。
|
||
func FetchDisplayLocation(ctx context.Context, lookupURL, ip string) (string, error) {
|
||
ip = strings.TrimSpace(ip)
|
||
if ip == "" {
|
||
return "", nil
|
||
}
|
||
base := strings.TrimSuffix(strings.TrimSpace(lookupURL), "/")
|
||
u, err := url.Parse(base)
|
||
if err != nil || u.Scheme == "" || u.Host == "" {
|
||
return "", fmt.Errorf("invalid lookup URL")
|
||
}
|
||
q := u.Query()
|
||
q.Set("ip", ip)
|
||
u.RawQuery = q.Encode()
|
||
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
client := &http.Client{Timeout: 6 * time.Second}
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
defer resp.Body.Close()
|
||
if resp.StatusCode != http.StatusOK {
|
||
return "", fmt.Errorf("geo status %d", resp.StatusCode)
|
||
}
|
||
var payload apiPayload
|
||
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
|
||
return "", err
|
||
}
|
||
if payload.Geo == nil {
|
||
return "", nil
|
||
}
|
||
return FormatDisplay(payload.Geo.CountryName, payload.Geo.RegionName, payload.Geo.CityName), nil
|
||
}
|