From 00c28fea9ca45db1ef3e10d9e68b0fc08aba00e3 Mon Sep 17 00:00:00 2001 From: Snowykami Date: Tue, 22 Jul 2025 09:15:12 +0800 Subject: [PATCH] :zap: implement user registration with email verification, enhance error handling, and update database configuration --- internal/dto/user.go | 15 +++++------ internal/model/user.go | 4 +-- internal/repo/init.go | 16 ++++++------ internal/service/user.go | 54 +++++++++++++++++++++++++++++++++++++++- pkg/errs/errors.go | 1 + 5 files changed, 72 insertions(+), 18 deletions(-) diff --git a/internal/dto/user.go b/internal/dto/user.go index 688a60c..dab5ed2 100644 --- a/internal/dto/user.go +++ b/internal/dto/user.go @@ -20,16 +20,17 @@ type UserLoginResp struct { } type UserRegisterReq struct { - Username string `json:"username"` // 用户名 - Nickname string `json:"nickname"` // 昵称 - Password string `json:"password"` // 密码 - Email string `json:"email"` // 邮箱 + Username string `json:"username"` // 用户名 + Nickname string `json:"nickname"` // 昵称 + Password string `json:"password"` // 密码 + Email string `json:"email"` // 邮箱 + VerificationCode string `json:"verification_code"` // 邮箱验证码 } type UserRegisterResp struct { - Token string `json:"token"` // 访问令牌 - RefreshToken string `json:"refresh_token"` // 刷新令牌 - User UserDto `json:"user"` // 用户信息 + Token string `json:"token"` // 访问令牌 + RefreshToken string `json:"refresh_token"` // 刷新令牌 + User *UserDto `json:"user"` // 用户信息 } type VerifyEmailReq struct { diff --git a/internal/model/user.go b/internal/model/user.go index 78f377e..e5e1b90 100644 --- a/internal/model/user.go +++ b/internal/model/user.go @@ -7,10 +7,10 @@ import ( type User struct { gorm.Model - Username string `gorm:"unique;index"` // 用户名,唯一 + Username string `gorm:"uniqueIndex"` // 用户名,唯一 Nickname string AvatarUrl string - Email string `gorm:"unique;index"` + Email string `gorm:"uniqueIndex"` Gender string Role string `gorm:"default:'user'"` diff --git a/internal/repo/init.go b/internal/repo/init.go index d82cdda..42d31e8 100644 --- a/internal/repo/init.go +++ b/internal/repo/init.go @@ -35,14 +35,14 @@ type DBConfig struct { // loadDBConfig 从配置文件加载数据库配置 func loadDBConfig() DBConfig { return DBConfig{ - Driver: utils.Env.Get("database.driver", "sqlite"), - Path: utils.Env.Get("database.path", "./data/data.db"), - Host: utils.Env.Get("database.host", "postgres"), - Port: utils.Env.GetenvAsInt("database.port", 5432), - User: utils.Env.Get("database.user", "spage"), - Password: utils.Env.Get("database.password", "spage"), - DBName: utils.Env.Get("database.dbname", "spage"), - SSLMode: utils.Env.Get("database.sslmode", "disable"), + Driver: utils.Env.Get("DB_DRIVER", "sqlite"), + Path: utils.Env.Get("DB_PATH", "./data/data.db"), + Host: utils.Env.Get("DB_HOST", "postgres"), + Port: utils.Env.GetenvAsInt("DB_PORT", 5432), + User: utils.Env.Get("DB_USER", "blog"), + Password: utils.Env.Get("DB_PASSWORD", "blog"), + DBName: utils.Env.Get("DB_NAME", "blog"), + SSLMode: utils.Env.Get("DB_SSLMODE", "disable"), } } diff --git a/internal/service/user.go b/internal/service/user.go index a811ae4..e49142e 100644 --- a/internal/service/user.go +++ b/internal/service/user.go @@ -3,11 +3,13 @@ package service import ( "github.com/sirupsen/logrus" "github.com/snowykami/neo-blog/internal/dto" + "github.com/snowykami/neo-blog/internal/model" "github.com/snowykami/neo-blog/internal/repo" "github.com/snowykami/neo-blog/internal/static" "github.com/snowykami/neo-blog/pkg/constant" "github.com/snowykami/neo-blog/pkg/errs" "github.com/snowykami/neo-blog/pkg/utils" + "net/http" "time" ) @@ -62,7 +64,57 @@ func (s *userService) UserLogin(req *dto.UserLoginReq) (*dto.UserLoginResp, erro } func (s *userService) UserRegister(req *dto.UserRegisterReq) (*dto.UserRegisterResp, error) { - return nil, nil + // 验证邮箱验证码 + kv := utils.KV.GetInstance() + verificationCode, ok := kv.Get(constant.KVKeyEmailVerificationCode + ":" + req.Email) + if !ok || verificationCode != req.VerificationCode { + return nil, errs.ErrInvalidCredentials + } + // 检查用户名或邮箱是否已存在 + existingUser, err := repo.User.GetByUsernameOrEmail(req.Username) + if err != nil { + return nil, errs.ErrInternalServer + } + if existingUser != nil { + return nil, errs.New(http.StatusConflict, "Username or email already exists", nil) + } + // 创建新用户 + + newUser := &model.User{ + Username: req.Username, + Nickname: req.Nickname, + Email: req.Email, + Gender: "", + Role: "user", + Password: "", + } + err = repo.User.Create(newUser) + if err != nil { + return nil, errs.ErrInternalServer + } + // 生成访问令牌和刷新令牌 + token := utils.Jwt.NewClaims(newUser.ID, "", false, time.Duration(utils.Env.GetenvAsInt(constant.EnvKeyTokenDuration, 24)*int(time.Hour))) + tokenString, err := token.ToString() + if err != nil { + return nil, errs.ErrInternalServer + } + refreshToken := utils.Jwt.NewClaims(newUser.ID, utils.Strings.GenerateRandomString(64), true, time.Duration(utils.Env.GetenvAsInt(constant.EnvKeyRefreshTokenDuration, 30)*int(time.Hour))) + refreshTokenString, err := refreshToken.ToString() + if err != nil { + return nil, errs.ErrInternalServer + } + // 对refresh token进行持久化存储 + err = repo.Session.SaveSession(refreshToken.SessionKey) + if err != nil { + return nil, errs.ErrInternalServer + } + + resp := &dto.UserRegisterResp{ + Token: tokenString, + RefreshToken: refreshTokenString, + User: newUser.ToDto(), + } + return resp, nil } func (s *userService) VerifyEmail(req *dto.VerifyEmailReq) (*dto.VerifyEmailResp, error) { diff --git a/pkg/errs/errors.go b/pkg/errs/errors.go index e147c4d..05b6c6a 100644 --- a/pkg/errs/errors.go +++ b/pkg/errs/errors.go @@ -24,6 +24,7 @@ func (e *ServiceError) Error() string { var ( ErrNotFound = &ServiceError{Code: http.StatusNotFound, Message: "not found"} ErrInvalidCredentials = &ServiceError{Code: http.StatusUnauthorized, Message: "invalid credentials"} + ErrConflict = &ServiceError{Code: http.StatusConflict, Message: "resource conflict"} ErrInternalServer = &ServiceError{Code: http.StatusInternalServerError, Message: "internal server error"} ErrBadRequest = &ServiceError{Code: http.StatusBadRequest, Message: "invalid request parameters"} ErrForbidden = &ServiceError{Code: http.StatusForbidden, Message: "access forbidden"}