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"
suspended:
other: "user is suspended"
username_invalid:
other: "username is invalid"
username_duplicate:
other: "username is already in use"
report:
spam:

View File

@ -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

View File

@ -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"

View File

@ -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))
}
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -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

View File

@ -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,20 +234,30 @@ 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)
// 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)
// 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 {
return "", err
} else {
displayName = str
}
}
username = strings.ReplaceAll(displayName, " ", "_")
username = strings.ToLower(username)
suffix := ""
re := regexp.MustCompile(`^[a-z0-9._-]{4,30}$`)
match := re.MatchString(username)
if !match {
return "", errors.BadRequest(reason.UsernameInvalid)
}
for {
_, has, err := us.userRepo.GetByUsername(ctx, username+suffix)
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)
if !has {
break
}
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()
bytes := make([]byte, 2)
_, _ = rand.Read(bytes)
suffix = hex.EncodeToString(bytes)
}
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 {
log.Error("pinyin Error", err)
return "", false
} else {
name = 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)
if !match {
return "", false
}
return name, true
return username + suffix, nil
}
// verifyPassword