From c39e115cdecaabbc6d546e9ad0ee702b0cc33861 Mon Sep 17 00:00:00 2001 From: LinkinStar Date: Fri, 14 Oct 2022 17:01:06 +0800 Subject: [PATCH] feat: support update username --- i18n/en_US.yaml | 6 +- internal/base/handler/handler.go | 2 +- internal/base/reason/reason.go | 2 + internal/base/validator/validator.go | 4 +- internal/controller/user_controller.go | 4 +- internal/repo/user/user_repo.go | 2 +- internal/schema/user_schema.go | 28 +++++- internal/service/user_service.go | 117 ++++++++++++------------- 8 files changed, 93 insertions(+), 72 deletions(-) diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml index da0c3d46..8e8f0a29 100644 --- a/i18n/en_US.yaml +++ b/i18n/en_US.yaml @@ -74,8 +74,10 @@ error: other: "user not found" suspended: other: "user is suspended" - - + username_invalid: + other: "username is invalid" + username_duplicate: + other: "username is already in use" report: spam: diff --git a/internal/base/handler/handler.go b/internal/base/handler/handler.go index ea06a945..07f283c6 100644 --- a/internal/base/handler/handler.go +++ b/internal/base/handler/handler.go @@ -55,7 +55,7 @@ func BindAndCheck(ctx *gin.Context, data interface{}) bool { errField, err := validator.GetValidatorByLang(lang.Abbr()).Check(data) if err != nil { - HandleResponse(ctx, myErrors.New(http.StatusBadRequest, reason.RequestFormatError).WithMsg(err.Error()), errField) + HandleResponse(ctx, err, errField) return true } return false diff --git a/internal/base/reason/reason.go b/internal/base/reason/reason.go index b3e0c8b5..fb64cd78 100644 --- a/internal/base/reason/reason.go +++ b/internal/base/reason/reason.go @@ -24,6 +24,8 @@ const ( DisallowVoteYourSelf = "error.object.disallow_vote_your_self" CaptchaVerificationFailed = "error.object.captcha_verification_failed" UserNotFound = "error.user.not_found" + UsernameInvalid = "error.user.username_invalid" + UsernameDuplicate = "error.user.username_duplicate" EmailDuplicate = "error.email.duplicate" EmailVerifyUrlExpired = "error.email.verify_url_expired" EmailNeedToBeVerified = "error.email.need_to_be_verified" diff --git a/internal/base/validator/validator.go b/internal/base/validator/validator.go index 94a515e5..eb8b45da 100644 --- a/internal/base/validator/validator.go +++ b/internal/base/validator/validator.go @@ -11,7 +11,9 @@ import ( "github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10/translations/en" "github.com/go-playground/validator/v10/translations/zh" + "github.com/segmentfault/answer/internal/base/reason" "github.com/segmentfault/answer/internal/base/translator" + myErrors "github.com/segmentfault/pacman/errors" "github.com/segmentfault/pacman/i18n" ) @@ -98,7 +100,7 @@ func (m *MyValidator) Check(value interface{}) (errField *ErrorField, err error) Key: translator.GlobalTrans.Tr(m.Lang, fieldError.Field()), Value: fieldError.Translate(m.Tran), } - return errField, errors.New(fieldError.Translate(m.Tran)) + return errField, myErrors.BadRequest(reason.RequestFormatError).WithMsg(fieldError.Translate(m.Tran)) } } diff --git a/internal/controller/user_controller.go b/internal/controller/user_controller.go index 831d830b..7f37801b 100644 --- a/internal/controller/user_controller.go +++ b/internal/controller/user_controller.go @@ -206,11 +206,11 @@ func (uc *UserController) UserLogout(ctx *gin.Context) { // @Tags User // @Accept json // @Produce json -// @Param data body schema.UserRegister true "UserRegister" +// @Param data body schema.UserRegisterReq true "UserRegisterReq" // @Success 200 {object} handler.RespBody{data=schema.GetUserResp} // @Router /answer/api/v1/user/register/email [post] func (uc *UserController) UserRegisterByEmail(ctx *gin.Context) { - req := &schema.UserRegister{} + req := &schema.UserRegisterReq{} if handler.BindAndCheck(ctx, req) { return } diff --git a/internal/repo/user/user_repo.go b/internal/repo/user/user_repo.go index c3ce2c98..85f2229d 100644 --- a/internal/repo/user/user_repo.go +++ b/internal/repo/user/user_repo.go @@ -108,7 +108,7 @@ func (ur *userRepo) UpdateEmail(ctx context.Context, userID, email string) (err // UpdateInfo update user info func (ur *userRepo) UpdateInfo(ctx context.Context, userInfo *entity.User) (err error) { _, err = ur.data.DB.Where("id = ?", userInfo.ID). - Cols("display_name", "avatar", "bio", "bio_html", "website", "location").Update(userInfo) + Cols("username", "display_name", "avatar", "bio", "bio_html", "website", "location").Update(userInfo) if err != nil { err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() } diff --git a/internal/schema/user_schema.go b/internal/schema/user_schema.go index 38a9938f..98027870 100644 --- a/internal/schema/user_schema.go +++ b/internal/schema/user_schema.go @@ -2,11 +2,14 @@ package schema import ( "encoding/json" + "regexp" "github.com/jinzhu/copier" + "github.com/segmentfault/answer/internal/base/reason" "github.com/segmentfault/answer/internal/base/validator" "github.com/segmentfault/answer/internal/entity" "github.com/segmentfault/answer/pkg/checker" + "github.com/segmentfault/pacman/errors" ) // UserVerifyEmailReq user verify email request @@ -179,10 +182,10 @@ type UserEmailLogin struct { CaptchaCode string `json:"captcha_code" ` // captcha_code } -// Register -type UserRegister struct { +// UserRegisterReq user register request +type UserRegisterReq struct { // name - Name string `validate:"required,gt=5,lte=50" json:"name"` + Name string `validate:"required,gt=4,lte=30" json:"name"` // email Email string `validate:"required,email,gt=0,lte=500" json:"e_mail" ` // password @@ -190,7 +193,7 @@ type UserRegister struct { IP string `json:"-" ` } -func (u *UserRegister) Check() (errField *validator.ErrorField, err error) { +func (u *UserRegisterReq) Check() (errField *validator.ErrorField, err error) { // TODO i18n err = checker.PassWordCheck(8, 32, 0, u.Pass) if err != nil { @@ -224,6 +227,8 @@ func (u *UserModifyPassWordRequest) Check() (errField *validator.ErrorField, err type UpdateInfoRequest struct { // display_name DisplayName string `validate:"required,gt=0,lte=30" json:"display_name"` + // username + Username string `validate:"omitempty,gt=0,lte=30" json:"username"` // avatar Avatar string `validate:"omitempty,gt=0,lte=500" json:"avatar"` // bio @@ -238,6 +243,21 @@ type UpdateInfoRequest struct { UserId string `json:"-" ` } +func (u *UpdateInfoRequest) Check() (errField *validator.ErrorField, err error) { + if len(u.Username) > 0 { + re := regexp.MustCompile(`^[a-z0-9._-]{4,30}$`) + match := re.MatchString(u.Username) + if !match { + err = errors.BadRequest(reason.UsernameInvalid) + return &validator.ErrorField{ + Key: "username", + Value: err.Error(), + }, err + } + } + return nil, nil +} + type UserRetrievePassWordRequest struct { Email string `validate:"required,email,gt=0,lte=500" json:"e_mail" ` // e_mail CaptchaID string `json:"captcha_id" ` // captcha_id diff --git a/internal/service/user_service.go b/internal/service/user_service.go index 8db4f662..85101562 100644 --- a/internal/service/user_service.go +++ b/internal/service/user_service.go @@ -2,7 +2,9 @@ package service import ( "context" + "encoding/hex" "fmt" + "math/rand" "regexp" "strings" @@ -17,7 +19,6 @@ import ( "github.com/segmentfault/answer/internal/service/service_config" usercommon "github.com/segmentfault/answer/internal/service/user_common" "github.com/segmentfault/answer/pkg/checker" - "github.com/segmentfault/answer/pkg/uid" "github.com/segmentfault/pacman/errors" "github.com/segmentfault/pacman/log" "golang.org/x/crypto/bcrypt" @@ -233,18 +234,28 @@ func (us *UserService) UserModifyPassWord(ctx context.Context, request *schema.U return nil } -// UpdateInfo -func (us *UserService) UpdateInfo(ctx context.Context, request *schema.UpdateInfoRequest) error { - userinfo := entity.User{} - userinfo.ID = request.UserId - userinfo.Avatar = request.Avatar - userinfo.DisplayName = request.DisplayName - userinfo.Bio = request.Bio - userinfo.BioHtml = request.BioHtml - userinfo.Location = request.Location - userinfo.Website = request.Website - err := us.userRepo.UpdateInfo(ctx, &userinfo) - if err != nil { +// UpdateInfo update user info +func (us *UserService) UpdateInfo(ctx context.Context, req *schema.UpdateInfoRequest) (err error) { + if len(req.Username) > 0 { + userInfo, exist, err := us.userRepo.GetByUsername(ctx, req.Username) + if err != nil { + return err + } + if exist && userInfo.ID != req.UserId { + return errors.BadRequest(reason.UsernameDuplicate) + } + } + + userInfo := entity.User{} + userInfo.ID = req.UserId + userInfo.Avatar = req.Avatar + userInfo.DisplayName = req.DisplayName + userInfo.Bio = req.Bio + userInfo.BioHtml = req.BioHtml + userInfo.Location = req.Location + userInfo.Website = req.Website + userInfo.Username = req.Username + if err := us.userRepo.UpdateInfo(ctx, &userInfo); err != nil { return err } return nil @@ -259,7 +270,7 @@ func (us *UserService) UserEmailHas(ctx context.Context, email string) (bool, er } // UserRegisterByEmail user register -func (us *UserService) UserRegisterByEmail(ctx context.Context, registerUserInfo *schema.UserRegister) ( +func (us *UserService) UserRegisterByEmail(ctx context.Context, registerUserInfo *schema.UserRegisterReq) ( resp *schema.GetUserResp, err error) { _, has, err := us.userRepo.GetByEmail(ctx, registerUserInfo.Email) if err != nil { @@ -276,7 +287,7 @@ func (us *UserService) UserRegisterByEmail(ctx context.Context, registerUserInfo if err != nil { return nil, err } - userInfo.Username, err = us.makeUserName(ctx, registerUserInfo.Name) + userInfo.Username, err = us.makeUsername(ctx, registerUserInfo.Name) if err != nil { return nil, err } @@ -408,58 +419,42 @@ func (us *UserService) UserVerifyEmail(ctx context.Context, req *schema.UserVeri return resp, nil } -// makeUserName -// Generate a unique Username based on the NickName -// todo Waiting to be realized -func (us *UserService) makeUserName(ctx context.Context, userName string) (string, error) { - userName = us.formatUserName(ctx, userName) - _, has, err := us.userRepo.GetByUsername(ctx, userName) - if err != nil { - return "", err - } - //If the user name is duplicated, it is generated recursively from the new one. - if has { - userName = uid.IDStr() - return us.makeUserName(ctx, userName) - } - return userName, nil -} - -// formatUserName -// Generate a Username through a nickname -func (us *UserService) formatUserName(ctx context.Context, Name string) string { - formatName, pass := us.CheckUserName(ctx, Name) - if !pass { - //todo 重新给用户 生成随机 username - return uid.IDStr() - } - return formatName -} - -func (us *UserService) CheckUserName(ctx context.Context, name string) (string, bool) { - name = strings.Replace(name, " ", "_", -1) - name = strings.ToLower(name) - //Chinese processing - has := checker.IsChinese(name) - if has { - str, err := pinyin.New(name).Split("").Mode(pinyin.WithoutTone).Convert() +// makeUsername +// Generate a unique Username based on the displayName +func (us *UserService) makeUsername(ctx context.Context, displayName string) (username string, err error) { + // Chinese processing + if has := checker.IsChinese(displayName); has { + str, err := pinyin.New(displayName).Split("").Mode(pinyin.WithoutTone).Convert() if err != nil { - log.Error("pinyin Error", err) - return "", false + return "", err } else { - name = str + displayName = str } } - //Format filtering - re, err := regexp.Compile(`^[a-z0-9._-]{4,20}$`) - if err != nil { - log.Error("regexp.Compile Error", err, "name", name) - } - match := re.MatchString(name) + + username = strings.ReplaceAll(displayName, " ", "_") + username = strings.ToLower(username) + suffix := "" + + re := regexp.MustCompile(`^[a-z0-9._-]{4,30}$`) + match := re.MatchString(username) if !match { - return "", false + return "", errors.BadRequest(reason.UsernameInvalid) } - return name, true + + for { + _, has, err := us.userRepo.GetByUsername(ctx, username+suffix) + if err != nil { + return "", err + } + if !has { + break + } + bytes := make([]byte, 2) + _, _ = rand.Read(bytes) + suffix = hex.EncodeToString(bytes) + } + return username + suffix, nil } // verifyPassword