// Package service 承载业务逻辑,隔离 handler 与数据层/系统层。 package service import ( "context" "errors" "strings" "gorm.io/gorm" "ws_usernode/internal/auth" "ws_usernode/internal/model" "ws_usernode/internal/pkg" ) // 管理员服务错误。 var ( ErrAdminExists = errors.New("service: 管理员已存在") ErrAdminNotFound = errors.New("service: 管理员不存在") ErrWeakPassword = errors.New("service: 密码过弱(至少 8 位,需含字母与数字)") ErrBadCredentials = errors.New("service: 用户名或密码错误") ) // AdminService 管理端账号服务(CLI admin create / reset-password 与登录共用)。 type AdminService struct { db *gorm.DB } // NewAdminService 创建管理员服务。 func NewAdminService(db *gorm.DB) *AdminService { return &AdminService{db: db} } // Create 创建初始管理员。password 为空时由调用方提示交互输入。 func (s *AdminService) Create(ctx context.Context, username, password, email string) (*model.AdminUser, error) { if err := validatePassword(password); err != nil { return nil, err } if err := pkg.ValidateEmail(email); err != nil { return nil, err } username = strings.TrimSpace(username) var count int64 if err := s.db.WithContext(ctx).Model(&model.AdminUser{}).Where("username = ?", username).Count(&count).Error; err != nil { return nil, err } if count > 0 { return nil, ErrAdminExists } hash, err := auth.HashPassword(password) if err != nil { return nil, err } adm := &model.AdminUser{ Username: username, PasswordHash: hash, Email: email, Role: "admin", Status: model.StatusActive, } if err := s.db.WithContext(ctx).Create(adm).Error; err != nil { return nil, err } return adm, nil } // ResetPassword 重置管理员密码(CLI 或邮件重置兜底)。 func (s *AdminService) ResetPassword(ctx context.Context, username, newPassword string) error { if err := validatePassword(newPassword); err != nil { return err } var adm model.AdminUser if err := s.db.WithContext(ctx).First(&adm, "username = ?", username).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrAdminNotFound } return err } hash, err := auth.HashPassword(newPassword) if err != nil { return err } return s.db.WithContext(ctx).Model(&adm).Update("password_hash", hash).Error } // GetByUsername 按用户名查询管理员。 func (s *AdminService) GetByUsername(ctx context.Context, username string) (*model.AdminUser, error) { var adm model.AdminUser if err := s.db.WithContext(ctx).First(&adm, "username = ?", username).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrAdminNotFound } return nil, err } return &adm, nil } // GetByID 按 ID 查询管理员。 func (s *AdminService) GetByID(ctx context.Context, id uint) (*model.AdminUser, error) { var adm model.AdminUser if err := s.db.WithContext(ctx).First(&adm, "id = ?", id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrAdminNotFound } return nil, err } return &adm, nil } // GetByEmail 按邮箱查询管理员(用于密码重置邮件)。 func (s *AdminService) GetByEmail(ctx context.Context, email string) (*model.AdminUser, error) { var adm model.AdminUser if err := s.db.WithContext(ctx).First(&adm, "email = ?", email).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrAdminNotFound } return nil, err } return &adm, nil } // Login 校验管理员用户名与口令(bcrypt)。失败统一返回 ErrBadCredentials, // 不暴露账号是否存在。登录限速由 AuthService 的 RateLimiter 处理。 func (s *AdminService) Login(ctx context.Context, username, password string) (*model.AdminUser, error) { adm, err := s.GetByUsername(ctx, strings.TrimSpace(username)) if err != nil { return nil, ErrBadCredentials } if !auth.VerifyPassword(adm.PasswordHash, password) { return nil, ErrBadCredentials } return adm, nil } // validatePassword 校验管理员密码强度(骨架阶段基础规则,M1 可加策略)。 func validatePassword(p string) error { if len(p) < 8 { return ErrWeakPassword } hasLetter, hasDigit := false, false for _, r := range p { if r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' { hasLetter = true } if r >= '0' && r <= '9' { hasDigit = true } } if !hasLetter || !hasDigit { return ErrWeakPassword } return nil }