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 type auth struct { jwt *jwt.JWT } var reUsername = regexp.MustCompile(`^[a-zA-Z0-9_]{3,32}$`) // Login 用户登录(平台侧,非超级管理员)。 // // @Summary 用户登录 // @Tags Web // @Accept json // @Produce json // @Param form body dto.LoginForm true "form" // @Success 200 {object} dto.LoginResponse "成功" // @Router /v1/auth/login [post] // @Bind form body func (ctl *auth) login(ctx fiber.Ctx, form *dto.LoginForm) (*dto.LoginResponse, error) { m, err := services.User.FindByUsername(ctx, form.Username) if err != nil { return nil, errorx.Wrap(err).WithMsg("用户名或密码错误") } if ok := m.ComparePassword(ctx, form.Password); !ok { return nil, errorx.Wrap(errorx.ErrInvalidCredentials).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 } // 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 // @Tags Web // @Accept json // @Produce json // @Success 200 {object} dto.LoginResponse "成功" // @Router /v1/auth/token [get] func (ctl *auth) token(ctx fiber.Ctx) (*dto.LoginResponse, error) { claims, ok := ctx.Locals(consts.CtxKeyClaims).(*jwt.Claims) if !ok || claims == nil || claims.UserID <= 0 { return nil, errorx.ErrTokenInvalid } token, err := ctl.jwt.CreateToken(ctl.jwt.CreateClaims(jwt.BaseClaims{ UserID: claims.UserID, })) if err != nil { return nil, errorx.Wrap(err).WithMsg("登录凭证生成失败") } return &dto.LoginResponse{Token: token}, nil }