182 lines
4.5 KiB
Go
182 lines
4.5 KiB
Go
|
|
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
|
|||
|
|
}
|