feat: support update username

This commit is contained in:
LinkinStar 2022-10-14 17:01:06 +08:00
parent 22251849d9
commit c39e115cde
8 changed files with 93 additions and 72 deletions

View File

@ -74,8 +74,10 @@ error:
other: "user not found" other: "user not found"
suspended: suspended:
other: "user is suspended" other: "user is suspended"
username_invalid:
other: "username is invalid"
username_duplicate:
other: "username is already in use"
report: report:
spam: spam:

View File

@ -55,7 +55,7 @@ func BindAndCheck(ctx *gin.Context, data interface{}) bool {
errField, err := validator.GetValidatorByLang(lang.Abbr()).Check(data) errField, err := validator.GetValidatorByLang(lang.Abbr()).Check(data)
if err != nil { if err != nil {
HandleResponse(ctx, myErrors.New(http.StatusBadRequest, reason.RequestFormatError).WithMsg(err.Error()), errField) HandleResponse(ctx, err, errField)
return true return true
} }
return false return false

View File

@ -24,6 +24,8 @@ const (
DisallowVoteYourSelf = "error.object.disallow_vote_your_self" DisallowVoteYourSelf = "error.object.disallow_vote_your_self"
CaptchaVerificationFailed = "error.object.captcha_verification_failed" CaptchaVerificationFailed = "error.object.captcha_verification_failed"
UserNotFound = "error.user.not_found" UserNotFound = "error.user.not_found"
UsernameInvalid = "error.user.username_invalid"
UsernameDuplicate = "error.user.username_duplicate"
EmailDuplicate = "error.email.duplicate" EmailDuplicate = "error.email.duplicate"
EmailVerifyUrlExpired = "error.email.verify_url_expired" EmailVerifyUrlExpired = "error.email.verify_url_expired"
EmailNeedToBeVerified = "error.email.need_to_be_verified" EmailNeedToBeVerified = "error.email.need_to_be_verified"

View File

@ -11,7 +11,9 @@ import (
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/go-playground/validator/v10/translations/en" "github.com/go-playground/validator/v10/translations/en"
"github.com/go-playground/validator/v10/translations/zh" "github.com/go-playground/validator/v10/translations/zh"
"github.com/segmentfault/answer/internal/base/reason"
"github.com/segmentfault/answer/internal/base/translator" "github.com/segmentfault/answer/internal/base/translator"
myErrors "github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/i18n" "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()), Key: translator.GlobalTrans.Tr(m.Lang, fieldError.Field()),
Value: fieldError.Translate(m.Tran), Value: fieldError.Translate(m.Tran),
} }
return errField, errors.New(fieldError.Translate(m.Tran)) return errField, myErrors.BadRequest(reason.RequestFormatError).WithMsg(fieldError.Translate(m.Tran))
} }
} }

View File

