Files
backend_v2/internal/service/auth_service.go
toom1996 30c9f21da4 update
2026-08-26 10:45:21 +08:00

118 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"context"
"errors"
"regexp"
"strings"
"fashionapi/internal/dto"
"fashionapi/internal/model"
"fashionapi/internal/pkg/jwt"
"fashionapi/internal/repository"
"golang.org/x/crypto/bcrypt"
)
// emailRegex 邮箱格式校验,与原项目一致。
var emailRegex = regexp.MustCompile(`^[^\s@]+@[^\s@]+\.[^\s@]+$`)
// minPasswordLength 密码最小长度。
const minPasswordLength = 6
// AuthService 账号体系业务接口。
type AuthService interface {
Register(ctx context.Context, req dto.AuthRequest) (*dto.AuthResult, error)
Login(ctx context.Context, req dto.AuthRequest) (*dto.AuthResult, error)
Me(ctx context.Context, userID uint32) (*dto.UserPayload, error)
}
type authService struct {
users repository.UserRepository
jwt *jwt.Manager
}
// NewAuthService 创建账号服务。
func NewAuthService(users repository.UserRepository, jwtManager *jwt.Manager) AuthService {
return &authService{users: users, jwt: jwtManager}
}
// Register 注册新用户,成功后直接签发令牌(免去前端再调一次登录)。
func (s *authService) Register(ctx context.Context, req dto.AuthRequest) (*dto.AuthResult, error) {
username := strings.TrimSpace(req.Username)
email := strings.TrimSpace(req.Email)
password := req.Password
if username == "" || email == "" || len(password) < minPasswordLength {
return nil, badRequest("username、email 与 password(>=6 位) 均为必填")
}
if !emailRegex.MatchString(email) {
return nil, badRequest("email 格式不正确")
}
exists, err := s.users.ExistsByUsernameOrEmail(ctx, username, email)
if err != nil {
return nil, internalErr("server error")
}
if exists {
return nil, conflict("该用户名或邮箱已被注册")
}
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return nil, internalErr("server error")
}
user := model.User{Username: username, Email: email, PasswordHash: string(hash)}
if err := s.users.Create(ctx, &user); err != nil {
return nil, internalErr("创建用户失败")
}
return s.issue(user)
}
// Login 登录:account 可以是邮箱或用户名。
func (s *authService) Login(ctx context.Context, req dto.AuthRequest) (*dto.AuthResult, error) {
account := strings.TrimSpace(req.Account)
password := req.Password
if account == "" || password == "" {
return nil, badRequest("账号与密码均为必填")
}
user, err := s.users.FindByAccount(ctx, account)
if err != nil {
if errors.Is(err, repository.ErrNotFound) {
// 账号不存在与密码错误返回同一文案,避免账号枚举
return nil, unauthorized("账号或密码错误")
}
return nil, internalErr("server error")
}
if bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)) != nil {
return nil, unauthorized("账号或密码错误")
}
return s.issue(*user)
}
// Me 用令牌中的 uid 取回最新用户信息。
func (s *authService) Me(ctx context.Context, userID uint32) (*dto.UserPayload, error) {
user, err := s.users.FindByID(ctx, userID)
if err != nil {
// 令牌有效但用户已不存在,同样视为未授权
return nil, unauthorized("unauthorized")
}
p := toUserPayload(*user)
return &p, nil
}
// issue 签发令牌并组装返回体。
func (s *authService) issue(u model.User) (*dto.AuthResult, error) {
token, err := s.jwt.Generate(u.ID, u.Username, u.Email)
if err != nil {
return nil, internalErr("server error")
}
return &dto.AuthResult{Token: token, User: toUserPayload(u)}, nil
}
func toUserPayload(u model.User) dto.UserPayload {
return dto.UserPayload{ID: u.ID, Username: u.Username, Email: u.Email}
}