Files
backend/internal/service/yggdrasil_session_service.go

182 lines
4.5 KiB
Go
Raw Normal View History

2025-12-03 10:58:39 +08:00
package service
import (
apperrors "carrotskin/internal/errors"
"carrotskin/pkg/redis"
"context"
"fmt"
"net"
"strings"
"time"
"go.uber.org/zap"
)
// SessionKeyPrefix Redis会话键前缀
const SessionKeyPrefix = "Join_"
// SessionTTL 会话超时时间 - 增加到15分钟
const SessionTTL = 15 * time.Minute
// SessionData 会话数据
type SessionData struct {
AccessToken string `json:"accessToken"`
UserName string `json:"userName"`
SelectedProfile string `json:"selectedProfile"`
IP string `json:"ip"`
}
// SessionService 会话管理服务接口
type SessionService interface {
// CreateSession 创建服务器会话
CreateSession(ctx context.Context, serverID, accessToken, username, profileUUID, ip string) error
// GetSession 获取会话数据
GetSession(ctx context.Context, serverID string) (*SessionData, error)
// ValidateSession 验证会话用户名和IP
ValidateSession(ctx context.Context, serverID, username, ip string) error
}
// yggdrasilSessionService 会话服务实现
type yggdrasilSessionService struct {
redis *redis.Client
logger *zap.Logger
}
// NewSessionService 创建会话服务实例
func NewSessionService(redisClient *redis.Client, logger *zap.Logger) SessionService {
return &yggdrasilSessionService{
redis: redisClient,
logger: logger,
}
}
// ValidateServerID 验证服务器ID格式
func ValidateServerID(serverID string) error {
if serverID == "" {
return apperrors.ErrInvalidServerID
}
if len(serverID) > 100 || strings.ContainsAny(serverID, "<>\"'&") {
return apperrors.ErrInvalidServerID
}
return nil
}
// ValidateIP 验证IP地址格式
func ValidateIP(ip string) error {
if ip == "" {
return nil // IP是可选的
}
if net.ParseIP(ip) == nil {
return apperrors.ErrIPMismatch
}
return nil
}
// CreateSession 创建服务器会话
func (s *yggdrasilSessionService) CreateSession(ctx context.Context, serverID, accessToken, username, profileUUID, ip string) error {
// 输入验证
if err := ValidateServerID(serverID); err != nil {
return err
}
if accessToken == "" {
return apperrors.ErrInvalidAccessToken
}
if username == "" {
return apperrors.ErrUsernameMismatch
}
if profileUUID == "" {
return apperrors.ErrProfileMismatch
}
if err := ValidateIP(ip); err != nil {
return err
}
// 创建会话数据
data := SessionData{
AccessToken: accessToken,
UserName: username,
SelectedProfile: profileUUID,
IP: ip,
}
// 序列化会话数据
marshaledData, err := json.Marshal(data)
if err != nil {
s.logger.Error("序列化会话数据失败",
zap.Error(err),
zap.String("serverID", serverID),
)
return fmt.Errorf("序列化会话数据失败: %w", err)
}
// 存储会话数据到Redis
sessionKey := SessionKeyPrefix + serverID
if err = s.redis.Set(ctx, sessionKey, marshaledData, SessionTTL); err != nil {
s.logger.Error("保存会话数据失败",
zap.Error(err),
zap.String("serverID", serverID),
)
return fmt.Errorf("保存会话数据失败: %w", err)
}
s.logger.Info("会话创建成功",
zap.String("username", username),
zap.String("serverID", serverID),
)
return nil
}
// GetSession 获取会话数据
func (s *yggdrasilSessionService) GetSession(ctx context.Context, serverID string) (*SessionData, error) {
if err := ValidateServerID(serverID); err != nil {
return nil, err
}
// 从Redis获取会话数据
sessionKey := SessionKeyPrefix + serverID
data, err := s.redis.GetBytes(ctx, sessionKey)
if err != nil {
s.logger.Error("获取会话数据失败",
zap.Error(err),
zap.String("serverID", serverID),
)
return nil, fmt.Errorf("获取会话数据失败: %w", err)
}
// 反序列化会话数据
var sessionData SessionData
if err = json.Unmarshal(data, &sessionData); err != nil {
s.logger.Error("解析会话数据失败",
zap.Error(err),
zap.String("serverID", serverID),
)
return nil, fmt.Errorf("解析会话数据失败: %w", err)
}
return &sessionData, nil
}
// ValidateSession 验证会话用户名和IP
func (s *yggdrasilSessionService) ValidateSession(ctx context.Context, serverID, username, ip string) error {
if serverID == "" || username == "" {
return apperrors.ErrSessionMismatch
}
sessionData, err := s.GetSession(ctx, serverID)
if err != nil {
return apperrors.ErrSessionNotFound
}
// 验证用户名
if sessionData.UserName != username {
return apperrors.ErrUsernameMismatch
}
// 验证IP如果提供
if ip != "" && sessionData.IP != ip {
return apperrors.ErrIPMismatch
}
return nil
}