mirror of
https://github.com/snowykami/neo-blog.git
synced 2025-09-26 11:06:23 +00:00
feat: 添加用户位置、操作系统和浏览器信息到评论功能
This commit is contained in:
@ -1 +1,5 @@
|
||||
package utils
|
||||
|
||||
import "resty.dev/v3"
|
||||
|
||||
var client = resty.New()
|
||||
|
@ -1 +1,60 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type IPData struct {
|
||||
IP string `json:"ip"`
|
||||
Dec string `json:"dec"`
|
||||
Country string `json:"country"`
|
||||
CountryCode string `json:"countryCode"`
|
||||
Province string `json:"province"`
|
||||
City string `json:"city"`
|
||||
Districts string `json:"districts"`
|
||||
IDC string `json:"idc"`
|
||||
ISP string `json:"isp"`
|
||||
Net string `json:"net"`
|
||||
Zipcode string `json:"zipcode"`
|
||||
Areacode string `json:"areacode"`
|
||||
Protocol string `json:"protocol"`
|
||||
Location string `json:"location"`
|
||||
MyIP string `json:"myip"`
|
||||
Time string `json:"time"`
|
||||
}
|
||||
|
||||
type IPInfoResponse struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data *IPData `json:"data"`
|
||||
}
|
||||
|
||||
func GetIPInfo(ip string) (*IPData, error) {
|
||||
// https://api.mir6.com/api/ip?ip={ip}&type=json
|
||||
ipInfoResponse := &IPInfoResponse{}
|
||||
logrus.Info(fmt.Sprintf("https://api.mir6.com/api/ip?ip=%s&type=json", ip))
|
||||
resp, err := client.R().
|
||||
SetResult(ipInfoResponse).
|
||||
Get(fmt.Sprintf("https://api.mir6.com/api/ip?ip=%s&type=json", ip))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode() != 200 {
|
||||
return nil, fmt.Errorf("状态码: %d,响应: %s", resp.StatusCode(), resp.String())
|
||||
}
|
||||
return ipInfoResponse.Data, nil
|
||||
}
|
||||
|
||||
func GetLocationString(ip string) string {
|
||||
ipInfo, err := GetIPInfo(ip)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return ""
|
||||
}
|
||||
if ipInfo == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s %s %s %s", ipInfo.Country, ipInfo.Province, ipInfo.City, ipInfo.ISP)
|
||||
}
|
||||
|
@ -1 +1,10 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetIPInfo(t *testing.T) {
|
||||
r, err := GetIPInfo("1.1.1.1")
|
||||
t.Log(r, err)
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/snowykami/neo-blog/pkg/constant"
|
||||
"time"
|
||||
)
|
||||
|
||||
type jwtUtils struct{}
|
||||
|
@ -1,8 +1,7 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"resty.dev/v3"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type oidcUtils struct{}
|
||||
@ -11,61 +10,59 @@ var Oidc = oidcUtils{}
|
||||
|
||||
// RequestToken 请求访问令牌
|
||||
func (u *oidcUtils) RequestToken(tokenEndpoint, clientID, clientSecret, code, redirectURI string) (*TokenResponse, error) {
|
||||
client := resty.New()
|
||||
tokenResp, err := client.R().
|
||||
SetFormData(map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": clientID,
|
||||
"client_secret": clientSecret,
|
||||
"code": code,
|
||||
"redirect_uri": redirectURI,
|
||||
}).
|
||||
SetHeader("Accept", "application/json").
|
||||
SetResult(&TokenResponse{}).
|
||||
Post(tokenEndpoint)
|
||||
tokenResp, err := client.R().
|
||||
SetFormData(map[string]string{
|
||||
"grant_type": "authorization_code",
|
||||
"client_id": clientID,
|
||||
"client_secret": clientSecret,
|
||||
"code": code,
|
||||
"redirect_uri": redirectURI,
|
||||
}).
|
||||
SetHeader("Accept", "application/json").
|
||||
SetResult(&TokenResponse{}).
|
||||
Post(tokenEndpoint)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tokenResp.StatusCode() != 200 {
|
||||
return nil, fmt.Errorf("状态码: %d,响应: %s", tokenResp.StatusCode(), tokenResp.String())
|
||||
}
|
||||
return tokenResp.Result().(*TokenResponse), nil
|
||||
if tokenResp.StatusCode() != 200 {
|
||||
return nil, fmt.Errorf("状态码: %d,响应: %s", tokenResp.StatusCode(), tokenResp.String())
|
||||
}
|
||||
return tokenResp.Result().(*TokenResponse), nil
|
||||
}
|
||||
|
||||
// RequestUserInfo 请求用户信息
|
||||
func (u *oidcUtils) RequestUserInfo(userInfoEndpoint, accessToken string) (*UserInfo, error) {
|
||||
client := resty.New()
|
||||
userInfoResp, err := client.R().
|
||||
SetHeader("Authorization", "Bearer "+accessToken).
|
||||
SetHeader("Accept", "application/json").
|
||||
SetResult(&UserInfo{}).
|
||||
Get(userInfoEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userInfoResp, err := client.R().
|
||||
SetHeader("Authorization", "Bearer "+accessToken).
|
||||
SetHeader("Accept", "application/json").
|
||||
SetResult(&UserInfo{}).
|
||||
Get(userInfoEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if userInfoResp.StatusCode() != 200 {
|
||||
return nil, fmt.Errorf("状态码: %d,响应: %s", userInfoResp.StatusCode(), userInfoResp.String())
|
||||
}
|
||||
if userInfoResp.StatusCode() != 200 {
|
||||
return nil, fmt.Errorf("状态码: %d,响应: %s", userInfoResp.StatusCode(), userInfoResp.String())
|
||||
}
|
||||
|
||||
return userInfoResp.Result().(*UserInfo), nil
|
||||
return userInfoResp.Result().(*UserInfo), nil
|
||||
}
|
||||
|
||||
type TokenResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
IDToken string `json:"id_token,omitempty"`
|
||||
RefreshToken string `json:"refresh_token,omitempty"`
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
IDToken string `json:"id_token,omitempty"`
|
||||
RefreshToken string `json:"refresh_token,omitempty"`
|
||||
}
|
||||
|
||||
// UserInfo 定义用户信息结构
|
||||
type UserInfo struct {
|
||||
Sub string `json:"sub"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Picture string `json:"picture,omitempty"`
|
||||
Groups []string `json:"groups,omitempty"` // 可选字段,OIDC提供的用户组信息
|
||||
Sub string `json:"sub"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Picture string `json:"picture,omitempty"`
|
||||
Groups []string `json:"groups,omitempty"` // 可选字段,OIDC提供的用户组信息
|
||||
}
|
||||
|
107
pkg/utils/ua.go
107
pkg/utils/ua.go
@ -1,61 +1,62 @@
|
||||
package utils
|
||||
|
||||
import "regexp"
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Result struct {
|
||||
OS string
|
||||
OSVersion string
|
||||
Browser string
|
||||
BrowserVer string
|
||||
type UAResult struct {
|
||||
OS string
|
||||
OSVersion string
|
||||
Browser string
|
||||
BrowserVer string
|
||||
}
|
||||
|
||||
// ParseUA 解析 UA,返回结构化信息
|
||||
func ParseUA(ua string) Result {
|
||||
r := Result{}
|
||||
func ParseUA(ua string) UAResult {
|
||||
r := UAResult{}
|
||||
// 1. 操作系统 + 版本
|
||||
osRe := []*regexp.Regexp{
|
||||
regexp.MustCompile(`\(Macintosh;.*Mac OS X ([0-9_]+)\)`),
|
||||
regexp.MustCompile(`\(Windows NT ([0-9.]+)\)`),
|
||||
regexp.MustCompile(`\(iPhone;.*OS ([0-9_]+)`),
|
||||
regexp.MustCompile(`\(Android ([0-9.]+)`),
|
||||
regexp.MustCompile(`\(X11;.*Linux `),
|
||||
}
|
||||
for _, re := range osRe {
|
||||
if m := re.FindStringSubmatch(ua); len(m) > 1 {
|
||||
switch {
|
||||
case strings.Contains(m[0], "Macintosh"):
|
||||
r.OS, r.OSVersion = "macOS", strings.Replace(m[1], "_", ".", -1)
|
||||
case strings.Contains(m[0], "Windows NT"):
|
||||
r.OS, r.OSVersion = "Windows", m[1]
|
||||
case strings.Contains(m[0], "iPhone"):
|
||||
r.OS, r.OSVersion = "iOS", strings.Replace(m[1], "_", ".", -1)
|
||||
case strings.Contains(m[0], "Android"):
|
||||
r.OS, r.OSVersion = "Android", m[1]
|
||||
case strings.Contains(m[0], "Linux"):
|
||||
r.OS = "Linux"
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 操作系统 + 版本
|
||||
osRe := []*regexp.Regexp{
|
||||
regexp.MustCompile(`\(Macintosh;.*Mac OS X ([0-9_]+)\)`),
|
||||
regexp.MustCompile(`\(Windows NT ([0-9.]+)\)`),
|
||||
regexp.MustCompile(`\(iPhone;.*OS ([0-9_]+)`),
|
||||
regexp.MustCompile(`\(Android ([0-9.]+)`),
|
||||
regexp.MustCompile(`\(X11;.*Linux `),
|
||||
}
|
||||
for _, re := range osRe {
|
||||
if m := re.FindStringSubmatch(ua); len(m) > 1 {
|
||||
switch {
|
||||
case strings.Contains(m[0], "Macintosh"):
|
||||
r.OS, r.OSVersion = "macOS", strings.Replace(m[1], "_", ".", -1)
|
||||
case strings.Contains(m[0], "Windows NT"):
|
||||
r.OS, r.OSVersion = "Windows", m[1]
|
||||
case strings.Contains(m[0], "iPhone"):
|
||||
r.OS, r.OSVersion = "iOS", strings.Replace(m[1], "_", ".", -1)
|
||||
case strings.Contains(m[0], "Android"):
|
||||
r.OS, r.OSVersion = "Android", m[1]
|
||||
case strings.Contains(m[0], "Linux"):
|
||||
r.OS = "Linux"
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 浏览器 + 版本(按优先级匹配)
|
||||
browserRe := []struct {
|
||||
re *regexp.Regexp
|
||||
name string
|
||||
}{
|
||||
{regexp.MustCompile(`Edg/([\d.]+)`), "Edge"},
|
||||
{regexp.MustCompile(`Chrome/([\d.]+)`), "Chrome"},
|
||||
{regexp.MustCompile(`Firefox/([\d.]+)`), "Firefox"},
|
||||
{regexp.MustCompile(`Version/([\d.]+).*Safari`), "Safari"},
|
||||
{regexp.MustCompile(`OPR/([\d.]+)`), "Opera"},
|
||||
}
|
||||
for _, b := range browserRe {
|
||||
if m := b.re.FindStringSubmatch(ua); len(m) > 1 {
|
||||
r.Browser, r.BrowserVer = b.name, m[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
// 2. 浏览器 + 版本(按优先级匹配)
|
||||
browserRe := []struct {
|
||||
re *regexp.Regexp
|
||||
name string
|
||||
}{
|
||||
{regexp.MustCompile(`Edg/([\d.]+)`), "Edge"},
|
||||
{regexp.MustCompile(`Chrome/([\d.]+)`), "Chrome"},
|
||||
{regexp.MustCompile(`Firefox/([\d.]+)`), "Firefox"},
|
||||
{regexp.MustCompile(`Version/([\d.]+).*Safari`), "Safari"},
|
||||
{regexp.MustCompile(`OPR/([\d.]+)`), "Opera"},
|
||||
}
|
||||
for _, b := range browserRe {
|
||||
if m := b.re.FindStringSubmatch(ua); len(m) > 1 {
|
||||
r.Browser, r.BrowserVer = b.name, m[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
@ -1 +1,11 @@
|
||||
package utils
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseUA(t *testing.T) {
|
||||
ua := "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0"
|
||||
result := ParseUA(ua)
|
||||
if result.OS != "macOS" || result.OSVersion != "10.15.7" || result.Browser != "Edge" || result.BrowserVer != "140.0.0.0" {
|
||||
t.Errorf("ParseUA failed, got %+v", result)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user