@ -206,11 +206,11 @@ func (uc *UserController) UserLogout(ctx *gin.Context) {
// @Tags User // @Tags User
// @Accept json // @Accept json
// @Produce 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} // @Success 200 {object} handler.RespBody{data=schema.GetUserResp}
// @Router /answer/api/v1/user/register/email [post] // @Router /answer/api/v1/user/register/email [post]
func (uc *UserController) UserRegisterByEmail(ctx *gin.Context) { func (uc *UserController) UserRegisterByEmail(ctx *gin.Context) {
req := &schema.UserRegister{} req := &schema.UserRegisterReq{}
if handler.BindAndCheck(ctx, req) { if handler.BindAndCheck(ctx, req) {
return return
} }

View File

@ -108,7 +108,7 @@ func (ur *userRepo) UpdateEmail(ctx context.Context, userID, email string) (err
// UpdateInfo update user info // UpdateInfo update user info
func (ur *userRepo) UpdateInfo(ctx context.Context, userInfo *entity.User) (err error) { func (ur *userRepo) UpdateInfo(ctx context.Context, userInfo *entity.User) (err error) {
_, err = ur.data.DB.Where("id = ?", userInfo.ID). _, 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 { if err != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
} }

View File

@ -2,11 +2,14 @@ package schema
import ( import (
"encoding/json" "encoding/json"
"regexp"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
"github.com/segmentfault/answer/internal/base/reason"
"github.com/segmentfault/answer/internal/base/validator" "github.com/segmentfault/answer/internal/base/validator"
"github.com/segmentfault/answer/internal/entity" "github.com/segmentfault/answer/internal/entity"
"github.com/segmentfault/answer/pkg/checker" "github.com/segmentfault/answer/pkg/checker"
"github.com/segmentfault/pacman/errors"
) )
// UserVerifyEmailReq user verify email request // UserVerifyEmailReq user verify email request
@ -179,10 +182,10 @@ type UserEmailLogin struct {
CaptchaCode string `json:"captcha_code" ` // captcha_code CaptchaCode string `json:"captcha_code" ` // captcha_code
} }
// Register // UserRegisterReq user register request
type UserRegister struct { type UserRegisterReq struct {
// name // name
Name string `validate:"required,gt=5,lte=50" json:"name"` Name string `validate:"required,gt=4,lte=30" json:"name"`
// email // email
Email string `validate:"required,email,gt=0,lte=500" json:"e_mail" ` Email string `validate:"required,email,gt=0,lte=500" json:"e_mail" `
// password // password
@ -190,7 +193,7 @@ type UserRegister struct {
IP string `json:"-" ` IP string `json:"-" `
} }
func (u *UserRegister) Check() (errField *validator.ErrorField, err error) { func (u *UserRegisterReq) Check() (errField *validator.ErrorField, err error) {
// TODO i18n // TODO i18n
err = checker.PassWordCheck(8, 32, 0, u.Pass) err = checker.PassWordCheck(8, 32, 0, u.Pass)
if err != nil { if err != nil {
@ -224,6 +227,8 @@ func (u *UserModifyPassWordRequest) Check() (errField *validator.ErrorField, err
type UpdateInfoRequest struct { type UpdateInfoRequest struct {
// display_name // display_name
DisplayName string `validate:"required,gt=0,lte=30" json:"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
Avatar string `validate:"omitempty,gt=0,lte=500" json:"avatar"` Avatar string `validate:"omitempty,gt=0,lte=500" json:"avatar"`
// bio // bio
@ -238,6 +243,21 @@ type UpdateInfoRequest struct {
UserId string `json:"-" ` 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 { type UserRetrievePassWordRequest struct {
Email string `validate:"required,email,gt=0,lte=500" json:"e_mail" ` // e_mail Email string `validate:"required,email,gt=0,lte=500" json:"e_mail" ` // e_mail
CaptchaID string `json:"captcha_id" ` // captcha_id CaptchaID string `json:"captcha_id" ` // captcha_id

View File

@ -2,7 +2,9 @@ package service
import ( import (
"context" "context"
"encoding/hex"
"fmt" "fmt"
"math/rand"
"regexp" "regexp"
"strings" "strings"
@ -17,7 +19,6 @@ import (
"github.com/segmentfault/answer/internal/service/service_config" "github.com/segmentfault/answer/internal/service/service_config"
usercommon "github.com/segmentfault/answer/internal/service/user_common" usercommon "github.com/segmentfault/answer/internal/service/user_common"
"github.com/segmentfault/answer/pkg/checker" "github.com/segmentfault/answer/pkg/checker"
"github.com/segmentfault/answer/pkg/uid"
"github.com/segmentfault/pacman/errors" "github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/log" "github.com/segmentfault/pacman/log"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -233,18 +234,28 @@ func (us *UserService) UserModifyPassWord(ctx context.Context, request *schema.U
return nil return nil
} }
// UpdateInfo // UpdateInfo update user info
func (us *UserService) UpdateInfo(ctx context.Context, request *schema.UpdateInfoRequest) error { func (us *UserService) UpdateInfo(ctx context.Context, req *schema.UpdateInfoRequest) (err error) {
userinfo := entity.User{} if len(req.Username) > 0 {
userinfo.ID = request.UserId userInfo, exist, err := us.userRepo.GetByUsername(ctx, req.Username)
userinfo.Avatar = request.Avatar if err != nil {
userinfo.DisplayName = request.DisplayName return err
userinfo.Bio = request.Bio }
userinfo.BioHtml = request.BioHtml if exist && userInfo.ID != req.UserId {
userinfo.Location = request.Location return errors.BadRequest(reason.UsernameDuplicate)
userinfo.Website = request.Website }
err := us.userRepo.UpdateInfo(ctx, &userinfo) }
if err != nil {
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 err
} }
return nil return nil
@ -259,7 +270,7 @@ func (us *UserService) UserEmailHas(ctx context.Context, email string) (bool, er
} }
// UserRegisterByEmail user register // 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) { resp *schema.GetUserResp, err error) {
_, has, err := us.userRepo.GetByEmail(ctx, registerUserInfo.Email) _, has, err := us.userRepo.GetByEmail(ctx, registerUserInfo.Email)
if err != nil { if err != nil {
@ -276,7 +287,7 @@ func (us *UserService) UserRegisterByEmail(ctx context.Context, registerUserInfo
if err != nil { if err != nil {
return nil, err return nil, err
} }
userInfo.Username, err = us.makeUserName(ctx, registerUserInfo.Name) userInfo.Username, err = us.makeUsername(ctx, registerUserInfo.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -408,58 +419,42 @@ func (us *UserService) UserVerifyEmail(ctx context.Context, req *schema.UserVeri
return resp, nil return resp, nil
} }
// makeUserName // makeUsername
// Generate a unique Username based on the NickName // Generate a unique Username based on the displayName
// todo Waiting to be realized func (us *UserService) makeUsername(ctx context.Context, displayName string) (username string, err error) {
func (us *UserService) makeUserName(ctx context.Context, userName string) (string, error) { // Chinese processing
userName = us.formatUserName(ctx, userName) if has := checker.IsChinese(displayName); has {
_, has, err := us.userRepo.GetByUsername(ctx, userName) str, err := pinyin.New(displayName).Split("").Mode(pinyin.WithoutTone).Convert()
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()
if err != nil { if err != nil {
log.Error("pinyin Error", err) return "", err
return "", false
} else { } else {
name = str displayName = str
} }
} }
//Format filtering
re, err := regexp.Compile(`^[a-z0-9._-]{4,20}$`) username = strings.ReplaceAll(displayName, " ", "_")
if err != nil { username = strings.ToLower(username)
log.Error("regexp.Compile Error", err, "name", name) suffix := ""
}
match := re.MatchString(name) re := regexp.MustCompile(`^[a-z0-9._-]{4,30}$`)
match := re.MatchString(username)
if !match { 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 // verifyPassword