From ba60b03bbfc74cc4e4387c00b723fae44d3798cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=94=E5=BF=B5?= <1@72wo.cn> Date: Wed, 31 Dec 2025 16:20:01 +0800 Subject: [PATCH] =?UTF-8?q?```=20feat(login):=20=E6=B7=BB=E5=8A=A0Bcrypt?= =?UTF-8?q?=E5=AF=86=E7=A0=81=E5=93=88=E5=B8=8C=E5=8A=9F=E8=83=BD=E5=B9=B6?= =?UTF-8?q?=E9=9B=86=E6=88=90=E7=94=A8=E6=88=B7=E8=AE=A4=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入golang.org/x/crypto/bcrypt包用于密码哈希处理 - 实现HashPassword函数对密码进行Bcrypt哈希 - 实现CheckPasswordHash函数验证密码与哈希匹配 - 添加示例代码演示密码哈希和验证功能 feat(login): 集成外部用户信息服务 - 实现GetUserInfo方法调用外部服务获取用户信息 - 添加用户信息展示的示例代码 - 集成用户登录验证流程 fix --- login/main.go | 39 ++++++ modules/base/model/base_sys_user.go | 2 +- modules/base/service/base.bbs.go | 171 +++++++++++++++++++++++++ modules/base/service/base_sys_login.go | 54 +++++++- modules/base/service/base_sys_user.go | 27 ++++ 5 files changed, 287 insertions(+), 6 deletions(-) create mode 100644 modules/base/service/base.bbs.go diff --git a/login/main.go b/login/main.go index 4ff86d590..5872b476a 100644 --- a/login/main.go +++ b/login/main.go @@ -2,6 +2,7 @@ package main import ( _ "github.com/gogf/gf/contrib/nosql/redis/v2" + "golang.org/x/crypto/bcrypt" "blazing/common/data/xmlres" _ "blazing/contrib/files/local" @@ -27,6 +28,31 @@ func init() { } func main() { + // 调用方法 + // userInfo, err := service.GetUserInfo("694425176@qq.com", "qq694425176") + // if err != nil { + // log.Fatal(err) + // } + + // // 输出结果 + // fmt.Printf("用户名: %s\n", userInfo.Data.Attributes.Username) + // fmt.Printf("显示名: %s\n", userInfo.Data.Attributes.DisplayName) + // fmt.Printf("头像: %s\n", userInfo.Data.Attributes.AvatarUrl) + // fmt.Printf("加入时间: %s\n", userInfo.Data.Attributes.JoinTime) + // fmt.Printf("金钱: %.2f\n", userInfo.Data.Attributes.Money) + // fmt.Printf("用户组: %s\n", userInfo.Included[0].Attributes.NameSingular) + // password := "12345678" + + // // 哈希密码 + // hash, err := HashPassword(password) + // if err != nil { + // log.Fatal(err) + // } + // fmt.Println("哈希结果:", hash) + // hash = "$2y$10$027xtOgP6yG8HRPP5zpNdO1ay1MzaxIeBDdNt73VgE2WWQqTUYih2" + // // 验证密码 + // match := CheckPasswordHash(password, hash) + // fmt.Println("密码匹配:", match) // service.NewShinyService().Args(53) //player.TestPureMatrixSplit() // for _, i := range xmlres.ItemsMAP { @@ -113,3 +139,16 @@ func kick(id int) { // fmt.Println(err) // }() } + +// HashPassword 对密码进行 Bcrypt 哈希 +func HashPassword(password string) (string, error) { + // bcrypt.DefaultCost = 10,与 Laravel 默认一致 + bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + return string(bytes), err +} + +// CheckPasswordHash 验证密码与哈希是否匹配 +func CheckPasswordHash(password, hash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil +} diff --git a/modules/base/model/base_sys_user.go b/modules/base/model/base_sys_user.go index cb5831682..a59a8b95c 100644 --- a/modules/base/model/base_sys_user.go +++ b/modules/base/model/base_sys_user.go @@ -12,7 +12,7 @@ type BaseSysUser struct { DepartmentID uint `gorm:"column:departmentId;type:bigint;index" json:"departmentId"` // 部门ID Username string `gorm:"column:username;type:varchar(100);not null;Index" json:"username"` // 用户名 - Password string `gorm:"column:password;type:varchar(255);not null" json:"password"` // 密码 + Password string `gorm:"column:password;type:varchar(255);" json:"password"` // 密码 PasswordV *int32 `gorm:"column:passwordV;type:int;not null;default:1" json:"passwordV"` // 密码版本, 作用是改完密码,让原来的token失效 HeadImg *string `gorm:"column:headImg;type:varchar(255)" json:"headImg"` // 头像 diff --git a/modules/base/service/base.bbs.go b/modules/base/service/base.bbs.go new file mode 100644 index 000000000..5f6866db7 --- /dev/null +++ b/modules/base/service/base.bbs.go @@ -0,0 +1,171 @@ +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 +} diff --git a/modules/base/service/base_sys_login.go b/modules/base/service/base_sys_login.go index 541d9de50..2d99d38c3 100644 --- a/modules/base/service/base_sys_login.go +++ b/modules/base/service/base_sys_login.go @@ -2,6 +2,8 @@ package service import ( "context" + "fmt" + "log" "time" "github.com/golang-jwt/jwt/v4" @@ -47,13 +49,54 @@ func (s *BaseSysLoginService) Login(ctx context.Context, req *v1.BaseOpenLoginRe err = gerror.New("验证码错误") return } - md5password, _ := gmd5.Encrypt(password) + // 调用方法 + userInfo, err := GetUserInfo(username, password) + if err != nil { + log.Fatal(err) + } + // // 输出结果 + // fmt.Printf("用户名: %s\n", userInfo.Data.Attributes.Username) + // fmt.Printf("显示名: %s\n", userInfo.Data.Attributes.DisplayName) + // fmt.Printf("头像: %s\n", userInfo.Data.Attributes.AvatarUrl) + // fmt.Printf("加入时间: %s\n", userInfo.Data.Attributes.JoinTime) + // fmt.Printf("金钱: %.2f\n", userInfo.Data.Attributes.Money) + // fmt.Printf("用户组: %s\n", userInfo.Included[0].Attributes.NameSingular) + md5password, _ := gmd5.Encrypt(password) var user *model.BaseSysUser - cool.DBM(baseSysUser).Where("username=?", username).Where("password=?", md5password).Where("status=?", 1).Scan(&user) - if user == nil { - err = gerror.New("账户或密码不正确~") - return + + if userInfo.Data.Attributes.Username != username { //说明没查找到用户 + //后端添加的账户 + cool.DBM(baseSysUser).Where("username=?", username).Where("password=?", md5password).Where("status=?", 1).Scan(&user) + if user == nil { + + err = gerror.New("账户或密码不正确~") + return + } + + } else { + m := cool.DBM(baseSysUser).Where("username=?", username).Where("status=?", 1) + m.Scan(&user) + if user == nil { + //这里实现注册用户 + //err = gerror.New("账户或密码不正确~") + // return + + NewBaseSysUserService().Gen(userInfo.Data.Attributes) + m := cool.DBM(baseSysUser).Where("username=?", username).Where("status=?", 1) + m.Scan(&user) + } else { + user.HeadImg = &userInfo.Data.Attributes.AvatarUrl + var ttt = *user.PasswordV + 1 + user.PasswordV = &ttt + _, err := m.Save(user) + if err != nil { + log.Fatal(err) + } + + cool.CacheManager.Set(ctx, fmt.Sprintf("admin:passwordVersion:%d", user.ID), user.PasswordV, 0) + } + } result, err = s.generateTokenByUser(ctx, user) @@ -90,6 +133,7 @@ func (*BaseSysLoginService) Captcha(req *v1.BaseOpenCaptchaReq) (interface{}, er // Logout 退出登录 func (*BaseSysLoginService) Logout(ctx context.Context) (err error) { userId := cool.GetAdmin(ctx).UserId + cool.CacheManager.Remove(ctx, "admin:department:"+gconv.String(userId)) cool.CacheManager.Remove(ctx, "admin:perms:"+gconv.String(userId)) cool.CacheManager.Remove(ctx, "admin:token:"+gconv.String(userId)) diff --git a/modules/base/service/base_sys_user.go b/modules/base/service/base_sys_user.go index ed1be9862..855477f65 100644 --- a/modules/base/service/base_sys_user.go +++ b/modules/base/service/base_sys_user.go @@ -105,6 +105,33 @@ func (s *BaseSysUserService) ModifyAfter(ctx context.Context, method string, par } return } +func (s *BaseSysUserService) Gen(user UserAttributes) (data interface{}, err error) { + var ( + m = cool.DBM(s.Model) + ) + + lastInsertId, err := m.Data(user).Data( + + g.Map{ + "username": user.Username, + "headImg": user.AvatarUrl, + "departmentId": 1, + }, + ).InsertAndGetId() + if err != nil { + return + } + data = g.Map{"id": lastInsertId} + roleArray := garray.NewArray() + roleArray.PushRight(g.Map{ + "userId": gconv.Uint(lastInsertId), + "roleId": gconv.Uint(13), + }) + + _, err = cool.DBM(model.NewBaseSysUserRole()).Fields("userId,roleId").Insert(roleArray) + + return +} // ServiceAdd 方法 添加用户 func (s *BaseSysUserService) ServiceAdd(ctx context.Context, req *cool.AddReq) (data interface{}, err error) {