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