feat: 添加用户注册功能,包括表单验证和路由注册

This commit is contained in:
2025-12-24 22:46:50 +08:00
parent 7a03ba3a00
commit fd9e54e9f4
7 changed files with 240 additions and 6 deletions

View File

@@ -1,13 +1,19 @@
package web
import (
"errors"
"regexp"
"strings"
"quyun/v2/app/errorx"
"quyun/v2/app/http/web/dto"
"quyun/v2/app/services"
"quyun/v2/database/models"
"quyun/v2/pkg/consts"
"quyun/v2/providers/jwt"
"github.com/gofiber/fiber/v3"
"gorm.io/gorm"
)
// @provider
@@ -15,6 +21,8 @@ type auth struct {
jwt *jwt.JWT
}
var reUsername = regexp.MustCompile(`^[a-zA-Z0-9_]{3,32}$`)
// Login 用户登录(平台侧,非超级管理员)。
//
// @Summary 用户登录
@@ -44,6 +52,64 @@ func (ctl *auth) login(ctx fiber.Ctx, form *dto.LoginForm) (*dto.LoginResponse,
return &dto.LoginResponse{Token: token}, nil
}
// Register 用户注册(平台侧,非超级管理员)。
//
// @Summary 用户注册
// @Tags Web
// @Accept json
// @Produce json
// @Param form body dto.RegisterForm true "form"
// @Success 200 {object} dto.LoginResponse "成功"
// @Router /v1/auth/register [post]
// @Bind form body
func (ctl *auth) register(ctx fiber.Ctx, form *dto.RegisterForm) (*dto.LoginResponse, error) {
username := strings.TrimSpace(form.Username)
if username == "" {
return nil, errorx.ErrMissingParameter.WithMsg("请输入用户名")
}
if !reUsername.MatchString(username) {
return nil, errorx.ErrInvalidParameter.WithMsg("用户名需为 3-32 位字母/数字/下划线")
}
if form.Password == "" {
return nil, errorx.ErrMissingParameter.WithMsg("请输入密码")
}
if len(form.Password) < 8 {
return nil, errorx.ErrParameterTooShort.WithMsg("密码至少 8 位,请设置更安全的密码")
}
if form.Password != form.ConfirmPassword {
return nil, errorx.ErrInvalidParameter.WithMsg("两次输入的密码不一致,请重新确认")
}
// 先查询用户名是否已存在,避免直接插入导致不友好的数据库错误信息。
_, err := services.User.FindByUsername(ctx, username)
if err == nil {
return nil, errorx.ErrRecordDuplicated.WithMsg("用户名已被占用,换一个试试")
}
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errorx.Wrap(err).WithMsg("用户信息校验失败,请稍后再试")
}
m := &models.User{
Username: username,
Password: form.Password,
Roles: []consts.Role{consts.RoleUser},
Status: consts.UserStatusVerified,
}
if _, err := services.User.Create(ctx, m); err != nil {
if errors.Is(err, gorm.ErrDuplicatedKey) {
return nil, errorx.ErrRecordDuplicated.WithMsg("用户名已被占用,换一个试试")
}
return nil, errorx.Wrap(err).WithMsg("注册失败,请稍后再试")
}
token, err := ctl.jwt.CreateToken(ctl.jwt.CreateClaims(jwt.BaseClaims{UserID: m.ID}))
if err != nil {
return nil, errorx.Wrap(err).WithMsg("登录凭证生成失败")
}
return &dto.LoginResponse{Token: token}, nil
}
// Token 刷新登录凭证。
//
// @Summary 刷新 Token

View File

@@ -9,6 +9,18 @@ type LoginForm struct {
Password string `json:"password,omitempty"`
}
// RegisterForm 平台侧用户注册表单(用于创建用户并获取 JWT 访问凭证)。
type RegisterForm struct {
// Username 用户名需全局唯一users.username建议仅允许字母/数字/下划线,且长度在合理范围内。
Username string `json:"username,omitempty"`
// Password 明文密码后端会在创建用户时自动加密bcrypt
Password string `json:"password,omitempty"`
// ConfirmPassword 确认密码;必须与 Password 一致,避免误输入导致无法登录。
ConfirmPassword string `json:"confirmPassword,omitempty"`
// VerifyCode 验证码(预留字段);当前版本仅透传/占位,不做后端校验。
VerifyCode string `json:"verifyCode,omitempty"`
}
// LoginResponse 登录响应。
type LoginResponse struct {
// Token JWT 访问令牌;前端应以 `Authorization: Bearer <token>` 方式携带。

View File

@@ -52,6 +52,11 @@ func (r *Routes) Register(router fiber.Router) {
r.auth.login,
Body[dto.LoginForm]("form"),
))
r.log.Debugf("Registering route: Post /v1/auth/register -> auth.register")
router.Post("/v1/auth/register"[len(r.Path()):], DataFunc1(
r.auth.register,
Body[dto.RegisterForm]("form"),
))
// Register routes for controller: me
r.log.Debugf("Registering route: Get /v1/me -> me.me")
router.Get("/v1/me"[len(r.Path()):], DataFunc0(