feat(login): 添加Bcrypt密码哈希功能并集成用户认证 - 引入golang.org/x/crypto/bcrypt包用于密码哈希处理 - 实现HashPassword函数对密码进行Bcrypt哈希 - 实现CheckPasswordHash函数验证密码与哈希匹配 - 添加示例代码演示密码哈希和验证功能 feat(login): 集成外部用户信息服务 - 实现GetUserInfo方法调用外部服务获取用户信息 - 添加用户信息展示的示例代码 - 集成用户登录验证流程 fix
172 lines
5.3 KiB
Go
172 lines
5.3 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/cookiejar"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
// TokenResponse 用来解析第一次请求返回的 JSON
|
|
type TokenResponse struct {
|
|
Token string `json:"token"`
|
|
UserID int `json:"userId"`
|
|
}
|
|
|
|
// UserResponse 最外层响应
|
|
type UserResponse struct {
|
|
Data UserData `json:"data"`
|
|
Included []Group `json:"included"`
|
|
}
|
|
|
|
// UserData 用户数据
|
|
type UserData struct {
|
|
Type string `json:"type"`
|
|
ID string `json:"id"`
|
|
Attributes UserAttributes `json:"attributes"`
|
|
Relationships Relationships `json:"relationships"`
|
|
}
|
|
|
|
// UserAttributes 用户详细属性
|
|
type UserAttributes struct {
|
|
Username string `json:"username"`
|
|
DisplayName string `json:"displayName"`
|
|
AvatarUrl string `json:"avatarUrl"`
|
|
Slug string `json:"slug"`
|
|
JoinTime string `json:"joinTime"`
|
|
DiscussionCount int `json:"discussionCount"`
|
|
CommentCount int `json:"commentCount"`
|
|
CanEdit bool `json:"canEdit"`
|
|
CanEditCredentials bool `json:"canEditCredentials"`
|
|
CanEditGroups bool `json:"canEditGroups"`
|
|
CanDelete bool `json:"canDelete"`
|
|
LastSeenAt string `json:"lastSeenAt"`
|
|
Achievements []string `json:"achievements"`
|
|
Followed *bool `json:"followed"` // 可能为 null
|
|
FollowerCount int `json:"followerCount"`
|
|
FollowingCount int `json:"followingCount"`
|
|
Money float64 `json:"money"`
|
|
CanEditMoney bool `json:"canEditMoney"`
|
|
CanSuspend bool `json:"canSuspend"`
|
|
LastCheckinTime string `json:"lastCheckinTime"`
|
|
TotalContinuousCheckIn int `json:"totalContinuousCheckIn"`
|
|
CheckInCompatibleExtensions []string `json:"checkInCompatibleExtensions"`
|
|
CanCheckin bool `json:"canCheckin"`
|
|
CanCheckinContinuous bool `json:"canCheckinContinuous"`
|
|
CanBeFollowed bool `json:"canBeFollowed"`
|
|
FofUploadUploadCountCurrent int `json:"fof-upload-uploadCountCurrent"`
|
|
FofUploadUploadCountAll int `json:"fof-upload-uploadCountAll"`
|
|
BestAnswerCount int `json:"bestAnswerCount"`
|
|
IsBanned bool `json:"isBanned"`
|
|
CanBanIP bool `json:"canBanIP"`
|
|
CanEditNickname bool `json:"canEditNickname"`
|
|
}
|
|
|
|
// Relationships 用户关系
|
|
type Relationships struct {
|
|
Groups RelationshipData `json:"groups"`
|
|
Achievements RelationshipData `json:"achievements"`
|
|
}
|
|
|
|
// RelationshipData 关系数据
|
|
type RelationshipData struct {
|
|
Data []RelationshipItem `json:"data"`
|
|
}
|
|
|
|
// RelationshipItem 关系项
|
|
type RelationshipItem struct {
|
|
Type string `json:"type"`
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
// Group 组信息
|
|
type Group struct {
|
|
Type string `json:"type"`
|
|
ID string `json:"id"`
|
|
Attributes GroupAttributes `json:"attributes"`
|
|
}
|
|
|
|
// GroupAttributes 组属性
|
|
type GroupAttributes struct {
|
|
NameSingular string `json:"nameSingular"`
|
|
NamePlural string `json:"namePlural"`
|
|
Color string `json:"color"`
|
|
Icon string `json:"icon"`
|
|
IsHidden int `json:"isHidden"`
|
|
}
|
|
|
|
// GetUserInfo 输入用户名和密码,返回用户信息结构体
|
|
func GetUserInfo(username, password string) (*UserResponse, error) {
|
|
// 创建带 Cookie 存储的 HTTP 客户端
|
|
jar, err := cookiejar.New(nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("创建 CookieJar 失败: %w", err)
|
|
}
|
|
client := &http.Client{Jar: jar}
|
|
|
|
// 1. POST 获取 token
|
|
tokenURL := "http://bs.seersun.com/api/token"
|
|
formData := url.Values{}
|
|
formData.Set("identification", username)
|
|
formData.Set("password", password)
|
|
|
|
req, err := http.NewRequest("POST", tokenURL, strings.NewReader(formData.Encode()))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("创建请求失败: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("发送请求失败: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取响应失败: %w", err)
|
|
}
|
|
|
|
// 解析 token
|
|
var tokenResp TokenResponse
|
|
err = json.Unmarshal(body, &tokenResp)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("解析 token 失败: %w", err)
|
|
}
|
|
|
|
// 提取 CSRF Token
|
|
csrfToken := resp.Header.Get("X-CSRF-Token")
|
|
|
|
// 2. GET 获取该用户的详细信息
|
|
usersURL := fmt.Sprintf("http://bs.seersun.com/api/users/%d", tokenResp.UserID)
|
|
req2, err := http.NewRequest("GET", usersURL, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("创建请求失败: %w", err)
|
|
}
|
|
req2.Header.Set("Authentication", tokenResp.Token)
|
|
req2.Header.Set("X-CSRF-Token", csrfToken)
|
|
|
|
resp2, err := client.Do(req2)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("发送请求失败: %w", err)
|
|
}
|
|
defer resp2.Body.Close()
|
|
|
|
body2, err := io.ReadAll(resp2.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取响应失败: %w", err)
|
|
}
|
|
|
|
// 解析用户信息
|
|
var userResp UserResponse
|
|
err = json.Unmarshal(body2, &userResp)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("解析用户信息失败: %w", err)
|
|
}
|
|
|
|
return &userResp, nil
|
|
}
|