Files
SharedClassManager/backend-go/internal/service/super_admin_service.go
canglan 4a82eff3c6 feat: 多班级版班级管理系统 v2.0
技术栈:Go (Gin + GORM) + PHP + MySQL 5.7 + Redis

主要功能:
- 多班级完全隔离(class_id 贯穿全系统)
- 后端 Go Gin(端口 56789),Nginx 反代
- 超级管理员独立登录(env 配置,默认账密 admin/Admin123)
- bcrypt 密码加密(无 PASSWORD_SALT)
- 科任老师/课代表新角色
- 课代表作业管理页面
- 排行榜分项排行(操行分/考勤/作业)
- 角色加减分上下限由班主任配置
- 家长改密功能(可开关)
- 班级角色按需开关
- 宿舍号格式:南0-000
- 周度/月度重置功能
- MySQL 5.7 兼容
- 43 轮代码审查 + 全部修复

开发者: Canglan
版权归属: Sea Network Technology Studio
许可证: Apache License 2.0
2026-06-23 04:41:49 +08:00

154 lines
5.1 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.
// ===========================================
// 多班级版班级管理系统 - 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
}