From 0191f13f730961a12661f4c6cada21e13327b5d0 Mon Sep 17 00:00:00 2001 From: Tohru <65994850+Tohrusky@users.noreply.github.com> Date: Mon, 15 Jul 2024 02:16:50 +0800 Subject: [PATCH] feat: update user info && fix some errors (#15) and refactor some code -- --------- Co-authored-by: ckappgit --- internal/common/dao/user.go | 16 +++++- internal/router/api/v1/api.go | 13 +++-- internal/service/user/login.go | 2 +- internal/service/user/logout.go | 8 +-- internal/service/user/profile.go | 66 ++++++++++------------ internal/service/user/profile_update.go | 73 +++++++++++++++++++++++++ internal/service/user/register.go | 16 ++++-- internal/service/user/reset.go | 9 +-- module/util/username.go | 25 +++++++++ 9 files changed, 162 insertions(+), 66 deletions(-) create mode 100644 internal/service/user/profile_update.go create mode 100644 module/util/username.go diff --git a/internal/common/dao/user.go b/internal/common/dao/user.go index 2f922c4..e58755d 100644 --- a/internal/common/dao/user.go +++ b/internal/common/dao/user.go @@ -13,10 +13,10 @@ func CreateUser(user *model.User) (err error) { return err } -// SetUserPassword 修改用户密码 -func SetUserPassword(user *model.User, newpass string) (err error) { +// SetUserPassword 设置用户密码 +func SetUserPassword(user *model.User, newPassword string) (err error) { u := query.User - password, err := bcrypt.GenerateFromPassword([]byte(newpass), bcrypt.DefaultCost) + password, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost) if err != nil { return err } @@ -27,6 +27,16 @@ func SetUserPassword(user *model.User, newpass string) (err error) { return err } +// UpdateUserDataByUserID 根据 map 更新用户信息,map 中的 key 为字段名 +func UpdateUserDataByUserID(userID int32, maps map[string]interface{}) (err error) { + u := query.User + _, err = u.Where(u.UserID.Eq(userID)).Updates(maps) + if err != nil { + return err + } + return err +} + // GetUserByEmail 根据 email 获取用户 func GetUserByEmail(email string) (user *model.User, err error) { q := query.User diff --git a/internal/router/api/v1/api.go b/internal/router/api/v1/api.go index 95817e6..fca2a86 100644 --- a/internal/router/api/v1/api.go +++ b/internal/router/api/v1/api.go @@ -40,6 +40,10 @@ func NewAPI() *gin.Engine { jwt.RequireAuth(cache.Clients[cache.JWTBlacklist], true), // 把 token 拉黑 user_service.Logout, ) + // 修改密码 + user.POST("password/reset", + jwt.RequireAuth(cache.Clients[cache.JWTBlacklist], false), + user_service.ResetPassword) // 用户信息 user.GET("profile/me", jwt.RequireAuth(cache.Clients[cache.JWTBlacklist], false), @@ -51,11 +55,10 @@ func NewAPI() *gin.Engine { middleware_cache.Response(cache.Clients[cache.RespCache], 1*time.Minute), user_service.ProfileOthers, ) - - // 修改密码 - user.POST("password/reset", - jwt.RequireAuth(cache.Clients[cache.JWTBlacklist], true), // 把 token 拉黑 - user_service.ResetPassword) + // 用户信息更新 + user.POST("profile/update", + jwt.RequireAuth(cache.Clients[cache.JWTBlacklist], false), + user_service.ProfileUpdate) } } diff --git a/internal/service/user/login.go b/internal/service/user/login.go index d77c523..05bfc87 100644 --- a/internal/service/user/login.go +++ b/internal/service/user/login.go @@ -21,7 +21,7 @@ type LoginResponse struct { func Login(c *gin.Context) { var req LoginRequest if err := c.ShouldBindJSON(&req); err != nil { - util.AbortWithMsg(c, "invalid request") + util.AbortWithMsg(c, "invalid request: "+err.Error()) return } diff --git a/internal/service/user/logout.go b/internal/service/user/logout.go index 8256c8a..1e547fc 100644 --- a/internal/service/user/logout.go +++ b/internal/service/user/logout.go @@ -8,13 +8,9 @@ import ( // Logout 用户登出 (POST /logout) func Logout(c *gin.Context) { - user, err := util.GetUserIDFromGinContext(c) - if err != nil { - util.AbortWithMsg(c, "Please login first") - return - } + userID, _ := util.GetUserIDFromGinContext(c) util.OKWithMsg(c, "Logout success") - log.Logger.Info("Logout success: " + util.StructToString(user)) + log.Logger.Info("Logout success, user ID: " + util.StructToString(userID)) } diff --git a/internal/service/user/profile.go b/internal/service/user/profile.go index 977268a..914f184 100644 --- a/internal/service/user/profile.go +++ b/internal/service/user/profile.go @@ -13,9 +13,9 @@ type ProfileResponse struct { Avatar string `json:"avatar"` Background string `json:"background"` CreatedAt string `json:"created_at"` - Email string `json:"email"` - Experience string `json:"experience"` - Inviter string `json:"inviter"` + Email *string `json:"email,omitempty"` + Experience *int32 `json:"experience,omitempty"` + Inviter *int32 `json:"inviter,omitempty"` LastActive string `json:"last_active"` Private bool `json:"private"` Roles []string `json:"roles,omitempty"` @@ -25,16 +25,12 @@ type ProfileResponse struct { } type ProfileOthersRequest struct { - UserId int32 `form:"user_id" binding:"required"` + UserID int32 `form:"user_id" binding:"required"` } // ProfileMe 获取用户自己的信息 (GET /profile/me) func ProfileMe(c *gin.Context) { - userID, err := util.GetUserIDFromGinContext(c) - if err != nil { - util.AbortWithMsg(c, "Please login first") - return - } + userID, _ := util.GetUserIDFromGinContext(c) user, err := dao.GetUserByID(userID) if err != nil { @@ -52,9 +48,9 @@ func ProfileMe(c *gin.Context) { Avatar: user.Avatar, Background: user.Background, CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"), - Email: user.Email, - Experience: strconv.Itoa(int(user.Experience)), - Inviter: strconv.Itoa(int(user.Inviter)), + Email: &user.Email, + Experience: &user.Experience, + Inviter: &user.Inviter, LastActive: user.LastActive.Format("2006-01-02 15:04:05"), Private: user.Private, Roles: roles, @@ -71,29 +67,20 @@ func ProfileOthers(c *gin.Context) { // 绑定参数 var req ProfileOthersRequest if err := c.ShouldBindQuery(&req); err != nil { - util.AbortWithMsg(c, "invalid request") - return - } - // 鉴权 - userID, err := util.GetUserIDFromGinContext(c) - if err != nil { - util.AbortWithMsg(c, "Please login first") - return - } - // 仅用于鉴权不使用 - _, err = dao.GetUserByID(userID) - if err != nil { - util.AbortWithMsg(c, "User not found") + util.AbortWithMsg(c, "invalid request: "+err.Error()) return } + + userID, _ := util.GetUserIDFromGinContext(c) + // 获取信息 - user, err := dao.GetUserByID(req.UserId) + user, err := dao.GetUserByID(req.UserID) if err != nil { util.AbortWithMsg(c, "User not found") return } - roles, err := dao.GetUserRolesByID(userID) + roles, err := dao.GetUserRolesByID(req.UserID) if err != nil { log.Logger.Info("Failed to get user roles: " + err.Error()) roles = []string{} @@ -104,14 +91,14 @@ func ProfileOthers(c *gin.Context) { util.OKWithData(c, ProfileResponse{ Avatar: user.Avatar, Background: user.Background, - CreatedAt: "", - Email: "", - Experience: "", - Inviter: "", - LastActive: "", - Private: user.Private, + CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"), + Email: nil, + Experience: nil, + Inviter: nil, + LastActive: user.LastActive.Format("2006-01-02 15:04:05"), + Private: true, Roles: nil, - Signature: "", + Signature: user.Signature, UserID: user.UserID, Username: user.Username, }) @@ -121,15 +108,18 @@ func ProfileOthers(c *gin.Context) { Avatar: user.Avatar, Background: user.Background, CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"), - Email: user.Email, - Experience: strconv.Itoa(int(user.Experience)), - Inviter: strconv.Itoa(int(user.Inviter)), + Email: &user.Email, + Experience: &user.Experience, + Inviter: &user.Inviter, LastActive: user.LastActive.Format("2006-01-02 15:04:05"), - Private: user.Private, + Private: false, Roles: roles, Signature: user.Signature, UserID: user.UserID, Username: user.Username, }) } + + log.Logger.Info("Get user profile success: " + strconv.Itoa(int(req.UserID)) + + ", by user ID: " + strconv.Itoa(int(userID))) } diff --git a/internal/service/user/profile_update.go b/internal/service/user/profile_update.go new file mode 100644 index 0000000..f1d1123 --- /dev/null +++ b/internal/service/user/profile_update.go @@ -0,0 +1,73 @@ +package user + +import ( + "strconv" + + "github.com/TensoRaws/NuxBT-Backend/internal/common/dao" + "github.com/TensoRaws/NuxBT-Backend/module/log" + "github.com/TensoRaws/NuxBT-Backend/module/util" + "github.com/gin-gonic/gin" +) + +type ProfileUpdateRequest struct { + Avatar *string `json:"avatar" binding:"omitempty"` + Background *string `json:"background" binding:"omitempty"` + Email *string `json:"email" binding:"omitempty,email"` + Private *bool `json:"private" binding:"omitempty"` + Signature *string `json:"signature" binding:"omitempty"` + Username *string `json:"username" binding:"omitempty"` +} + +// ProfileUpdate 用户信息更新 (POST /profile/update) +func ProfileUpdate(c *gin.Context) { + // 参数绑定 + var req ProfileUpdateRequest + if err := c.ShouldBindJSON(&req); err != nil { + util.AbortWithMsg(c, "invalid request: "+err.Error()) + return + } + + userID, _ := util.GetUserIDFromGinContext(c) + + // 准备更新数据 + updates := make(map[string]interface{}) + + if req.Private != nil { + updates["private"] = req.Private + } + + if req.Username != nil && *req.Username != "" { + err := util.CheckUsername(*req.Username) + if err != nil { + util.AbortWithMsg(c, "invalid username: "+err.Error()) + return + } + updates["username"] = *req.Username + } + + if req.Email != nil { + updates["email"] = *req.Email + } + + if req.Avatar != nil { + updates["avatar"] = *req.Avatar + } + + if req.Signature != nil { + updates["signature"] = *req.Signature + } + + if req.Background != nil { + updates["background"] = *req.Background + } + // 执行更新 + err := dao.UpdateUserDataByUserID(userID, updates) + if err != nil { + util.AbortWithMsg(c, "update failed: "+err.Error()) + return + } + + util.OKWithMsg(c, "update success") + + log.Logger.Info("update user profile success: " + strconv.Itoa(int(userID))) +} diff --git a/internal/service/user/register.go b/internal/service/user/register.go index a072bed..bc0d33f 100644 --- a/internal/service/user/register.go +++ b/internal/service/user/register.go @@ -31,7 +31,13 @@ type RegisterDataResponse struct { func Register(c *gin.Context) { var req RegisterRequest if err := c.ShouldBindJSON(&req); err != nil { - util.AbortWithMsg(c, "invalid request") + util.AbortWithMsg(c, "invalid request: "+err.Error()) + return + } + + err := util.CheckUsername(req.Username) + if err != nil { + util.AbortWithMsg(c, "invalid username: "+err.Error()) return } @@ -42,10 +48,8 @@ func Register(c *gin.Context) { return } } else { - // 有邀请码注册,检查邀请码是否有效 - // do something - // 未实现 - // OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO + // TODO: 邀请码功能, 有邀请码注册,检查邀请码是否有效 + log.Logger.Info("invitation code: " + *req.InvitationCode) } password, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) @@ -62,7 +66,7 @@ func Register(c *gin.Context) { LastActive: time.Now(), }) if err != nil { - util.AbortWithMsg(c, "failed to register: ") + util.AbortWithMsg(c, "failed to register: "+err.Error()) log.Logger.Error("failed to register: " + err.Error()) return } diff --git a/internal/service/user/reset.go b/internal/service/user/reset.go index 4f07d2b..842a69f 100644 --- a/internal/service/user/reset.go +++ b/internal/service/user/reset.go @@ -16,16 +16,11 @@ func ResetPassword(c *gin.Context) { // 绑定参数 var req ResetPasswordRequest if err := c.ShouldBindJSON(&req); err != nil { - util.AbortWithMsg(c, "invalid request") + util.AbortWithMsg(c, "invalid request: "+err.Error()) return } - // 鉴权 - userID, err := util.GetUserIDFromGinContext(c) - if err != nil { - util.AbortWithMsg(c, "Please login first") - return - } + userID, _ := util.GetUserIDFromGinContext(c) user, err := dao.GetUserByID(userID) if err != nil { diff --git a/module/util/username.go b/module/util/username.go new file mode 100644 index 0000000..ce1ce67 --- /dev/null +++ b/module/util/username.go @@ -0,0 +1,25 @@ +package util + +import "fmt" + +func CheckUsername(username string) error { + l := len([]rune(username)) + if l > 20 { + return fmt.Errorf("username too long") + } + if l < 2 { + return fmt.Errorf("username too short") + } + if username == "" { + return fmt.Errorf("username cannot be empty") + } + adminWords := []string{"admin", "root", "administrator", "管理员", "超级管理员", "版主", "站长", "moderator"} + for _, word := range adminWords { + if username == word { + return fmt.Errorf("username cannot be %s", word) + } + } + // TODO: more checks,如检查敏感词 + + return nil +}