Files
SharedClassManager/backend-go/internal/service/super_admin_service.go
canglan 4193a1a153 refactor: 清理旧版兼容性,升级为 bcrypt 密码算法
- 密码哈希从 MD5+SHA1 升级为 bcrypt
- 删除 super_admins/users 表中的 salt 字段
- 删除旧版升级文件(upgrade.php, check_upgrade, execute_upgrade, sql/upgrades/)
- 删除 PASSWORD_SALT 配置项
- 清理所有'兼容 Python 版'注释
- 新项目独立,无历史包袱
2026-06-22 10:45:13 +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
}