feat: 添加用户位置、操作系统和浏览器信息到评论功能

This commit is contained in:
2025-09-13 15:08:46 +08:00
parent 44f15e1ff1
commit 011dc298c2
12 changed files with 253 additions and 148 deletions

View File

@ -1 +1,5 @@
package utils
import "resty.dev/v3"
var client = resty.New()

View File

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

View File

@ -1 +1,10 @@
package utils
import (
"testing"
)
func TestGetIPInfo(t *testing.T) {
r, err := GetIPInfo("1.1.1.1")
t.Log(r, err)
}

View File

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

View File

@ -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提供的用户组信息
}

View File

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

View File

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