// =========================================== // 多班级版班级管理系统 - Go 后端 // // 开发者: Canglan // 联系方式: admin@sea-studio.top // 版权归属: Sea Network Technology Studio // 许可证: Apache License 2.0 // // 版权所有 © Sea Network Technology Studio // =========================================== package service import ( "context" "fmt" "hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/config" "hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/repository" "hz-gitea.sea-studio.top/canglan/SharedClassManager/pkg/crypto" "hz-gitea.sea-studio.top/canglan/SharedClassManager/pkg/database" appJwt "hz-gitea.sea-studio.top/canglan/SharedClassManager/pkg/jwt" "hz-gitea.sea-studio.top/canglan/SharedClassManager/pkg/logger" ) // SuperAdminService 超级管理员服务 type SuperAdminService struct { superAdminRepo *repository.SuperAdminRepo logService *LogService } // NewSuperAdminService 创建超级管理员服务 func NewSuperAdminService(superAdminRepo *repository.SuperAdminRepo, logService *LogService) *SuperAdminService { return &SuperAdminService{superAdminRepo: superAdminRepo, logService: logService} } // EnsureDefaultAdmin 确保默认超级管理员存在 func (s *SuperAdminService) EnsureDefaultAdmin() error { cfg := config.AppConfig logger.Sugared.Warnf("⚠️ 当前使用默认超级管理员密码,部署环境请务必修改 SUPER_ADMIN_DEFAULT_PASSWORD 并重启服务") passwordHash, err := crypto.HashPassword(cfg.SuperAdminDefaultPass) if err != nil { return fmt.Errorf("密码哈希失败: %w", err) } if err := s.superAdminRepo.EnsureDefaultAdmin( cfg.SuperAdminDefaultUser, passwordHash, "系统管理员", ); err != nil { return fmt.Errorf("创建默认超级管理员失败: %w", err) } return nil } // Login 超级管理员登录 func (s *SuperAdminService) Login(username, password, ip, userAgent string) (map[string]interface{}, error) { ctx := context.Background() cfg := config.AppConfig // 检查登录失败次数(用户名级 + IP 级双重限流,使用原子 Incr 防止 TOCTOU 竞态) attemptsKey := fmt.Sprintf("login_attempts:sa:%s", username) ipAttemptsKey := fmt.Sprintf("login_attempts:ip:super_admin:%s", ip) count, _ := incrWithExpireAtomic(ctx, attemptsKey, 300) if count > 5 { return map[string]interface{}{"success": false, "message": "登录失败次数过多,请5分钟后重试"}, nil } // IP 级限流 ipCount, _ := incrWithExpireAtomic(ctx, ipAttemptsKey, 300) if ipCount > 20 { return map[string]interface{}{"success": false, "message": "登录失败次数过多,请5分钟后重试"}, nil } admin, err := s.superAdminRepo.GetByUsername(username) if err != nil { s.logService.WriteLoginLog(username, 0, ip, userAgent, "用户名或密码错误") return map[string]interface{}{"success": false, "message": "用户名或密码错误"}, nil } if !crypto.VerifyPassword(password, admin.PasswordHash) { s.logService.WriteLoginLog(username, 0, ip, userAgent, "用户名或密码错误") return map[string]interface{}{"success": false, "message": "用户名或密码错误"}, nil } // 清除用户名级登录失败记录,IP 级计数由 TTL 自然过期(与普通用户策略一致,防止同 IP 其他用户限流被重置) database.RDB.Del(ctx, attemptsKey) s.logService.WriteLoginLog(username, 1, ip, userAgent, "") // 生成 Token token, err := appJwt.CreateToken( admin.ID, admin.Username, "super_admin", nil, "系统管理员", admin.RealName, nil, false, ) if err != nil { return map[string]interface{}{"success": false, "message": "生成令牌失败"}, nil } _ = database.SetUserToken(ctx, admin.ID, token, cfg.JWTIdleTimeoutMinutes) needChangePassword := admin.NeedChangePassword == 1 redirect := "/admin/dashboard.php" if needChangePassword { redirect = "/admin/password.php" } return map[string]interface{}{ "success": true, "token": token, "user_id": admin.ID, "username": admin.Username, "real_name": admin.RealName, "user_type": "super_admin", "need_change_password": needChangePassword, "redirect": redirect, }, nil } // ChangePassword 超级管理员修改密码 func (s *SuperAdminService) ChangePassword(adminID int, oldPassword, newPassword string, force bool) error { admin, err := s.superAdminRepo.GetByID(adminID) if err != nil { return fmt.Errorf("超级管理员不存在") } // 验证原密码(强制改密时跳过) if !force { if !crypto.VerifyPassword(oldPassword, admin.PasswordHash) { return fmt.Errorf("原密码错误") } } // 验证新密码强度 if valid, msg := crypto.ValidatePasswordStrength(newPassword); !valid { return fmt.Errorf("%s", msg) } newHash, err := crypto.HashPassword(newPassword) if err != nil { return fmt.Errorf("密码加密失败: %w", err) } if err := s.superAdminRepo.UpdatePassword(adminID, newHash); err != nil { return fmt.Errorf("密码修改失败") } // 清除旧 Token,强制重新登录 ctx := context.Background() _ = database.DeleteUserToken(ctx, adminID) return nil }